diff --git a/lib/builtin/git/src/git_status_item.dart b/lib/builtin/git/src/git_status_item.dart index 0dfdba7a..939f452d 100644 --- a/lib/builtin/git/src/git_status_item.dart +++ b/lib/builtin/git/src/git_status_item.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:clide/kernel/kernel.dart'; import 'package:clide/widgets/widgets.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class GitStatusItem extends StatefulWidget { @@ -116,13 +117,22 @@ class _BranchPicker extends StatefulWidget { class _BranchPickerState extends State<_BranchPicker> { List> _branches = const []; bool _loading = true; + String? _error; + late final FocusNode _focus; @override void initState() { super.initState(); + _focus = FocusNode()..requestFocus(); unawaited(_load()); } + @override + void dispose() { + _focus.dispose(); + super.dispose(); + } + Future _load() async { final r = await widget.ipc.request('git.branches'); if (!mounted) return; @@ -133,6 +143,8 @@ class _BranchPickerState extends State<_BranchPicker> { for (final b in (r.data['branches'] as List? ?? const [])) (b as Map).cast(), ]; + } else { + _error = r.error?.message ?? 'failed to load branches'; } }); } @@ -142,53 +154,75 @@ class _BranchPickerState extends State<_BranchPicker> { widget.onDismiss(); } + KeyEventResult _onKey(FocusNode node, KeyEvent event) { + if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) { + widget.onDismiss(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } + @override Widget build(BuildContext context) { final tokens = ClideTheme.of(context).surface; - return Container( - width: 320, - constraints: const BoxConstraints(maxHeight: 320), - decoration: BoxDecoration( - color: tokens.dropdownBackground, - border: Border.all(color: tokens.dropdownBorder), - borderRadius: BorderRadius.circular(6), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(12), - child: ClideText( - 'Switch branch', - fontSize: clideFontCaption, - color: tokens.globalTextMuted, - fontFamily: clideMonoFamily, + return Focus( + focusNode: _focus, + onKeyEvent: _onKey, + child: Container( + width: 320, + constraints: const BoxConstraints(maxHeight: 320), + decoration: BoxDecoration( + color: tokens.dropdownBackground, + border: Border.all(color: tokens.dropdownBorder), + borderRadius: BorderRadius.circular(6), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Expanded( + child: ClideText('Switch branch', fontSize: clideFontCaption, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + ), + GestureDetector( + onTap: () => widget.onDismiss(), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: ClideIcon(PhosphorIcons.xMark, size: 12, color: tokens.globalTextMuted), + ), + ), + ], + ), ), - ), - if (_loading) - const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Loading…', muted: true), - ), - Flexible( - child: ListView.builder( - shrinkWrap: true, - padding: EdgeInsets.zero, - itemCount: _branches.length, - itemBuilder: (ctx, i) { - final b = _branches[i]; - final name = b['name'] as String? ?? ''; - final current = b['current'] as bool? ?? false; - return _BranchRow( - name: name, - current: current, - onTap: current ? null : () => unawaited(_checkout(name)), - ); - }, - ), - ), - ], + if (_loading) + const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)), + if (_error != null) + Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true)), + if (!_loading && _error == null && _branches.isEmpty) + const Padding(padding: EdgeInsets.all(12), child: ClideText('No branches found.', muted: true)), + if (_branches.isNotEmpty) + Flexible( + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _branches.length, + itemBuilder: (ctx, i) { + final b = _branches[i]; + final name = b['name'] as String? ?? ''; + final current = b['current'] as bool? ?? false; + return _BranchRow( + name: name, + current: current, + onTap: current ? null : () => unawaited(_checkout(name)), + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/kernel/src/dialog.dart b/lib/kernel/src/dialog.dart index 6b88cf82..a2701fc7 100644 --- a/lib/kernel/src/dialog.dart +++ b/lib/kernel/src/dialog.dart @@ -93,10 +93,16 @@ class DialogHost extends StatelessWidget { final b = router.current; if (b == null) return const SizedBox.shrink(); return Positioned.fill( - child: ColoredBox( - color: backdropColor, - child: Center( - child: b(ctx, router.dismiss), + child: GestureDetector( + onTap: () => router.dismiss(), + child: ColoredBox( + color: backdropColor, + child: Center( + child: GestureDetector( + onTap: () {}, + child: b(ctx, router.dismiss), + ), + ), ), ), );