diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index b9ef60077e89..e58e89af124d 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.0.2 +- Adds support for `TypedRelativeGoRoute`. - Restricts `build` to versions less than 2.5.0. ## 3.0.1 diff --git a/packages/go_router_builder/example/lib/go_relative.dart b/packages/go_router_builder/example/lib/go_relative.dart new file mode 100644 index 000000000000..06a0a7585396 --- /dev/null +++ b/packages/go_router_builder/example/lib/go_relative.dart @@ -0,0 +1,198 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs, unreachable_from_main + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +part 'go_relative.g.dart'; + +void main() => runApp(const MyApp()); + +/// The main app. +class MyApp extends StatelessWidget { + /// Constructs a [MyApp] + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: _router, + ); + } +} + +/// The route configuration. +final GoRouter _router = GoRouter( + routes: $appRoutes, +); +const TypedRelativeGoRoute detailRoute = + TypedRelativeGoRoute( + path: 'details/:detailId', + routes: >[ + TypedRelativeGoRoute(path: 'settings/:settingId'), + ], +); + +@TypedGoRoute( + path: '/', + routes: >[ + TypedGoRoute( + path: '/dashboard', + routes: >[detailRoute], + ), + detailRoute, + ], +) +class HomeRoute extends GoRouteData with _$HomeRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const HomeScreen(); + } +} + +class DashboardRoute extends GoRouteData with _$DashboardRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const DashboardScreen(); + } +} + +class DetailsRoute extends RelativeGoRouteData with _$DetailsRoute { + const DetailsRoute({required this.detailId}); + final String detailId; + + @override + Widget build(BuildContext context, GoRouterState state) { + return DetailsScreen(id: detailId); + } +} + +class SettingsRoute extends RelativeGoRouteData with _$SettingsRoute { + const SettingsRoute({ + required this.settingId, + }); + final String settingId; + + @override + Widget build(BuildContext context, GoRouterState state) { + return SettingsScreen(id: settingId); + } +} + +/// The home screen +class HomeScreen extends StatelessWidget { + /// Constructs a [HomeScreen] + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home Screen')), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + const DetailsRoute(detailId: 'DetailsId').goRelative(context); + }, + child: const Text('Go to the Details screen'), + ), + ElevatedButton( + onPressed: () { + DashboardRoute().go(context); + }, + child: const Text('Go to the Dashboard screen'), + ), + ], + ), + ); + } +} + +/// The home screen +class DashboardScreen extends StatelessWidget { + /// Constructs a [DashboardScreen] + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Dashboard Screen')), + body: Column( + children: [ + ElevatedButton( + onPressed: () { + const DetailsRoute(detailId: 'DetailsId').goRelative(context); + }, + child: const Text('Go to the Details screen'), + ), + ElevatedButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ], + ), + ); + } +} + +/// The details screen +class DetailsScreen extends StatelessWidget { + /// Constructs a [DetailsScreen] + const DetailsScreen({ + super.key, + required this.id, + }); + + final String id; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Details Screen $id')), + body: Center( + child: Column( + children: [ + ElevatedButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ElevatedButton( + onPressed: () => const SettingsRoute( + settingId: 'SettingsId', + ).goRelative(context), + child: const Text('Go to the Settings screen'), + ), + ], + ), + ), + ); + } +} + +/// The details screen +class SettingsScreen extends StatelessWidget { + /// Constructs a [SettingsScreen] + const SettingsScreen({ + super.key, + required this.id, + }); + + final String id; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Settings Screen $id')), + body: Center( + child: TextButton( + onPressed: () => context.pop(), + child: const Text('Go back'), + ), + ), + ); + } +} diff --git a/packages/go_router_builder/example/lib/go_relative.g.dart b/packages/go_router_builder/example/lib/go_relative.g.dart new file mode 100644 index 000000000000..eb02878aac23 --- /dev/null +++ b/packages/go_router_builder/example/lib/go_relative.g.dart @@ -0,0 +1,152 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'go_relative.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $homeRoute, + ]; + +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + factory: _$HomeRoute._fromState, + routes: [ + GoRouteData.$route( + path: '/dashboard', + factory: _$DashboardRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'details/:detailId', + factory: _$DetailsRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'settings/:settingId', + factory: _$SettingsRoute._fromState, + ), + ], + ), + ], + ), + RelativeGoRouteData.$route( + path: 'details/:detailId', + factory: _$DetailsRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'settings/:settingId', + factory: _$SettingsRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$HomeRoute on GoRouteData { + static HomeRoute _fromState(GoRouterState state) => HomeRoute(); + + @override + String get location => GoRouteData.$location( + '/', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$DashboardRoute on GoRouteData { + static DashboardRoute _fromState(GoRouterState state) => DashboardRoute(); + + @override + String get location => GoRouteData.$location( + '/dashboard', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$DetailsRoute on RelativeGoRouteData { + static DetailsRoute _fromState(GoRouterState state) => DetailsRoute( + detailId: state.pathParameters['detailId']!, + ); + + DetailsRoute get _self => this as DetailsRoute; + + @override + String get location => RelativeGoRouteData.$location( + 'details/${Uri.encodeComponent(_self.detailId)}', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +mixin _$SettingsRoute on RelativeGoRouteData { + static SettingsRoute _fromState(GoRouterState state) => SettingsRoute( + settingId: state.pathParameters['settingId']!, + ); + + SettingsRoute get _self => this as SettingsRoute; + + @override + String get location => RelativeGoRouteData.$location( + 'settings/${Uri.encodeComponent(_self.settingId)}', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 93e165668d03..63f475d08908 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - go_router: ^16.0.0 + go_router: ^16.1.1 provider: 6.0.5 dev_dependencies: diff --git a/packages/go_router_builder/example/test/go_relative_test.dart b/packages/go_router_builder/example/test/go_relative_test.dart new file mode 100644 index 000000000000..3efd323e8c23 --- /dev/null +++ b/packages/go_router_builder/example/test/go_relative_test.dart @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_builder_example/go_relative.dart' as example; + +void main() { + testWidgets('example works', (WidgetTester tester) async { + await tester.pumpWidget(const example.MyApp()); + expect(find.byType(example.HomeScreen), findsOneWidget); + + // From Home screen, go to Details screen + await tester.tap(find.text('Go to the Details screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Settings screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.SettingsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.HomeScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Dashboard screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DashboardScreen), findsOneWidget); + + // From Dashboard screen, go to Details screen + await tester.tap(find.text('Go to the Details screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go to the Settings screen')); + await tester.pumpAndSettle(); + expect(find.byType(example.SettingsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DetailsScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.DashboardScreen), findsOneWidget); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.byType(example.HomeScreen), findsOneWidget); + }); +} diff --git a/packages/go_router_builder/lib/src/go_router_generator.dart b/packages/go_router_builder/lib/src/go_router_generator.dart index 88803b29e5cc..8b97374f62b4 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -16,6 +16,7 @@ const String _routeDataUrl = 'package:go_router/src/route_data.dart'; const Map _annotations = { 'TypedGoRoute': 'GoRouteData', + 'TypedRelativeGoRoute': 'RelativeGoRouteData', 'TypedShellRoute': 'ShellRouteData', 'TypedStatefulShellBranch': 'StatefulShellBranchData', 'TypedStatefulShellRoute': 'StatefulShellRouteData', diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index d95663d15acc..375a54608080 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -189,45 +189,12 @@ class StatefulShellBranchConfig extends RouteBaseConfig { String get dataConvertionFunctionName => r'$branch'; } -/// The configuration to generate class declarations for a GoRouteData. -class GoRouteConfig extends RouteBaseConfig { - GoRouteConfig._({ - required this.path, - required this.name, - required this.caseSensitive, - required this.parentNavigatorKey, - required super.routeDataClass, - required super.parent, - }) : super._(); - - /// The path of the GoRoute to be created by this configuration. - final String path; - - /// The name of the GoRoute to be created by this configuration. - final String? name; - - /// The case sensitivity of the GoRoute to be created by this configuration. - final bool caseSensitive; - - /// The parent navigator key. - final String? parentNavigatorKey; +/// A mixin that provides common functionality for GoRoute-based configurations. +mixin _GoRouteMixin on RouteBaseConfig { + String get _basePathForLocation; late final Set _pathParams = - pathParametersFromPattern(_rawJoinedPath); - - String get _rawJoinedPath { - final List pathSegments = []; - - RouteBaseConfig? config = this; - while (config != null) { - if (config is GoRouteConfig) { - pathSegments.add(config.path); - } - config = config.parent; - } - - return p.url.joinAll(pathSegments.reversed); - } + pathParametersFromPattern(_basePathForLocation); // construct path bits using parent bits // if there are any queryParam objects, add in the `queryParam` bits @@ -254,10 +221,13 @@ class GoRouteConfig extends RouteBaseConfig { return MapEntry(pathParameter, valueBuffer.toString()); }), ); - final String location = patternToPath(_rawJoinedPath, pathParameters); + final String location = patternToPath(_basePathForLocation, pathParameters); return "'$location'"; } + /// The definition of the mixin to be generated. + String get _mixinDefinition; + ParameterElement? get _extraParam => _ctor.parameters .singleWhereOrNull((ParameterElement element) => element.isExtraField); @@ -398,6 +368,79 @@ class GoRouteConfig extends RouteBaseConfig { ..._enumDeclarations(), ]; + /// Returns code representing the constant maps that contain the `enum` to + /// [String] mapping for each referenced enum. + Iterable _enumDeclarations() { + final Set enumParamTypes = {}; + + for (final ParameterElement ctorParam in [ + ..._ctorParams, + ..._ctorQueryParams, + ]) { + DartType potentialEnumType = ctorParam.type; + if (potentialEnumType is ParameterizedType && + (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { + potentialEnumType = + (ctorParam.type as ParameterizedType).typeArguments.first; + } + + if (potentialEnumType.isEnum) { + enumParamTypes.add(potentialEnumType as InterfaceType); + } + } + return enumParamTypes.map(_enumMapConst); + } + + @override + String get factorConstructorParameters => 'factory: $_mixinName._fromState,'; + + @override + String get dataConvertionFunctionName => r'$route'; +} + +/// The configuration to generate class declarations for a GoRouteData. +class GoRouteConfig extends RouteBaseConfig with _GoRouteMixin { + GoRouteConfig._({ + required this.path, + required this.name, + required this.caseSensitive, + required this.parentNavigatorKey, + required super.routeDataClass, + required super.parent, + }) : super._(); + + /// The path of the GoRoute to be created by this configuration. + final String path; + + /// The name of the GoRoute to be created by this configuration. + final String? name; + + /// The case sensitivity of the GoRoute to be created by this configuration. + final bool caseSensitive; + + /// The parent navigator key. + final String? parentNavigatorKey; + + String get _rawJoinedPath { + final List pathSegments = []; + + RouteBaseConfig? config = this; + while (config != null) { + if (config + case GoRouteConfig(:final String path) || + RelativeGoRouteConfig(:final String path)) { + pathSegments.add(path); + } + config = config.parent; + } + + return p.url.joinAll(pathSegments.reversed); + } + + @override + String get _basePathForLocation => _rawJoinedPath; + + @override String get _mixinDefinition { final bool hasMixin = getNodeDeclaration(routeDataClass) ?.withClause @@ -438,45 +481,93 @@ mixin $_mixinName on GoRouteData { '''; } - /// Returns code representing the constant maps that contain the `enum` to - /// [String] mapping for each referenced enum. - Iterable _enumDeclarations() { - final Set enumParamTypes = {}; + @override + String get routeConstructorParameters => ''' + path: ${escapeDartString(path)}, + ${name != null ? 'name: ${escapeDartString(name!)},' : ''} + ${caseSensitive ? '' : 'caseSensitive: $caseSensitive,'} + ${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'} +'''; - for (final ParameterElement ctorParam in [ - ..._ctorParams, - ..._ctorQueryParams, - ]) { - DartType potentialEnumType = ctorParam.type; - if (potentialEnumType is ParameterizedType && - (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { - potentialEnumType = - (ctorParam.type as ParameterizedType).typeArguments.first; - } + @override + String get routeDataClassName => 'GoRouteData'; +} - if (potentialEnumType.isEnum) { - enumParamTypes.add(potentialEnumType as InterfaceType); - } +/// The configuration to generate class declarations for a RelativeGoRouteConfig. +class RelativeGoRouteConfig extends RouteBaseConfig with _GoRouteMixin { + RelativeGoRouteConfig._({ + required this.path, + required this.caseSensitive, + required this.parentNavigatorKey, + required super.routeDataClass, + required super.parent, + }) : super._(); + + /// The path of the GoRoute to be created by this configuration. + final String path; + + /// The case sensitivity of the GoRoute to be created by this configuration. + final bool caseSensitive; + + /// The parent navigator key. + final String? parentNavigatorKey; + + @override + String get _basePathForLocation => path; + + @override + String get _mixinDefinition { + final bool hasMixin = getNodeDeclaration(routeDataClass) + ?.withClause + ?.mixinTypes + .any((NamedType e) => e.name2.toString() == _mixinName) ?? + false; + + if (!hasMixin) { + throw InvalidGenerationSourceError( + 'Missing mixin clause `with $_mixinName`', + element: routeDataClass, + ); } - return enumParamTypes.map(_enumMapConst); - } + return ''' +mixin $_mixinName on RelativeGoRouteData { + static $_className _fromState(GoRouterState state) $_fromStateConstructor + $_castedSelf @override - String get factorConstructorParameters => 'factory: $_mixinName._fromState,'; + String get location => RelativeGoRouteData.\$location($_locationArgs,$_locationQueryParams); + + @override + String get relativeLocation => './\$location'; + + @override + void goRelative(BuildContext context) => + context.go(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); +} +'''; + } @override String get routeConstructorParameters => ''' path: ${escapeDartString(path)}, - ${name != null ? 'name: ${escapeDartString(name!)},' : ''} ${caseSensitive ? '' : 'caseSensitive: $caseSensitive,'} ${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'} '''; @override - String get routeDataClassName => 'GoRouteData'; - - @override - String get dataConvertionFunctionName => r'$route'; + String get routeDataClassName => 'RelativeGoRouteData'; } /// Represents a `TypedGoRoute` annotation to the builder. @@ -513,6 +604,14 @@ abstract class RouteBaseConfig { assert(!reader.isNull, 'reader should not be null'); final InterfaceType type = reader.objectValue.type! as InterfaceType; final String typeName = type.element.name; + + if (parent is RelativeGoRouteConfig && typeName == 'TypedGoRoute') { + throw InvalidGenerationSourceError( + 'TypedRelativeGoRoute cannot have a TypedGoRoute as a child.', + element: element, + ); + } + final DartType typeParamType = type.typeArguments.single; if (typeParamType is! InterfaceType) { throw InvalidGenerationSourceError( @@ -611,6 +710,25 @@ abstract class RouteBaseConfig { parameterName: r'$parentNavigatorKey', ), ); + case 'TypedRelativeGoRoute': + final ConstantReader pathValue = reader.read('path'); + if (pathValue.isNull) { + throw InvalidGenerationSourceError( + 'Missing `path` value on annotation.', + element: element, + ); + } + final ConstantReader caseSensitiveValue = reader.read('caseSensitive'); + value = RelativeGoRouteConfig._( + path: pathValue.stringValue, + caseSensitive: caseSensitiveValue.boolValue, + routeDataClass: classElement, + parent: parent, + parentNavigatorKey: _generateParameterGetterCode( + classElement, + parameterName: r'$parentNavigatorKey', + ), + ); default: throw UnsupportedError('Unrecognized type $typeName'); } diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 7d7dde0c8931..7229af74fd9a 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 3.0.1 +version: 3.0.2 repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22 @@ -29,7 +29,7 @@ dev_dependencies: dart_style: '>=2.3.7 <4.0.0' flutter: sdk: flutter - go_router: ^15.1.0 + go_router: ^16.1.1 leak_tracker_flutter_testing: ">=3.0.0" package_config: ^2.1.1 pub_semver: ^2.1.5 diff --git a/packages/go_router_builder/test_inputs/go_relative.dart b/packages/go_router_builder/test_inputs/go_relative.dart new file mode 100644 index 000000000000..0ef85ed4b9ae --- /dev/null +++ b/packages/go_router_builder/test_inputs/go_relative.dart @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:go_router/go_router.dart'; + +mixin _$Route1 {} +mixin _$Route2 {} +mixin _$RelativeRoute {} +mixin _$InnerRelativeRoute {} + +const TypedRelativeGoRoute relativeRoute = + TypedRelativeGoRoute( + path: 'relative-route', + routes: >[ + TypedRelativeGoRoute(path: 'inner-relative-route') + ], +); + +@TypedGoRoute( + path: 'route-1', + routes: >[relativeRoute], +) +class Route1 extends GoRouteData with _$Route1 { + const Route1(); +} + +@TypedGoRoute( + path: 'route-2', + routes: >[relativeRoute], +) +class Route2 extends GoRouteData with _$Route2 { + const Route2(); +} + +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} + +class InnerRelativeRoute extends RelativeGoRouteData with _$InnerRelativeRoute { + const InnerRelativeRoute(); +} diff --git a/packages/go_router_builder/test_inputs/go_relative.dart.expect b/packages/go_router_builder/test_inputs/go_relative.dart.expect new file mode 100644 index 000000000000..45697254696a --- /dev/null +++ b/packages/go_router_builder/test_inputs/go_relative.dart.expect @@ -0,0 +1,132 @@ +RouteBase get $route1 => GoRouteData.$route( + path: 'route-1', + factory: _$Route1._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'relative-route', + factory: _$RelativeRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'inner-relative-route', + factory: _$InnerRelativeRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$Route1 on GoRouteData { + static Route1 _fromState(GoRouterState state) => const Route1(); + + @override + String get location => GoRouteData.$location( + 'route-1', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin _$RelativeRoute on RelativeGoRouteData { + static RelativeRoute _fromState(GoRouterState state) => const RelativeRoute(); + + @override + String get location => RelativeGoRouteData.$location( + 'relative-route', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +mixin _$InnerRelativeRoute on RelativeGoRouteData { + static InnerRelativeRoute _fromState(GoRouterState state) => + const InnerRelativeRoute(); + + @override + String get location => RelativeGoRouteData.$location( + 'inner-relative-route', + ); + + @override + String get relativeLocation => './$location'; + + @override + void goRelative(BuildContext context) => context.go(relativeLocation); + + @override + Future pushRelative(BuildContext context) => + context.push(relativeLocation); + + @override + void pushReplacementRelative(BuildContext context) => + context.pushReplacement(relativeLocation); + + @override + void replaceRelative(BuildContext context) => + context.replace(relativeLocation); +} + +RouteBase get $route2 => GoRouteData.$route( + path: 'route-2', + factory: _$Route2._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'relative-route', + factory: _$RelativeRoute._fromState, + routes: [ + RelativeGoRouteData.$route( + path: 'inner-relative-route', + factory: _$InnerRelativeRoute._fromState, + ), + ], + ), + ], + ); + +mixin _$Route2 on GoRouteData { + static Route2 _fromState(GoRouterState state) => const Route2(); + + @override + String get location => GoRouteData.$location( + 'route-2', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart b/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart new file mode 100644 index 000000000000..6642b4d44dbf --- /dev/null +++ b/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:go_router/go_router.dart'; + +mixin _$HomeRoute {} +mixin _$RelativeRoute {} +mixin _$NonRelativeRoute {} + +@TypedGoRoute( + path: '/', + routes: >[relativeRoute], +) +class HomeRoute extends GoRouteData with _$HomeRoute { + const HomeRoute(); +} + +const TypedRelativeGoRoute relativeRoute = + TypedRelativeGoRoute( + path: 'relative-route', + routes: >[ + TypedGoRoute(path: 'non-relative-route'), + ], +); + +class RelativeRoute extends RelativeGoRouteData with _$RelativeRoute { + const RelativeRoute(); +} + +class NonRelativeRoute extends GoRouteData with _$NonRelativeRoute { + const NonRelativeRoute(); +} diff --git a/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart.expect b/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart.expect new file mode 100644 index 000000000000..3d7274dfa222 --- /dev/null +++ b/packages/go_router_builder/test_inputs/invalid_relative_route_tree.dart.expect @@ -0,0 +1 @@ +TypedRelativeGoRoute cannot have a TypedGoRoute as a child. \ No newline at end of file diff --git a/packages/go_router_builder/test_inputs/no_mixin_relative.dart b/packages/go_router_builder/test_inputs/no_mixin_relative.dart new file mode 100644 index 000000000000..f6e44d2dc45d --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin_relative.dart @@ -0,0 +1,8 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:go_router/go_router.dart'; + +@TypedRelativeGoRoute(path: '/') +class HomeRoute extends RelativeGoRouteData {} diff --git a/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect new file mode 100644 index 000000000000..77df30f3c0a8 --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin_relative.dart.expect @@ -0,0 +1 @@ +Missing mixin clause `with _$HomeRoute` \ No newline at end of file