diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index b04d17ce59a1..2ab60143fd82 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.12 + +* Adds support for enhanced enums. [#105876](https://github.com/flutter/flutter/issues/105876). + ## 1.0.11 * Replaces mentions of the deprecated `GoRouteData.buildPage` with `GoRouteData.buildPageWithState`. diff --git a/packages/go_router_builder/example/lib/all_types.dart b/packages/go_router_builder/example/lib/all_types.dart index b9a35f96b178..8197697511a0 100644 --- a/packages/go_router_builder/example/lib/all_types.dart +++ b/packages/go_router_builder/example/lib/all_types.dart @@ -11,150 +11,353 @@ import 'shared/data.dart'; part 'all_types.g.dart'; -@TypedGoRoute( - path: '/:requiredBigIntField/:requiredBoolField/:requiredDateTimeField' - '/:requiredDoubleField/:requiredEnumField/:requiredIntField' - '/:requiredNumField/:requiredStringField/:requiredUriField', -) +@TypedGoRoute(path: '/', routes: >[ + TypedGoRoute(path: 'big-int-route/:requiredBigIntField'), + TypedGoRoute(path: 'bool-route/:requiredBoolField'), + TypedGoRoute(path: 'date-time-route/:requiredDateTimeField'), + TypedGoRoute(path: 'double-route/:requiredDoubleField'), + TypedGoRoute(path: 'int-route/:requiredIntField'), + TypedGoRoute(path: 'num-route/:requiredNumField'), + TypedGoRoute(path: 'double-route/:requiredDoubleField'), + TypedGoRoute(path: 'enum-route/:requiredEnumField'), + TypedGoRoute( + path: 'enhanced-enum-route/:requiredEnumField'), + TypedGoRoute(path: 'string-route/:requiredStringField'), + TypedGoRoute(path: 'uri-route/:requiredUriField'), +]) @immutable -class AllTypesRoute extends GoRouteData { - const AllTypesRoute({ +class AllTypesBaseRoute extends GoRouteData { + const AllTypesBaseRoute(); + + @override + Widget build(BuildContext context) => const BasePage( + dataTitle: 'Root', + param: null, + ); +} + +class BigIntRoute extends GoRouteData { + BigIntRoute({ required this.requiredBigIntField, - required this.requiredBoolField, - required this.requiredDateTimeField, - required this.requiredDoubleField, - required this.requiredEnumField, - required this.requiredIntField, - required this.requiredNumField, - required this.requiredStringField, - required this.requiredUriField, this.bigIntField, - this.boolField, - this.dateTimeField, - this.doubleField, - this.enumField, - this.intField, - this.numField, - this.stringField, - this.uriField, }); final BigInt requiredBigIntField; + final BigInt? bigIntField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'BigIntRoute', + param: requiredBigIntField, + queryParam: bigIntField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('BigIntRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class BoolRoute extends GoRouteData { + BoolRoute({ + required this.requiredBoolField, + this.boolField, + }); + final bool requiredBoolField; + final bool? boolField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'BoolRoute', + param: requiredBoolField, + queryParam: boolField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('BoolRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class DateTimeRoute extends GoRouteData { + DateTimeRoute({ + required this.requiredDateTimeField, + this.dateTimeField, + }); + final DateTime requiredDateTimeField; + final DateTime? dateTimeField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'DateTimeRoute', + param: requiredDateTimeField, + queryParam: dateTimeField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('DateTimeRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class DoubleRoute extends GoRouteData { + DoubleRoute({ + required this.requiredDoubleField, + this.doubleField, + }); + final double requiredDoubleField; - final PersonDetails requiredEnumField; + final double? doubleField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'DoubleRoute', + param: requiredDoubleField, + queryParam: doubleField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('DoubleRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class IntRoute extends GoRouteData { + IntRoute({ + required this.requiredIntField, + this.intField, + }); + final int requiredIntField; + final int? intField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'IntRoute', + param: requiredIntField, + queryParam: intField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('IntRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class NumRoute extends GoRouteData { + NumRoute({ + required this.requiredNumField, + this.numField, + }); + final num requiredNumField; - final String requiredStringField; - final Uri requiredUriField; + final num? numField; - final BigInt? bigIntField; - final bool? boolField; - final DateTime? dateTimeField; - final double? doubleField; + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'NumRoute', + param: requiredNumField, + queryParam: numField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('NumRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class EnumRoute extends GoRouteData { + EnumRoute({ + required this.requiredEnumField, + this.enumField, + }); + + final PersonDetails requiredEnumField; final PersonDetails? enumField; - final int? intField; - final num? numField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'EnumRoute', + param: requiredEnumField, + queryParam: enumField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('EnumRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class EnhancedEnumRoute extends GoRouteData { + EnhancedEnumRoute({ + required this.requiredEnumField, + this.enumField, + }); + + final SportDetails requiredEnumField; + final SportDetails? enumField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'EnhancedEnumRoute', + param: requiredEnumField, + queryParam: enumField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('EnhancedEnumRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class StringRoute extends GoRouteData { + StringRoute({ + required this.requiredStringField, + this.stringField, + }); + + final String requiredStringField; final String? stringField; + + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'StringRoute', + param: requiredStringField, + queryParam: stringField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('StringRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class UriRoute extends GoRouteData { + UriRoute({ + required this.requiredUriField, + this.uriField, + }); + + final Uri requiredUriField; final Uri? uriField; + @override + Widget build(BuildContext context) => BasePage( + dataTitle: 'UriRoute', + param: requiredUriField, + queryParam: uriField, + ); + + Widget drawerTile(BuildContext context) => ListTile( + title: const Text('UriRoute'), + onTap: () => go(context), + selected: GoRouter.of(context).location == location, + ); +} + +class BasePage extends StatelessWidget { + const BasePage({ + required this.dataTitle, + required this.param, + this.queryParam, + super.key, + }); + + final String dataTitle; + final T param; + final T? queryParam; + @override Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: const Text('Go router typed routes'), + ), + drawer: Drawer( + child: ListView( + children: [ + BigIntRoute( + requiredBigIntField: BigInt.two, + bigIntField: BigInt.zero, + ).drawerTile(context), + BoolRoute( + requiredBoolField: true, + boolField: false, + ).drawerTile(context), + DateTimeRoute( + requiredDateTimeField: DateTime(1970), + dateTimeField: DateTime(0), + ).drawerTile(context), + DoubleRoute( + requiredDoubleField: 3.14, + doubleField: -3.14, + ).drawerTile(context), + IntRoute( + requiredIntField: 42, + intField: -42, + ).drawerTile(context), + NumRoute( + requiredNumField: 2.71828, + numField: -2.71828, + ).drawerTile(context), + StringRoute( + requiredStringField: r'$!/#bob%%20', + stringField: r'$!/#bob%%20', + ).drawerTile(context), + EnumRoute( + requiredEnumField: PersonDetails.favoriteSport, + enumField: PersonDetails.favoriteFood, + ).drawerTile(context), + EnhancedEnumRoute( + requiredEnumField: SportDetails.football, + enumField: SportDetails.volleyball, + ).drawerTile(context), + UriRoute( + requiredUriField: Uri.parse('https://dart.dev'), + uriField: Uri.parse('https://dart.dev'), + ).drawerTile(context), + ], + )), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text('built!'), - SelectableText(location), + const Text('Built!'), + Text(dataTitle), + Text('Param: $param'), + Text('Query param: $queryParam'), + SelectableText(GoRouter.of(context).location), ], ), ), ); - - @override - int get hashCode => Object.hashAll(_items); - - @override - bool operator ==(Object other) { - if (other is AllTypesRoute) { - final List mine = _items; - final List theirs = other._items; - for (int i = 0; i < mine.length; i++) { - if (mine[i] != theirs[i]) { - return false; - } - } - } - return true; - } - - List get _items => [ - requiredBigIntField, - requiredBoolField, - requiredDateTimeField, - requiredDoubleField, - requiredEnumField, - requiredIntField, - requiredNumField, - requiredStringField, - requiredUriField, - bigIntField, - boolField, - dateTimeField, - doubleField, - enumField, - intField, - numField, - stringField, - uriField, - ]; } void main() => runApp(AllTypesApp()); class AllTypesApp extends StatelessWidget { - AllTypesApp({Key? key}) : super(key: key); + AllTypesApp({super.key}); @override Widget build(BuildContext context) => MaterialApp.router( routeInformationParser: _router.routeInformationParser, routerDelegate: _router.routerDelegate, + routeInformationProvider: _router.routeInformationProvider, ); late final GoRouter _router = GoRouter( debugLogDiagnostics: true, routes: $appRoutes, - - // redirect to the login page if the user is not logged in - redirect: (GoRouterState state) { - if (state.location == '/') { - final String location = AllTypesRoute( - requiredBigIntField: BigInt.two, - requiredBoolField: true, - requiredDateTimeField: DateTime.now(), - requiredDoubleField: 3.14, - requiredEnumField: PersonDetails.favoriteSport, - requiredIntField: -42, - requiredNumField: 3.15, - requiredStringField: r'$!/#bob%%20', - requiredUriField: Uri.parse('https://dart.dev'), - bigIntField: BigInt.zero, - boolField: false, - dateTimeField: DateTime(0), - doubleField: 3.14, - enumField: PersonDetails.favoriteSport, - intField: -42, - numField: 3.15, - stringField: r'$!/#bob%%20', - uriField: Uri.parse('https://dart.dev'), - ).location; - - return location; - } - - // no need to redirect at all - return null; - }, + initialLocation: const AllTypesBaseRoute().location, ); } diff --git a/packages/go_router_builder/example/lib/all_types.g.dart b/packages/go_router_builder/example/lib/all_types.g.dart index 26435376edc6..4aeb4173f408 100644 --- a/packages/go_router_builder/example/lib/all_types.g.dart +++ b/packages/go_router_builder/example/lib/all_types.g.dart @@ -9,57 +9,256 @@ part of 'all_types.dart'; // ************************************************************************** List get $appRoutes => [ - $allTypesRoute, + $allTypesBaseRoute, ]; -GoRoute get $allTypesRoute => GoRouteData.$route( - path: - '/:requiredBigIntField/:requiredBoolField/:requiredDateTimeField/:requiredDoubleField/:requiredEnumField/:requiredIntField/:requiredNumField/:requiredStringField/:requiredUriField', - factory: $AllTypesRouteExtension._fromState, +GoRoute get $allTypesBaseRoute => GoRouteData.$route( + path: '/', + factory: $AllTypesBaseRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'big-int-route/:requiredBigIntField', + factory: $BigIntRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'bool-route/:requiredBoolField', + factory: $BoolRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'date-time-route/:requiredDateTimeField', + factory: $DateTimeRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'double-route/:requiredDoubleField', + factory: $DoubleRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'int-route/:requiredIntField', + factory: $IntRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'num-route/:requiredNumField', + factory: $NumRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'double-route/:requiredDoubleField', + factory: $DoubleRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'enum-route/:requiredEnumField', + factory: $EnumRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'enhanced-enum-route/:requiredEnumField', + factory: $EnhancedEnumRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'string-route/:requiredStringField', + factory: $StringRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'uri-route/:requiredUriField', + factory: $UriRouteExtension._fromState, + ), + ], ); -extension $AllTypesRouteExtension on AllTypesRoute { - static AllTypesRoute _fromState(GoRouterState state) => AllTypesRoute( +extension $AllTypesBaseRouteExtension on AllTypesBaseRoute { + static AllTypesBaseRoute _fromState(GoRouterState state) => + const AllTypesBaseRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $BigIntRouteExtension on BigIntRoute { + static BigIntRoute _fromState(GoRouterState state) => BigIntRoute( requiredBigIntField: BigInt.parse(state.params['requiredBigIntField']!), - requiredBoolField: _$boolConverter(state.params['requiredBoolField']!), - requiredDateTimeField: - DateTime.parse(state.params['requiredDateTimeField']!), - requiredDoubleField: double.parse(state.params['requiredDoubleField']!), - requiredEnumField: _$PersonDetailsEnumMap - ._$fromName(state.params['requiredEnumField']!), - requiredIntField: int.parse(state.params['requiredIntField']!), - requiredNumField: num.parse(state.params['requiredNumField']!), - requiredStringField: state.params['requiredStringField']!, - requiredUriField: Uri.parse(state.params['requiredUriField']!), bigIntField: _$convertMapValue('big-int-field', state.queryParams, BigInt.parse), + ); + + String get location => GoRouteData.$location( + '/big-int-route/${Uri.encodeComponent(requiredBigIntField.toString())}', + queryParams: { + if (bigIntField != null) 'big-int-field': bigIntField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $BoolRouteExtension on BoolRoute { + static BoolRoute _fromState(GoRouterState state) => BoolRoute( + requiredBoolField: _$boolConverter(state.params['requiredBoolField']!), boolField: _$convertMapValue('bool-field', state.queryParams, _$boolConverter), + ); + + String get location => GoRouteData.$location( + '/bool-route/${Uri.encodeComponent(requiredBoolField.toString())}', + queryParams: { + if (boolField != null) 'bool-field': boolField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $DateTimeRouteExtension on DateTimeRoute { + static DateTimeRoute _fromState(GoRouterState state) => DateTimeRoute( + requiredDateTimeField: + DateTime.parse(state.params['requiredDateTimeField']!), dateTimeField: _$convertMapValue( 'date-time-field', state.queryParams, DateTime.parse), + ); + + String get location => GoRouteData.$location( + '/date-time-route/${Uri.encodeComponent(requiredDateTimeField.toString())}', + queryParams: { + if (dateTimeField != null) + 'date-time-field': dateTimeField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $DoubleRouteExtension on DoubleRoute { + static DoubleRoute _fromState(GoRouterState state) => DoubleRoute( + requiredDoubleField: double.parse(state.params['requiredDoubleField']!), doubleField: _$convertMapValue('double-field', state.queryParams, double.parse), - enumField: _$convertMapValue( - 'enum-field', state.queryParams, _$PersonDetailsEnumMap._$fromName), + ); + + String get location => GoRouteData.$location( + '/double-route/${Uri.encodeComponent(requiredDoubleField.toString())}', + queryParams: { + if (doubleField != null) 'double-field': doubleField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $IntRouteExtension on IntRoute { + static IntRoute _fromState(GoRouterState state) => IntRoute( + requiredIntField: int.parse(state.params['requiredIntField']!), intField: _$convertMapValue('int-field', state.queryParams, int.parse), + ); + + String get location => GoRouteData.$location( + '/int-route/${Uri.encodeComponent(requiredIntField.toString())}', + queryParams: { + if (intField != null) 'int-field': intField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $NumRouteExtension on NumRoute { + static NumRoute _fromState(GoRouterState state) => NumRoute( + requiredNumField: num.parse(state.params['requiredNumField']!), numField: _$convertMapValue('num-field', state.queryParams, num.parse), - stringField: state.queryParams['string-field'], - uriField: _$convertMapValue('uri-field', state.queryParams, Uri.parse), ); String get location => GoRouteData.$location( - '/${Uri.encodeComponent(requiredBigIntField.toString())}/${Uri.encodeComponent(requiredBoolField.toString())}/${Uri.encodeComponent(requiredDateTimeField.toString())}/${Uri.encodeComponent(requiredDoubleField.toString())}/${Uri.encodeComponent(_$PersonDetailsEnumMap[requiredEnumField]!)}/${Uri.encodeComponent(requiredIntField.toString())}/${Uri.encodeComponent(requiredNumField.toString())}/${Uri.encodeComponent(requiredStringField)}/${Uri.encodeComponent(requiredUriField.toString())}', + '/num-route/${Uri.encodeComponent(requiredNumField.toString())}', + queryParams: { + if (numField != null) 'num-field': numField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $EnumRouteExtension on EnumRoute { + static EnumRoute _fromState(GoRouterState state) => EnumRoute( + requiredEnumField: _$PersonDetailsEnumMap + ._$fromName(state.params['requiredEnumField']!), + enumField: _$convertMapValue( + 'enum-field', state.queryParams, _$PersonDetailsEnumMap._$fromName), + ); + + String get location => GoRouteData.$location( + '/enum-route/${Uri.encodeComponent(_$PersonDetailsEnumMap[requiredEnumField]!)}', queryParams: { - if (bigIntField != null) 'big-int-field': bigIntField!.toString(), - if (boolField != null) 'bool-field': boolField!.toString(), - if (dateTimeField != null) - 'date-time-field': dateTimeField!.toString(), - if (doubleField != null) 'double-field': doubleField!.toString(), if (enumField != null) 'enum-field': _$PersonDetailsEnumMap[enumField!]!, - if (intField != null) 'int-field': intField!.toString(), - if (numField != null) 'num-field': numField!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $EnhancedEnumRouteExtension on EnhancedEnumRoute { + static EnhancedEnumRoute _fromState(GoRouterState state) => EnhancedEnumRoute( + requiredEnumField: _$SportDetailsEnumMap + ._$fromName(state.params['requiredEnumField']!), + enumField: _$convertMapValue( + 'enum-field', state.queryParams, _$SportDetailsEnumMap._$fromName), + ); + + String get location => GoRouteData.$location( + '/enhanced-enum-route/${Uri.encodeComponent(_$SportDetailsEnumMap[requiredEnumField]!)}', + queryParams: { + if (enumField != null) + 'enum-field': _$SportDetailsEnumMap[enumField!]!, + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $StringRouteExtension on StringRoute { + static StringRoute _fromState(GoRouterState state) => StringRoute( + requiredStringField: state.params['requiredStringField']!, + stringField: state.queryParams['string-field'], + ); + + String get location => GoRouteData.$location( + '/string-route/${Uri.encodeComponent(requiredStringField)}', + queryParams: { if (stringField != null) 'string-field': stringField!, + }, + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +extension $UriRouteExtension on UriRoute { + static UriRoute _fromState(GoRouterState state) => UriRoute( + requiredUriField: Uri.parse(state.params['requiredUriField']!), + uriField: _$convertMapValue('uri-field', state.queryParams, Uri.parse), + ); + + String get location => GoRouteData.$location( + '/uri-route/${Uri.encodeComponent(requiredUriField.toString())}', + queryParams: { if (uriField != null) 'uri-field': uriField!.toString(), }, ); @@ -75,6 +274,13 @@ const _$PersonDetailsEnumMap = { PersonDetails.favoriteSport: 'favorite-sport', }; +const _$SportDetailsEnumMap = { + SportDetails.volleyball: 'volleyball', + SportDetails.football: 'football', + SportDetails.tennis: 'tennis', + SportDetails.hockey: 'hockey', +}; + T? _$convertMapValue( String key, Map map, diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index e58a5ace09de..1023f8c31758 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -15,7 +15,7 @@ part 'main.g.dart'; void main() => runApp(App()); class App extends StatelessWidget { - App({Key? key}) : super(key: key); + App({super.key}); final LoginInfo loginInfo = LoginInfo(); static const String title = 'GoRouter Example: Named Routes'; @@ -148,7 +148,7 @@ class PersonDetailsRoute extends GoRouteData { } class HomeScreen extends StatelessWidget { - const HomeScreen({Key? key}) : super(key: key); + const HomeScreen({super.key}); @override Widget build(BuildContext context) { @@ -184,7 +184,7 @@ class HomeScreen extends StatelessWidget { } class FamilyScreen extends StatelessWidget { - const FamilyScreen({required this.family, Key? key}) : super(key: key); + const FamilyScreen({required this.family, super.key}); final Family family; @override @@ -203,8 +203,7 @@ class FamilyScreen extends StatelessWidget { } class PersonScreen extends StatelessWidget { - const PersonScreen({required this.family, required this.person, Key? key}) - : super(key: key); + const PersonScreen({required this.family, required this.person, super.key}); final Family family; final Person person; @@ -250,8 +249,8 @@ class PersonDetailsPage extends StatelessWidget { required this.person, required this.detailsKey, this.extra, - Key? key, - }) : super(key: key); + super.key, + }); final Family family; final Person person; @@ -278,7 +277,7 @@ class PersonDetailsPage extends StatelessWidget { } class LoginScreen extends StatelessWidget { - const LoginScreen({this.from, Key? key}) : super(key: key); + const LoginScreen({this.from, super.key}); final String? from; @override diff --git a/packages/go_router_builder/example/lib/shared/data.dart b/packages/go_router_builder/example/lib/shared/data.dart index 3f017fc4faa6..edc3f205eb41 100644 --- a/packages/go_router_builder/example/lib/shared/data.dart +++ b/packages/go_router_builder/example/lib/shared/data.dart @@ -14,6 +14,46 @@ enum PersonDetails { favoriteSport, } +enum SportDetails { + volleyball( + imageUrl: '/sportdetails/url/volleyball.jpg', + playerPerTeam: 6, + accessory: null, + hasNet: true, + ), + football( + imageUrl: '/sportdetails/url/Football.jpg', + playerPerTeam: 11, + accessory: null, + hasNet: true, + ), + tennis( + imageUrl: '/sportdetails/url/tennis.jpg', + playerPerTeam: 2, + accessory: 'Rackets', + hasNet: true, + ), + hockey( + imageUrl: '/sportdetails/url/hockey.jpg', + playerPerTeam: 6, + accessory: 'Hockey sticks', + hasNet: true, + ), + ; + + const SportDetails({ + required this.accessory, + required this.hasNet, + required this.imageUrl, + required this.playerPerTeam, + }); + + final String imageUrl; + final int playerPerTeam; + final String? accessory; + final bool hasNet; +} + /// sample Person class class Person { Person({ diff --git a/packages/go_router_builder/example/lib/simple_example.dart b/packages/go_router_builder/example/lib/simple_example.dart index 1597104ffc09..965747579579 100644 --- a/packages/go_router_builder/example/lib/simple_example.dart +++ b/packages/go_router_builder/example/lib/simple_example.dart @@ -14,7 +14,7 @@ part 'simple_example.g.dart'; void main() => runApp(App()); class App extends StatelessWidget { - App({Key? key}) : super(key: key); + App({super.key}); @override Widget build(BuildContext context) => MaterialApp.router( @@ -50,7 +50,7 @@ class FamilyRoute extends GoRouteData { } class HomeScreen extends StatelessWidget { - const HomeScreen({Key? key}) : super(key: key); + const HomeScreen({super.key}); @override Widget build(BuildContext context) => Scaffold( @@ -68,7 +68,7 @@ class HomeScreen extends StatelessWidget { } class FamilyScreen extends StatelessWidget { - const FamilyScreen({required this.family, Key? key}) : super(key: key); + const FamilyScreen({required this.family, super.key}); final Family family; @override diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml index 90edafe45ec5..4a4f72f03d31 100644 --- a/packages/go_router_builder/example/pubspec.yaml +++ b/packages/go_router_builder/example/pubspec.yaml @@ -3,7 +3,7 @@ description: go_router_builder examples publish_to: none environment: - sdk: ">=2.14.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: flutter: diff --git a/packages/go_router_builder/example/test/all_types_test.dart b/packages/go_router_builder/example/test/all_types_test.dart new file mode 100644 index 000000000000..4a0cac663d8f --- /dev/null +++ b/packages/go_router_builder/example/test/all_types_test.dart @@ -0,0 +1,114 @@ +// 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/material.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router_builder_example/all_types.dart'; +import 'package:go_router_builder_example/shared/data.dart'; + +void main() { + testWidgets('Test typed route navigation', (WidgetTester tester) async { + await tester.pumpWidget(AllTypesApp()); + + final ScaffoldState scaffoldState = + tester.firstState(find.byType(Scaffold)); + + BigIntRoute( + requiredBigIntField: BigInt.from(4), + bigIntField: BigInt.from(8), + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('BigIntRoute'), findsOneWidget); + expect(find.text('Param: 4'), findsOneWidget); + expect(find.text('Query param: 8'), findsOneWidget); + + BoolRoute( + requiredBoolField: false, + boolField: true, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('BoolRoute'), findsOneWidget); + expect(find.text('Param: false'), findsOneWidget); + expect(find.text('Query param: true'), findsOneWidget); + + final DateTime param = DateTime.now(); + final DateTime query = DateTime(2017, 9, 7, 17, 30); + DateTimeRoute( + requiredDateTimeField: param, + dateTimeField: query, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('DateTimeRoute'), findsOneWidget); + expect(find.text('Param: $param'), findsOneWidget); + expect(find.text('Query param: $query'), findsOneWidget); + + DoubleRoute( + requiredDoubleField: 3.14, + doubleField: -3.14, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('DoubleRoute'), findsOneWidget); + expect(find.text('Param: 3.14'), findsOneWidget); + expect(find.text('Query param: -3.14'), findsOneWidget); + + IntRoute( + requiredIntField: 65, + intField: -65, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('IntRoute'), findsOneWidget); + expect(find.text('Param: 65'), findsOneWidget); + expect(find.text('Query param: -65'), findsOneWidget); + + NumRoute( + requiredNumField: 987.32, + numField: -987.32, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('NumRoute'), findsOneWidget); + expect(find.text('Param: 987.32'), findsOneWidget); + expect(find.text('Query param: -987.32'), findsOneWidget); + + StringRoute( + requiredStringField: r'Tytire tu patulae recubans sub tegmine fagi.', + stringField: r'Tytire tu patulae recubans sub tegmine fagi.', + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('StringRoute'), findsOneWidget); + expect(find.text('Param: Tytire tu patulae recubans sub tegmine fagi.'), + findsOneWidget); + expect( + find.text('Query param: Tytire tu patulae recubans sub tegmine fagi.'), + findsOneWidget); + + EnumRoute( + requiredEnumField: PersonDetails.favoriteFood, + enumField: PersonDetails.favoriteSport, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('EnumRoute'), findsOneWidget); + expect(find.text('Param: PersonDetails.favoriteFood'), findsOneWidget); + expect( + find.text('Query param: PersonDetails.favoriteSport'), findsOneWidget); + + EnhancedEnumRoute( + requiredEnumField: SportDetails.football, + enumField: SportDetails.hockey, + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('EnhancedEnumRoute'), findsOneWidget); + expect(find.text('Param: SportDetails.football'), findsOneWidget); + expect(find.text('Query param: SportDetails.hockey'), findsOneWidget); + + UriRoute( + requiredUriField: Uri.parse('https://dart.dev'), + uriField: Uri.parse('https://dart.dev'), + ).go(scaffoldState.context); + await tester.pumpAndSettle(); + expect(find.text('UriRoute'), findsOneWidget); + expect(find.text('Param: https://dart.dev'), findsOneWidget); + expect(find.text('Query param: https://dart.dev'), findsOneWidget); + }); +} diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 49ebc5d22a36..f058a2cdff6d 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -368,7 +368,7 @@ String _enumMapConst(InterfaceType type) { final StringBuffer buffer = StringBuffer('const ${enumMapName(type)} = {'); for (final FieldElement enumField in type.element2.fields - .where((FieldElement element) => !element.isSynthetic)) { + .where((FieldElement element) => element.isEnumConstant)) { buffer.writeln( '$enumName.${enumField.name}: ${escapeDartString(enumField.name.kebab)},', ); diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index ac221eac3a7a..81781eb8d634 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: 1.0.11 +version: 1.0.12 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 diff --git a/packages/go_router_builder/test/builder_test.dart b/packages/go_router_builder/test/builder_test.dart index bcd25d2ba870..147e8251a1fc 100644 --- a/packages/go_router_builder/test/builder_test.dart +++ b/packages/go_router_builder/test/builder_test.dart @@ -31,4 +31,5 @@ const Set _expectedAnnotatedTests = { 'NullableRequiredParam', 'UnsupportedType', 'theAnswer', + 'EnumParam', }; diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart index 03a7dedd350c..ca204efaaf94 100644 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart @@ -70,3 +70,49 @@ class MissingPathParam extends GoRouteData { MissingPathParam({required this.id}); final String id; } + +@ShouldGenerate(r''' +GoRoute get $enumParam => GoRouteData.$route( + path: '/:y', + factory: $EnumParamExtension._fromState, + ); + +extension $EnumParamExtension on EnumParam { + static EnumParam _fromState(GoRouterState state) => EnumParam( + y: _$EnumTestEnumMap._$fromName(state.params['y']!), + ); + + String get location => GoRouteData.$location( + '/${Uri.encodeComponent(_$EnumTestEnumMap[y]!)}', + ); + + void go(BuildContext context) => context.go(location, extra: this); + + void push(BuildContext context) => context.push(location, extra: this); +} + +const _$EnumTestEnumMap = { + EnumTest.a: 'a', + EnumTest.b: 'b', + EnumTest.c: 'c', +}; + +extension on Map { + T _$fromName(String value) => + entries.singleWhere((element) => element.value == value).key; +} +''') +@TypedGoRoute(path: '/:y') +class EnumParam extends GoRouteData { + EnumParam({required this.y}); + final EnumTest y; +} + +enum EnumTest { + a(1), + b(3), + c(5); + + const EnumTest(this.x); + final int x; +}