Skip to content

Commit 52fa2e2

Browse files
committed
app_shell improvements
* add back navigation support using the back mouse button * fix some part of the NavigationAppBar's leading area was not clickable * fix NavigationAppBar's leading button was disabled in certain cases * fix incorrect index when using `--route`
1 parent 237e9a2 commit 52fa2e2

File tree

1 file changed

+170
-146
lines changed

1 file changed

+170
-146
lines changed

src/lib/core/routing/app_shell.dart

Lines changed: 170 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:io';
22

33
import 'package:fluent_ui/fluent_ui.dart';
44
import 'package:fluentui_system_icons/fluentui_system_icons.dart' as msicons;
5+
import 'package:flutter/gestures.dart';
56
import 'package:flutter_riverpod/flutter_riverpod.dart';
67
import 'package:go_router/go_router.dart';
78
import 'package:win32_registry/win32_registry.dart';
@@ -64,6 +65,24 @@ class _AppShellState extends ConsumerState<AppShell> {
6465
r'C:\ProgramData\Microsoft\User Account Pictures\user-192.png',
6566
);
6667

68+
void _updateNavigationIndex() {
69+
WidgetsBinding.instance.addPostFrameCallback((_) {
70+
if (!mounted) return;
71+
final location = GoRouterState.of(context).uri.toString();
72+
final RouteMeta? route = RouteMeta.fromPath(location, allowPrefix: true);
73+
final int? index = AppRoutes.getPaneIndexFromRoute(route);
74+
if (index != null) {
75+
ref.read(navigationIndexProvider.notifier).index = index;
76+
}
77+
});
78+
}
79+
80+
@override
81+
void initState() {
82+
_updateNavigationIndex();
83+
super.initState();
84+
}
85+
6786
@override
6887
void dispose() {
6988
_searchController.dispose();
@@ -79,165 +98,170 @@ class _AppShellState extends ConsumerState<AppShell> {
7998

8099
final FluentLocalizations localizations = FluentLocalizations.of(context);
81100

82-
return SafeArea(
83-
child: NavigationView(
84-
key: _viewKey,
85-
contentShape: const RoundedRectangleBorder(
86-
side: BorderSide(width: 0, color: Colors.transparent),
87-
borderRadius: BorderRadius.only(topLeft: Radius.circular(8.0)),
88-
),
89-
appBar: NavigationAppBar(
90-
automaticallyImplyLeading: false,
91-
leading: () {
92-
final bool enabled =
93-
widget.shellContext != null &&
94-
ref.read(appRouterProvider).canPop();
95-
final Null Function()? onPressed = enabled
96-
? () {
97-
context.pop();
98-
WidgetsBinding.instance.addPostFrameCallback((_) {
99-
if (!mounted) return;
100-
final location = GoRouterState.of(context).uri.toString();
101-
final RouteMeta? route = RouteMeta.fromPath(
102-
location,
103-
allowPrefix: true,
104-
);
105-
final int? index = AppRoutes.getPaneIndexFromRoute(route);
106-
if (index != null) {
107-
ref.read(navigationIndexProvider.notifier).index =
108-
index;
109-
}
110-
});
111-
}
112-
: null;
101+
return Listener(
102+
onPointerDown: (PointerDownEvent event) {
103+
if (event.buttons & kBackMouseButton != 0 && context.canPop()) {
104+
context.pop();
105+
_updateNavigationIndex();
106+
}
107+
},
108+
child: SafeArea(
109+
child: NavigationView(
110+
key: _viewKey,
111+
contentShape: const RoundedRectangleBorder(
112+
side: BorderSide(width: 0, color: Colors.transparent),
113+
borderRadius: BorderRadius.only(topLeft: Radius.circular(8.0)),
114+
),
115+
appBar: NavigationAppBar(
116+
automaticallyImplyLeading: false,
117+
leading: () {
118+
final GoRouter router = ref.read(appRouterProvider);
119+
final List<RouteMatchBase> matches =
120+
router.routerDelegate.currentConfiguration.matches;
121+
final firstMatch = matches.first as ShellRouteMatch;
113122

114-
return NavigationPaneTheme(
115-
data: NavigationPaneTheme.of(context).merge(
116-
NavigationPaneThemeData(
117-
unselectedIconColor: WidgetStateProperty.resolveWith((
118-
states,
119-
) {
120-
if (states.isDisabled) {
121-
return ButtonThemeData.buttonColor(context, states);
123+
final VoidCallback? onPressed = firstMatch.matches.length > 1
124+
? () {
125+
context.pop();
126+
_updateNavigationIndex();
122127
}
123-
return ButtonThemeData.uncheckedInputColor(
124-
FluentTheme.of(context),
128+
: null;
129+
130+
return NavigationPaneTheme(
131+
data: NavigationPaneTheme.of(context).merge(
132+
NavigationPaneThemeData(
133+
unselectedIconColor: WidgetStateProperty.resolveWith((
125134
states,
126-
).basedOnLuminance();
127-
}),
135+
) {
136+
if (states.isDisabled) {
137+
return ButtonThemeData.buttonColor(context, states);
138+
}
139+
return ButtonThemeData.uncheckedInputColor(
140+
FluentTheme.of(context),
141+
states,
142+
).basedOnLuminance();
143+
}),
144+
),
128145
),
129-
),
130-
child: Builder(
131-
builder: (context) => PaneItem(
132-
icon: const Center(child: Icon(FluentIcons.back, size: 12.0)),
133-
title: Text(localizations.backButtonTooltip),
134-
body: const SizedBox.shrink(),
135-
enabled: enabled,
136-
).build(context, false, onPressed, displayMode: .compact),
137-
),
138-
);
139-
}(),
140-
title: const Text('Revision Tool'),
141-
actions: RepaintBoundary(child: WindowCaption()),
142-
),
143-
pane: NavigationPane(
144-
size: const NavigationPaneSize(openWidth: 300),
145-
selected: ref.watch(navigationIndexProvider),
146-
onItemPressed: (index) {
147-
final RouteMeta route = AppRoutes.navigationRoutes[index];
148-
ref.read(navigationIndexProvider.notifier).index = index;
149-
context.push(route.path);
150-
},
151-
displayMode: context.mqSize.width >= 800
152-
? PaneDisplayMode.open
153-
: PaneDisplayMode.minimal,
154-
header: RepaintBoundary(
155-
child: SizedBox(
156-
height: 90,
157-
// height: kOneLineTileHeight,
158-
child: Row(
159-
children: [
160-
const SizedBox(width: 5.0),
161-
ClipRRect(
162-
borderRadius: const BorderRadius.all(Radius.circular(30.0)),
163-
child: Image.file(
164-
width: imgXY,
165-
height: imgXY,
166-
cacheWidth: imgCacheSize,
167-
cacheHeight: imgCacheSize,
168-
_userImageFile,
146+
child: Builder(
147+
builder: (context) => PaneItem(
148+
icon: const Center(
149+
child: Icon(FluentIcons.back, size: 12.0),
169150
),
170-
),
171-
const SizedBox(width: 13.0),
172-
Column(
173-
mainAxisAlignment: .center,
174-
crossAxisAlignment: .start,
175-
children: [
176-
Text(
177-
_username ?? 'User',
178-
style: const TextStyle(
179-
fontWeight: FontWeight.w500,
180-
fontSize: 14,
181-
),
151+
title: Text(localizations.backButtonTooltip),
152+
body: const SizedBox.shrink(),
153+
enabled: onPressed != null,
154+
).build(context, false, onPressed, displayMode: .compact),
155+
),
156+
);
157+
}(),
158+
title: const Text('Revision Tool'),
159+
actions: Padding(
160+
padding: const .only(left: 50),
161+
child: RepaintBoundary(child: WindowCaption()),
162+
),
163+
),
164+
pane: NavigationPane(
165+
size: const NavigationPaneSize(openWidth: 300),
166+
selected: ref.watch(navigationIndexProvider),
167+
onItemPressed: (index) {
168+
if (index == ref.read(navigationIndexProvider)) return;
169+
170+
final RouteMeta route = AppRoutes.navigationRoutes[index];
171+
ref.read(navigationIndexProvider.notifier).index = index;
172+
context.push(route.path);
173+
},
174+
displayMode: context.mqSize.width >= 800 ? .open : .minimal,
175+
header: RepaintBoundary(
176+
child: SizedBox(
177+
height: 90,
178+
// height: kOneLineTileHeight,
179+
child: Row(
180+
children: [
181+
const SizedBox(width: 5.0),
182+
ClipRRect(
183+
clipBehavior: .hardEdge,
184+
borderRadius: const BorderRadius.all(
185+
Radius.circular(30.0),
182186
),
183-
const Text(
184-
'Proud ReviOS user',
185-
style: TextStyle(
186-
fontSize: 11,
187-
fontWeight: FontWeight.normal,
188-
),
187+
child: Image.file(
188+
width: imgXY,
189+
height: imgXY,
190+
cacheWidth: imgCacheSize,
191+
cacheHeight: imgCacheSize,
192+
_userImageFile,
189193
),
190-
],
191-
),
192-
],
194+
),
195+
const SizedBox(width: 13.0),
196+
Column(
197+
mainAxisAlignment: .center,
198+
crossAxisAlignment: .start,
199+
children: [
200+
Text(
201+
_username ?? 'User',
202+
style: const TextStyle(
203+
fontWeight: FontWeight.w500,
204+
fontSize: 14,
205+
),
206+
),
207+
const Text(
208+
'Proud ReviOS user',
209+
style: TextStyle(
210+
fontSize: 11,
211+
fontWeight: FontWeight.normal,
212+
),
213+
),
214+
],
215+
),
216+
],
217+
),
193218
),
194219
),
195-
),
196-
autoSuggestBox: Padding(
197-
padding: const EdgeInsets.symmetric(vertical: 4),
198-
child: AutoSuggestBox(
199-
key: _searchKey,
200-
trailingIcon: const Padding(
201-
padding: EdgeInsets.only(right: 7.0, bottom: 2),
202-
child: Icon(msicons.FluentIcons.search_20_regular),
220+
autoSuggestBox: Padding(
221+
padding: const .symmetric(vertical: 4),
222+
child: AutoSuggestBox(
223+
key: _searchKey,
224+
trailingIcon: const Padding(
225+
padding: .only(right: 7.0, bottom: 2),
226+
child: Icon(msicons.FluentIcons.search_20_regular),
227+
),
228+
focusNode: _searchFocusNode,
229+
controller: _searchController,
230+
placeholder: t.suggestionBoxPlaceholder,
231+
items: _searchItems,
203232
),
204-
focusNode: _searchFocusNode,
205-
controller: _searchController,
206-
placeholder: t.suggestionBoxPlaceholder,
207-
items: _searchItems,
208233
),
234+
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
235+
items: AppRoutes.mainPaneItems,
236+
footerItems: [
237+
...AppRoutes.footerPaneItems,
238+
PaneItemSeparator(color: Colors.transparent),
239+
],
209240
),
210-
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
211-
items: AppRoutes.mainPaneItems,
212-
footerItems: [
213-
...AppRoutes.footerPaneItems,
214-
PaneItemSeparator(color: Colors.transparent),
215-
],
216-
),
217-
paneBodyBuilder: (item, child) {
218-
final String? name = item?.key is ValueKey
219-
? (item!.key! as ValueKey).value.toString()
220-
: null;
221-
return FocusTraversalGroup(
222-
key: ValueKey('body$name'),
223-
child: Builder(
224-
builder: (context) => Column(
225-
children: [
226-
if (NavigationView.of(context).displayMode ==
227-
PaneDisplayMode.minimal)
228-
const Padding(
229-
padding: EdgeInsets.only(left: 13),
230-
child: PageHeaderBreadcrumbs(),
231-
)
232-
else
233-
const PageHeaderBreadcrumbs(),
234-
Expanded(child: widget.child),
235-
],
241+
paneBodyBuilder: (item, child) {
242+
final String? name = item?.key is ValueKey
243+
? (item!.key! as ValueKey).value.toString()
244+
: null;
245+
return FocusTraversalGroup(
246+
key: ValueKey('body$name'),
247+
child: Builder(
248+
builder: (context) => Column(
249+
children: [
250+
if (NavigationView.of(context).displayMode == .minimal)
251+
const Padding(
252+
padding: EdgeInsets.only(left: 13),
253+
child: PageHeaderBreadcrumbs(),
254+
)
255+
else
256+
const PageHeaderBreadcrumbs(),
257+
Expanded(child: widget.child),
258+
],
259+
),
236260
),
237-
),
238-
);
239-
},
240-
onOpenSearch: () => _searchFocusNode.requestFocus(),
261+
);
262+
},
263+
onOpenSearch: () => _searchFocusNode.requestFocus(),
264+
),
241265
),
242266
);
243267
}

0 commit comments

Comments
 (0)