@@ -3,6 +3,7 @@ import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
33import 'package:flutter_bloc/flutter_bloc.dart' ;
44import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_bloc.dart' ;
55import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart' ;
6+ import 'package:flutter_news_app_web_dashboard_full_source_code/router/route_permissions.dart' ;
67import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart' ;
78import 'package:go_router/go_router.dart' ;
89import 'package:ui_kit/ui_kit.dart' ;
@@ -24,22 +25,17 @@ class AppShell extends StatelessWidget {
2425
2526 @override
2627 Widget build (BuildContext context) {
27- final l10n = AppLocalizationsX (context).l10n;
28- final theme = Theme .of (context);
28+ return BlocBuilder <AppBloc , AppState >(
29+ builder: (context, state) {
30+ final l10n = AppLocalizationsX (context).l10n;
31+ final theme = Theme .of (context);
32+ final userRole = state.user? .dashboardRole;
2933
30- // Use the same text style as the NavigationRail labels for consistency.
31- final navRailLabelStyle = theme.textTheme.labelMedium;
34+ // Use the same text style as the NavigationRail labels for consistency.
35+ final navRailLabelStyle = theme.textTheme.labelMedium;
3236
33- return Scaffold (
34- body: AdaptiveScaffold (
35- selectedIndex: navigationShell.currentIndex,
36- onSelectedIndexChange: (index) {
37- navigationShell.goBranch (
38- index,
39- initialLocation: index == navigationShell.currentIndex,
40- );
41- },
42- destinations: [
37+ // A complete list of all possible navigation destinations.
38+ final allDestinations = [
4339 NavigationDestination (
4440 icon: const Icon (Icons .dashboard_outlined),
4541 selectedIcon: const Icon (Icons .dashboard),
@@ -60,119 +56,172 @@ class AppShell extends StatelessWidget {
6056 selectedIcon: const Icon (Icons .settings_applications),
6157 label: l10n.appConfiguration,
6258 ),
63- ],
64- leadingUnextendedNavRail: Padding (
65- padding: const EdgeInsets .symmetric (vertical: AppSpacing .lg),
66- child: Icon (
67- Icons .newspaper_outlined,
68- color: theme.colorScheme.primary,
69- ),
70- ),
71- leadingExtendedNavRail: Padding (
72- padding: const EdgeInsets .all (AppSpacing .lg),
73- child: Row (
74- children: [
75- Icon (
59+ ];
60+
61+ // A parallel list of route names for permission checking, matching the
62+ // order of `allDestinations`.
63+ const allRouteNames = [
64+ Routes .overviewName,
65+ Routes .contentManagementName,
66+ Routes .userManagementName,
67+ Routes .appConfigurationName,
68+ ];
69+
70+ // Create a list of records containing the destination, its original
71+ // index, and its route name.
72+ final indexedDestinations = [
73+ for (var i = 0 ; i < allDestinations.length; i++ )
74+ (
75+ destination: allDestinations[i],
76+ originalIndex: i,
77+ routeName: allRouteNames[i],
78+ ),
79+ ];
80+
81+ // Filter the destinations based on the user's role and allowed routes.
82+ final allowedRoutes = routePermissions[userRole] ?? {};
83+ final accessibleNavItems = indexedDestinations
84+ .where ((item) => allowedRoutes.contains (item.routeName))
85+ .toList ();
86+
87+ final accessibleDestinations = accessibleNavItems
88+ .map ((item) => item.destination)
89+ .toList ();
90+
91+ // Find the current index in the list of *accessible* destinations.
92+ final selectedIndex = accessibleNavItems.indexWhere (
93+ (item) => item.originalIndex == navigationShell.currentIndex,
94+ );
95+
96+ return Scaffold (
97+ body: AdaptiveScaffold (
98+ selectedIndex: selectedIndex > - 1 ? selectedIndex : 0 ,
99+ onSelectedIndexChange: (index) {
100+ // Map the index from the accessible list back to the original
101+ // branch index.
102+ final originalBranchIndex =
103+ accessibleNavItems[index].originalIndex;
104+ navigationShell.goBranch (
105+ originalBranchIndex,
106+ initialLocation:
107+ originalBranchIndex == navigationShell.currentIndex,
108+ );
109+ },
110+ destinations: accessibleDestinations,
111+ leadingUnextendedNavRail: Padding (
112+ padding: const EdgeInsets .symmetric (vertical: AppSpacing .lg),
113+ child: Icon (
76114 Icons .newspaper_outlined,
77115 color: theme.colorScheme.primary,
78116 ),
79- const SizedBox (width: AppSpacing .md),
80- Text (
81- l10n.dashboardTitle,
82- style: theme.textTheme.titleLarge,
117+ ),
118+ leadingExtendedNavRail: Padding (
119+ padding: const EdgeInsets .all (AppSpacing .lg),
120+ child: Row (
121+ children: [
122+ Icon (
123+ Icons .newspaper_outlined,
124+ color: theme.colorScheme.primary,
125+ ),
126+ const SizedBox (width: AppSpacing .md),
127+ Text (
128+ l10n.dashboardTitle,
129+ style: theme.textTheme.titleLarge,
130+ ),
131+ ],
83132 ),
84- ],
85- ),
86- ),
87- trailingNavRail: Builder (
88- builder: (context) {
89- final isExtended =
90- Breakpoints .mediumLargeAndUp.isActive (context) ||
91- Breakpoints .small.isActive (context);
92- return Expanded (
93- child: Padding (
94- padding: const EdgeInsets .only (bottom: AppSpacing .lg),
95- child: Column (
96- mainAxisAlignment: MainAxisAlignment .end,
97- children: [
98- // Settings Tile
99- InkWell (
100- onTap: () => context.goNamed (Routes .settingsName),
101- child: Padding (
102- padding: EdgeInsets .symmetric (
103- vertical: AppSpacing .md,
104- horizontal: isExtended ? 24 : 16 ,
105- ),
106- child: Row (
107- mainAxisAlignment: isExtended
108- ? MainAxisAlignment .start
109- : MainAxisAlignment .center,
110- children: [
111- Icon (
112- Icons .settings_outlined,
113- color: theme.colorScheme.onSurfaceVariant,
114- size: 24 ,
133+ ),
134+ trailingNavRail: Builder (
135+ builder: (context) {
136+ final isExtended =
137+ Breakpoints .mediumLargeAndUp.isActive (context) ||
138+ Breakpoints .small.isActive (context);
139+ return Expanded (
140+ child: Padding (
141+ padding: const EdgeInsets .only (bottom: AppSpacing .lg),
142+ child: Column (
143+ mainAxisAlignment: MainAxisAlignment .end,
144+ children: [
145+ // Settings Tile - universally accessible to all roles.
146+ InkWell (
147+ onTap: () => context.goNamed (Routes .settingsName),
148+ child: Padding (
149+ padding: EdgeInsets .symmetric (
150+ vertical: AppSpacing .md,
151+ horizontal: isExtended ? 24 : 16 ,
115152 ),
116- if (isExtended) ...[
117- const SizedBox (width : AppSpacing .lg),
118- Text (
119- l10n.settings ,
120- style : navRailLabelStyle,
121- ),
122- ] ,
123- ] ,
124- ) ,
125- ),
126- ),
127- // Sign Out Tile
128- InkWell (
129- onTap : () => context. read < AppBloc >(). add (
130- const AppLogoutRequested () ,
131- ),
132- child : Padding (
133- padding : EdgeInsets . symmetric (
134- vertical : AppSpacing .md ,
135- horizontal : isExtended ? 24 : 16 ,
153+ child : Row (
154+ mainAxisAlignment : isExtended
155+ ? MainAxisAlignment .start
156+ : MainAxisAlignment .center ,
157+ children : [
158+ Icon (
159+ Icons .settings_outlined ,
160+ color : theme.colorScheme.onSurfaceVariant ,
161+ size : 24 ,
162+ ),
163+ if (isExtended) ...[
164+ const SizedBox (width : AppSpacing .lg),
165+ Text (
166+ l10n.settings,
167+ style : navRailLabelStyle ,
168+ ),
169+ ],
170+ ],
171+ ) ,
172+ ) ,
136173 ),
137- child : Row (
138- mainAxisAlignment : isExtended
139- ? MainAxisAlignment .start
140- : MainAxisAlignment .center ,
141- children : [
142- Icon (
143- Icons .logout,
144- color : theme.colorScheme.error ,
145- size : 24 ,
174+ // Sign Out Tile
175+ InkWell (
176+ onTap : () => context. read < AppBloc >(). add (
177+ const AppLogoutRequested () ,
178+ ),
179+ child : Padding (
180+ padding : EdgeInsets . symmetric (
181+ vertical : AppSpacing .md ,
182+ horizontal : isExtended ? 24 : 16 ,
146183 ),
147- if (isExtended) ...[
148- const SizedBox (width: AppSpacing .lg),
149- Text (
150- l10n.signOut,
151- style: navRailLabelStyle? .copyWith (
184+ child: Row (
185+ mainAxisAlignment: isExtended
186+ ? MainAxisAlignment .start
187+ : MainAxisAlignment .center,
188+ children: [
189+ Icon (
190+ Icons .logout,
152191 color: theme.colorScheme.error,
192+ size: 24 ,
153193 ),
154- ),
155- ],
156- ],
194+ if (isExtended) ...[
195+ const SizedBox (width: AppSpacing .lg),
196+ Text (
197+ l10n.signOut,
198+ style: navRailLabelStyle? .copyWith (
199+ color: theme.colorScheme.error,
200+ ),
201+ ),
202+ ],
203+ ],
204+ ),
205+ ),
157206 ),
158- ) ,
207+ ] ,
159208 ),
160- ],
161- ),
209+ ),
210+ );
211+ },
212+ ),
213+ body: (_) => Padding (
214+ padding: const EdgeInsets .fromLTRB (
215+ 0 ,
216+ AppSpacing .sm,
217+ AppSpacing .sm,
218+ AppSpacing .sm,
162219 ),
163- );
164- },
165- ),
166- body: (_) => Padding (
167- padding: const EdgeInsets .fromLTRB (
168- 0 ,
169- AppSpacing .sm,
170- AppSpacing .sm,
171- AppSpacing .sm,
220+ child: navigationShell,
221+ ),
172222 ),
173- child: navigationShell,
174- ),
175- ),
223+ );
224+ },
176225 );
177226 }
178227}
0 commit comments