Skip to content

Commit 24b5035

Browse files
committed
Refactor: use go_router StatefulShellRoute for main page navigation
Refactored the main page navigation to use go_router's `StatefulShellRoute.indexedStack`. This allows for better navigation management and state preservation within the main navigation structure. The `MainIndexPage` now manages the navigation shell and uses `AdaptiveNavigationScaffold` to render the bottom navigation bar or navigation rail based on the screen orientation. The `AdaptiveNavigationScaffold` component handles the adaptive layout and navigation logic. The routing configuration has been updated to include the new StatefulShellRoute for the main navigation.
1 parent dd05d45 commit 24b5035

File tree

3 files changed

+191
-54
lines changed

3 files changed

+191
-54
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import 'dart:math';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:go_router/go_router.dart';
5+
import 'package:rettulf/rettulf.dart';
6+
7+
typedef AdaptiveNavigationItem = ({String route, IconData icon, IconData activeIcon, String label});
8+
9+
extension _AdaptiveNavigationItemEX on AdaptiveNavigationItem {
10+
NavigationDestination toBarItem() {
11+
return NavigationDestination(
12+
icon: Icon(icon),
13+
selectedIcon: Icon(activeIcon),
14+
label: label,
15+
);
16+
}
17+
18+
NavigationRailDestination toRailDest() {
19+
return NavigationRailDestination(
20+
icon: Icon(icon),
21+
selectedIcon: Icon(activeIcon),
22+
label: Text(label),
23+
);
24+
}
25+
}
26+
27+
class AdaptiveNavigationScaffold extends StatelessWidget {
28+
final StatefulNavigationShell navigationShell;
29+
final Color? navigationBarColor;
30+
final Color? scaffoldBackgroundColor;
31+
final List<AdaptiveNavigationItem> items;
32+
final Widget Function(BuildContext context, Widget child)? bottomBarBuilder;
33+
34+
const AdaptiveNavigationScaffold({
35+
super.key,
36+
required this.navigationShell,
37+
required this.items,
38+
this.scaffoldBackgroundColor,
39+
this.navigationBarColor,
40+
this.bottomBarBuilder,
41+
});
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
if (context.isPortrait) {
46+
final bottomBarBuilder = this.bottomBarBuilder;
47+
final bottomBar = buildNavigationBar(context, items);
48+
return Scaffold(
49+
backgroundColor: scaffoldBackgroundColor,
50+
body: navigationShell,
51+
bottomNavigationBar: bottomBarBuilder != null ? bottomBarBuilder(context, bottomBar) : bottomBar,
52+
);
53+
} else {
54+
return Scaffold(
55+
backgroundColor: scaffoldBackgroundColor,
56+
body: [
57+
buildNavigationRail(context, items),
58+
navigationShell.expanded(),
59+
].row(),
60+
);
61+
}
62+
}
63+
64+
Widget buildNavigationBar(BuildContext context, List<AdaptiveNavigationItem> items) {
65+
return NavigationBar(
66+
backgroundColor: navigationBarColor,
67+
selectedIndex: getSelectedIndex(context, items),
68+
onDestinationSelected: (index) => onItemTapped(index, items),
69+
destinations: items.map((e) => e.toBarItem()).toList(),
70+
);
71+
}
72+
73+
Widget buildNavigationRail(BuildContext context, List<AdaptiveNavigationItem> items) {
74+
return NavigationRail(
75+
backgroundColor: navigationBarColor,
76+
groupAlignment: 0,
77+
labelType: NavigationRailLabelType.all,
78+
selectedIndex: getSelectedIndex(context, items),
79+
onDestinationSelected: (index) => onItemTapped(index, items),
80+
destinations: items.map((e) => e.toRailDest()).toList(),
81+
);
82+
}
83+
84+
int getSelectedIndex(BuildContext context, List<AdaptiveNavigationItem> items) {
85+
final location = GoRouterState.of(context).uri.toString();
86+
return max(0, items.indexWhere((item) => location.startsWith(item.route)));
87+
}
88+
89+
void onItemTapped(int index, List<AdaptiveNavigationItem> items) {
90+
final item = items[index];
91+
final branchIndex = navigationShell.route.routes.indexWhere((r) {
92+
if (r is GoRoute) {
93+
return r.path.startsWith(item.route);
94+
}
95+
return false;
96+
});
97+
navigationShell.goBranch(
98+
branchIndex >= 0 ? branchIndex : index,
99+
initialLocation: index == navigationShell.currentIndex,
100+
);
101+
}
102+
}

escape_wild/lib/route.dart

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,88 @@
1-
import 'package:escape_wild/ui/game/home.dart';
1+
import 'package:escape_wild/ui/game/index.dart';
2+
import 'package:escape_wild/ui/main/game.dart';
3+
import 'package:escape_wild/ui/main/index.dart';
4+
import 'package:escape_wild/ui/main/mine.dart';
5+
import 'package:flutter/widgets.dart';
26
import 'package:go_router/go_router.dart';
37

4-
import 'ui/main/home.dart';
5-
6-
// final _$main = GlobalKey<NavigatorState>();
7-
// final _$game = GlobalKey<NavigatorState>();
8+
final _$mainGame = GlobalKey<NavigatorState>();
9+
final _$mainMine = GlobalKey<NavigatorState>();
810

