Skip to content

Conversation

@amaury901130
Copy link
Collaborator

Description

  • Refine and streamline overall app navigation.
  • Add auth-aware routing: guard protected screens, redirect unauthenticated users to Login, and persist/restore the intended destination after sign-in.
Copilot finished reviewing on behalf of amaury901130 November 12, 2025 20:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements auth-aware routing with protected route guards and automatic redirection for unauthenticated users. The changes introduce a new Routes enum-based navigation system and refactor the routing architecture to handle authentication state changes.

  • Adds isLoggedIn() method to AuthCubit for checking authentication status
  • Implements nested route structure with auth and app route groups
  • Moves authentication state listening from app-level to route-level within the root '/' route

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
modules/domain/lib/bloc/auth/auth_cubit.dart Adds isLoggedIn() helper method to check if user is authenticated
app/pubspec.yaml Upgrades go_router from v7 to v17, adds flutter_dotenv and flutter_web_plugins dependencies
app/lib/presentation/ui/pages/splash/splash_page.dart Adds instant parameter to control splash screen delay duration
app/lib/presentation/ui/pages/login/login_page.dart Removes unused app_themes import
app/lib/presentation/ui/pages/home/home_view.dart Adds title and removes back button from home page AppBar
app/lib/presentation/navigation/routers.dart Complete rewrite introducing Routes enum and nested route structure with auth guards
app/lib/main/init.dart Replaces url_strategy package with flutter_web_plugins for URL strategy
app/lib/main/app.dart Moves BlocListener from app level to root route and simplifies builder fallback

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@amaury901130 amaury901130 force-pushed the enhancement/navigation-config branch 2 times, most recently from b062693 to c09b44e Compare November 12, 2025 20:28
@amaury901130 amaury901130 requested a review from Copilot November 12, 2025 20:28
Copilot finished reviewing on behalf of amaury901130 November 12, 2025 20:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

