diff --git a/go_router/example/lib/breadcrumbs.dart b/go_router/example/lib/breadcrumbs.dart new file mode 100644 index 00000000..a89f8841 --- /dev/null +++ b/go_router/example/lib/breadcrumbs.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'shared/data.dart'; + +void main() => runApp(App()); + +class App extends StatelessWidget { + App({Key? key}) : super(key: key); + + static const title = 'GoRouter Example: Breadcrumbs'; + + @override + Widget build(BuildContext context) => MaterialApp.router( + routeInformationParser: _router.routeInformationParser, + routerDelegate: _router.routerDelegate, + title: title, + ); + + late final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + name: 'Home', + builder: (context, state) => HomeScreen(families: Families.data), + routes: [ + GoRoute( + name: 'Family', + path: 'family/:fid', + builder: (context, state) => FamilyScreen( + family: Families.family(state.params['fid']!), + ), + routes: [ + GoRoute( + name: 'Person', + path: 'person/:pid', + builder: (context, state) { + final family = Families.family(state.params['fid']!); + final person = family.person(state.params['pid']!); + + return PersonScreen(family: family, person: person); + }, + ), + ], + ), + ], + ), + ], + navigatorBuilder: (context, child) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + BreadCrumbs(router: _router), + if (child != null) + Expanded( + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: child, + ), + ), + ], + ), + ); +} + +class BreadCrumbs extends StatelessWidget { + const BreadCrumbs({ + required this.router, + Key? key, + }) : super(key: key); + + final GoRouter router; + + @override + Widget build(BuildContext context) { + // ignore: invalid_use_of_visible_for_testing_member + final matches = router.routerDelegate.matches; + return Container( + height: 100, + alignment: Alignment.centerLeft, + color: Colors.black, + child: SafeArea( + bottom: false, + child: Material( + color: Colors.transparent, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + for (final match in matches) ...[ + const Icon( + Icons.arrow_right_outlined, + color: Colors.white, + ), + Padding( + padding: const EdgeInsets.all(8), + child: ActionChip( + label: Text( + match.route.name ?? 'Hey', + style: TextStyle( + color: matches.last == match + ? Colors.black + : Colors.white, + ), + ), + backgroundColor: + matches.last == match ? Colors.white : Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + side: const BorderSide(color: Colors.white)), + onPressed: () { + if (matches.last == match) return; + router.navigator!.popUntil( + (route) => route.settings.name == match.route.name, + ); + }, + ), + ), + ] + ].skip(1).toList(), + ), + ), + ), + ), + ); + } +} + +class HomeScreen extends StatelessWidget { + const HomeScreen({required this.families, Key? key}) : super(key: key); + final List families; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: const Text(App.title)), + body: ListView( + children: [ + for (final f in families) + ListTile( + title: Text(f.name), + onTap: () => context.go('/family/${f.id}'), + ) + ], + ), + ); +} + +class FamilyScreen extends StatelessWidget { + const FamilyScreen({required this.family, Key? key}) : super(key: key); + final Family family; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(family.name)), + body: ListView( + children: [ + for (final p in family.people) + ListTile( + title: Text(p.name), + onTap: () => context.go('/family/${family.id}/person/${p.id}'), + ), + ], + ), + ); +} + +class PersonScreen extends StatelessWidget { + const PersonScreen({required this.family, required this.person, Key? key}) + : super(key: key); + + final Family family; + final Person person; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(person.name)), + body: Text('${person.name} ${family.name} is ${person.age} years old'), + ); +} diff --git a/go_router/example/lib/cupertino_back_button.dart b/go_router/example/lib/cupertino_back_button.dart new file mode 100644 index 00000000..19819223 --- /dev/null +++ b/go_router/example/lib/cupertino_back_button.dart @@ -0,0 +1,164 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +// ignore: implementation_imports +import 'package:go_router/src/go_route_match.dart'; + +void main() => runApp(App()); + +class App extends StatelessWidget { + App({Key? key}) : super(key: key); + + static const title = 'GoRouter Example: Cupertino Back Button'; + + @override + Widget build(BuildContext context) => CupertinoApp.router( + routeInformationParser: _router.routeInformationParser, + routerDelegate: _router.routerDelegate, + title: title, + localizationsDelegates: const [ + DefaultMaterialLocalizations.delegate, + DefaultCupertinoLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + ); + + final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + name: 'Page1', + builder: (context, state) => const Page1Screen(), + routes: [ + GoRoute( + path: 'page2', + name: 'Page2', + builder: (context, state) => const Page2Screen(), + routes: [ + GoRoute( + path: 'page3', + name: 'Page3', + builder: (context, state) => const Page3Screen(), + ), + ], + ), + ], + ), + ], + ); +} + +class Page1Screen extends StatelessWidget { + const Page1Screen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text(App.title), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoButton( + onPressed: () => context.go('/page2'), + child: const Text('Go to page 2'), + ), + ], + ), + ), + ); +} + +class Page2Screen extends StatelessWidget { + const Page2Screen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Page2'), + leading: BackButton(), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoButton( + onPressed: () => context.go('/page2/page3'), + child: const Text('Go to page 3'), + ), + ], + ), + ), + ); +} + +class Page3Screen extends StatelessWidget { + const Page3Screen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Page3'), + leading: BackButton(), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text('Hold pressed on the back button to choose route') + ], + ), + ), + ); +} + +class BackButton extends StatelessWidget { + const BackButton({Key? key}) : super(key: key); + + @override + // ignore: prefer_expression_function_bodies + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: GestureDetector( + onLongPress: () async { + final router = GoRouter.of(context); + final navigator = Navigator.of(context); + // ignore: invalid_use_of_visible_for_testing_member + final matches = router.routerDelegate.matches; + if (matches.isEmpty) return; + final match = await showMenu( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + context: context, + position: const RelativeRect.fromLTRB(0, 0, 20, 20), + items: >[ + for (final match in matches.reversed.skip(1)) + PopupMenuItem( + value: match, + child: Text(match.route.name ?? 'Hey'), + ), + ], + ); + if (match != null) { + // Router go does not animate views that pop + // final location = router.routerDelegate.locationForMatch(match); + // router.go(location); + + navigator.popUntil( + (route) => route.settings.name == match.route.name, + ); + } + }, + child: CupertinoNavigationBarBackButton( + onPressed: () { + Navigator.maybePop(context); + }, + ), + ), + ); + } +} diff --git a/go_router/lib/src/go_router_delegate.dart b/go_router/lib/src/go_router_delegate.dart index c7c23eb8..0e142f16 100644 --- a/go_router/lib/src/go_router_delegate.dart +++ b/go_router/lib/src/go_router_delegate.dart @@ -173,8 +173,11 @@ class GoRouterDelegate extends RouterDelegate } /// Get the current location, e.g. /family/f2/person/p1 - String get location => - _addQueryParams(_matches.last.subloc, _matches.last.queryParams); + String get location => locationForMatch(_matches.last); + + /// Get the location for a given match, e.g. /family/f2/person/p1 + String locationForMatch(GoRouteMatch match) => + _addQueryParams(match.subloc, match.queryParams); /// For internal use; visible for testing only. @visibleForTesting