-
-
Save by90/a9f77e3674f1364ce9160b82c0a17e1e to your computer and use it in GitHub Desktop.
This file contains 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 characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp( | |
App(), | |
); | |
} | |
class App extends StatefulWidget { | |
@override | |
_AppState createState() => _AppState(); | |
} | |
class _AppState extends State<App> { | |
final AppRouterDelegate _routerDelegate = AppRouterDelegate(); | |
final AppRouteInformationParser _routeInformationParser = | |
AppRouteInformationParser(); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp.router( | |
title: 'Coe Flutter App', | |
routerDelegate: _routerDelegate, | |
routeInformationParser: _routeInformationParser, | |
); | |
} | |
} | |
class Address { | |
Address(this.title, this.author); | |
final String title; | |
final String author; | |
} | |
class AppRouter extends StatefulWidget { | |
@override | |
_AppRouterState createState() => _AppRouterState(); | |
} | |
class _AppRouterState extends State<AppRouter> { | |
final AppRouterDelegate _routerDelegate = AppRouterDelegate(); | |
final AppRouteInformationParser _routeInformationParser = | |
AppRouteInformationParser(); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp.router( | |
title: 'CoE Flutter Skeleton', | |
routerDelegate: _routerDelegate, | |
routeInformationParser: _routeInformationParser, | |
); | |
} | |
} | |
class AddressesAppState extends ChangeNotifier { | |
AddressesAppState() : _selectedIndex = 0; | |
int _selectedIndex; | |
Address _selectedAddress; | |
final List<Address> addresses = [ | |
Address('TSC', '〒104 hogehoge'), | |
Address('AIT', '〒108 fugagu'), | |
Address('AIC', '〒107 po'), | |
]; | |
int get selectedIndex => _selectedIndex; | |
set selectedIndex(int idx) { | |
_selectedIndex = idx; | |
if (_selectedIndex == 1) { | |
selectedAddress = null; | |
} | |
notifyListeners(); | |
} | |
Address get selectedAddress => _selectedAddress; | |
set selectedAddress(Address book) { | |
_selectedAddress = book; | |
notifyListeners(); | |
} | |
int getSelectedAddressById() { | |
if (!addresses.contains(_selectedAddress)) { | |
return 0; | |
} | |
return addresses.indexOf(_selectedAddress); | |
} | |
void setSelectedAddressById(int id) { | |
if (id < 0 || id > addresses.length - 1) { | |
return; | |
} | |
_selectedAddress = addresses[id]; | |
notifyListeners(); | |
} | |
} | |
class AppRouteInformationParser | |
extends RouteInformationParser<AddressRoutePath> { | |
@override | |
Future<AddressRoutePath> parseRouteInformation( | |
RouteInformation routeInformation) async { | |
final uri = Uri.parse(routeInformation.location); | |
if (uri.pathSegments.isNotEmpty && uri.pathSegments.first == 'settings') { | |
return AddressesSettingsPath(); | |
} else { | |
if (uri.pathSegments.length >= 2) { | |
if (uri.pathSegments[0] == 'book') { | |
return AddressesDetailsPath(int.tryParse(uri.pathSegments[1])); | |
} | |
} | |
return AddressesListPath(); | |
} | |
} | |
@override | |
RouteInformation restoreRouteInformation(AddressRoutePath configuration) { | |
if (configuration is AddressesListPath) { | |
return const RouteInformation(location: '/home'); | |
} | |
if (configuration is AddressesSettingsPath) { | |
return const RouteInformation(location: '/settings'); | |
} | |
if (configuration is AddressesDetailsPath) { | |
return RouteInformation(location: '/book/${configuration.id}'); | |
} | |
return null; | |
} | |
} | |
class AppRouterDelegate extends RouterDelegate<AddressRoutePath> | |
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AddressRoutePath> { | |
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() { | |
appState.addListener(notifyListeners); | |
} | |
AddressesAppState appState = AddressesAppState(); | |
@override | |
final GlobalKey<NavigatorState> navigatorKey; | |
@override | |
AddressRoutePath get currentConfiguration { | |
if (appState.selectedIndex == 1) { | |
return AddressesSettingsPath(); | |
} else { | |
if (appState.selectedAddress == null) { | |
return AddressesListPath(); | |
} else { | |
return AddressesDetailsPath(appState.getSelectedAddressById()); | |
} | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Navigator( | |
key: navigatorKey, | |
pages: [ | |
MaterialPage<dynamic>( | |
child: AppShell(appState: appState), | |
), | |
], | |
onPopPage: (route, dynamic result) { | |
if (!route.didPop(result)) { | |
return false; | |
} | |
if (appState.selectedAddress != null) { | |
appState.selectedAddress = null; | |
} | |
notifyListeners(); | |
return true; | |
}, | |
); | |
} | |
@override | |
Future<void> setNewRoutePath(AddressRoutePath configuration) async { | |
if (configuration is AddressesListPath) { | |
appState | |
..selectedIndex = 0 | |
..selectedAddress = null; | |
} else if (configuration is AddressesSettingsPath) { | |
appState.selectedIndex = 1; | |
} else if (configuration is AddressesDetailsPath) { | |
appState.setSelectedAddressById(configuration.id); | |
} | |
} | |
} | |
// Routes | |
abstract class AddressRoutePath {} | |
class AddressesListPath extends AddressRoutePath {} | |
class AddressesSettingsPath extends AddressRoutePath {} | |
class AddressesDetailsPath extends AddressRoutePath { | |
AddressesDetailsPath(this.id); | |
final int id; | |
} | |
// Widget that contains the AdaptiveNavigationScaffold | |
class AppShell extends StatefulWidget { | |
const AppShell({ | |
@required this.appState, | |
}); | |
final AddressesAppState appState; | |
@override | |
_AppShellState createState() => _AppShellState(); | |
} | |
enum HomeTabIndex { address, setting, sample } | |
class _AppShellState extends State<AppShell> { | |
InnerRouterDelegate _routerDelegate; | |
ChildBackButtonDispatcher _backButtonDispatcher; | |
@override | |
void initState() { | |
super.initState(); | |
_routerDelegate = InnerRouterDelegate(widget.appState); | |
} | |
@override | |
void didUpdateWidget(covariant AppShell oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
_routerDelegate.appState = widget.appState; | |
} | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
// Defer back button dispatching to the child router | |
_backButtonDispatcher = Router.of(context) | |
.backButtonDispatcher | |
.createChildBackButtonDispatcher(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final appState = widget.appState; | |
// Claim priority, If there are parallel sub router, you will need | |
// to pick which one should take priority; | |
_backButtonDispatcher.takePriority(); | |
return Scaffold( | |
body: Router( | |
routerDelegate: _routerDelegate, | |
backButtonDispatcher: _backButtonDispatcher, | |
), | |
bottomNavigationBar: BottomNavigationBar( | |
items: const [ | |
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), | |
BottomNavigationBarItem( | |
icon: Icon(Icons.settings), label: 'Settings'), | |
BottomNavigationBarItem(icon: Icon(Icons.code), label: 'Sample'), | |
], | |
currentIndex: appState.selectedIndex, | |
onTap: (newIndex) { | |
appState.selectedIndex = newIndex; | |
}, | |
), | |
); | |
} | |
} | |
class InnerRouterDelegate extends RouterDelegate<AddressRoutePath> | |
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AddressRoutePath> { | |
InnerRouterDelegate(this._appState); | |
@override | |
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); | |
AddressesAppState get appState => _appState; | |
AddressesAppState _appState; | |
set appState(AddressesAppState value) { | |
if (value == _appState) { | |
return; | |
} | |
_appState = value; | |
notifyListeners(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final targetPage = [ | |
if (appState.selectedIndex == HomeTabIndex.address.index) ...[ | |
FadeAnimationPage( | |
child: AddressesListScreen( | |
addresses: appState.addresses, | |
onTapped: _handleAddressTapped, | |
), | |
key: const ValueKey('AddressesListPage'), | |
), | |
if (appState.selectedAddress != null) | |
MaterialPage<dynamic>( | |
key: ValueKey(appState.selectedAddress), | |
child: AddressDetailsScreen(book: appState.selectedAddress), | |
), | |
] else if (appState.selectedIndex == HomeTabIndex.setting.index) ...[ | |
FadeAnimationPage( | |
child: SettingsScreen(), | |
key: const ValueKey('SettingsPage'), | |
), | |
] else | |
FadeAnimationPage( | |
child: SettingsScreen(), | |
key: const ValueKey('SettingsPage'), | |
), | |
]; | |
return Navigator( | |
key: navigatorKey, | |
pages: targetPage, | |
onPopPage: (route, dynamic result) { | |
appState.selectedAddress = null; | |
notifyListeners(); | |
return route.didPop(result); | |
}, | |
); | |
} | |
@override | |
Future<void> setNewRoutePath(AddressRoutePath configuration) async { | |
// This is not required for inner router delegate because it does not | |
// parse route | |
assert(false); | |
} | |
void _handleAddressTapped(Address book) { | |
appState.selectedAddress = book; | |
notifyListeners(); | |
} | |
} | |
class FadeAnimationPage extends Page<dynamic> { | |
const FadeAnimationPage({Key key, this.child}) : super(key: key as LocalKey); | |
final Widget child; | |
@override | |
Route createRoute(BuildContext context) { | |
return PageRouteBuilder<dynamic>( | |
settings: this, | |
pageBuilder: (context, animation, animation2) { | |
final curveTween = CurveTween(curve: Curves.easeIn); | |
return FadeTransition( | |
opacity: animation.drive(curveTween), | |
child: child, | |
); | |
}, | |
); | |
} | |
} | |
// Screens | |
class AddressesListScreen extends StatelessWidget { | |
const AddressesListScreen({ | |
@required this.addresses, | |
@required this.onTapped, | |
}); | |
final List<Address> addresses; | |
final ValueChanged<Address> onTapped; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(), | |
body: ListView( | |
children: [ | |
for (var book in addresses) | |
ListTile( | |
title: Text(book.title), | |
subtitle: Text(book.author), | |
onTap: () => onTapped(book), | |
) | |
], | |
), | |
); | |
} | |
} | |
class AddressDetailsScreen extends StatelessWidget { | |
const AddressDetailsScreen({ | |
@required this.book, | |
}); | |
final Address book; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(), | |
body: Padding( | |
padding: const EdgeInsets.all(8), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
if (book != null) ...[ | |
Text(book.title, style: Theme.of(context).textTheme.headline6), | |
Text(book.author, style: Theme.of(context).textTheme.subtitle1), | |
], | |
], | |
), | |
), | |
); | |
} | |
} | |
class SettingsScreen extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(), | |
body: const Center( | |
child: Text('Settings screen'), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment