@@ -18,6 +18,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/ads/models/ad_th
1818import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart' ;
1919import 'package:flutter_news_app_mobile_client_full_source_code/app/view/app_shell.dart' ;
2020import 'package:flutter_news_app_mobile_client_full_source_code/authentication/bloc/authentication_bloc.dart' ;
21+ import 'package:flutter_news_app_mobile_client_full_source_code/authentication/view/account_linking_page.dart' ;
2122import 'package:flutter_news_app_mobile_client_full_source_code/authentication/view/authentication_page.dart' ;
2223import 'package:flutter_news_app_mobile_client_full_source_code/authentication/view/email_code_verification_page.dart' ;
2324import 'package:flutter_news_app_mobile_client_full_source_code/authentication/view/request_code_page.dart' ;
@@ -86,6 +87,8 @@ GoRouter createRouter({
8687 // Add any other necessary observers here. If none, this can be an empty list.
8788 ],
8889 // --- Redirect Logic ---
90+ // This function is the single source of truth for route protection.
91+ // It's driven by the AppBloc's AppLifeCycleStatus.
8992 redirect: (BuildContext context, GoRouterState state) {
9093 final appStatus = context.read <AppBloc >().state.status;
9194 final currentLocation = state.matchedLocation;
@@ -98,141 +101,86 @@ GoRouter createRouter({
98101
99102 const rootPath = '/' ;
100103 const authenticationPath = Routes .authentication;
104+ const accountLinkingPath = Routes .accountLinking;
101105 const feedPath = Routes .feed;
102106 final isGoingToAuth = currentLocation.startsWith (authenticationPath);
103-
104- // With the current App startup architecture, the router is only active when
105- // the app is in a stable, running state. The `redirect` function's
106- // only responsibility is to handle auth-based route protection.
107- // States like `configFetching`, `underMaintenance`, etc., are now
108- // handled by the root App widget *before* this router is ever built.
107+ final isGoingToLinking = currentLocation.startsWith (accountLinkingPath);
109108
110109 // --- Case 1: Unauthenticated User ---
111- // If the user is unauthenticated, they should be on an auth path.
112- // If they are trying to access any other part of the app, redirect them .
110+ // If the user is unauthenticated, they must be on an auth path.
111+ // If they try to go anywhere else, they are redirected to the sign-in page .
113112 if (appStatus == AppLifeCycleStatus .unauthenticated) {
114113 print (' Redirect: User is unauthenticated.' );
115- // If they are already on an auth path, allow it. Otherwise, redirect.
116114 return isGoingToAuth ? null : authenticationPath;
117115 }
118116
119- // --- Case 2: Anonymous or Authenticated User ---
120- // If a user is anonymous or authenticated, they should not be able to
121- // access the main authentication flows, with an exception for account
122- // linking for anonymous users.
123- if (appStatus == AppLifeCycleStatus .anonymous ||
124- appStatus == AppLifeCycleStatus .authenticated) {
125- print (' Redirect: User is $appStatus .' );
126-
127- // If the user is trying to access an authentication path:
117+ // --- Case 2: Anonymous User ---
118+ // An anonymous user is partially authenticated. They can browse the app.
119+ if (appStatus == AppLifeCycleStatus .anonymous) {
120+ print (' Redirect: User is anonymous.' );
121+ // Block anonymous users from the main sign-in page.
128122 if (isGoingToAuth) {
129- // A fully authenticated user should never see auth pages.
130- if (appStatus == AppLifeCycleStatus .authenticated) {
131- print (
132- ' Action: Authenticated user on auth path. Redirecting to feed.' ,
133- );
134- return feedPath;
135- }
136-
137- // An anonymous user is only allowed on auth paths for account linking.
138- final isLinking =
139- state.uri.queryParameters['context' ] == 'linking' ||
140- currentLocation.contains ('/linking/' );
141-
142- if (isLinking) {
143- print (' Action: Anonymous user on linking path. Allowing.' );
144- return null ;
145- } else {
146- print (
147- ' Action: Anonymous user on non-linking auth path. Redirecting to feed.' ,
148- );
149- return feedPath;
150- }
123+ print (
124+ ' Action: Anonymous user on auth path. Redirecting to feed.' ,
125+ );
126+ return feedPath;
151127 }
128+ // If at the root, send them to the feed.
129+ if (currentLocation == rootPath) {
130+ print (' Action: User at root. Redirecting to feed.' );
131+ return feedPath;
132+ }
133+ // Allow navigation to other pages, including the new linking page.
134+ return null ;
135+ }
152136
153- // If the user is at the root path, they should be sent to the feed.
137+ // --- Case 3: Authenticated User ---
138+ // A fully authenticated user should be blocked from all auth/linking pages.
139+ if (appStatus == AppLifeCycleStatus .authenticated) {
140+ print (' Redirect: User is authenticated.' );
141+ if (isGoingToAuth || isGoingToLinking) {
142+ print (
143+ ' Action: Authenticated user on auth/linking path. Redirecting to feed.' ,
144+ );
145+ return feedPath;
146+ }
147+ // If at the root, send them to the feed.
154148 if (currentLocation == rootPath) {
155149 print (' Action: User at root. Redirecting to feed.' );
156150 return feedPath;
157151 }
158152 }
159153
160154 // --- Fallback ---
161- // For any other case, allow navigation.
155+ // For any other case (or if no conditions are met) , allow navigation.
162156 print (' Redirect: No condition met. Allowing navigation.' );
163157 return null ;
164158 },
165159 // --- Authentication Routes ---
166160 routes: [
167161 // A neutral root route that the app starts on. The redirect logic will
168- // immediately move the user to the correct location. This route's
169- // builder will never be called in practice.
162+ // immediately move the user to the correct location.
170163 GoRoute (path: '/' , builder: (context, state) => const SizedBox .shrink ()),
164+ // --- Simplified Authentication Route for New Users ---
171165 GoRoute (
172166 path: Routes .authentication,
173167 name: Routes .authenticationName,
174168 builder: (BuildContext context, GoRouterState state) {
175- final l10n = context.l10n;
176- // Determine context from query parameter
177- final isLinkingContext =
178- state.uri.queryParameters['context' ] == 'linking' ;
179-
180- // Define content based on context
181- final String headline;
182- final String subHeadline;
183- final bool showAnonymousButton;
184-
185- if (isLinkingContext) {
186- headline = l10n.authenticationLinkingHeadline;
187- subHeadline = l10n.authenticationLinkingSubheadline;
188- showAnonymousButton = false ;
189- } else {
190- headline = l10n.authenticationSignInHeadline;
191- subHeadline = l10n.authenticationSignInSubheadline;
192- showAnonymousButton = true ;
193- }
194-
169+ // This page is now self-contained and doesn't need parameters.
170+ // It's only for truly unauthenticated users.
195171 return BlocProvider (
196172 create: (context) => AuthenticationBloc (
197173 authenticationRepository: context.read <AuthRepository >(),
198174 ),
199- child: AuthenticationPage (
200- headline: headline,
201- subHeadline: subHeadline,
202- showAnonymousButton: showAnonymousButton,
203- isLinkingContext: isLinkingContext,
204- ),
175+ child: const AuthenticationPage (),
205176 );
206177 },
207178 routes: [
208- // Nested route for account linking flow (defined first for priority)
209- GoRoute (
210- path: Routes .accountLinking,
211- name: Routes .accountLinkingName,
212- builder: (context, state) => const SizedBox .shrink (),
213- routes: [
214- GoRoute (
215- path: Routes .requestCode,
216- name: Routes .linkingRequestCodeName,
217- builder: (context, state) =>
218- const RequestCodePage (isLinkingContext: true ),
219- ),
220- GoRoute (
221- path: '${Routes .verifyCode }/:email' ,
222- name: Routes .linkingVerifyCodeName,
223- builder: (context, state) {
224- final email = state.pathParameters['email' ]! ;
225- return EmailCodeVerificationPage (email: email);
226- },
227- ),
228- ],
229- ),
230- // Non-linking authentication routes (defined after linking routes)
179+ // Sub-routes for the standard sign-in flow.
231180 GoRoute (
232181 path: Routes .requestCode,
233182 name: Routes .requestCodeName,
234- builder: (context, state) =>
235- const RequestCodePage (isLinkingContext: false ),
183+ builder: (context, state) => const RequestCodePage (),
236184 ),
237185 GoRoute (
238186 path: '${Routes .verifyCode }/:email' ,
@@ -244,9 +192,40 @@ GoRouter createRouter({
244192 ),
245193 ],
246194 ),
195+ // --- New Top-Level Modal Route for Account Linking ---
196+ GoRoute (
197+ path: Routes .accountLinking,
198+ name: Routes .accountLinkingName,
199+ // Use pageBuilder to create a modal (fullscreen dialog) presentation.
200+ pageBuilder: (context, state) {
201+ return MaterialPage (
202+ fullscreenDialog: true ,
203+ child: BlocProvider (
204+ create: (context) => AuthenticationBloc (
205+ authenticationRepository: context.read <AuthRepository >(),
206+ ),
207+ child: const AccountLinkingPage (),
208+ ),
209+ );
210+ },
211+ routes: [
212+ // Nested routes for the account linking email/code flow.
213+ GoRoute (
214+ path: Routes .requestCode,
215+ name: Routes .accountLinkingRequestCodeName,
216+ builder: (context, state) => const RequestCodePage (),
217+ ),
218+ GoRoute (
219+ path: '${Routes .verifyCode }/:email' ,
220+ name: Routes .accountLinkingVerifyCodeName,
221+ builder: (context, state) {
222+ final email = state.pathParameters['email' ]! ;
223+ return EmailCodeVerificationPage (email: email);
224+ },
225+ ),
226+ ],
227+ ),
247228 // --- Entity Details Route (Top Level) ---
248- // This route handles displaying details for various content entities
249- // (Topic, Source, Country) based on path parameters.
250229 GoRoute (
251230 path: Routes .entityDetails,
252231 name: Routes .entityDetailsName,
@@ -300,29 +279,6 @@ GoRouter createRouter({
300279 },
301280 ),
302281 // --- Global Article Details Route (Top Level) ---
303- // This GoRoute provides a top-level, globally accessible way to view the
304- // HeadlineDetailsPage.
305- //
306- // Purpose:
307- // It is specifically designed for navigating to article details from contexts
308- // that are *outside* the main StatefulShellRoute's branches (e.g., from
309- // EntityDetailsPage, which is itself a top-level route, or potentially
310- // from other future top-level pages or deep links).
311- //
312- // Why it's necessary:
313- // Attempting to push a route that is deeply nested within a specific shell
314- // branch (like '/feed/article/:id') from a BuildContext outside of that
315- // shell can lead to navigator context issues and assertion failures.
316- // This global route avoids such problems by providing a clean, direct path
317- // to the HeadlineDetailsPage.
318- //
319- // How it differs:
320- // This route is distinct from the article detail routes nested within the
321- // StatefulShellRoute branches (e.g., Routes.articleDetailsName under /feed,
322- // Routes.searchArticleDetailsName under /search). Those nested routes are
323- // intended for navigation *within* their respective shell branches,
324- // preserving the shell's UI (like the bottom navigation bar).
325- // This global route, being top-level, will typically cover the entire screen.
326282 GoRoute (
327283 path: Routes .globalArticleDetails,
328284 name: Routes .globalArticleDetailsName,
0 commit comments