diff --git a/lib/authentication/view/authentication_page.dart b/lib/authentication/view/authentication_page.dart index ef0edb4e..7c4a849d 100644 --- a/lib/authentication/view/authentication_page.dart +++ b/lib/authentication/view/authentication_page.dart @@ -48,20 +48,18 @@ class AuthenticationPage extends StatelessWidget { backgroundColor: Colors.transparent, elevation: 0, // Conditionally add the leading close button only in linking context - leading: - isLinkingContext - ? IconButton( - icon: const Icon(Icons.close), - tooltip: - MaterialLocalizations.of( - context, - ).closeButtonTooltip, // Accessibility - onPressed: () { - // Navigate back to the account page when close is pressed - context.goNamed(Routes.accountName); - }, - ) - : null, // No leading button if not linking (relies on system back if pushed) + leading: isLinkingContext + ? IconButton( + icon: const Icon(Icons.close), + tooltip: MaterialLocalizations.of( + context, + ).closeButtonTooltip, // Accessibility + onPressed: () { + // Navigate back to the account page when close is pressed + context.goNamed(Routes.accountName); + }, + ) + : null, // No leading button if not linking (relies on system back if pushed) ), body: SafeArea( child: BlocConsumer( @@ -130,15 +128,15 @@ class AuthenticationPage extends StatelessWidget { // --- Email Sign-In Button --- ElevatedButton.icon( icon: const Icon(Icons.email_outlined), - onPressed: - isLoading - ? null - : () { - context.goNamed( - Routes.requestCodeName, - extra: isLinkingContext, - ); - }, + onPressed: isLoading + ? null + : () { + context.goNamed( + isLinkingContext + ? Routes.linkingRequestCodeName + : Routes.requestCodeName, + ); + }, label: Text(l10n.authenticationEmailSignInButton), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( @@ -153,12 +151,11 @@ class AuthenticationPage extends StatelessWidget { if (showAnonymousButton) ...[ OutlinedButton.icon( icon: const Icon(Icons.person_outline), - onPressed: - isLoading - ? null - : () => context.read().add( - const AuthenticationAnonymousSignInRequested(), - ), + onPressed: isLoading + ? null + : () => context.read().add( + const AuthenticationAnonymousSignInRequested(), + ), label: Text(l10n.authenticationAnonymousSignInButton), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric( diff --git a/lib/authentication/view/request_code_page.dart b/lib/authentication/view/request_code_page.dart index 18548184..f4f95fec 100644 --- a/lib/authentication/view/request_code_page.dart +++ b/lib/authentication/view/request_code_page.dart @@ -60,7 +60,8 @@ class _RequestCodeView extends StatelessWidget { // If linking, go back to Auth page preserving the linking query param. context.goNamed( Routes.authenticationName, - queryParameters: {'context': 'linking'}, + queryParameters: + isLinkingContext ? {'context': 'linking'} : const {}, ); } else { // If normal sign-in, just go back to the Auth page. @@ -84,7 +85,9 @@ class _RequestCodeView extends StatelessWidget { } else if (state is AuthenticationCodeSentSuccess) { // Navigate to the code verification page on success, passing the email context.goNamed( - Routes.verifyCodeName, + isLinkingContext + ? Routes.linkingVerifyCodeName + : Routes.verifyCodeName, pathParameters: {'email': state.email}, ); } diff --git a/lib/main.dart b/lib/main.dart index ac841503..646499e1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,6 +34,7 @@ void main() async { enabled: true, builder: (context) => appWidget, tools: const [DeviceSection()], + backgroundColor: Colors.black87, ), ); } else { diff --git a/lib/router/router.dart b/lib/router/router.dart index cbe82da3..237809fe 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -135,23 +135,47 @@ GoRouter createRouter({ appStatus == AppStatus.authenticated) { print(' Redirect Decision: User is $appStatus.'); - final isLinkingContext = - currentUri.queryParameters['context'] == 'linking'; + final isLinkingContextQueryPresent = state.uri.queryParameters['context'] == 'linking'; + final isLinkingPathSegmentPresent = currentLocation.contains('/linking/'); - // If an authenticated/anonymous user tries to access the BASE /authentication path - // AND it's NOT for account linking, redirect them to the feed. - if (currentLocation == authenticationPath && !isLinkingContext) { + // Determine if the current location is part of any linking flow (either via query or path segment) + final isAnyLinkingContext = isLinkingContextQueryPresent || isLinkingPathSegmentPresent; + + // If an authenticated/anonymous user is on any authentication-related path: + if (currentLocation.startsWith(authenticationPath)) { + print(' Debug: Auth path detected. Current Location: $currentLocation'); + print(' Debug: URI Query Parameters: ${state.uri.queryParameters}'); + print(' Debug: isLinkingContextQueryPresent: $isLinkingContextQueryPresent'); + print(' Debug: isLinkingPathSegmentPresent: $isLinkingPathSegmentPresent'); + print(' Debug: isAnyLinkingContext evaluated to: $isAnyLinkingContext'); + + // If the user is authenticated, always redirect away from auth paths. + if (appStatus == AppStatus.authenticated) { + print( + ' Action: Authenticated user on auth path ($currentLocation). Redirecting to $feedPath', + ); + return feedPath; + } + + // If the user is anonymous, allow navigation within auth paths if in a linking context. + // Otherwise, redirect anonymous users trying to access non-linking auth paths to feed. + if (isAnyLinkingContext) { + print( + ' Action: Anonymous user on auth linking path ($currentLocation). Allowing navigation.', + ); + return null; + } else { + print( + ' Action: Anonymous user trying to access non-linking auth path ($currentLocation). Redirecting to $feedPath', + ); + return feedPath; + } + } + // Allow access to other routes (non-auth paths) print( - ' Action: $appStatus user trying to access base auth path without linking context. Redirecting to $feedPath', + ' Action: Allowing navigation to $currentLocation for $appStatus user (non-auth path).', ); - return feedPath; - } - - // Allow access to other routes (including auth sub-routes if linking, or any other app route) - print( - ' Action: Allowing navigation to $currentLocation for $appStatus user.', - ); - return null; + return null; } // Fallback (should ideally not be reached if all statuses are handled) @@ -200,24 +224,41 @@ GoRouter createRouter({ ); }, routes: [ + // Nested route for account linking flow (defined first for priority) GoRoute( - path: Routes.requestCode, // Use new path - name: Routes.requestCodeName, // Use new name - builder: (context, state) { - // Extract the linking context flag from 'extra', default to false. - final isLinking = (state.extra as bool?) ?? false; - return RequestCodePage(isLinkingContext: isLinking); - }, + path: Routes.accountLinking, // This is 'linking' + name: Routes.accountLinkingName, // Name for the linking segment + builder: (context, state) => const SizedBox.shrink(), // Placeholder + routes: [ + GoRoute( + path: Routes.requestCode, // Path: /authentication/linking/request-code + name: Routes.linkingRequestCodeName, + builder: (context, state) => + const RequestCodePage(isLinkingContext: true), + ), + GoRoute( + path: '${Routes.verifyCode}/:email', // Path: /authentication/linking/verify-code/:email + name: Routes.linkingVerifyCodeName, + builder: (context, state) { + final email = state.pathParameters['email']!; + return EmailCodeVerificationPage(email: email); + }, + ), + ], ), + // Non-linking authentication routes (defined after linking routes) GoRoute( - path: - '${Routes.verifyCode}/:email', // Use new path with email parameter - name: Routes.verifyCodeName, // Use new name + path: Routes.requestCode, + name: Routes.requestCodeName, + builder: (context, state) => + const RequestCodePage(isLinkingContext: false), + ), + GoRoute( + path: '${Routes.verifyCode}/:email', + name: Routes.verifyCodeName, builder: (context, state) { - final email = state.pathParameters['email']!; // Extract email - return EmailCodeVerificationPage( - email: email, - ); // Use renamed page + final email = state.pathParameters['email']!; + return EmailCodeVerificationPage(email: email); }, ), ], @@ -731,7 +772,7 @@ GoRouter createRouter({ .read< HtDataRepository >(), - ), + ), ), BlocProvider( create: diff --git a/lib/router/routes.dart b/lib/router/routes.dart index 39a2d27e..9164f1bc 100644 --- a/lib/router/routes.dart +++ b/lib/router/routes.dart @@ -58,6 +58,12 @@ abstract final class Routes { static const verifyCode = 'verify-code'; static const verifyCodeName = 'verifyCode'; + // Linking-specific authentication routes + static const linkingRequestCode = 'linking/request-code'; + static const linkingRequestCodeName = 'linkingRequestCode'; + static const linkingVerifyCode = 'linking/verify-code'; + static const linkingVerifyCodeName = 'linkingVerifyCode'; + // --- Settings Sub-Routes (relative to /account/settings) --- static const settingsAppearance = 'appearance'; static const settingsAppearanceName = 'settingsAppearance'; diff --git a/pubspec.lock b/pubspec.lock index 5227bc87..4194ec4f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" cli_config: dependency: transitive description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -270,6 +286,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_localizations: dependency: "direct main" description: flutter @@ -356,7 +380,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "028ebfa29ebc8f95c2da30fee68566723d1e6897" + resolved-ref: "3a8dc5ff81c59805fa59996517eb0fdf136a0b67" url: "https://github.com/headlines-toolkit/ht-auth-inmemory" source: git version: "0.0.0" @@ -501,10 +525,18 @@ packages: dependency: transitive description: name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.6.7" + js_interop: + dependency: "direct main" + description: + name: js_interop + sha256: "7ec859c296958ccea34dc770504bd3ff4ae52fdd9e7eeb2bacc7081ad476a1f5" + url: "https://pub.dev" + source: hosted + version: "0.0.1" json_annotation: dependency: transitive description: @@ -1050,10 +1082,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: