diff --git a/lib/app.dart b/lib/app.dart index 0b620ff2..f64ac4af 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -87,12 +87,12 @@ class _RootShellState extends State<_RootShell> { color: tokens.globalBackground, child: ClideResizeBorder( windowControls: widget.services.window, - child: DialogHost( - router: widget.services.dialog, - child: Column( - children: [ - _HatBar(kernel: widget.services), - Expanded( + child: Column( + children: [ + _HatBar(kernel: widget.services), + Expanded( + child: DialogHost( + router: widget.services.dialog, child: Stack( children: [ const Positioned.fill(child: RootLayout()), @@ -101,8 +101,8 @@ class _RootShellState extends State<_RootShell> { ], ), ), - ], - ), + ), + ], ), ), ), diff --git a/lib/builtin/ipc_status/src/status_item.dart b/lib/builtin/ipc_status/src/status_item.dart index 9be49ec0..ec8ea72c 100644 --- a/lib/builtin/ipc_status/src/status_item.dart +++ b/lib/builtin/ipc_status/src/status_item.dart @@ -1,82 +1,45 @@ -import 'dart:io'; - import 'package:clide/kernel/kernel.dart'; import 'package:clide/widgets/widgets.dart'; import 'package:flutter/widgets.dart'; -class ToolStatusItem extends StatefulWidget { +class ToolStatusItem extends StatelessWidget { const ToolStatusItem({super.key}); - @override - State createState() => _ToolStatusItemState(); -} - -class _ToolStatusItemState extends State { - bool _ptycOk = false; - bool _pqlOk = false; - bool _checked = false; - - @override - void initState() { - super.initState(); - _check(); - } - - Future _check() async { - final cwd = Directory.current.path; - final ptycResult = _exists('$cwd/native/linux-x64/ptyc') || _exists('$cwd/ptyc/bin/ptyc') || await _which('ptyc'); - final pqlResult = await _which('pql'); - if (!mounted) return; - setState(() { - _ptycOk = ptycResult; - _pqlOk = pqlResult; - _checked = true; - }); - } - - bool _exists(String path) => File(path).existsSync(); - - Future _which(String name) async { - try { - final r = await Process.run('which', [name]); - return r.exitCode == 0; - } catch (_) { - return false; - } - } - @override Widget build(BuildContext context) { + final kernel = ClideKernel.of(context); final tokens = ClideTheme.of(context).surface; - if (!_checked) return const SizedBox.shrink(); - - if (_ptycOk && _pqlOk) { - return _chip('ok', tokens.statusSuccess, tokens); - } - - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (!_ptycOk) _chip('ptyc not found', tokens.statusWarning, tokens), - if (!_ptycOk && !_pqlOk) const SizedBox(width: 10), - if (!_pqlOk) _chip('pql not found', tokens.statusWarning, tokens), - ], + return ListenableBuilder( + listenable: kernel.toolCheck, + builder: (ctx, _) { + final tc = kernel.toolCheck; + if (!tc.checked) return const SizedBox.shrink(); + if (tc.allOk) { + return _chip('ok', tokens.statusSuccess, tokens); + } + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < tc.errors.length; i++) ...[ + if (i > 0) const SizedBox(width: 10), + _chip(tc.errors[i], tokens.statusWarning, tokens), + ], + ], + ); + }, ); } Widget _chip(String label, Color color, SurfaceTokens tokens) { - return Semantics( - label: label, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), - const SizedBox(width: 6), - ClideText(label, fontSize: clideFontCaption, color: color), - ], - ), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), + const SizedBox(width: 6), + ClideText(label, fontSize: clideFontCaption, color: color), + ], ), ); } diff --git a/lib/builtin/welcome/src/welcome_view.dart b/lib/builtin/welcome/src/welcome_view.dart index 018b4d20..a417946a 100644 --- a/lib/builtin/welcome/src/welcome_view.dart +++ b/lib/builtin/welcome/src/welcome_view.dart @@ -57,8 +57,8 @@ class _Header extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( - width: 72, - height: 72, + width: 144, + height: 144, child: ScalableImageWidget.fromSISource( si: ScalableImageSource.fromSvg(rootBundle, 'assets/logo/logo.svg'), ), @@ -272,21 +272,21 @@ class _StatusLine extends StatelessWidget { @override Widget build(BuildContext context) { + final themeName = kernel.theme.currentName; return ListenableBuilder( - listenable: kernel.ipc, + listenable: kernel.toolCheck, builder: (ctx, _) { - final connected = kernel.ipc.isConnected; - final themeName = kernel.theme.currentName; + final tc = kernel.toolCheck; return Row( children: [ ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily), ClideText(' · ', muted: true, fontSize: 12), - ClideText( - connected ? 'daemon connected' : 'daemon disconnected', - fontSize: 12, - fontFamily: clideMonoFamily, - color: connected ? tokens.statusSuccess : tokens.statusError, - ), + if (!tc.checked) + ClideText('checking…', muted: true, fontSize: 12, fontFamily: clideMonoFamily) + else if (tc.allOk) + ClideText('application ok', fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusSuccess) + else + ClideText(tc.errors.join(' · '), fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusWarning), ClideText(' · ', muted: true, fontSize: 12), ClideText('theme: ', muted: true, fontSize: 12, fontFamily: clideMonoFamily), ClideText(themeName, fontSize: 12, fontFamily: clideMonoFamily, color: tokens.globalFocus), diff --git a/lib/kernel/kernel.dart b/lib/kernel/kernel.dart index f210aecb..bdd0b571 100644 --- a/lib/kernel/kernel.dart +++ b/lib/kernel/kernel.dart @@ -46,4 +46,5 @@ export 'src/theme/palette.dart'; export 'src/theme/resolver.dart'; export 'src/theme/semantic.dart'; export 'src/theme/tokens.dart'; +export 'src/tool_check.dart'; export 'src/window_controls.dart'; diff --git a/lib/kernel/src/facade.dart b/lib/kernel/src/facade.dart index 4866e1b5..e4c6a940 100644 --- a/lib/kernel/src/facade.dart +++ b/lib/kernel/src/facade.dart @@ -25,6 +25,7 @@ import 'package:clide/kernel/src/secrets.dart'; import 'package:clide/kernel/src/settings.dart'; import 'package:clide/kernel/src/theme/controller.dart'; import 'package:clide/kernel/src/theme/loader.dart'; +import 'package:clide/kernel/src/tool_check.dart'; import 'package:clide/kernel/src/tray.dart'; import 'package:clide/kernel/src/window_controls.dart'; import 'package:flutter/widgets.dart'; @@ -57,6 +58,7 @@ class KernelServices { required this.project, required this.extensions, required this.window, + required this.toolCheck, }); final Logger log; @@ -82,6 +84,7 @@ class KernelServices { final ProjectManager project; final ExtensionManager extensions; final WindowControls window; + final ToolCheck toolCheck; static Future boot({ required Directory appDir, @@ -128,6 +131,7 @@ class KernelServices { final net = NetworkStatus(); final focus = FocusTracker(); final window = WindowControls(); + final toolCheck = ToolCheck(); final project = ProjectManager( log: log, events: events, @@ -192,6 +196,7 @@ class KernelServices { project: project, extensions: extensions, window: window, + toolCheck: toolCheck, ); } diff --git a/lib/kernel/src/tool_check.dart b/lib/kernel/src/tool_check.dart new file mode 100644 index 00000000..90166f91 --- /dev/null +++ b/lib/kernel/src/tool_check.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; + +class ToolCheck extends ChangeNotifier { + bool ptycOk = false; + bool pqlOk = false; + bool tmuxOk = false; + bool gitOk = false; + bool checked = false; + + bool get allOk => ptycOk && pqlOk && tmuxOk && gitOk; + + List get errors => [ + if (!ptycOk) 'ptyc not found', + if (!pqlOk) 'pql not found', + if (!tmuxOk) 'tmux not found', + if (!gitOk) 'git not found', + ]; + + Future check() async { + final cwd = Directory.current.path; + ptycOk = File('$cwd/native/linux-x64/ptyc').existsSync() || + File('$cwd/ptyc/bin/ptyc').existsSync() || + await _which('ptyc'); + pqlOk = await _which('pql'); + tmuxOk = await _which('tmux'); + gitOk = await _which('git'); + checked = true; + notifyListeners(); + } + + static Future _which(String name) async { + try { + final r = await Process.run('which', [name]); + return r.exitCode == 0; + } catch (_) { + return false; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 9e247061..00bedcf9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:clide/app.dart'; import 'package:clide/builtin/canvas/canvas.dart'; import 'package:clide/builtin/claude/claude.dart'; @@ -107,6 +109,7 @@ Future main() async { await services.extensions.activateAll(); if (!kIsWeb) { + unawaited(services.toolCheck.check()); await services.project.loadRecents(); var opened = await services.project.openLast(); if (!opened) {