Skip to content

Instantly share code, notes, and snippets.

@acn-masatadakurihara
Created October 15, 2020 16:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save acn-masatadakurihara/11d35e3e75af296d25b3d1d035e3a07b to your computer and use it in GitHub Desktop.
Save acn-masatadakurihara/11d35e3e75af296d25b3d1d035e3a07b to your computer and use it in GitHub Desktop.
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