Skip to content

Commit e194ddb

Browse files
committed
Merge PR #1866
2 parents 4264443 + 26ddbd5 commit e194ddb

File tree

9 files changed

+561
-522
lines changed

9 files changed

+561
-522
lines changed

lib/app/shortcuts.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,14 @@ class GlobalShortcuts extends ConsumerWidget {
198198
},
199199
),
200200
EscapeIntent: CallbackAction<EscapeIntent>(
201-
onInvoke: (_) {
202-
FocusManager.instance.primaryFocus?.unfocus();
201+
onInvoke: (_) async {
202+
await ref.read(withContextProvider)((context) async {
203+
FocusScopeNode mainScope = FocusScope.of(context);
204+
// Avoid moving focus outside of main scope
205+
if (!mainScope.hasPrimaryFocus) {
206+
FocusManager.instance.primaryFocus?.unfocus();
207+
}
208+
});
203209
return null;
204210
},
205211
),

lib/app/views/app_page.dart

Lines changed: 68 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ class _AppPageState extends ConsumerState<AppPage> {
570570
);
571571
}
572572

573-
Scaffold _buildScaffold(
573+
Widget _buildScaffold(
574574
BuildContext context,
575575
bool hasDrawer,
576576
bool hasRail,
@@ -599,51 +599,58 @@ class _AppPageState extends ConsumerState<AppPage> {
599599
);
600600
}
601601
if (hasRail || hasManage) {
602-
body = GestureDetector(
603-
behavior: HitTestBehavior.deferToChild,
604-
onTap: () {
605-
Actions.invoke(context, const EscapeIntent());
606-
FocusManager.instance.primaryFocus?.unfocus();
607-
},
608-
child: SafeArea(
602+
body = SafeArea(
603+
child: FocusTraversalGroup(
604+
policy: OrderedTraversalPolicy(),
609605
child: Row(
610606
crossAxisAlignment: CrossAxisAlignment.stretch,
611607
children: [
612608
if (hasRail && (!fullyExpanded || !showNavigation))
613-
SizedBox(
614-
width: 72,
615-
child: _VisibilityListener(
616-
targetKey: _navKey,
617-
controller: _navController,
618-
child: SingleChildScrollView(
619-
child: NavigationContent(
620-
key: _navKey,
621-
shouldPop: false,
622-
extended: false,
609+
FocusTraversalOrder(
610+
order: NumericFocusOrder(1),
611+
child: SizedBox(
612+
width: 72,
613+
child: _VisibilityListener(
614+
targetKey: _navKey,
615+
controller: _navController,
616+
child: SingleChildScrollView(
617+
child: NavigationContent(
618+
key: _navKey,
619+
shouldPop: false,
620+
extended: false,
621+
),
623622
),
624623
),
625624
),
626625
),
627626
if (fullyExpanded && showNavigation)
628-
SizedBox(
629-
width: 280,
630-
child: _VisibilityListener(
631-
controller: _navController,
632-
targetKey: _navExpandedKey,
633-
child: SingleChildScrollView(
634-
child: Material(
635-
type: MaterialType.transparency,
636-
child: NavigationContent(
637-
key: _navExpandedKey,
638-
shouldPop: false,
639-
extended: true,
627+
FocusTraversalOrder(
628+
order: NumericFocusOrder(1),
629+
child: SizedBox(
630+
width: 280,
631+
child: _VisibilityListener(
632+
controller: _navController,
633+
targetKey: _navExpandedKey,
634+
child: SingleChildScrollView(
635+
child: Material(
636+
type: MaterialType.transparency,
637+
child: NavigationContent(
638+
key: _navExpandedKey,
639+
shouldPop: false,
640+
extended: true,
641+
),
640642
),
641643
),
642644
),
643645
),
644646
),
645647
const SizedBox(width: 8),
646-
Expanded(child: body),
648+
Expanded(
649+
child: FocusTraversalOrder(
650+
order: NumericFocusOrder(2),
651+
child: body,
652+
),
653+
),
647654
if (hasManage &&
648655
!hasDetailsOrKeyActions &&
649656
showDetailView &&
@@ -654,22 +661,25 @@ class _AppPageState extends ConsumerState<AppPage> {
654661
// - pages without Capabilities
655662
const SizedBox(width: 336), // simulate column
656663
if (hasManage && hasDetailsOrKeyActions && showDetailView)
657-
_VisibilityListener(
658-
controller: _detailsController,
659-
targetKey: _detailsViewGlobalKey,
660-
child: SingleChildScrollView(
661-
child: Padding(
662-
padding: const EdgeInsets.symmetric(horizontal: 8),
663-
child: SizedBox(
664-
width: 320,
665-
child: Column(
666-
key: _detailsViewGlobalKey,
667-
children: [
668-
if (widget.detailViewBuilder != null)
669-
widget.detailViewBuilder!(context),
670-
if (widget.keyActionsBuilder != null)
671-
widget.keyActionsBuilder!(context),
672-
],
664+
FocusTraversalOrder(
665+
order: NumericFocusOrder(3),
666+
child: _VisibilityListener(
667+
controller: _detailsController,
668+
targetKey: _detailsViewGlobalKey,
669+
child: SingleChildScrollView(
670+
child: Padding(
671+
padding: const EdgeInsets.symmetric(horizontal: 8),
672+
child: SizedBox(
673+
width: 320,
674+
child: Column(
675+
key: _detailsViewGlobalKey,
676+
children: [
677+
if (widget.detailViewBuilder != null)
678+
widget.detailViewBuilder!(context),
679+
if (widget.keyActionsBuilder != null)
680+
widget.keyActionsBuilder!(context),
681+
],
682+
),
673683
),
674684
),
675685
),
@@ -680,13 +690,15 @@ class _AppPageState extends ConsumerState<AppPage> {
680690
),
681691
);
682692
}
683-
return Scaffold(
684-
key: scaffoldGlobalKey,
685-
appBar: _GestureDetectorAppBar(
686-
onTap: () {
687-
Actions.invoke(context, const EscapeIntent());
688-
FocusManager.instance.primaryFocus?.unfocus();
689-
},
693+
return GestureDetector(
694+
behavior: HitTestBehavior.deferToChild,
695+
onTap: () {
696+
// If tap is not absorbed downstream, treat it as dead space
697+
// and invoke escape intent
698+
Actions.invoke(context, EscapeIntent());
699+
},
700+
child: Scaffold(
701+
key: scaffoldGlobalKey,
690702
appBar: AppBar(
691703
bottom: PreferredSize(
692704
preferredSize: const Size.fromHeight(1.0),
@@ -822,31 +834,11 @@ class _AppPageState extends ConsumerState<AppPage> {
822834
),
823835
],
824836
),
837+
drawer: hasDrawer ? _buildDrawer(context) : null,
838+
body: body,
825839
),
826-
drawer: hasDrawer ? _buildDrawer(context) : null,
827-
body: body,
828-
);
829-
}
830-
}
831-
832-
class _GestureDetectorAppBar extends StatelessWidget
833-
implements PreferredSizeWidget {
834-
final AppBar appBar;
835-
final void Function() onTap;
836-
837-
const _GestureDetectorAppBar({required this.appBar, required this.onTap});
838-
839-
@override
840-
Widget build(BuildContext context) {
841-
return GestureDetector(
842-
behavior: HitTestBehavior.deferToChild,
843-
onTap: onTap,
844-
child: appBar,
845840
);
846841
}
847-
848-
@override
849-
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
850842
}
851843

852844
class CapabilityBadge extends ConsumerWidget {

lib/app/views/navigation.dart

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import '../state.dart';
2525
import 'device_picker.dart';
2626
import 'keys.dart';
2727

28-
class NavigationItem extends StatelessWidget {
28+
class NavigationItem extends StatefulWidget {
2929
final Widget leading;
3030
final String title;
3131
final bool collapsed;
@@ -41,16 +41,29 @@ class NavigationItem extends StatelessWidget {
4141
this.onTap,
4242
});
4343

44+
@override
45+
State<StatefulWidget> createState() => _NavigationItemState();
46+
}
47+
48+
class _NavigationItemState extends State<NavigationItem> {
49+
final FocusNode _focusNode = FocusNode();
50+
51+
@override
52+
void dispose() {
53+
_focusNode.dispose();
54+
super.dispose();
55+
}
56+
4457
@override
4558
Widget build(BuildContext context) {
4659
final theme = Theme.of(context);
4760
final colorScheme = theme.colorScheme;
4861

49-
if (collapsed) {
62+
if (widget.collapsed) {
5063
return Padding(
5164
padding: const EdgeInsets.symmetric(vertical: 8.0),
5265
child:
53-
selected
66+
widget.selected
5467
? Theme(
5568
data: theme.copyWith(
5669
colorScheme: colorScheme.copyWith(
@@ -59,30 +72,32 @@ class NavigationItem extends StatelessWidget {
5972
),
6073
),
6174
child: IconButton.filled(
62-
icon: leading,
63-
tooltip: title,
75+
focusNode: _focusNode,
76+
icon: widget.leading,
77+
tooltip: widget.title,
6478
padding: const EdgeInsets.symmetric(horizontal: 16),
65-
onPressed: onTap,
79+
onPressed: widget.onTap,
6680
),
6781
)
6882
: IconButton(
69-
icon: leading,
70-
tooltip: title,
83+
focusNode: _focusNode,
84+
icon: widget.leading,
85+
tooltip: widget.title,
7186
padding: const EdgeInsets.symmetric(horizontal: 16),
72-
onPressed: onTap,
87+
onPressed: widget.onTap,
7388
),
7489
);
7590
} else {
7691
return ListTile(
77-
enabled: onTap != null,
92+
enabled: widget.onTap != null,
7893
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(48)),
79-
leading: leading,
80-
title: Text(title),
94+
leading: widget.leading,
95+
title: Text(widget.title),
8196
minVerticalPadding: 16,
82-
onTap: onTap,
83-
tileColor: selected ? colorScheme.secondaryContainer : null,
84-
textColor: selected ? colorScheme.onSecondaryContainer : null,
85-
iconColor: selected ? colorScheme.onSecondaryContainer : null,
97+
onTap: widget.onTap,
98+
tileColor: widget.selected ? colorScheme.secondaryContainer : null,
99+
textColor: widget.selected ? colorScheme.onSecondaryContainer : null,
100+
iconColor: widget.selected ? colorScheme.onSecondaryContainer : null,
86101
contentPadding: const EdgeInsets.only(left: 16.0),
87102
);
88103
}

lib/fido/state.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import '../widgets/flex_box.dart';
2222
import 'models.dart';
2323

2424
final passkeysSearchProvider =
25-
StateNotifierProvider<PasskeysSearchNotifier, String>(
25+
StateNotifierProvider.autoDispose<PasskeysSearchNotifier, String>(
2626
(ref) => PasskeysSearchNotifier(),
2727
);
2828

0 commit comments

Comments
 (0)