Last active
November 22, 2022 03:17
-
-
Save tolo/cfff35673064571285470a7552740eed to your computer and use it in GitHub Desktop.
Revisions
-
tolo revised this gist
Nov 19, 2022 . No changes.There are no files selected for viewing
-
tolo created this gist
Nov 15, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,299 @@ // 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 'dart:math'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; // A quick sample showcasing dynamic number of sections in a stateful navigation // with a bottom navigation bar. Based on stateful_nested_navigation.dart from // https://github.com/flutter/packages/pull/2650 void main() { runApp(TopStateWidget()); } class TopStateWidget extends StatefulWidget { @override State<StatefulWidget> createState() => TopState(); } class TopState extends State<TopStateWidget> { bool _loggedIn = false; int _sections = 1; void login(int sections) => setState(() { _sections = sections; _loggedIn = true; }); void logout() => setState(() { _loggedIn = false; }); @override Widget build(BuildContext context) { return InheritedTopState( topState: this, child: NestedTabNavigationExampleApp( loggedIn: _loggedIn, sections: _sections), ); } } class InheritedTopState extends InheritedWidget { const InheritedTopState({ required super.child, required this.topState, super.key, }) : super(); final TopState topState; @override bool updateShouldNotify(covariant InheritedTopState oldWidget) { return topState != oldWidget.topState; } } /// An example demonstrating how to use nested navigators class NestedTabNavigationExampleApp extends StatelessWidget { /// Creates a NestedTabNavigationExampleApp NestedTabNavigationExampleApp( {required this.loggedIn, required this.sections, Key? key}) : super(key: key); final bool loggedIn; final int sections; GoRouter get _router => GoRouter( initialLocation: loggedIn ? '/a0' : '/', routes: <RouteBase>[ GoRoute(path: '/', builder: (context, state) => const LoginScreen()), if (loggedIn) StatefulShellRoute( /// To enable preloading of the root routes of the branches, pass true /// for the parameter preloadBranches. // preloadBranches: true, branches: List<ShellRouteBranch>.generate( sections, (index) => /// The route branch for the first tab of the bottom navigation bar. ShellRouteBranch( routes: <RouteBase>[ GoRoute( /// The screen to display as the root in the first tab of the /// bottom navigation bar. path: '/a$index', builder: (BuildContext context, GoRouterState state) => RootScreen( label: 'A$index', detailsPath: '/a$index/details'), routes: <RouteBase>[ /// The details screen to display stacked on navigator of the /// first tab. This will cover screen A but not the application /// shell (bottom navigation bar). GoRoute( path: 'details', builder: (BuildContext context, GoRouterState state) => DetailsScreen(label: 'A$index'), ), ], ), ], ), ), builder: (BuildContext context, GoRouterState state, Widget child) { return ScaffoldWithNavBar(body: child, sections: sections); }, ), ], ); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), routerConfig: _router, ); } } class LoginScreen extends StatelessWidget { const LoginScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Text('Login screen', style: Theme.of(context).textTheme.titleLarge), const Padding(padding: EdgeInsets.all(4)), TextButton( onPressed: () { final InheritedTopState? top = context.dependOnInheritedWidgetOfExactType(); top!.topState.login(2 + Random().nextInt(9)); }, child: const Text('Login'), ), ], ), ); } } /// Builds the "shell" for the app by building a Scaffold with a /// BottomNavigationBar, where [child] is placed in the body of the Scaffold. class ScaffoldWithNavBar extends StatelessWidget { /// Constructs an [ScaffoldWithNavBar]. const ScaffoldWithNavBar({ required this.body, required this.sections, Key? key, }) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar')); /// Body, i.e. the index stack final Widget body; final int sections; @override Widget build(BuildContext context) { final StatefulShellRouteState shellState = StatefulShellRoute.of(context); return Scaffold( body: body, bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, items: List<BottomNavigationBarItem>.generate( sections, (index) => BottomNavigationBarItem( icon: index % 2 == 0 ? const Icon(Icons.home) : const Icon(Icons.work), label: 'Section $index')), currentIndex: shellState.index, onTap: (int tappedIndex) => shellState.goBranch(index: tappedIndex), ), ); } } /// Widget for the root/initial pages in the bottom navigation bar. class RootScreen extends StatelessWidget { /// Creates a RootScreen const RootScreen({ required this.label, required this.detailsPath, Key? key, }) : super(key: key); /// The label final String label; /// The path to the detail page final String detailsPath; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Tab root - $label'), ), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Text('Screen $label', style: Theme.of(context).textTheme.titleLarge), const Padding(padding: EdgeInsets.all(4)), TextButton( onPressed: () { GoRouter.of(context).go(detailsPath, extra: '$label-XYZ'); }, child: const Text('View details'), ), const Padding(padding: EdgeInsets.all(4)), TextButton( onPressed: () { final InheritedTopState? top = context.dependOnInheritedWidgetOfExactType(); top!.topState.logout(); }, child: const Text('Logout'), ), ], ), ), ); } } /// The details screen for either the A or B screen. class DetailsScreen extends StatefulWidget { /// Constructs a [DetailsScreen]. const DetailsScreen({ required this.label, this.param, Key? key, }) : super(key: key); /// The label to display in the center of the screen. final String label; /// Optional param final String? param; @override State<StatefulWidget> createState() => DetailsScreenState(); } /// The state for DetailsScreen class DetailsScreenState extends State<DetailsScreen> { int _counter = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Details Screen - ${widget.label}'), ), body: _build(context), ); } Widget _build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Text('Details for ${widget.label} - Counter: $_counter', style: Theme.of(context).textTheme.titleLarge), const Padding(padding: EdgeInsets.all(4)), TextButton( onPressed: () { setState(() { _counter++; }); }, child: const Text('Increment counter'), ), const Padding(padding: EdgeInsets.all(8)), if (widget.param != null) Text('Parameter: ${widget.param!}', style: Theme.of(context).textTheme.titleMedium), const Padding(padding: EdgeInsets.all(8)), ], ), ); } }