routes: [
GoRoute(
name: Routes.auth.name,
path: Routes.auth.path,
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested route structure has a critical issue: the auth route uses path: Routes.auth.path which evaluates to '/auth', but since it's a child route of the root '/', the full path becomes '//auth' (double slash). Child routes in go_router should use relative paths without the leading slash.

Change path: Routes.auth.path to path: Routes.auth.subPath (or just 'auth') to fix the path construction.

Suggested change
path: Routes.auth.path,
path: Routes.auth.subPath,
Copilot uses AI. Check for mistakes.
GoRoute(
name: Routes.app.name,
path: Routes.app.path,
builder: (context, state) => const SplashPage(),
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builder for the /app route returns const SplashPage() without the instant parameter, which defaults to true based on line 7 of splash_page.dart. However, this causes the splash screen to call _authCubit.onValidate() immediately, which will trigger navigation back through the BlocListener at the root route. This creates a circular navigation pattern and unnecessary re-validation.

Consider removing this builder entirely or using a different widget, as the app route should only serve as a container for its child routes, not display a splash screen.

Suggested change
builder: (context, state) => const SplashPage(),
builder: (context, state) => const SizedBox.shrink(),
Copilot uses AI. Check for mistakes.


void init() async {
WidgetsFlutterBinding.ensureInitialized();
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usePathUrlStrategy() call should be placed after WidgetsFlutterBinding.ensureInitialized() but before any other initialization. However, it's currently placed after the binding initialization but on a separate line without a comment. While functionally correct, consider adding a comment explaining why path-based URLs are being used instead of hash-based URLs (e.g., for better SEO, cleaner URLs).

Suggested change
WidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
// Use path-based URLs instead of hash-based URLs for better SEO and cleaner URLs.
Copilot uses AI. Check for mistakes.
Comment on lines 11 to 29
enum Routes {
auth,
login,
signUp,
app,
home;

String get path => '/$name';
String get subPath => name;

void nav({Object? extra}) {
Routers.authRouter.goNamed(
name,
extra: extra,
);
}

static GoRouter get router => Routers.authRouter;
}
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Routes enum lacks documentation explaining its purpose and how it should be used. Given its central role in the navigation architecture, consider adding documentation:

  • What each route represents
  • When to use path vs subPath
  • How the nav() method should be used
  • The relationship with the Routers class

This is especially important since this appears to be a new architectural pattern for the project.

Copilot uses AI. Check for mistakes.
path: Routes.auth.path,
redirect: (context, state) {
if (context.read<AuthCubit>().isLoggedIn()) {
return '${Routes.app.path}${Routes.home.path}';
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect logic has a critical issue: when accessing /auth route directly, it will be constructed with path '/$name' which evaluates to '/auth'. However, the redirect at line 62 returns '${Routes.app.path}${Routes.home.path}' which evaluates to '/app/home'. This creates inconsistent path construction - the auth route uses path while the redirect uses nested path concatenation. This may cause navigation issues.

Consider making the path construction consistent: either use Routes.app.path + '/' + Routes.home.subPath or restructure to avoid manual path concatenation.

Suggested change
return '${Routes.app.path}${Routes.home.path}';
return '${Routes.app.path}/${Routes.home.subPath}';
Copilot uses AI. Check for mistakes.
Comment on lines +37 to +54
builder: (context, state) {
return BlocListener<AuthCubit, Resource>(
listenWhen: (previous, current) => current is RSuccess,
listener: (_, state) {
if (state is RSuccess) {
switch (state.data) {
case AuthStateAuthenticated _:
Routes.app.nav();
break;
case AuthStateUnauthenticated _:
Routes.auth.nav();
break;
case _:
}
}
},
child: const SplashPage(instant: true),
);
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root route ('/') should not have a builder that displays a page. In go_router, when a route has child routes, the parent route should typically not have a builder, or it should use a ShellRoute pattern. The current implementation displays SplashPage at the root while also having nested auth and app routes, which creates ambiguous navigation behavior.

Consider either:

  1. Removing the builder and using redirect logic to navigate to the appropriate child route, or
  2. Using a ShellRoute if you need persistent UI across child routes.
Suggested change
builder: (context, state) {
return BlocListener<AuthCubit, Resource>(
listenWhen: (previous, current) => current is RSuccess,
listener: (_, state) {
if (state is RSuccess) {
switch (state.data) {
case AuthStateAuthenticated _:
Routes.app.nav();
break;
case AuthStateUnauthenticated _:
Routes.auth.nav();
break;
case _:
}
}
},
child: const SplashPage(instant: true),
);
redirect: (context, state) {
final authCubit = context.read<AuthCubit>();
if (authCubit.isLoggedIn()) {
return '${Routes.app.path}${Routes.home.path}';
} else {
return '${Routes.auth.path}${Routes.login.path}';
}
Copilot uses AI. Check for mistakes.
Comment on lines +60 to +67
redirect: (context, state) {
if (context.read<AuthCubit>().isLoggedIn()) {
return '${Routes.app.path}${Routes.home.path}';
}

/// Continue to auth routes
return null;
},
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect callback accesses context.read<AuthCubit>().isLoggedIn() synchronously, but the auth state might be in transition during navigation. If the AuthCubit state changes between the redirect check and when the BlocListener at the root responds (lines 38-52), it could cause race conditions leading to incorrect navigation. Consider adding state checks to ensure the auth state is stable (not in loading) before making redirect decisions.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +91
redirect: (context, state) {
if (!context.read<AuthCubit>().isLoggedIn()) {
return '${Routes.auth.path}${Routes.login.path}';
}

/// Continue to app routes
return null;
},
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect callback at the /app route has the same concurrency issue as the /auth route. The auth state might change during navigation, creating race conditions between this redirect and the BlocListener at the root (lines 38-52).

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 79
GoRoute(
name: Routes.auth.name,
path: Routes.auth.path,
redirect: (context, state) {
if (context.read<AuthCubit>().isLoggedIn()) {
return '${Routes.app.path}${Routes.home.path}';
}

/// Continue to auth routes
return null;
},
routes: [
GoRoute(
name: Routes.login.name,
path: Routes.login.subPath,
builder: (context, state) => const LoginPage(),
),
GoRoute(
name: Routes.signUp.name,
path: Routes.signUp.subPath,
builder: (context, state) => const SignUpPage(),
),
],
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /auth route lacks a builder but has child routes with builders. In go_router, when a parent route doesn't have a builder, navigating directly to the parent path (e.g., /auth) will result in a blank screen or error. Either add a redirect to a default child route (e.g., redirect to /auth/login when no child is specified), or add a builder with appropriate content.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +35
bool isLoggedIn() =>
state is RSuccess && (state as RSuccess).data is AuthStateAuthenticated;
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isLoggedIn() method performs runtime type checking with state as RSuccess without first verifying the cast will succeed. While the first condition state is RSuccess protects against null/wrong type, this pattern could be simplified. Consider using pattern matching or extracting the data check:

bool isLoggedIn() { return state case RSuccess(data: AuthStateAuthenticated()) => true, _ => false; }

Or using a safer approach:

bool isLoggedIn() { if (state is! RSuccess) return false; return (state as RSuccess).data is AuthStateAuthenticated; }
Suggested change
bool isLoggedIn() =>
state is RSuccess && (state as RSuccess).data is AuthStateAuthenticated;
// Use pattern matching for safer and more concise type checking.
bool isLoggedIn() {
return state case RSuccess(data: AuthStateAuthenticated()) => true, _ => false;
}
Copilot uses AI. Check for mistakes.
@amaury901130 amaury901130 force-pushed the enhancement/navigation-config branch from c09b44e to 0d72ffe Compare November 12, 2025 20:37
@amaury901130 amaury901130 force-pushed the enhancement/navigation-config branch from 0d72ffe to b10b72b Compare November 12, 2025 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants