Skip to content

Commit 2dc972c

Browse files
committed
refactor(router)!: create unified account navigation stack
BREAKING This commit refactors the entire account navigation flow to be more robust and maintainable. - A new top-level route /account has been created, which presents a new AccountPage as a full-screen modal dialog. - All previous top-level account-related routes (/settings, /saved-headlines, etc.) have been moved to become sub-routes of the new /account route. -The pageBuilder property on these sub-routes has been changed to a standard builder, ensuring they navigate within the parent account modal, creating a proper drill-down navigation experience. This change solves a bug where the modal dismiss button would fail after deep navigation and provides a cleaner, more unified architecture for the entire account section.
1 parent be4322d commit 2dc972c

File tree

1 file changed

+166
-168
lines changed

1 file changed

+166
-168
lines changed

lib/router/router.dart

Lines changed: 166 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
66
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/countries/add_country_to_follow_page.dart';
77
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/countries/followed_countries_list_page.dart';
88
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/manage_followed_items_page.dart';
9+
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/account_page.dart';
910
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/sources/add_source_to_follow_page.dart';
1011
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/sources/followed_sources_list_page.dart';
1112
import 'package:flutter_news_app_mobile_client_full_source_code/account/view/manage_followed_items/topics/add_topic_to_follow_page.dart';
@@ -228,6 +229,171 @@ GoRouter createRouter({
228229
),
229230
],
230231
),
232+
// --- Account Routes (Top-Level Modal) ---
233+
// This route defines the main '/account' page as a full-screen modal.
234+
// All other account-related pages (settings, saved items, etc.) are
235+
// defined as sub-routes of this page, creating a unified navigation
236+
// stack for the entire account section.
237+
GoRoute(
238+
path: Routes.account,
239+
name: Routes.accountName,
240+
pageBuilder: (context, state) =>
241+
const MaterialPage(fullscreenDialog: true, child: AccountPage()),
242+
routes: [
243+
// ShellRoute for settings to provide SettingsBloc to children
244+
ShellRoute(
245+
builder: (BuildContext context, GoRouterState state, Widget child) {
246+
final appBloc = context.read<AppBloc>();
247+
final userId = appBloc.state.user?.id;
248+
249+
return BlocProvider<SettingsBloc>(
250+
create: (context) {
251+
final settingsBloc = SettingsBloc(
252+
userAppSettingsRepository: context
253+
.read<DataRepository<UserAppSettings>>(),
254+
inlineAdCacheService: inlineAdCacheService,
255+
);
256+
if (userId != null) {
257+
settingsBloc.add(SettingsLoadRequested(userId: userId));
258+
} else {
259+
logger.warning(
260+
'User ID is null when creating SettingsBloc. '
261+
'Settings will not be loaded.',
262+
);
263+
}
264+
return settingsBloc;
265+
},
266+
child: child,
267+
);
268+
},
269+
routes: [
270+
GoRoute(
271+
path: Routes.settings,
272+
name: Routes.settingsName,
273+
builder: (context, state) => const SettingsPage(),
274+
routes: [
275+
GoRoute(
276+
path: Routes.settingsAppearance,
277+
name: Routes.settingsAppearanceName,
278+
builder: (context, state) => const AppearanceSettingsPage(),
279+
routes: [
280+
GoRoute(
281+
path: Routes.settingsAppearanceTheme,
282+
name: Routes.settingsAppearanceThemeName,
283+
builder: (context, state) => const ThemeSettingsPage(),
284+
),
285+
GoRoute(
286+
path: Routes.settingsAppearanceFont,
287+
name: Routes.settingsAppearanceFontName,
288+
builder: (context, state) => const FontSettingsPage(),
289+
),
290+
],
291+
),
292+
GoRoute(
293+
path: Routes.settingsFeed,
294+
name: Routes.settingsFeedName,
295+
builder: (context, state) => const FeedSettingsPage(),
296+
),
297+
GoRoute(
298+
path: Routes.settingsNotifications,
299+
name: Routes.settingsNotificationsName,
300+
builder: (context, state) =>
301+
const NotificationSettingsPage(),
302+
),
303+
GoRoute(
304+
path: Routes.settingsLanguage,
305+
name: Routes.settingsLanguageName,
306+
builder: (context, state) => const LanguageSettingsPage(),
307+
),
308+
],
309+
),
310+
],
311+
),
312+
GoRoute(
313+
path: Routes.manageFollowedItems,
314+
name: Routes.manageFollowedItemsName,
315+
builder: (context, state) => const ManageFollowedItemsPage(),
316+
routes: [
317+
GoRoute(
318+
path: Routes.followedTopicsList,
319+
name: Routes.followedTopicsListName,
320+
builder: (context, state) => const FollowedTopicsListPage(),
321+
routes: [
322+
GoRoute(
323+
path: Routes.addTopicToFollow,
324+
name: Routes.addTopicToFollowName,
325+
builder: (context, state) => const AddTopicToFollowPage(),
326+
),
327+
],
328+
),
329+
GoRoute(
330+
path: Routes.followedSourcesList,
331+
name: Routes.followedSourcesListName,
332+
builder: (context, state) => const FollowedSourcesListPage(),
333+
routes: [
334+
GoRoute(
335+
path: Routes.addSourceToFollow,
336+
name: Routes.addSourceToFollowName,
337+
builder: (context, state) => const AddSourceToFollowPage(),
338+
),
339+
],
340+
),
341+
GoRoute(
342+
path: Routes.followedCountriesList,
343+
name: Routes.followedCountriesListName,
344+
builder: (context, state) => const FollowedCountriesListPage(),
345+
routes: [
346+
GoRoute(
347+
path: Routes.addCountryToFollow,
348+
name: Routes.addCountryToFollowName,
349+
builder: (context, state) => const AddCountryToFollowPage(),
350+
),
351+
],
352+
),
353+
],
354+
),
355+
GoRoute(
356+
path: Routes.accountSavedHeadlines,
357+
name: Routes.accountSavedHeadlinesName,
358+
builder: (context, state) => const SavedHeadlinesPage(),
359+
routes: [
360+
GoRoute(
361+
path: Routes.accountArticleDetails,
362+
name: Routes.accountArticleDetailsName,
363+
builder: (context, state) {
364+
final headlineFromExtra = state.extra as Headline?;
365+
final headlineIdFromPath = state.pathParameters['id'];
366+
return MultiBlocProvider(
367+
providers: [
368+
BlocProvider(
369+
create: (context) => HeadlineDetailsBloc(
370+
headlinesRepository: context
371+
.read<DataRepository<Headline>>(),
372+
),
373+
),
374+
BlocProvider(
375+
create: (context) => SimilarHeadlinesBloc(
376+
headlinesRepository: context
377+
.read<DataRepository<Headline>>(),
378+
),
379+
),
380+
],
381+
child: HeadlineDetailsPage(
382+
initialHeadline: headlineFromExtra,
383+
headlineId: headlineFromExtra?.id ?? headlineIdFromPath,
384+
),
385+
);
386+
},
387+
),
388+
],
389+
),
390+
GoRoute(
391+
path: Routes.accountSavedFilters,
392+
name: Routes.accountSavedFiltersName,
393+
builder: (context, state) => const SavedFiltersPage(),
394+
),
395+
],
396+
),
231397

232398
// --- Entity Details Route (Top Level) ---
233399
//
@@ -369,174 +535,6 @@ GoRouter createRouter({
369535
);
370536
},
371537
),
372-
// --- Account-related routes (Top-Level Modals) ---
373-
// These routes are for pages launched from the AccountSheet. They are
374-
// defined at the top level and use `fullscreenDialog: true` to be
375-
// presented modally over the entire app, including the AppShell.
376-
377-
// ShellRoute for settings to provide SettingsBloc to children
378-
ShellRoute(
379-
builder: (BuildContext context, GoRouterState state, Widget child) {
380-
final appBloc = context.read<AppBloc>();
381-
final userId = appBloc.state.user?.id;
382-
383-
return BlocProvider<SettingsBloc>(
384-
create: (context) {
385-
final settingsBloc = SettingsBloc(
386-
userAppSettingsRepository: context
387-
.read<DataRepository<UserAppSettings>>(),
388-
inlineAdCacheService: inlineAdCacheService,
389-
);
390-
if (userId != null) {
391-
settingsBloc.add(SettingsLoadRequested(userId: userId));
392-
} else {
393-
logger.warning(
394-
'User ID is null when creating SettingsBloc. '
395-
'Settings will not be loaded.',
396-
);
397-
}
398-
return settingsBloc;
399-
},
400-
child: child,
401-
);
402-
},
403-
routes: [
404-
GoRoute(
405-
path: Routes.settings,
406-
name: Routes.settingsName,
407-
pageBuilder: (context, state) => const MaterialPage(
408-
fullscreenDialog: true,
409-
child: SettingsPage(),
410-
),
411-
routes: [
412-
GoRoute(
413-
path: Routes.settingsAppearance,
414-
name: Routes.settingsAppearanceName,
415-
builder: (context, state) => const AppearanceSettingsPage(),
416-
routes: [
417-
GoRoute(
418-
path: Routes.settingsAppearanceTheme,
419-
name: Routes.settingsAppearanceThemeName,
420-
builder: (context, state) => const ThemeSettingsPage(),
421-
),
422-
GoRoute(
423-
path: Routes.settingsAppearanceFont,
424-
name: Routes.settingsAppearanceFontName,
425-
builder: (context, state) => const FontSettingsPage(),
426-
),
427-
],
428-
),
429-
GoRoute(
430-
path: Routes.settingsFeed,
431-
name: Routes.settingsFeedName,
432-
builder: (context, state) => const FeedSettingsPage(),
433-
),
434-
GoRoute(
435-
path: Routes.settingsNotifications,
436-
name: Routes.settingsNotificationsName,
437-
builder: (context, state) => const NotificationSettingsPage(),
438-
),
439-
GoRoute(
440-
path: Routes.settingsLanguage,
441-
name: Routes.settingsLanguageName,
442-
builder: (context, state) => const LanguageSettingsPage(),
443-
),
444-
],
445-
),
446-
],
447-
),
448-
GoRoute(
449-
path: Routes.manageFollowedItems,
450-
name: Routes.manageFollowedItemsName,
451-
pageBuilder: (context, state) => const MaterialPage(
452-
fullscreenDialog: true,
453-
child: ManageFollowedItemsPage(),
454-
),
455-
routes: [
456-
GoRoute(
457-
path: Routes.followedTopicsList,
458-
name: Routes.followedTopicsListName,
459-
builder: (context, state) => const FollowedTopicsListPage(),
460-
routes: [
461-
GoRoute(
462-
path: Routes.addTopicToFollow,
463-
name: Routes.addTopicToFollowName,
464-
builder: (context, state) => const AddTopicToFollowPage(),
465-
),
466-
],
467-
),
468-
GoRoute(
469-
path: Routes.followedSourcesList,
470-
name: Routes.followedSourcesListName,
471-
builder: (context, state) => const FollowedSourcesListPage(),
472-
routes: [
473-
GoRoute(
474-
path: Routes.addSourceToFollow,
475-
name: Routes.addSourceToFollowName,
476-
builder: (context, state) => const AddSourceToFollowPage(),
477-
),
478-
],
479-
),
480-
GoRoute(
481-
path: Routes.followedCountriesList,
482-
name: Routes.followedCountriesListName,
483-
builder: (context, state) => const FollowedCountriesListPage(),
484-
routes: [
485-
GoRoute(
486-
path: Routes.addCountryToFollow,
487-
name: Routes.addCountryToFollowName,
488-
builder: (context, state) => const AddCountryToFollowPage(),
489-
),
490-
],
491-
),
492-
],
493-
),
494-
GoRoute(
495-
path: Routes.accountSavedHeadlines,
496-
name: Routes.accountSavedHeadlinesName,
497-
pageBuilder: (context, state) => const MaterialPage(
498-
fullscreenDialog: true,
499-
child: SavedHeadlinesPage(),
500-
),
501-
routes: [
502-
GoRoute(
503-
path: Routes.accountArticleDetails,
504-
name: Routes.accountArticleDetailsName,
505-
builder: (context, state) {
506-
final headlineFromExtra = state.extra as Headline?;
507-
final headlineIdFromPath = state.pathParameters['id'];
508-
return MultiBlocProvider(
509-
providers: [
510-
BlocProvider(
511-
create: (context) => HeadlineDetailsBloc(
512-
headlinesRepository: context
513-
.read<DataRepository<Headline>>(),
514-
),
515-
),
516-
BlocProvider(
517-
create: (context) => SimilarHeadlinesBloc(
518-
headlinesRepository: context
519-
.read<DataRepository<Headline>>(),
520-
),
521-
),
522-
],
523-
child: HeadlineDetailsPage(
524-
initialHeadline: headlineFromExtra,
525-
headlineId: headlineFromExtra?.id ?? headlineIdFromPath,
526-
),
527-
);
528-
},
529-
),
530-
],
531-
),
532-
GoRoute(
533-
path: Routes.accountSavedFilters,
534-
name: Routes.accountSavedFiltersName,
535-
pageBuilder: (context, state) => const MaterialPage(
536-
fullscreenDialog: true,
537-
child: SavedFiltersPage(),
538-
),
539-
),
540538
// --- Main App Shell ---
541539
StatefulShellRoute.indexedStack(
542540
builder: (context, state, navigationShell) {

0 commit comments

Comments
 (0)