Repository: flutter
Issue: [go_router_builder] Support extendable routes
Labels: c: new feature,package,c: proposal,P3,p: go_router_builder,team-go_router,triaged-go_router

## Use case
Generating extensions on `GoRouteData` classes is a clean design but it becomes impossible to extend. Sometimes we may want different route behaviours under the same path base on runtime states (like mocking a route in testing). Another drawback is difficult to read runtime states in a route class. The only way I know is to declare the states globally (correct me if it's not true).

## Proposal
Probably we can create a class (E.g. `AppRoutes` in the example) to help manage the routes and generate a route creator for each route, then we can extend `AppRoutes` to override the route behaviours. I'm not sure if build runner can work like this way but all required params are given.

Codes we need to write:
``` dart
class HomeRoute extends GoRouteData {
  const HomeRoute(super.creator);
}

class CourseRoute extends GoRouteData {
  const CourseRoute(super.creator, this.id, {this.$extra});

  final String id;
  final CourseCacheData? $extra;
}

class WelcomeRoute extends GoRouteData {
  const WelcomeRoute(super.creator);
}

@TypedGoRoutes(routes: [
  TypedGoRoute<HomeRoute>(
    name: 'home',
    path: '/',
    routes: [
      TypedGoRoute<CourseRoute>(
        name: 'course',
        path: 'course/:id',
      ),
    ],
  ),
  TypedGoRoute<WelcomeRoute>(
    name: 'welcome',
    path: '/welcome',
  ),
])
class AppRoutes with _$AppRoutes {
  AppRoutes(this.loginInfo);
  final LoginInfo loginInfo;

  @override
  late final home = HomeRouteCreator(
    builder: (context, data) => const HomePage(),
  );

  @override
  late final course = CourseRouteCreator(
    builder: (context, data) => CoursePage(id: data.id, cache: data.$extra),
    redirect: (data) => loginInfo.isLoggedIn ? null : home().location,
  );

  @override
  late final welcome = WelcomeRouteCreator(
    builder: (context, data) => const WelcomePage(),
  );
}
```

Part of base classes:
``` dart
abstract class GoRouteData {
  const GoRouteData(this.creator);

  final GoRouteCreator? creator;

  String get location => creator?.$location(this) ?? '';

  Widget build(BuildContext context) => (creator?.builder ?? defaultBuilder)(context, this);
  Page<void> pageBuilder(BuildContext context) => (creator?.pageBuilder ?? defaultPageBuilder)(context, this);
  String? redirect() => (creator?.redirect ?? defaultRedirect)(this);
}

abstract class GoRouteCreator<T extends GoRouteData> {
  const GoRouteCreator({this.builder, this.pageBuilder, this.redirect});

  String $location(T data);
  T $fromState(GoRouterState state);
  GoRoute $route({required String path, List<GoRoute> routes = const []}) {
    return GoRouteData.$route(
      path: path,
      factory: $fromState,
      routes: routes,
    );
  } 

  final Widget Function(BuildContext context, T data)? builder;
  final Page<void> Function(BuilderContext context, T data)? pageBuilder;
  final String? Function(T data)? redirect;
}
```

Generated file:
``` dart
abstract class _$AppRoutes {
  List<GoRoute> get $routes => [
    home.$route(
      path: '/',
      routes: [
        course.$route(path: 'course/:id'),
      ],
    ),
    welcome.$route(path: '/welcome'),
  ];

  HomeRouteCreator get home;
  CourseRouteCreator get course;
  WelcomeRouteCreator get welcome;
}

class HomeRouteCreator extends GoRouteCreator<HomeRoute> {
  const HomeRouteCreator({
    super.builder,
    super.pageBuilder,
    super.redirect,
  });

  @override
  String $location(HomeRoute data) => '/';

  @override
  HomeRoute $fromState(GoRouterState state) => HomeRoute(this);

  HomeRoute call() => HomeRoute(this);
}

class CourseRouteCreator extends GoRouteCreator<CourseRoute> {
  const CourseRouteCreator({
    super.builder,
    super.pageBuilder,
    super.redirect,
  });

  @override
  String $location(CourseRoute data) => '/course/${data.id}';

  @override
  CourseRoute $fromState(GoRouterState state) => CourseRoute(this, state..params['id']!, $extra: state.extra as CourseCacheData?);

  CourseRoute call(String id, {CourseCacheData? $extra}) => CourseRoute(this, id, $extra: $extra);
}

class WelcomeRouteCreator extends GoRouteCreator<WelcomeRoute> {
  const WelcomeRouteCreator({
    super.builder,
    super.pageBuilder,
    super.redirect,
  });

  @override
  String $location(WelcomeRoute data) => '/welcome';

  @override
  WelcomeRoute $fromState(GoRouterState state) => WelcomeRoute(this);

  WelcomeRoute call() => WelcomeRoute(this);
}
```

Now we can get app routes like this:
``` dart
/// Gets [loginInfo] from somewhere.
final appRoutes = AppRoutes(loginInfo);
/// Passes [appRoutes.$routes] to [GoRouter].

/// Redirects to course route with id 'COURESID'.
appRoutes.course('COURESID').go(context);
```

In the end, thank you so much for your amazing work!