appRoutes top-level property

List<RouteBase> appRoutes
final

App routes configuration using StatefulShellRoute for state preservation

Routes structure:

  • /login (standalone, no shell)
  • /game/:gameId (standalone, no shell, full-screen)
  • StatefulShellRoute (with drawer, preserves state):
    • Branch 0: /home
    • Branch 1: /lobby
    • Branch 2: /history
    • Branch 3: /social
    • Branch 4: /about
    • Branch 5: /settings
      • /settings/profile

Implementation

final List<RouteBase> appRoutes = [
  // The web app is hosted at the game origin. Give its root a real route so a
  // cold load does not depend on the router's exception fallback.
  GoRoute(path: '/', redirect: (context, state) => '/home'),

  // Login route - outside shell (no drawer)
  GoRoute(
    path: '/login',
    name: 'login',
    parentNavigatorKey: rootNavigatorKey,
    builder: (context, state) => const LoginScreen(),
  ),

  // Game route - outside shell (parentNavigatorKey: rootNavigatorKey) so it
  // covers the shell entirely; pushed onto the root navigator so back returns
  // to the source screen (home, lobby, or history).
  GoRoute(
    path: '/game/:gameId',
    name: 'game',
    parentNavigatorKey: rootNavigatorKey,
    builder: (context, state) {
      final gameId = state.pathParameters['gameId']!;
      return GameScreen(gameId: gameId);
    },
    routes: [
      // Replay - full-screen over the game screen (root navigator), so back
      // returns to the finished game. Used both by a participant replaying
      // their own game and by a non-participant replaying a public one.
      GoRoute(
        path: 'replay',
        name: 'replay',
        parentNavigatorKey: rootNavigatorKey,
        builder: (context, state) {
          final gameId = state.pathParameters['gameId']!;
          return ReplayScreen(gameId: gameId);
        },
      ),
    ],
  ),

  // Join route - handles deep linking or manual code entry
  GoRoute(
    path: '/join/:code',
    name: 'join',
    parentNavigatorKey: rootNavigatorKey,
    builder: (context, state) {
      final code = state.pathParameters['code']!;
      return JoinGameScreen(code: code);
    },
  ),

  // Stateful shell route - preserves state across branch switches
  StatefulShellRoute.indexedStack(
    parentNavigatorKey: rootNavigatorKey,
    builder: (context, state, navigationShell) {
      // ShellScaffold now receives NavigationShell for built-in navigation
      return ShellScaffold(navigationShell: navigationShell);
    },
    branches: [
      // Branch 0: Home
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/home',
            name: 'home',
            builder: (context, state) => const HomeScreen(),
          ),
        ],
      ),

      // Branch 1: Lobby
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/lobby',
            name: 'lobby',
            builder: (context, state) => const LobbyScreen(),
          ),
        ],
      ),

      // Branch 2: History
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/history',
            name: 'history',
            builder: (context, state) => const HistoryScreen(),
          ),
        ],
      ),

      // Branch 3: Social
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/social',
            name: 'social',
            builder: (context, state) => const SocialScreen(),
          ),
        ],
      ),

      // Branch 4: About
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/about',
            name: 'about',
            builder: (context, state) => const AboutScreen(),
          ),
        ],
      ),

      // Branch 5: Settings
      StatefulShellBranch(
        routes: [
          GoRoute(
            path: '/settings',
            name: 'settings',
            builder: (context, state) => const SettingsScreen(),
            routes: [
              // Profile is a full-screen edit flow: it escapes the shell
              // intentionally so the drawer is not accessible while editing.
              GoRoute(
                path: 'profile',
                name: 'profile',
                parentNavigatorKey: rootNavigatorKey,
                builder: (context, state) => const ProfileScreen(),
              ),
              // Under settings rather than its own tab: a game that sells
              // nothing should not grow a navigation destination, and the
              // screen says so plainly when it is reached anyway.
              GoRoute(
                path: 'store',
                name: 'store',
                builder: (context, state) => const StoreScreen(),
              ),
            ],
          ),
        ],
      ),
    ],
  ),
];