Skip to content

Commit af0328b

Browse files
committed
feat(app_shell): implement role-based navigation filtering
- Add core dependency for AppBloc and AppState - Implement role-based filtering for navigation destinations - Admin can see all destinations - Publisher can only see Overview and Content Management - Refactor widget build process to use BlocBuilder
1 parent 77f1cd6 commit af0328b

File tree

1 file changed

+140
-114
lines changed

1 file changed

+140
-114
lines changed

lib/app/view/app_shell.dart

Lines changed: 140 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:core/core.dart';
12
import 'package:flutter/material.dart';
23
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
34
import 'package:flutter_bloc/flutter_bloc.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,149 @@ 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+
// Filter the destinations based on the user's role.
62+
final accessibleDestinations = allDestinations.where((destination) {
63+
if (userRole == null) return false;
64+
65+
switch (userRole) {
66+
case DashboardUserRole.admin:
67+
// Admin can see all destinations.
68+
return true;
69+
case DashboardUserRole.publisher:
70+
// Publisher can only see Overview and Content Management.
71+
return destination.label == l10n.overview ||
72+
destination.label == l10n.contentManagement;
73+
case DashboardUserRole.none:
74+
return false;
75+
}
76+
}).toList();
77+
78+
return Scaffold(
79+
body: AdaptiveScaffold(
80+
selectedIndex: navigationShell.currentIndex,
81+
onSelectedIndexChange: (index) {
82+
navigationShell.goBranch(
83+
index,
84+
initialLocation: index == navigationShell.currentIndex,
85+
);
86+
},
87+
destinations: accessibleDestinations,
88+
leadingUnextendedNavRail: Padding(
89+
padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg),
90+
child: Icon(
7691
Icons.newspaper_outlined,
7792
color: theme.colorScheme.primary,
7893
),
79-
const SizedBox(width: AppSpacing.md),
80-
Text(
81-
l10n.dashboardTitle,
82-
style: theme.textTheme.titleLarge,
94+
),
95+
leadingExtendedNavRail: Padding(
96+
padding: const EdgeInsets.all(AppSpacing.lg),
97+
child: Row(
98+
children: [
99+
Icon(
100+
Icons.newspaper_outlined,
101+
color: theme.colorScheme.primary,
102+
),
103+
const SizedBox(width: AppSpacing.md),
104+
Text(
105+
l10n.dashboardTitle,
106+
style: theme.textTheme.titleLarge,
107+
),
108+
],
83109
),
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,
110+
),
111+
trailingNavRail: Builder(
112+
builder: (context) {
113+
final isExtended =
114+
Breakpoints.mediumLargeAndUp.isActive(context) ||
115+
Breakpoints.small.isActive(context);
116+
return Expanded(
117+
child: Padding(
118+
padding: const EdgeInsets.only(bottom: AppSpacing.lg),
119+
child: Column(
120+
mainAxisAlignment: MainAxisAlignment.end,
121+
children: [
122+
// Settings Tile - universally accessible to all roles.
123+
InkWell(
124+
onTap: () => context.goNamed(Routes.settingsName),
125+
child: Padding(
126+
padding: EdgeInsets.symmetric(
127+
vertical: AppSpacing.md,
128+
horizontal: isExtended ? 24 : 16,
115129
),
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,
130+
child: Row(
131+
mainAxisAlignment: isExtended
132+
? MainAxisAlignment.start
133+
: MainAxisAlignment.center,
134+
children: [
135+
Icon(
136+
Icons.settings_outlined,
137+
color: theme.colorScheme.onSurfaceVariant,
138+
size: 24,
139+
),
140+
if (isExtended) ...[
141+
const SizedBox(width: AppSpacing.lg),
142+
Text(
143+
l10n.settings,
144+
style: navRailLabelStyle,
145+
),
146+
],
147+
],
148+
),
149+
),
136150
),
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,
151+
// Sign Out Tile
152+
InkWell(
153+
onTap: () => context.read<AppBloc>().add(
154+
const AppLogoutRequested(),
155+
),
156+
child: Padding(
157+
padding: EdgeInsets.symmetric(
158+
vertical: AppSpacing.md,
159+
horizontal: isExtended ? 24 : 16,
146160
),
147-
if (isExtended) ...[
148-
const SizedBox(width: AppSpacing.lg),
149-
Text(
150-
l10n.signOut,
151-
style: navRailLabelStyle?.copyWith(
161+
child: Row(
162+
mainAxisAlignment: isExtended
163+
? MainAxisAlignment.start
164+
: MainAxisAlignment.center,
165+
children: [
166+
Icon(
167+
Icons.logout,
152168
color: theme.colorScheme.error,
169+
size: 24,
153170
),
154-
),
155-
],
156-
],
171+
if (isExtended) ...[
172+
const SizedBox(width: AppSpacing.lg),
173+
Text(
174+
l10n.signOut,
175+
style: navRailLabelStyle?.copyWith(
176+
color: theme.colorScheme.error,
177+
),
178+
),
179+
],
180+
],
181+
),
182+
),
157183
),
158-
),
184+
],
159185
),
160-
],
161-
),
186+
),
187+
);
188+
},
189+
),
190+
body: (_) => Padding(
191+
padding: const EdgeInsets.fromLTRB(
192+
0,
193+
AppSpacing.sm,
194+
AppSpacing.sm,
195+
AppSpacing.sm,
162196
),
163-
);
164-
},
165-
),
166-
body: (_) => Padding(
167-
padding: const EdgeInsets.fromLTRB(
168-
0,
169-
AppSpacing.sm,
170-
AppSpacing.sm,
171-
AppSpacing.sm,
197+
child: navigationShell,
198+
),
172199
),
173-
child: navigationShell,
174-
),
175-
),
200+
);
201+
},
176202
);
177203
}
178204
}

0 commit comments

Comments
 (0)