911
RoutingConfig buildRoutingConfig() {
1012
return RoutingConfig(
1113
routes: [
1214
GoRoute(
1315
path: "/",
14-
redirect: (ctx, state) => "/main",
16+
redirect: (ctx, state) => "/main/game",
1517
),
1618
GoRoute(
1719
path: "/main",
18-
builder: (ctx, state) => const MainHomepage(),
20+
redirect: (ctx, state) {
21+
if (state.fullPath == "/main") return "/main/game";
22+
return null;
23+
},
24+
routes: [
25+
StatefulShellRoute.indexedStack(
26+
builder: (ctx, state, navigationShell) {
27+
return MainIndexPage(navigationShell: navigationShell);
28+
},
29+
branches: [
30+
StatefulShellBranch(
31+
navigatorKey: _$mainGame,
32+
routes: [
33+
GoRoute(
34+
path: "/game",
35+
builder: (ctx, state) => const GamePage(),
36+
),
37+
],
38+
),
39+
StatefulShellBranch(
40+
navigatorKey: _$mainMine,
41+
routes: [
42+
GoRoute(
43+
path: "/mine",
44+
builder: (ctx, state) => const MinePage(),
45+
),
46+
],
47+
),
48+
],
49+
),
50+
],
1951
),
2052
GoRoute(
2153
path: "/game",
22-
builder: (ctx, state) => const GameHomepage(),
54+
builder: (ctx, state) => const GameIndexPage(),
55+
// redirect: (ctx, state) {
56+
// if (state.fullPath == "/game") return "/game/action";
57+
// return null;
58+
// },
59+
// routes: [
60+
// StatefulShellRoute.indexedStack(
61+
// builder: (ctx, state, navigationShell) {
62+
// return GameIndexPage(navigationShell: navigationShell);
63+
// },
64+
// branches: [
65+
// StatefulShellBranch(
66+
// navigatorKey: _$mainGame,
67+
// routes: [
68+
// GoRoute(
69+
// path: "/action",
70+
// builder: (ctx, state) => const GamePage(),
71+
// ),
72+
// ],
73+
// ),
74+
// StatefulShellBranch(
75+
// navigatorKey: _$mainMine,
76+
// routes: [
77+
// GoRoute(
78+
// path: "/mine",
79+
// builder: (ctx, state) => const MinePage(),
80+
// ),
81+
// ],
82+
// ),
83+
// ],
84+
// ),
85+
// ],
2386
),
2487
],
2588
);

escape_wild/lib/ui/main/index.dart

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,41 @@
11
import 'package:easy_localization/easy_localization.dart';
2+
import 'package:escape_wild/design/adaptive_navigation.dart';
23
import 'package:flutter/material.dart';
3-
4-
import 'game.dart';
5-
import 'mine.dart';
4+
import 'package:go_router/go_router.dart';
65

76
part 'index.i18n.dart';
87

98
class MainIndexPage extends StatefulWidget {
10-
const MainIndexPage({super.key});
9+
final StatefulNavigationShell navigationShell;
10+
11+
const MainIndexPage({
12+
super.key,
13+
required this.navigationShell,
14+
});
1115

1216
@override
1317
State<MainIndexPage> createState() => _MainIndexPageState();
1418
}
1519

16-
class _P {
17-
_P._();
18-
19-
static const game = 0;
20-
static const mine = 1;
21-
static const content = 1;
22-
}
23-
2420
class _MainIndexPageState extends State<MainIndexPage> {
25-
var curIndex = _P.game;
26-
2721
@override
2822
Widget build(BuildContext context) {
29-
return Scaffold(
30-
body: buildBody(),
31-
bottomNavigationBar: buildBottom(),
32-
);
33-
}
34-
35-
Widget buildBottom() {
36-
return BottomNavigationBar(
37-
type: BottomNavigationBarType.fixed,
38-
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
39-
currentIndex: curIndex,
40-
onTap: (newIndex) {
41-
if (newIndex != curIndex) {
42-
setState(() {
43-
curIndex = newIndex;
44-
});
45-
}
46-
},
23+
return AdaptiveNavigationScaffold(
24+
navigationShell: widget.navigationShell,
4725
items: [
48-
BottomNavigationBarItem(
26+
(
27+
route: "/game",
28+
icon: Icons.sports_esports_outlined,
29+
activeIcon: Icons.sports_esports_rounded,
4930
label: _I.game,
50-
icon: const Icon(Icons.sports_esports_outlined),
51-
activeIcon: const Icon(Icons.sports_esports_rounded),
5231
),
53-
BottomNavigationBarItem(
32+
(
33+
route: "/mine",
34+
icon: Icons.person_outline_rounded,
35+
activeIcon: Icons.person_rounded,
5436
label: _I.mine,
55-
icon: const Icon(Icons.person_outline_rounded),
56-
activeIcon: const Icon(Icons.person_rounded),
5737
),
5838
],
5939
);
6040
}
61-
62-
Widget buildBody() {
63-
if (curIndex == _P.game) {
64-
return const GamePage();
65-
} else /* if (curIndex == _P.mine) */ {
66-
return const MinePage();
67-
}
68-
}
6941
}

0 commit comments

Comments
 (0)