7 Commits

Author SHA1 Message Date
toly
89eebe3ad8 ext 2024-01-22 08:26:35 +08:00
toly
2a69cb706b fix pushAndRemoveUntil demo 2024-01-11 14:58:25 +08:00
toly
74f9c288ab fix pushAndRemoveUntil demo 2024-01-11 14:55:19 +08:00
toly
72c3cc607d done 2023-12-27 01:06:25 +08:00
toly
1aeab4fa64 1 2023-12-27 00:55:04 +08:00
toly
34d8e6dd75 路由生命周期状态 2023-12-07 08:10:09 +08:00
toly
25b3bc16b2 2023-10-21 10:23:50 +08:00
228 changed files with 1157 additions and 11538 deletions

View File

@@ -1,4 +1,2 @@
01: 分析 push 方法。
A、B、C 三个界面跳转。
02: replace

View File

@@ -23,7 +23,7 @@ class MyApp extends StatelessWidget {
titleTextStyle: TextStyle(color: Colors.black,fontSize: 18,fontWeight: FontWeight.bold)
)
),
home: const PageA(),
home: const HomePage(),
onUnknownRoute: _onUnknownRoute,
);
}
@@ -46,3 +46,28 @@ class MyApp extends StatelessWidget {
return null;
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xffCCFFFF);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(title: const Text('主页'),
backgroundColor: bgColor,
),
body: Center(
child: ElevatedButton(
onPressed: ()=>toPageA(context),
child: const Text('Push A'),
),
),
);
}
void toPageA(BuildContext context){
Navigator.of(context).pushNamed('/a');
}
}

4
lib/09/节点介绍.txt Normal file
View File

@@ -0,0 +1,4 @@
01: 分析 pop 方法。
A、B、C 三个界面跳转。
02: 分析 popUntil 方法。

46
lib/10/app_info.dart Normal file
View File

@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
class AppInfo extends StatelessWidget {
const AppInfo({super.key});
@override
Widget build(BuildContext context) {
const String msg = 'FlutterUnit 是一个辅助开发者,了解 Flutter 组件和编程技术的开源项目。 ';
const String msg2 = '由张风捷特烈维护,开源地址:\n https://github.com/toly1994328/FlutterUnit ';
double height = MediaQuery.of(context).size.height;
return SizedBox(
height: height*(1-0.618),
child: Column(
children: [
AppBar(
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false,
centerTitle: true,
leading: Align(child: FlutterLogo()),
actions: [
CloseButton()
],
title: Column(
children: [
Text('FlutterUnit',style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold)),
Text('v2.9.3',style: TextStyle(fontSize: 12,),),
],
),),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0,vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// direction: Axis.vertical,
children: [
Text(msg,style: TextStyle(color: Colors.grey),),
const SizedBox(height: 8,),
Text(msg2,style: TextStyle(color: Colors.grey),),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,107 @@
// Copyright 2014 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/cupertino.dart';
import 'package:flutter/material.dart';
/// Flutter code sample for [CupertinoContextMenu].
final DecorationTween _tween = DecorationTween(
begin: BoxDecoration(
color: CupertinoColors.systemPurple,
boxShadow: const <BoxShadow>[],
borderRadius: BorderRadius.circular(20.0),
),
end: BoxDecoration(
color: CupertinoColors.systemPurple,
boxShadow: CupertinoContextMenu.kEndBoxShadow,
borderRadius: BorderRadius.circular(20.0),
),
);
class CupertinoContextMenuDemo extends StatelessWidget {
const CupertinoContextMenuDemo({super.key});
// Or just do this inline in the builder below?
static Animation<Decoration> _boxDecorationAnimation(
Animation<double> animation) {
return _tween.animate(
CurvedAnimation(
parent: animation,
curve: Interval(
0.0,
CupertinoContextMenu.animationOpensAt,
),
),
);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('CupertinoContextMenu 案例'),
),
child: Center(
child: SizedBox(
width: 100,
height: 100,
child: CupertinoContextMenu.builder(
actions: <Widget>[
CupertinoContextMenuAction(
onPressed: () {
Navigator.pop(context);
},
isDefaultAction: true,
trailingIcon: CupertinoIcons.doc_on_clipboard_fill,
child: const Text('Copy'),
),
CupertinoContextMenuAction(
onPressed: () {
Navigator.pop(context);
},
trailingIcon: CupertinoIcons.share,
child: const Text('Share'),
),
CupertinoContextMenuAction(
onPressed: () {
Navigator.pop(context);
},
trailingIcon: CupertinoIcons.heart,
child: const Text('Favorite'),
),
CupertinoContextMenuAction(
onPressed: () {
Navigator.pop(context);
},
isDestructiveAction: true,
trailingIcon: CupertinoIcons.delete,
child: const Text('Delete'),
),
],
builder: (BuildContext context, Animation<double> animation) {
final Animation<Decoration> boxDecorationAnimation =
_boxDecorationAnimation(animation);
return Container(
decoration:
animation.value < CupertinoContextMenu.animationOpensAt
? boxDecorationAnimation.value
: null,
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: CupertinoColors.systemPurple,
borderRadius: BorderRadius.circular(20.0),
),
child: const FlutterLogo(size: 250.0),
),
);
},
),
),
),
);
}
}

View File

@@ -0,0 +1,83 @@
// Copyright 2014 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';
/// Flutter code sample for [DropdownButton.selectedItemBuilder].
const Map<String, Color> kColors = <String, Color>{
'红色': Colors.red,
'黄色': Colors.yellowAccent,
'蓝色': Colors.blue,
'绿色': Colors.greenAccent,
'橙色': Colors.orange,
'紫色': Colors.purple,
};
class DropdownButtonColor extends StatefulWidget {
const DropdownButtonColor({super.key});
@override
State<DropdownButtonColor> createState() => _DropdownButtonColorState();
}
class _DropdownButtonColorState extends State<DropdownButtonColor> {
String selectedItem = kColors.keys.first;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('选择颜色:', style: Theme.of(context).textTheme.bodyLarge),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: DropdownButton<String>(
value: selectedItem,
onChanged: (String? value) {
setState(() => selectedItem = value!);
},
selectedItemBuilder: _buildSelectItem,
items: _buildItems(),
),
),
],
);
}
List<DropdownMenuItem<String>> _buildItems(){
return kColors.keys.map<DropdownMenuItem<String>>((String key) {
return DropdownMenuItem<String>(
value: key,
child: Wrap(
spacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: kColors[key],
borderRadius: BorderRadius.circular(2)),
),
Text(key),
],
),
);
}).toList();
}
List<Widget> _buildSelectItem(BuildContext context) {
return kColors.values.map<Widget>((Color color) {
return Align(
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(2)),
constraints: const BoxConstraints(minWidth: 24,maxHeight: 24),
),
);
}).toList();
}
}

137
lib/10/main.dart Normal file
View File

@@ -0,0 +1,137 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app_info.dart';
import 'cupertino_context_menu_demo.dart';
import 'dropdown_button_color.dart';
import 'more_pop_icon.dart';
import 'search_anchor_demo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: GlobalMaterialLocalizations.delegates,
supportedLocales: const [
Locale('zh', 'CN'),
],
locale: const Locale('zh', 'CN'),
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("弹出层测试"),
actions: [
MorePopIcon(onTapItem: _onTapItem,)
],
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: showConformDialog,
child: Text('关于应用'),
),
DropdownButtonColor(),
ElevatedButton(
onPressed: _showModalBottomSheet,
child: Text('showModalBottomSheet'),
),
ElevatedButton(
onPressed: _showCupertinoModalPopup,
child: Text('showCupertinoModalPopup'),
),
ElevatedButton(
onPressed: _toSearchAnchor,
child: Text('SearchAnchor 案例'),
), ElevatedButton(
onPressed: _toCupertinoContextMenu,
child: Text('CupertinoContextMenu 案例'),
),
],
),
),
);
}
void showConformDialog(){
showDialog(context: context, builder: _buildAbout);
// showCupertinoModalPopup(context: context, builder: builder)
// showCupertinoDialog(context: context, builder: _buildAbout);
}
void _showModalBottomSheet(){
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)
),
context: context, builder: (_)=>AppInfo());
}
void _showCupertinoModalPopup(){
showCupertinoModalPopup(
context: context, builder: (_)=>Material(child: const AppInfo()));
}
Widget _buildAbout(BuildContext context){
const String msg = 'FlutterUnit 是一个辅助开发者,了解 Flutter 组件和编程技术的开源项目。';
return const AboutDialog(
applicationName: "FlutterUnit",
applicationVersion: "v2.9.3",
applicationIcon: FlutterLogo(),
children: [
Text(msg,style: TextStyle(color: Colors.grey),)
],
);
}
void _onTapItem(MenuAction value) {
print(value.label);
switch(value){
case MenuAction.setting:
break;
case MenuAction.about:
showConformDialog();
break;
case MenuAction.help:
break;
}
}
void _toSearchAnchor() {
Navigator.of(context).push(MaterialPageRoute(builder: (_)=>SearchAnchorDemo()));
}
void _toCupertinoContextMenu() {
Navigator.of(context).push(MaterialPageRoute(builder: (_)=>CupertinoContextMenuDemo()));
}
}

58
lib/10/more_pop_icon.dart Normal file
View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
enum MenuAction implements Comparable<MenuAction> {
setting(label: '应用设置', iconData: Icons.settings),
about(label: '关于应用', iconData: Icons.info),
help(label: '帮助中心', iconData: Icons.help);
final String label;
final IconData iconData;
const MenuAction({
required this.label,
required this.iconData,
});
@override
int compareTo(MenuAction other) => label.compareTo(other.label);
bool operator >(MenuAction other) {
return compareTo(other) > 0;
}
}
class MorePopIcon extends StatelessWidget {
final ValueChanged<MenuAction> onTapItem;
const MorePopIcon({super.key, required this.onTapItem});
@override
Widget build(BuildContext context) {
return PopupMenuButton<MenuAction>(
itemBuilder: _buildItem,
onSelected: onTapItem,
icon: const Icon(Icons.more_vert_outlined),
position: PopupMenuPosition.under,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
);
}
List<PopupMenuEntry<MenuAction>> _buildItem(BuildContext context) {
return MenuAction.values
.map((MenuAction action) => PopupMenuItem<MenuAction>(
height: 35,
value: action,
child: Center(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 6,
children: [
Text(action.label),
Icon(action.iconData, size: 18),
],
)),
))
.toList();
}
}

View File

@@ -0,0 +1,162 @@
// Copyright 2014 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';
class SearchAnchorDemo extends StatefulWidget {
const SearchAnchorDemo({super.key});
@override
State<SearchAnchorDemo> createState() => _SearchAnchorDemoState();
}
class _SearchAnchorDemoState extends State<SearchAnchorDemo> {
Color? selectedColorSeed;
List<ColorLabel> searchHistory = <ColorLabel>[];
Iterable<Widget> getHistoryList(SearchController controller) {
return searchHistory.map(
(ColorLabel color) => ListTile(
leading: const Icon(Icons.history),
title: Text(color.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = color.label;
controller.selection = TextSelection.collapsed(offset: controller.text.length);
},
),
),
);
}
Iterable<Widget> getSuggestions(SearchController controller) {
final String input = controller.value.text;
return ColorLabel.values.where((ColorLabel color) => color.label.contains(input)).map(
(ColorLabel filteredColor) => ListTile(
leading: CircleAvatar(backgroundColor: filteredColor.color),
title: Text(filteredColor.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = filteredColor.label;
controller.selection = TextSelection.collapsed(offset: controller.text.length);
},
),
onTap: () {
controller.closeView(filteredColor.label);
handleSelection(filteredColor);
},
),
);
}
void handleSelection(ColorLabel selectedColor) {
setState(() {
selectedColorSeed = selectedColor.color;
if (searchHistory.length >= 5) {
searchHistory.removeLast();
}
searchHistory.insert(0, selectedColor);
});
}
Map<String,Color> colorMaps(ColorScheme colors) => {
'primary':colors.primary ,
'secondary':colors.secondary ,
'inverseSurface':colors.inverseSurface ,
'background':colors.background ,
'onPrimaryContainer':colors.onPrimaryContainer ,
'onErrorContainer':colors.onErrorContainer ,
'onBackground':colors.onBackground ,
'onTertiaryContainer':colors.onTertiaryContainer ,
'error':colors.error ,
'inversePrimary':colors.inversePrimary ,
'primaryContainer':colors.primaryContainer ,
'errorContainer':colors.errorContainer ,
'onPrimary':colors.onPrimary ,
'onSecondary':colors.onSecondary ,
'onError':colors.onError ,
'onTertiary':colors.onTertiary ,
'onSurfaceVariant':colors.onSurfaceVariant ,
'shadow':colors.shadow ,
};
@override
Widget build(BuildContext context) {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: selectedColorSeed);
final ColorScheme colors = themeData.colorScheme;
Map<String,Color> data = colorMaps(colors);
SizedBox cardSize = const SizedBox(
width: 80,
height: 30,
child: Text(''),
);
return Scaffold(
appBar: AppBar(title: const Text('搜索颜色')),
body: Align(
alignment: Alignment.topCenter,
child: Column(
children: <Widget>[
SearchAnchor.bar(
barHintText: '输入颜色',
suggestionsBuilder: (BuildContext context, SearchController controller) {
if (controller.text.isEmpty) {
if (searchHistory.isNotEmpty) {
return getHistoryList(controller);
}
return <Widget>[Center(child: Text('暂无历史记录.', style: TextStyle(color: colors.outline)))];
}
return getSuggestions(controller);
},
),
cardSize,
Wrap(
children: data.keys.map((key) => Card(color: data[key], child: SizedBox(
width: 80,
height: 30,
child: Center(child: Text(key,style: TextStyle(fontSize: 8,color: Colors.white,shadows: [
BoxShadow(color: Colors.black,offset: Offset(.1,.1),blurRadius: 2)
]),)),
))).toList(),
),
],
),
),
);
}
}
enum ColorLabel {
red('red', Colors.red),
orange('orange', Colors.orange),
yellow('yellow', Colors.yellow),
green('green', Colors.green),
blue('blue', Colors.blue),
indigo('indigo', Colors.indigo),
violet('violet', Color(0xFF8F00FF)),
purple('purple', Colors.purple),
pink('pink', Colors.pink),
silver('silver', Color(0xFF808080)),
gold('gold', Color(0xFFFFD700)),
beige('beige', Color(0xFFF5F5DC)),
brown('brown', Colors.brown),
grey('grey', Colors.grey),
black('black', Colors.black),
white('white', Colors.white);
const ColorLabel(this.label, this.color);
final String label;
final Color color;
}

5
lib/10/节点介绍.txt Normal file
View File

@@ -0,0 +1,5 @@
app_info.dart: 弹出对话框界面
cupertino_context_menu_demo.dart: 弹出 CupertinoContextMenu
dropdown_button_color.dart: 弹出 DropdownButton
more_pop_icon.dart: 弹出 PopupMenuButton
search_anchor_demo.dart: 弹出 SearchAnchor

24
lib/11/color_detail.dart Normal file
View File

@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
class ColorDetailPage extends StatelessWidget {
final Color color;
const ColorDetailPage({super.key, required this.color});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
centerTitle: true,
iconTheme: IconThemeData(color: Colors.white),
titleTextStyle: TextStyle(color: Colors.white,fontSize: 16,fontWeight: FontWeight.bold),
backgroundColor: color,
title: Text('颜色详情页'),
),
body: Center(
child: Text( '#${color.value.toRadixString(16)}',style: TextStyle(color: Colors.white,fontSize: 26,fontWeight: FontWeight.bold),),
),
);
}
}

66
lib/11/color_page.dart Normal file
View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import '../common/components/colors_panel.dart';
import 'color_detail.dart';
import 'transition.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<ColorPage> createState() => _ColorPageState();
}
class _ColorPageState extends State<ColorPage> {
final List<Color> _colors = [
Colors.red,
Colors.black,
Colors.blue,
Colors.green,
Colors.orange,
Colors.pink,
Colors.purple,
Colors.indigo,
Colors.amber,
Colors.cyan,
Colors.redAccent,
Colors.grey,
Colors.blueAccent,
Colors.greenAccent,
Colors.orangeAccent,
Colors.pinkAccent,
Colors.purpleAccent,
Colors.indigoAccent,
Colors.amberAccent,
Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
);
}
void _selectColor(Color color) {
Route route = PageRouteBuilder<void>(
barrierColor: Colors.white,
pageBuilder: (_, __, ___) => ColorDetailPage(color:color),
transitionsBuilder: kSlideBottomToTopWithSecondary,
);
Navigator.push(context, route);
// Navigator.push(
// context,
// MaterialPageRoute(builder: (_)=>ColorDetailPage(color:color)));
}
void _toAddPage() {
}
}

72
lib/11/main.dart Normal file
View File

@@ -0,0 +1,72 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:iroute/11/transition.dart';
import 'color_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: GlobalMaterialLocalizations.delegates,
supportedLocales: const [
Locale('zh', 'CN'),
],
locale: const Locale('zh', 'CN'),
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Cose 酷色"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: _toColorHome,
child: Text('打开颜色界面'),
),
],
),
),
);
}
void _toColorHome() {
Route route = PageRouteBuilder<void>(
barrierColor: Colors.white,
pageBuilder: (_, __, ___) => const ColorPage(),
transitionsBuilder: kSlideBottomToTopWithSecondary,
);
Navigator.push(context, route);
// Navigator.pushReplacement(context, route);
}
}

22
lib/11/transition.dart Normal file
View File

@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
RouteTransitionsBuilder kSlideBottomToTopWithSecondary = (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, 1.0),
).animate(secondaryAnimation),
child: child,
),
);
};

1
lib/11/节点介绍.txt Normal file
View File

@@ -0,0 +1 @@
路由第二动画控制器效果演示

View File

@@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
import 'pages/app/app.dart';
void main() {
runApp(const App());
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'pages/app.dart';
void main() {
setHashUrlStrategy();
runApp(const App());
}

View File

@@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import '../route/parser.dart';
import '../route/route_state.dart';
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
late final RootRouterDelegate _delegate;
late final AppRouteParser _parser;
late final RouteState _routeState;
@override
void initState() {
super.initState();
_parser = AppRouteParser(initialRoute: '/color', allowPaths: [
'/color',
'/color/add',
'/color/detail/:colorHex',
]);
_routeState = RouteState(_parser);
_delegate = RootRouterDelegate(_routeState);
}
@override
Widget build(BuildContext context) {
return RouteStateScope(
notifier: _routeState,
child: MaterialApp.router(
routerDelegate: _delegate,
routeInformationParser: _parser,
theme: ThemeData(
useMaterial3: true,
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
),
);
}
}

View File

@@ -1,67 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
appBar: AppBar(
title: Text('添加颜色'),
actions: [IconButton(onPressed: _selectColor, icon: Icon(Icons.check ))],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 20),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
}

View File

@@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
import '../route/route_state.dart';
import 'color_add_page.dart';
import '../../../common/components/colors_panel.dart';
import '../../../common/pages/stl_color_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
);
}
void _selectColor(Color color){
String value = '#${color.value.toRadixString(16)}';
RouteStateScope.of(context).go('/color/detail/$value');
}
void _toAddPage() async {
RouteStateScope.of(context).go('/color/add');
}
}

View File

@@ -1,27 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:quiver/core.dart';
/// A route path that has been parsed by [TemplateRouteParser].
class ParsedRoute extends Equatable{
/// The current path location without query parameters. (/book/123)
final String path;
/// The path template (/book/:id)
final String pathTemplate;
/// The path parameters ({id: 123})
final Map<String, String> parameters;
/// The query parameters ({search: abc})
final Map<String, String> queryParameters;
const ParsedRoute(this.path, this.pathTemplate, this.parameters, this.queryParameters);
@override
List<Object?> get props => [path,pathTemplate,parameters,queryParameters];
}

View File

@@ -1,135 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:iroute/common/pages/stl_color_page.dart';
import 'package:path_to_regexp/path_to_regexp.dart';
import '../pages/home_page.dart';
import '../pages/color_add_page.dart';
import 'parsed_route.dart';
import 'route_state.dart';
class AppRouteParser extends RouteInformationParser<ParsedRoute> {
final List<String> allowPaths;
final ParsedRoute initialRoute;
AppRouteParser({
required this.allowPaths,
String initialRoute = '/',
}):initialRoute = ParsedRoute(initialRoute, initialRoute, {}, {});
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
print("=======parseRouteInformation:${routeInformation.uri.path}===================");
final uri = routeInformation.uri;
final path = uri.toString();
final queryParams = uri.queryParameters;
ParsedRoute parsedRoute = initialRoute;
for (var pathTemplate in allowPaths) {
final parameters = <String>[];
var pathRegExp = pathToRegExp(pathTemplate, parameters: parameters);
if (pathRegExp.hasMatch(path)) {
final match = pathRegExp.matchAsPrefix(path);
if (match == null) continue;
final params = extract(parameters, match);
parsedRoute = ParsedRoute(path, pathTemplate, params, queryParams);
}
}
return parsedRoute;
}
@override
RouteInformation? restoreRouteInformation(ParsedRoute configuration) {
print("=======restoreRouteInformation:${configuration}===================");
return RouteInformation(uri: Uri.parse(configuration.path));
}
}
class RootRouterDelegate extends RouterDelegate<ParsedRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final RouteState routeState;
RootRouterDelegate(this.routeState) {
routeState.addListener(notifyListeners);
}
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
ParsedRoute get currentConfiguration {
print("=======currentConfiguration:${routeState.route.path}===================");
return routeState.route;
}
@override
void dispose() {
routeState.removeListener(notifyListeners);
routeState.dispose();
super.dispose();
}
final _addColorKey = const ValueKey('addColor');
@override
Widget build(BuildContext context) {
final routeState = RouteStateScope.of(context);
List<Page> pages =[];
final String pathTemplate = routeState.route.pathTemplate;
pages.add(MaterialPage(child: HomePage()));
String? selectedColor;
if (pathTemplate == '/color/detail/:colorHex') {
selectedColor = routeState.route.parameters['colorHex'];
}
if(selectedColor!=null){
pages.add(MaterialPage(child: StlColorPage(
color: Colors.redAccent,
)));
}
bool isAddPage = pathTemplate == '/color/add';
if(isAddPage){
pages.add(MaterialPage(
key: _addColorKey,
child: ColorAddPage()));
}
print("===build====================");
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: (route, result) {
print(result);
if (route.settings is Page &&
(route.settings as Page).key == _addColorKey) {
routeState.go('/color');
}
// appState.selectedBook = null;
// notifyListeners();
return route.didPop(result);
},
);
}
/// 格式: /a /b
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
print("===setNewRoutePath===${configuration}=================");
routeState.route = configuration;
return;
}
// @override
// Future<void> setInitialRoutePath( configuration) async{
// _pages = [_pageMap['/']!];
// notifyListeners();
// }
}

View File

@@ -1,48 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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/widgets.dart';
import 'parsed_route.dart';
import 'parser.dart';
/// The current route state. To change the current route, call obtain the state
/// using `RouteStateScope.of(context)` and call `go()`:
///
/// ```
/// RouteStateScope.of(context).go('/book/2');
/// ```
class RouteState extends ChangeNotifier {
final AppRouteParser _parser;
ParsedRoute _route;
RouteState(this._parser) : _route = _parser.initialRoute;
ParsedRoute get route => _route;
set route(ParsedRoute route) {
// Don't notify listeners if the path hasn't changed.
if (_route == route) return;
_route = route;
notifyListeners();
}
Future<void> go(String route) async {
this.route = await _parser
.parseRouteInformation(RouteInformation(uri: Uri.parse(route)));
}
}
/// Provides the current [RouteState] to descendant widgets in the tree.
class RouteStateScope extends InheritedNotifier<RouteState> {
const RouteStateScope({
required super.notifier,
required super.child,
super.key,
});
static RouteState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<RouteStateScope>()!.notifier!;
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'pages/app.dart';
void main() {
setHashUrlStrategy();
runApp(const App());
}

View File

@@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
import '../route/parser.dart';
import '../route/route_state.dart';
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
late final RootRouterDelegate _delegate;
late final AppRouteParser _parser;
late final RouteState _routeState;
@override
void initState() {
super.initState();
_parser = AppRouteParser();
_routeState = RouteState(_parser,initial: '/color');
_delegate = RootRouterDelegate(_routeState);
}
@override
Widget build(BuildContext context) {
return RouteStateScope(
notifier: _routeState,
child: MaterialApp.router(
routerDelegate: _delegate,
routeInformationParser: _parser,
theme: ThemeData(
useMaterial3: true,
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
),
);
}
}

View File

@@ -1,67 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
appBar: AppBar(
title: Text('添加颜色'),
actions: [IconButton(onPressed: _selectColor, icon: Icon(Icons.check ))],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 20),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
}

View File

@@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
import '../route/route_state.dart';
import 'color_add_page.dart';
import '../../../common/components/colors_panel.dart';
import '../../../common/pages/stl_color_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
);
}
void _selectColor(Color color){
String value = color.value.toRadixString(16);
RouteStateScope.of(context).go('/color/detail?color=$value');
}
void _toAddPage() async {
RouteStateScope.of(context).go('/color/add');
}
}

View File

@@ -1,21 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:quiver/core.dart';
/// A route path that has been parsed by [TemplateRouteParser].
class ParsedRoute extends Equatable{
/// The current path location without query parameters. (/book/123)
final String path;
/// The query parameters ({search: abc})
final Map<String, String> queryParameters;
const ParsedRoute(this.path, this.queryParameters);
@override
List<Object?> get props => [path,queryParameters];
}

View File

@@ -1,119 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:iroute/common/pages/stl_color_page.dart';
import 'package:path_to_regexp/path_to_regexp.dart';
import '../pages/home_page.dart';
import '../pages/color_add_page.dart';
import 'parsed_route.dart';
import 'route_state.dart';
class AppRouteParser extends RouteInformationParser<ParsedRoute> {
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
print("=======parseRouteInformation:${routeInformation.uri.path}===================");
final uri = routeInformation.uri;
final path = uri.toString();
final queryParams = uri.queryParameters;
return ParsedRoute(path, queryParams);
}
@override
RouteInformation? restoreRouteInformation(ParsedRoute configuration) {
print("=======restoreRouteInformation:${configuration}===================");
return RouteInformation(uri: Uri.parse(configuration.path));
}
}
class RootRouterDelegate extends RouterDelegate<ParsedRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final RouteState routeState;
RootRouterDelegate(this.routeState) {
routeState.addListener(notifyListeners);
}
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
ParsedRoute get currentConfiguration {
print("=======currentConfiguration:${routeState.route.path}===================");
return routeState.route;
}
@override
void dispose() {
routeState.removeListener(notifyListeners);
routeState.dispose();
super.dispose();
}
final _addColorKey = const ValueKey('addColor');
@override
Widget build(BuildContext context) {
final routeState = RouteStateScope.of(context);
List<Page> pages =[];
final String path = routeState.route.path;
pages.add(MaterialPage(child: HomePage()));
String? selectedColor;
if (path.startsWith('/color/detail') ) {
selectedColor = routeState.route.queryParameters['color'];
}
if(selectedColor!=null){
pages.add(MaterialPage(child: StlColorPage(
color: Color(int.parse(selectedColor,radix: 16)),
)));
}
bool isAddPage = path == '/color/add';
if(isAddPage){
pages.add(MaterialPage(
key: _addColorKey,
child: ColorAddPage()));
}
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: (route, result) {
if (route.settings is Page && (route.settings as Page).key == _addColorKey) {
routeState.go('/color');
}
if(selectedColor!=null){
selectedColor = null;
routeState.go('/color');
}
return route.didPop(result);
},
);
}
/// 格式: /a /b
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
print("===setNewRoutePath===${configuration}=================");
routeState.route = configuration;
return;
}
// @override
// Future<void> setInitialRoutePath( configuration) async{
// _pages = [_pageMap['/']!];
// notifyListeners();
// }
}

View File

@@ -1,48 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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/widgets.dart';
import 'parsed_route.dart';
import 'parser.dart';
/// The current route state. To change the current route, call obtain the state
/// using `RouteStateScope.of(context)` and call `go()`:
///
/// ```
/// RouteStateScope.of(context).go('/book/2');
/// ```
class RouteState extends ChangeNotifier {
final AppRouteParser _parser;
ParsedRoute _route;
RouteState(this._parser,{String initial=''}) : _route = ParsedRoute(initial,{});
ParsedRoute get route => _route;
set route(ParsedRoute route) {
// Don't notify listeners if the path hasn't changed.
if (_route == route) return;
_route = route;
notifyListeners();
}
Future<void> go(String route) async {
this.route = await _parser
.parseRouteInformation(RouteInformation(uri: Uri.parse(route)));
}
}
/// Provides the current [RouteState] to descendant widgets in the tree.
class RouteStateScope extends InheritedNotifier<RouteState> {
const RouteStateScope({
required super.notifier,
required super.child,
super.key,
});
static RouteState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<RouteStateScope>()!.notifier!;
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'pages/app.dart';
void main() {
setHashUrlStrategy();
runApp(const App());
}

View File

@@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import '../route/parser.dart';
import '../route/route_state.dart';
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
AppRouteParser parser = AppRouteParser();
AppRouterDelegate routerDelegate = AppRouterDelegate();
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: parser,
// backButtonDispatcher: RootBackButtonDispatcher(),
theme: ThemeData(
useMaterial3: true,
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
);
}
}

View File

@@ -1,67 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
appBar: AppBar(
title: Text('添加颜色'),
actions: [IconButton(onPressed: _selectColor, icon: Icon(Icons.check ))],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 20),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
}

View File

@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
import '../route/route_state.dart';
import 'app.dart';
import 'color_add_page.dart';
import '../../../common/components/colors_panel.dart';
import '../../../common/pages/stl_color_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
);
}
void _selectColor(Color color){
String value = color.value.toRadixString(16);
routerDelegate.go('/color/detail?color=$value');
}
void _toAddPage() async {
routerDelegate.go('/color/add');
}
}

View File

@@ -1,21 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:quiver/core.dart';
/// A route path that has been parsed by [TemplateRouteParser].
class ParsedRoute extends Equatable{
/// The current path location without query parameters. (/book/123)
final String path;
/// The query parameters ({search: abc})
final Map<String, String> queryParameters;
const ParsedRoute(this.path, this.queryParameters);
@override
List<Object?> get props => [path,queryParameters];
}

View File

@@ -1,122 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:iroute/common/pages/stl_color_page.dart';
import 'package:path_to_regexp/path_to_regexp.dart';
import '../pages/app.dart';
import '../pages/home_page.dart';
import '../pages/color_add_page.dart';
import 'parsed_route.dart';
import 'route_state.dart';
class AppRouteParser extends RouteInformationParser<ParsedRoute> {
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
print("=======parseRouteInformation:${routeInformation.uri.path}===================");
final uri = routeInformation.uri;
final path = uri.toString();
final queryParams = uri.queryParameters;
return ParsedRoute(path, queryParams);
}
@override
RouteInformation? restoreRouteInformation(ParsedRoute configuration) {
print("=======restoreRouteInformation:${configuration}===================");
return RouteInformation(uri: Uri.parse(configuration.path));
}
}
class AppRouterDelegate extends RouterDelegate<ParsedRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
AppRouterDelegate({String initial = '/'}) {
_routes.add(ParsedRoute(initial, {}));
}
final List<ParsedRoute> _routes = [];
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
ParsedRoute get currentConfiguration {
return _routes.last;
}
Future<void> go(String route) async {
ParsedRoute _route = await parser.parseRouteInformation(RouteInformation(uri: Uri.parse(route)));
if(_route!=_routes.last){
_routes.add(_route);
}
notifyListeners();
}
final _addColorKey = const ValueKey('addColor');
final _colorDetailKey = const ValueKey('ColorDetail');
@override
Widget build(BuildContext context) {
List<Page> pages = [];
for (ParsedRoute route in _routes) {
Page? page = _buildPageByPath(route);
if(page!=null){
pages.add(page);
}
}
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: _onPagePop,
);
}
Page? _buildPageByPath(ParsedRoute route) {
if(route.path == '/'){
return MaterialPage(child: HomePage());
}
if(route.path.startsWith('/color/detail')){
String? selectedColor = route.queryParameters['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
return MaterialPage(key: _colorDetailKey, child: StlColorPage(color: color));
}
}
if (route.path == '/color/add') {
return MaterialPage(key: _addColorKey, child: ColorAddPage());
}
return null;
}
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
print("===setNewRoutePath===${configuration}=================");
if(configuration!=_routes.last){
_routes.add(configuration);
}
}
bool _onPagePop(Route route, result) {
RouteSettings settings = route.settings;
if(settings is Page){
if(settings.key==_addColorKey){
_routes.removeLast();
notifyListeners();
}
if(settings.key==_colorDetailKey){
_routes.removeLast();
notifyListeners();
}
}
return route.didPop(result);
}
}

View File

@@ -1,48 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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/widgets.dart';
import 'parsed_route.dart';
import 'parser.dart';
/// The current route state. To change the current route, call obtain the state
/// using `RouteStateScope.of(context)` and call `go()`:
///
/// ```
/// RouteStateScope.of(context).go('/book/2');
/// ```
class RouteState extends ChangeNotifier {
final AppRouteParser _parser;
ParsedRoute _route;
RouteState(this._parser,{String initial=''}) : _route = ParsedRoute(initial,{});
ParsedRoute get route => _route;
set route(ParsedRoute route) {
// Don't notify listeners if the path hasn't changed.
if (_route == route) return;
_route = route;
notifyListeners();
}
Future<void> go(String route) async {
this.route = await _parser
.parseRouteInformation(RouteInformation(uri: Uri.parse(route)));
}
}
// /// Provides the current [RouteState] to descendant widgets in the tree.
// class RouteStateScope extends InheritedNotifier<RouteState> {
// const RouteStateScope({
// required super.notifier,
// required super.child,
// super.key,
// });
//
// static RouteState of(BuildContext context) =>
// context.dependOnInheritedWidgetOfExactType<RouteStateScope>()!.notifier!;
// }

View File

@@ -1,29 +0,0 @@
import 'package:flutter/material.dart';
import '../../main.dart';
import 'app_tool_bar.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppToolBar(),
body: Router(routerDelegate: router),
));
}
}

View File

@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import '../page_a.dart';
import '../page_b.dart';
import '../page_c.dart';
import '../home_page.dart';
class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
AppRouterDelegate({String initial = '/'}):_path=initial;
String _path;
String get path=> _path;
void go(String path){
_path = path;
notifyListeners();
}
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: parserConfig(_path).map((e) => _pageMap[e]!).toList(),
);
}
final Map<String, Page> _pageMap = const {
'/': MaterialPage(child: HomePage()),
'a': MaterialPage(child: PageA()),
'b': MaterialPage(child: PageB()),
'c': MaterialPage(child: PageC()),
};
Widget buildNavigatorByConfig(String path) {
return Navigator(
onPopPage: _onPopPage,
pages: parserConfig(path).map((e) => _pageMap[e]!).toList(),
);
}
bool _onPopPage(Route route, result) {
if(path.length>1){
int last = path.lastIndexOf('/');
_path = path.substring(0,last);
if(_path.isEmpty) _path = '/';
notifyListeners();
}
return route.didPop(result);
}
@override
GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
@override
Future<void> setNewRoutePath(String configuration) async{
_path = configuration;
}
List<String> parserConfig(String path){
/// 例: /a/b/c 进栈 /,a,b,c 界面
List<String> result = ['/'];
if(path.startsWith('/')){
path = path.substring(1);
if(path.isNotEmpty){
List<String> parts = path.split('/');
result.addAll(parts);
}
}
return result;
}
}

View File

@@ -1,64 +0,0 @@
import 'package:flutter/material.dart';
import '../../main.dart';
class AppToolBar extends StatefulWidget implements PreferredSizeWidget{
const AppToolBar({super.key});
@override
State<AppToolBar> createState() => _AppToolBarState();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _AppToolBarState extends State<AppToolBar> {
TextEditingController _ctrl = TextEditingController();
@override
void initState() {
super.initState();
_changRoute();
router.addListener(_changRoute);
}
@override
void dispose() {
router.removeListener(_changRoute);
_ctrl.dispose();
super.dispose();
}
void _changRoute() {
_ctrl.text=router.path;
}
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
title : TextField(
controller: _ctrl,
onSubmitted: _onSubmitted,
decoration: InputDecoration( //装饰
filled: true, //填充
fillColor: Color(0xffF3F6F9), //填充颜色
constraints: BoxConstraints(maxHeight: 34), //约束信息
contentPadding: EdgeInsets.only(top: -14,left: 10),
border: UnderlineInputBorder( //边线信息
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(6)),
),
hintText: "请输入路由", //提示字
hintStyle: TextStyle(fontSize: 14) //提示字样式
),
),
);
}
void _onSubmitted(String value) {
router.go(value);
}
}

View File

@@ -1,30 +0,0 @@
import 'package:flutter/material.dart';
import '../main.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xffCCFFCC);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: const Text('主页起点'),
backgroundColor: bgColor,
),
body: Center(child: ElevatedButton(
onPressed: () => toPageA(context),
child: const Text('Push A'),
),
));
}
void toPageA(BuildContext context) {
router.go('/a');
}
}

View File

@@ -1,30 +0,0 @@
import 'package:flutter/material.dart';
import '../main.dart';
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xffCCFFFF);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: const Text('A 界面'),
backgroundColor: bgColor,
),
body: Center(
child: ElevatedButton(
onPressed: () => toPageB(context),
child: const Text('Push B'),
),
));
}
void toPageB(BuildContext context) {
// router.value = ['/', 'a', 'b'];
router.go('/a/b');
}
}

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import '../main.dart';
class PageB extends StatelessWidget {
const PageB({super.key});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xffCCE5FF);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: const Text('B 界面'),
backgroundColor: bgColor,
),
body: Center(
child: ElevatedButton(
onPressed: () => toPageC(context),
child: const Text('Push C'),
),
));
}
void toPageC(BuildContext context){
router.go('/a/b/c');
}
}

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
import '../main.dart';
class PageC extends StatelessWidget {
const PageC({super.key});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xffFFE6CC);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: const Text('C 界面'),
backgroundColor: bgColor,
),
body: Center(
child: Text('到达终点'),
));
}
}

View File

@@ -1,14 +0,0 @@
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'pages/app/app.dart';
import 'pages/app/app_router_delegate.dart';
AppRouterDelegate router = AppRouterDelegate();
void main() {
runApp(const App());
}

View File

@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
import 'pages/app/app.dart';
void main() {
runApp(const App());
}

View File

@@ -1,83 +0,0 @@
import 'package:toly_menu/toly_menu.dart';
List<MenuData> menus = [
MenuData(path: '/dashboard', label: '总览面板', children: [
MenuData(path: '/dashboard/data_analyse', label: '数据分析', deep: 1),
MenuData(path: '/dashboard/work_board', label: '工作台', deep: 1, children: [
MenuData(path: '/dashboard/work_board/a', label: '第一工作区', deep: 2),
MenuData(path: '/dashboard/work_board/b', label: '第二工作区', deep: 2),
MenuData(path: '/dashboard/work_board/c', label: '第三工作区', deep: 2),
]),
]),
MenuData(path: '/cases', label: '案例演示', children: [
MenuData(path: '/cases/counter', label: '计数器项目', deep: 1),
MenuData(path: '/cases/guess', label: '猜数字项目', deep: 1),
MenuData(path: '/cases/muyu', label: '电子木鱼项目', deep: 1),
MenuData(path: '/cases/canvas', label: '白板绘制项目', deep: 1),
MenuData(path: '/cases/timer', label: '计时器项目', deep: 1),
]),
MenuData(path: '/manager', label: '系统管理', children: [
MenuData(path: '/manager/account', label: '账号管理', deep: 1),
MenuData(path: '/manager/role', label: '角色管理', deep: 1),
MenuData(path: '/manager/menu', label: '菜单管理', deep: 1),
]),
MenuData(path: '/knowledge', label: '知识库管理', children: [
MenuData(path: '/knowledge/a', label: '语文知识', deep: 1, children: [
MenuData(
path: '/knowledge/a/1',
label: '诗词歌赋',
deep: 2,
),
MenuData(
path: '/knowledge/a/2',
label: '人物故事',
deep: 2,
),
MenuData(
path: '/knowledge/a/3',
label: '名著推荐',
deep: 2,
)
]),
MenuData(
path: '/knowledge/b',
label: '数学知识',
children: [
MenuData(
path: '/knowledge/b/1',
label: '人物故事',
deep: 2,
),
MenuData(
path: '/knowledge/b/2',
label: '数学定理',
deep: 2,
),
MenuData(
path: '/knowledge/b/3',
label: '几何知识',
deep: 2,
),
MenuData(
path: '/knowledge/b/4',
label: '代数知识',
deep: 2,
)
],
deep: 1),
MenuData(
path: '/knowledge/c',
label: '英语知识',
deep: 1,
),
]),
MenuData(path: '/widgets', label: '组件集录', children: [
MenuData(path: '/widgets/stateless', label: '无状态组件', deep: 1),
MenuData(path: '/widgets/stateful', label: '有状态组件', deep: 1),
MenuData(path: '/widgets/single_child', label: '单子渲染组件', deep: 1),
MenuData(path: '/widgets/mutli_child', label: '多子渲染组件', deep: 1),
MenuData(path: '/widgets/sliver', label: '滑片组件', deep: 1),
MenuData(path: '/widgets/proxy', label: '代理组件', deep: 1),
MenuData(path: '/widgets/other', label: '其他组件', deep: 1),
])
];

View File

@@ -1,107 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:toly_menu/toly_menu.dart';
import '../../menus.dart';
import '../../transition/fade_transition.dart';
import 'root_content.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.windows: FadeTransitionsBuilder(),
}
),
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
home: AppLayout());
}
}
class AppLayout extends StatelessWidget {
const AppLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(
width: 210,
child: MenuPanel(),
),
// Container()
Expanded(child: RootContent())
],
),
// appBar: AppToolBar(),
// body: AppNavigation(),
);
}
}
class MenuPanel extends StatefulWidget {
const MenuPanel({super.key});
@override
State<MenuPanel> createState() => _MenuPanelState();
}
class _MenuPanelState extends State<MenuPanel> {
MenuState state = MenuState(expandMenus: ['/dashboard'], activeMenu: '/dashboard/data_analyse', items: menus);
@override
Widget build(BuildContext context) {
return TolyMenu(state: state, onSelect: _onSelect);
}
void _onSelect(MenuData menu) {
if(menu.isLeaf){
state = state.copyWith(activeMenu: menu.path);
print("menu.path${menu.path}");
goRouter.go(menu.path);
}else{
List<String> menus = [];
String path = menu.path.substring(1);
List<String> parts = path.split('/');
if(parts.isNotEmpty){
String path = '';
for (String part in parts) {
path+='/$part';
menus.add(path);
}
}
if(state.expandMenus.contains(menu.path)){
menus.remove(menu.path);
}
state = state.copyWith(expandMenus: menus);
}
setState(() {
});
}
}

View File

@@ -1,140 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:iroute/cases/cases.dart';
import '../color_page.dart';
// GoRouter configuration
final goRouter = GoRouter(
initialLocation: '/manager',
routes: [
GoRoute(
path: '/dashboard',
builder: (_,__)=>SizedBox(),
routes: [
GoRoute(
path: 'data_analyse',
// builder: (context, state) => ColorPage(title: 'data_analyse',),
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: ColorPage(title: 'data_analyse',),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// Change the opacity of the screen using a Curve based on the the animation's
// value
return FadeTransition(
opacity:
CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
),
GoRoute(
path: 'work_board',
builder: (_,__)=>SizedBox(),
routes: [
GoRoute(
path: 'a',
builder: (context, state) => ColorPage(title: '第一工作区',),
),
GoRoute(
path: 'b',
builder: (context, state) => ColorPage(title: '第二工作区',),
),
GoRoute(
path: 'c',
builder: (context, state) => ColorPage(title: '第三工作区',),
),
]
),
]
),
GoRoute(
path: '/cases',
builder: (_,__)=>SizedBox(),
routes: [
GoRoute(
path: 'counter',
builder: (context, state) => CounterPage(),
),
GoRoute(
path: 'guess',
builder: (context, state) => GuessPage(),
),
GoRoute(
path: 'muyu',
builder: (context, state) => ColorPage(title: '第三工作区',),
), GoRoute(
path: 'canvas',
builder: (context, state) => Paper(),
), GoRoute(
path: 'muyu',
builder: (context, state) => ColorPage(title: '第三工作区',),
),
]
),
GoRoute(
path: '/manager', builder: (_,__)=>SizedBox(),
routes: [
GoRoute(
path: 'account',
builder: (context, state) => ColorPage(title: 'account',),
),
GoRoute(
path: 'role',
builder: (context, state) => ColorPage(title: 'role',),
),
]
),
],
);
class RootContent extends StatelessWidget {
const RootContent({super.key});
@override
Widget build(BuildContext context) {
return Router.withConfig(config: goRouter);
}
}
class RootContentDelegate extends RouterDelegate with ChangeNotifier,PopNavigatorRouterDelegateMixin{
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
],
onPopPage: (route, result) {
// appState.selectedBook = null;
// notifyListeners();
return route.didPop(result);
},
);
}
@override
Future<void> setNewRoutePath(configuration) {
// TODO: implement setNewRoutePath
throw UnimplementedError();
}
}

View File

@@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
class ColorPage extends StatelessWidget {
final String title;
const ColorPage({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text(title)));
}
}

View File

@@ -1,58 +0,0 @@
import 'package:flutter/material.dart';
class FadeTransitionsBuilder extends PageTransitionsBuilder {
/// Constructs a page transition animation that slides the page up.
const FadeTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T>? route,
BuildContext? context,
Animation<double> animation,
Animation<double>? secondaryAnimation,
Widget child,
) {
return _FadeUpwardsPageTransition(routeAnimation: animation, child: child);
}
}
class _FadeUpwardsPageTransition extends StatelessWidget {
_FadeUpwardsPageTransition({
required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
required this.child,
}) : _positionAnimation = routeAnimation.drive(_bottomUpTween.chain(_fastOutSlowInTween)),
_opacityAnimation = routeAnimation.drive(_easeInTween);
// Fractional offset from 1/4 screen below the top to fully on screen.
static final Tween<Offset> _bottomUpTween = Tween<Offset>(
begin: const Offset(0.25,0),
end: Offset.zero,
);
static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
final Animation<Offset> _positionAnimation;
final Animation<double> _opacityAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
// return FadeTransition(
// opacity: _opacityAnimation,
// child: child,
// );
return SlideTransition(
position: _positionAnimation,
child: FadeTransition(
opacity: _opacityAnimation,
child: child,
),
);
}
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'pages/app.dart';
void main() {
setHashUrlStrategy();
runApp(const App());
}

View File

@@ -1,56 +0,0 @@
import 'package:flutter/material.dart';
import '../store/app_state.dart';
import '../route/parser.dart';
import '../route/route_state.dart';
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
late final RootRouterDelegate _delegate;
late final AppRouteParser _parser;
late final AppState _appState;
late final RouteState _routeState;
@override
void initState() {
super.initState();
_parser = AppRouteParser(initialRoute: '/color', allowPaths: [
'/color',
'/color/add',
'/color/detail/:colorHex',
]);
_appState = AppState.initial();
_routeState = RouteState(_parser);
_delegate = RootRouterDelegate(_routeState);
}
@override
Widget build(BuildContext context) {
return AppStateScope(
notifier: _appState,
child: RouteStateScope(
notifier: _routeState,
child: MaterialApp.router(
routerDelegate: _delegate,
routeInformationParser: _parser,
theme: ThemeData(
useMaterial3: true,
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
),
),
);
}
}

View File

@@ -1,67 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
appBar: AppBar(
title: Text('添加颜色'),
actions: [IconButton(onPressed: _selectColor, icon: Icon(Icons.check ))],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 20),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
}

View File

@@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
import '../store/app_state.dart';
import '../route/route_state.dart';
import 'color_add_page.dart';
import '../../../common/components/colors_panel.dart';
import '../../../common/pages/stl_color_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
AppState state = AppStateScope.of(context);
return Scaffold(
appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: ColorsPanel(
colors: state.colors,
onSelect: _selectColor,
),
);
}
void _selectColor(Color color){
String value = '#${color.value.toRadixString(16)}';
RouteStateScope.of(context).update('/color/detail/$value');
}
void _toAddPage() async {
RouteStateScope.of(context).update('/color/add');
}
}

View File

@@ -1,49 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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:collection/collection.dart';
import 'package:quiver/core.dart';
/// A route path that has been parsed by [TemplateRouteParser].
class ParsedRoute {
/// The current path location without query parameters. (/book/123)
final String path;
/// The path template (/book/:id)
final String pathTemplate;
/// The path parameters ({id: 123})
final Map<String, String> parameters;
/// The query parameters ({search: abc})
final Map<String, String> queryParameters;
static const _mapEquality = MapEquality<String, String>();
ParsedRoute(
this.path, this.pathTemplate, this.parameters, this.queryParameters);
@override
bool operator ==(Object other) =>
other is ParsedRoute &&
other.pathTemplate == pathTemplate &&
other.path == path &&
_mapEquality.equals(parameters, other.parameters) &&
_mapEquality.equals(queryParameters, other.queryParameters);
@override
int get hashCode => hash4(
path,
pathTemplate,
_mapEquality.hash(parameters),
_mapEquality.hash(queryParameters),
);
@override
String toString() => '<ParsedRoute '
'template: $pathTemplate '
'path: $path '
'parameters: $parameters '
'query parameters: $queryParameters>';
}

View File

@@ -1,153 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:iroute/common/pages/stl_color_page.dart';
import 'package:path_to_regexp/path_to_regexp.dart';
import '../pages/home_page.dart';
import '../pages/color_add_page.dart';
import '../store/app_state.dart';
import 'parsed_route.dart';
import 'route_state.dart';
class AppRouteParser extends RouteInformationParser<ParsedRoute> {
final List<String> allowPaths;
final ParsedRoute initialRoute;
AppRouteParser({
required this.allowPaths,
String initialRoute = '/',
}) : initialRoute = ParsedRoute(initialRoute, initialRoute, {}, {});
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
print(
"=======parseRouteInformation:${routeInformation.uri.path}===================");
final uri = routeInformation.uri;
final path = uri.toString();
final queryParams = uri.queryParameters;
ParsedRoute parsedRoute = initialRoute;
for (var pathTemplate in allowPaths) {
final parameters = <String>[];
var pathRegExp = pathToRegExp(pathTemplate, parameters: parameters);
if (pathRegExp.hasMatch(path)) {
final match = pathRegExp.matchAsPrefix(path);
if (match == null) continue;
final params = extract(parameters, match);
parsedRoute = ParsedRoute(path, pathTemplate, params, queryParams);
}
}
return parsedRoute;
}
@override
RouteInformation? restoreRouteInformation(ParsedRoute configuration) {
print("=======restoreRouteInformation:${configuration}===================");
return RouteInformation(uri: Uri.parse(configuration.path));
}
}
class RootRouterDelegate extends RouterDelegate<ParsedRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final RouteState routeState;
RootRouterDelegate(this.routeState) {
routeState.addListener(notifyListeners);
}
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
ParsedRoute get currentConfiguration {
print("=======currentConfiguration:${routeState.route.path}===================");
return routeState.route;
}
@override
void dispose() {
routeState.removeListener(notifyListeners);
routeState.dispose();
super.dispose();
}
final _addColorKey = const ValueKey('addColor');
final _detailColorKey = const ValueKey('detailColor');
@override
Widget build(BuildContext context) {
final routeState = RouteStateScope.of(context);
List<Page> pages = [];
final String pathTemplate = routeState.route.pathTemplate;
pages.add(MaterialPage(child: HomePage()));
String? selectedColor;
if (pathTemplate == '/color/detail/:colorHex') {
selectedColor = routeState.route.parameters['colorHex'];
}
if (selectedColor != null) {
int value = int.parse(
selectedColor.substring(
1,
),
radix: 16);
pages.add(MaterialPage(
key: _detailColorKey,
child: StlColorPage(
color: Color(value),
)));
}
bool isAddPage = pathTemplate == '/color/add';
if (isAddPage) {
pages.add(
MaterialPage(key: _addColorKey, child: ColorAddPage()),
);
}
print("===build====================");
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: (route, result) {
print(result);
if (route.settings is Page &&
(route.settings as Page).key == _addColorKey) {
if(result is Color){
AppStateScope.of(context).addColor(result);
}
routeState.update('/color');
}
if (route.settings is Page &&
(route.settings as Page).key == _detailColorKey) {
routeState.update('/color');
}
// appState.selectedBook = null;
// notifyListeners();
return route.didPop(result);
},
);
}
/// 格式: /a /b
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
print("===setNewRoutePath===${configuration}=================");
routeState.route = configuration;
return;
}
// @override
// Future<void> setInitialRoutePath( configuration) async{
// _pages = [_pageMap['/']!];
// notifyListeners();
// }
}

View File

@@ -1,48 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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/widgets.dart';
import 'parsed_route.dart';
import 'parser.dart';
/// The current route state. To change the current route, call obtain the state
/// using `RouteStateScope.of(context)` and call `go()`:
///
/// ```
/// RouteStateScope.of(context).go('/book/2');
/// ```
class RouteState extends ChangeNotifier {
final AppRouteParser _parser;
ParsedRoute _route;
RouteState(this._parser) : _route = _parser.initialRoute;
ParsedRoute get route => _route;
set route(ParsedRoute route) {
// Don't notify listeners if the path hasn't changed.
if (_route == route) return;
_route = route;
notifyListeners();
}
Future<void> update(String route) async {
this.route = await _parser
.parseRouteInformation(RouteInformation(uri: Uri.parse(route)));
}
}
/// Provides the current [RouteState] to descendant widgets in the tree.
class RouteStateScope extends InheritedNotifier<RouteState> {
const RouteStateScope({
required super.notifier,
required super.child,
super.key,
});
static RouteState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<RouteStateScope>()!.notifier!;
}

View File

@@ -1,32 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier{
final List<Color> colors;
AppState({required this.colors,});
AppState.initial():colors=[
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
void addColor(Color color){
colors.add(color);
notifyListeners();
}
}
/// Provides the current [RouteState] to descendant widgets in the tree.
class AppStateScope extends InheritedNotifier<AppState> {
const AppStateScope({super.key, required super.child, required super.notifier,});
static AppState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<AppStateScope>()!.notifier!;
}

View File

@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
import 'pages/app/app.dart';
void main() {
runApp(const App());
}

View File

@@ -1,76 +0,0 @@
import 'package:toly_menu/toly_menu.dart';
List<MenuData> menus = [
MenuData(path: '/dashboard', label: '总览面板', children: [
MenuData(path: '/dashboard/data_analyse', label: '数据分析', deep: 1),
MenuData(path: '/dashboard/work_board', label: '工作台', deep: 1, children: [
MenuData(path: '/dashboard/work_board/a', label: '第一工作区', deep: 2),
MenuData(path: '/dashboard/work_board/b', label: '第二工作区', deep: 2),
MenuData(path: '/dashboard/work_board/c', label: '第三工作区', deep: 2),
]),
]),
MenuData(path: '/manager', label: '系统管理', children: [
MenuData(path: '/manager/account', label: '账号管理', deep: 1),
MenuData(path: '/manager/role', label: '角色管理', deep: 1),
MenuData(path: '/manager/menu', label: '菜单管理', deep: 1),
]),
MenuData(path: '/knowledge', label: '知识库管理', children: [
MenuData(path: '/knowledge/a', label: '语文知识', deep: 1, children: [
MenuData(
path: '/knowledge/a/1',
label: '诗词歌赋',
deep: 2,
),
MenuData(
path: '/knowledge/a/2',
label: '人物故事',
deep: 2,
),
MenuData(
path: '/knowledge/a/3',
label: '名著推荐',
deep: 2,
)
]),
MenuData(
path: '/knowledge/b',
label: '数学知识',
children: [
MenuData(
path: '/knowledge/b/1',
label: '人物故事',
deep: 2,
),
MenuData(
path: '/knowledge/b/2',
label: '数学定理',
deep: 2,
),
MenuData(
path: '/knowledge/b/3',
label: '几何知识',
deep: 2,
),
MenuData(
path: '/knowledge/b/4',
label: '代数知识',
deep: 2,
)
],
deep: 1),
MenuData(
path: '/knowledge/c',
label: '英语知识',
deep: 1,
),
]),
MenuData(path: '/widgets', label: '组件集录', children: [
MenuData(path: '/widgets/stateless', label: '无状态组件', deep: 1),
MenuData(path: '/widgets/stateful', label: '有状态组件', deep: 1),
MenuData(path: '/widgets/single_child', label: '单子渲染组件', deep: 1),
MenuData(path: '/widgets/mutli_child', label: '多子渲染组件', deep: 1),
MenuData(path: '/widgets/sliver', label: '滑片组件', deep: 1),
MenuData(path: '/widgets/proxy', label: '代理组件', deep: 1),
MenuData(path: '/widgets/other', label: '其他组件', deep: 1),
])
];

View File

@@ -1,89 +0,0 @@
import 'package:flutter/material.dart';
import 'package:toly_menu/toly_menu.dart';
import '../../menus.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
home: AppLayout());
}
}
class AppLayout extends StatelessWidget {
const AppLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(
width: 210,
child: MenuPanel(),
),
// Container()
],
),
// appBar: AppToolBar(),
// body: AppNavigation(),
);
}
}
class MenuPanel extends StatefulWidget {
const MenuPanel({super.key});
@override
State<MenuPanel> createState() => _MenuPanelState();
}
class _MenuPanelState extends State<MenuPanel> {
MenuState state = MenuState(expandMenus: ['/dashboard'], activeMenu: '/dashboard/data_analyse', items: menus);
@override
Widget build(BuildContext context) {
return TolyMenu(state: state, onSelect: _onSelect);
}
void _onSelect(MenuData menu) {
if(menu.isLeaf){
state = state.copyWith(activeMenu: menu.path);
}else{
List<String> menus = [];
String path = menu.path.substring(1);
List<String> parts = path.split('/');
if(parts.isNotEmpty){
String path = '';
for (String part in parts) {
path+='/$part';
menus.add(path);
}
}
if(state.expandMenus.contains(menu.path)){
menus.remove(menu.path);
}
state = state.copyWith(expandMenus: menus);
}
setState(() {
});
}
}

View File

@@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
class RootContent extends StatelessWidget {
const RootContent({super.key});
@override
Widget build(BuildContext context) {
return Router(routerDelegate: RootContentDelegate());
}
}
class RootContentDelegate extends RouterDelegate with ChangeNotifier,PopNavigatorRouterDelegateMixin{
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
],
onPopPage: (route, result) {
// appState.selectedBook = null;
// notifyListeners();
return route.didPop(result);
},
);
}
@override
Future<void> setNewRoutePath(configuration) {
// TODO: implement setNewRoutePath
throw UnimplementedError();
}
}

View File

@@ -1,208 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:iroute/13/04/app/navigation/router/iroute.dart';
import 'package:iroute/common/pages/stl_color_page.dart';
import '../../pages/color/color_page.dart';
import '../../pages/empty/empty_page.dart';
import '../../pages/settings/settings_page.dart';
import '../../pages/counter/counter_page.dart';
import '../../pages/user/user_page.dart';
import '../../transition/fade_transition_page.dart';
import '../../pages/color/color_add_page.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
int? get activeIndex {
if(path.startsWith('/color')) return 0;
if(path.startsWith('/counter')) return 1;
if(path.startsWith('/user')) return 2;
if(path.startsWith('/settings')) return 3;
return null;
}
final Map<String,Completer<dynamic>> _completerMap = {};
Completer<dynamic>? completer;
Future<dynamic> changePathForResult(String value) async{
Completer<dynamic> completer = Completer();
_completerMap[value] = completer;
path = value;
return completer.future;
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
// IRoute? parserPath(String path){
//
// List<String> parts = path.split('/');
// String lever1 = '/${parts[1]}';
// List<IRoute> iRoutes = kDestinationsIRoutes.where((e) => e.path == lever1).toList();
//
// int counter = 2;
//
// IRoute? result;
// String check = lever1;
// for(int i = 0;i<iRoutes.length;i++){
// check = check +"/" + parts[counter];
// String path = iRoutes[i].path;
// if(path == check){
// result = iRoutes[i];
// break;
// }
// // String path =
// // result.children.add(IRoute(path: parts[i]);
// }
// }
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPageByPath(path),
);
}
List<Page> _buildPageByPath(String path) {
if(path.startsWith('/color')){
return buildColorPages(path);
}
Widget? child;
if (path == kDestinationsPaths[1]) {
child = const CounterPage();
}
if (path == kDestinationsPaths[2]) {
child = const UserPage();
}
if (path == kDestinationsPaths[3]) {
child = const SettingPage();
}
return [
FadeTransitionPage(
// key: ValueKey(path),
child: child ?? const EmptyPage(),
)
];
}
List<Page> buildColorPages(String path){
List<Page> result = [];
Uri uri = Uri.parse(path);
for (String segment in uri.pathSegments) {
if(segment == 'color'){
result.add( const FadeTransitionPage(
key: ValueKey('/color'),
child:ColorPage(),
));
}
if(segment =='detail'){
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:StlColorPage(color: color),
));
}
}
if(segment == 'add'){
result.add( const FadeTransitionPage(
key: ValueKey('/color/add'),
child:ColorAddPage(),
));
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if(_completerMap.containsKey(path)){
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path){
Uri uri = Uri.parse(path);
if(uri.pathSegments.length==1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}
// class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
//
// List<String> _value = ['/'];
//
//
// List<String> get value => _value;
//
// set value(List<String> value){
// _value = value;
// notifyListeners();
// }
//
// @override
// Widget build(BuildContext context) {
// return Navigator(
// onPopPage: _onPopPage,
// pages: _value.map((e) => _pageMap[e]!).toList(),
// );
// }
//
// final Map<String, Page> _pageMap = const {
// '/': MaterialPage(child: HomePage()),
// 'a': MaterialPage(child: PageA()),
// 'b': MaterialPage(child: PageB()),
// 'c': MaterialPage(child: PageC()),
// };
//
// bool _onPopPage(Route route, result) {
// _value = List.of(_value)..removeLast();
// notifyListeners();
// return route.didPop(result);
// }
//
// @override
// GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
//
// @override
// Future<void> setNewRoutePath(String configuration) async{
// }
// }

View File

@@ -1,38 +0,0 @@
import 'package:flutter/material.dart';
import 'navigation/app_router_delegate.dart';
import 'navigation/views/app_navigation_rail.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Row(
children: [
const AppNavigationRail(),
Expanded(
child: Router(
routerDelegate: router,
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
],
),
));
}
}

View File

@@ -1,244 +0,0 @@
// 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:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
// This scenario demonstrates how to use redirect to handle a asynchronous
// sign-in flow.
//
// The `StreamAuth` is a mock of google_sign_in. This example wraps it with an
// InheritedNotifier, StreamAuthScope, and relies on
// `dependOnInheritedWidgetOfExactType` to create a dependency between the
// notifier and go_router's parsing pipeline. When StreamAuth broadcasts new
// event, the dependency will cause the go_router to reparse the current url
// which will also trigger the redirect.
void main() => runApp(StreamAuthScope(child: App()));
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Redirection';
// add the login info into the tree as app state that can change over time
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
debugShowCheckedModeBanner: false,
);
late final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
// redirect to the login page if the user is not logged in
redirect: (BuildContext context, GoRouterState state) async {
// Using `of` method creates a dependency of StreamAuthScope. It will
// cause go_router to reparse current route if StreamAuth has new sign-in
// information.
final bool loggedIn = await StreamAuthScope.of(context).isSignedIn();
final bool loggingIn = state.matchedLocation == '/login';
if (!loggedIn) {
return '/login';
}
// if the user is logged in but still on the login page, send them to
// the home page
if (loggingIn) {
return '/';
}
// no need to redirect at all
return null;
},
);
}
/// The login screen.
class LoginScreen extends StatefulWidget {
/// Creates a [LoginScreen].
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
bool loggingIn = false;
late final AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..addListener(() {
setState(() {});
});
controller.repeat();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (loggingIn) CircularProgressIndicator(value: controller.value),
if (!loggingIn)
ElevatedButton(
onPressed: () {
StreamAuthScope.of(context).signIn('test-user');
setState(() {
loggingIn = true;
});
},
child: const Text('Login'),
),
],
),
),
);
}
/// The home screen.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final StreamAuth info = StreamAuthScope.of(context);
return Scaffold(
appBar: AppBar(
title: const Text(App.title),
actions: <Widget>[
IconButton(
onPressed: () => info.signOut(),
tooltip: 'Logout: ${info.currentUser}',
icon: const Icon(Icons.logout),
)
],
),
body: const Center(
child: Text('HomeScreen'),
),
);
}
}
/// A scope that provides [StreamAuth] for the subtree.
class StreamAuthScope extends InheritedNotifier<StreamAuthNotifier> {
/// Creates a [StreamAuthScope] sign in scope.
StreamAuthScope({
super.key,
required super.child,
}) : super(
notifier: StreamAuthNotifier(),
);
/// Gets the [StreamAuth].
static StreamAuth of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<StreamAuthScope>()!
.notifier!
.streamAuth;
}
}
/// A class that converts [StreamAuth] into a [ChangeNotifier].
class StreamAuthNotifier extends ChangeNotifier {
/// Creates a [StreamAuthNotifier].
StreamAuthNotifier() : streamAuth = StreamAuth() {
streamAuth.onCurrentUserChanged.listen((String? string) {
notifyListeners();
});
}
/// The stream auth client.
final StreamAuth streamAuth;
}
/// An asynchronous log in services mock with stream similar to google_sign_in.
///
/// This class adds an artificial delay of 3 second when logging in an user, and
/// will automatically clear the login session after [refreshInterval].
class StreamAuth {
/// Creates an [StreamAuth] that clear the current user session in
/// [refeshInterval] second.
StreamAuth({this.refreshInterval = 20})
: _userStreamController = StreamController<String?>.broadcast() {
_userStreamController.stream.listen((String? currentUser) {
_currentUser = currentUser;
});
}
/// The current user.
String? get currentUser => _currentUser;
String? _currentUser;
/// Checks whether current user is signed in with an artificial delay to mimic
/// async operation.
Future<bool> isSignedIn() async {
await Future<void>.delayed(const Duration(seconds: 1));
return _currentUser != null;
}
/// A stream that notifies when current user has changed.
Stream<String?> get onCurrentUserChanged => _userStreamController.stream;
final StreamController<String?> _userStreamController;
/// The interval that automatically signs out the user.
final int refreshInterval;
Timer? _timer;
Timer _createRefreshTimer() {
return Timer(Duration(seconds: refreshInterval), () {
_userStreamController.add(null);
_timer = null;
});
}
/// Signs in a user with an artificial delay to mimic async operation.
Future<void> signIn(String newUserName) async {
await Future<void>.delayed(const Duration(seconds: 3));
_userStreamController.add(newUserName);
_timer?.cancel();
_timer = _createRefreshTimer();
}
/// Signs out the current user.
Future<void> signOut() async {
_timer?.cancel();
_timer = null;
_userStreamController.add(null);
}
}

View File

@@ -1,171 +0,0 @@
// 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:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'src/auth.dart';
import 'src/data/author.dart';
import 'src/data/book.dart';
import 'src/data/library.dart';
import 'src/screens/author_details.dart';
import 'src/screens/authors.dart';
import 'src/screens/book_details.dart';
import 'src/screens/books.dart';
import 'src/screens/scaffold.dart';
import 'src/screens/settings.dart';
import 'src/screens/sign_in.dart';
void main() => runApp(Bookstore());
/// The book store view.
class Bookstore extends StatelessWidget {
/// Creates a [Bookstore].
Bookstore({super.key});
final ValueKey<String> _scaffoldKey = const ValueKey<String>('App scaffold');
@override
Widget build(BuildContext context) => BookstoreAuthScope(
notifier: _auth,
child: MaterialApp.router(
routerConfig: _router,
),
);
final BookstoreAuth _auth = BookstoreAuth();
late final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
redirect: (_, __) => '/books',
),
GoRoute(
path: '/signin',
pageBuilder: (BuildContext context, GoRouterState state) =>
FadeTransitionPage(
key: state.pageKey,
child: SignInScreen(
onSignIn: (Credentials credentials) {
BookstoreAuthScope.of(context)
.signIn(credentials.username, credentials.password);
},
),
),
),
GoRoute(
path: '/books',
redirect: (_, __) => '/books/popular',
),
GoRoute(
path: '/book/:bookId',
redirect: (BuildContext context, GoRouterState state) =>
'/books/all/${state.pathParameters['bookId']}',
),
GoRoute(
path: '/books/:kind(new|all|popular)',
pageBuilder: (BuildContext context, GoRouterState state) =>
FadeTransitionPage(
key: _scaffoldKey,
child: BookstoreScaffold(
selectedTab: ScaffoldTab.books,
child: BooksScreen(state.pathParameters['kind']!),
),
),
routes: <GoRoute>[
GoRoute(
path: ':bookId',
builder: (BuildContext context, GoRouterState state) {
final String bookId = state.pathParameters['bookId']!;
final Book? selectedBook = libraryInstance.allBooks
.firstWhereOrNull((Book b) => b.id.toString() == bookId);
return BookDetailsScreen(book: selectedBook);
},
),
],
),
GoRoute(
path: '/author/:authorId',
redirect: (BuildContext context, GoRouterState state) =>
'/authors/${state.pathParameters['authorId']}',
),
GoRoute(
path: '/authors',
pageBuilder: (BuildContext context, GoRouterState state) =>
FadeTransitionPage(
key: _scaffoldKey,
child: const BookstoreScaffold(
selectedTab: ScaffoldTab.authors,
child: AuthorsScreen(),
),
),
routes: <GoRoute>[
GoRoute(
path: ':authorId',
builder: (BuildContext context, GoRouterState state) {
final int authorId = int.parse(state.pathParameters['authorId']!);
final Author? selectedAuthor = libraryInstance.allAuthors
.firstWhereOrNull((Author a) => a.id == authorId);
return AuthorDetailsScreen(author: selectedAuthor);
},
),
],
),
GoRoute(
path: '/settings',
pageBuilder: (BuildContext context, GoRouterState state) =>
FadeTransitionPage(
key: _scaffoldKey,
child: const BookstoreScaffold(
selectedTab: ScaffoldTab.settings,
child: SettingsScreen(),
),
),
),
],
redirect: _guard,
refreshListenable: _auth,
debugLogDiagnostics: true,
);
String? _guard(BuildContext context, GoRouterState state) {
final bool signedIn = _auth.signedIn;
final bool signingIn = state.matchedLocation == '/signin';
// Go to /signin if the user is not signed in
if (!signedIn && !signingIn) {
return '/signin';
}
// Go to /books if the user is signed in and tries to go to /signin.
else if (signedIn && signingIn) {
return '/books';
}
// no redirect
return null;
}
}
/// A page that fades in an out.
class FadeTransitionPage extends CustomTransitionPage<void> {
/// Creates a [FadeTransitionPage].
FadeTransitionPage({
required LocalKey super.key,
required super.child,
}) : super(
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
FadeTransition(
opacity: animation.drive(_curveTween),
child: child,
));
static final CurveTween _curveTween = CurveTween(curve: Curves.easeIn);
}

View File

@@ -1,46 +0,0 @@
// 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/widgets.dart';
/// A mock authentication service.
class BookstoreAuth extends ChangeNotifier {
bool _signedIn = false;
/// Whether user has signed in.
bool get signedIn => _signedIn;
/// Signs out the current user.
Future<void> signOut() async {
await Future<void>.delayed(const Duration(milliseconds: 200));
// Sign out.
_signedIn = false;
notifyListeners();
}
/// Signs in a user.
Future<bool> signIn(String username, String password) async {
await Future<void>.delayed(const Duration(milliseconds: 200));
// Sign in. Allow any password.
_signedIn = true;
notifyListeners();
return _signedIn;
}
}
/// An inherited notifier to host [BookstoreAuth] for the subtree.
class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
/// Creates a [BookstoreAuthScope].
const BookstoreAuthScope({
required BookstoreAuth super.notifier,
required super.child,
super.key,
});
/// Gets the [BookstoreAuth] above the context.
static BookstoreAuth of(BuildContext context) => context
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()!
.notifier!;
}

View File

@@ -1,7 +0,0 @@
// 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.
export 'data/author.dart';
export 'data/book.dart';
export 'data/library.dart';

View File

@@ -1,23 +0,0 @@
// 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 'book.dart';
/// Author data class.
class Author {
/// Creates an author data object.
Author({
required this.id,
required this.name,
});
/// The id of the author.
final int id;
/// The name of the author.
final String name;
/// The books of the author.
final List<Book> books = <Book>[];
}

View File

@@ -1,32 +0,0 @@
// 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 'author.dart';
/// Book data class.
class Book {
/// Creates a book data object.
Book({
required this.id,
required this.title,
required this.isPopular,
required this.isNew,
required this.author,
});
/// The id of the book.
final int id;
/// The title of the book.
final String title;
/// The author of the book.
final Author author;
/// Whether the book is popular.
final bool isPopular;
/// Whether the book is new.
final bool isNew;
}

View File

@@ -1,76 +0,0 @@
// 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 'author.dart';
import 'book.dart';
/// Library data mock.
final Library libraryInstance = Library()
..addBook(
title: 'Left Hand of Darkness',
authorName: 'Ursula K. Le Guin',
isPopular: true,
isNew: true)
..addBook(
title: 'Too Like the Lightning',
authorName: 'Ada Palmer',
isPopular: false,
isNew: true)
..addBook(
title: 'Kindred',
authorName: 'Octavia E. Butler',
isPopular: true,
isNew: false)
..addBook(
title: 'The Lathe of Heaven',
authorName: 'Ursula K. Le Guin',
isPopular: false,
isNew: false);
/// A library that contains books and authors.
class Library {
/// The books in the library.
final List<Book> allBooks = <Book>[];
/// The authors in the library.
final List<Author> allAuthors = <Author>[];
/// Adds a book into the library.
void addBook({
required String title,
required String authorName,
required bool isPopular,
required bool isNew,
}) {
final Author author = allAuthors.firstWhere(
(Author author) => author.name == authorName,
orElse: () {
final Author value = Author(id: allAuthors.length, name: authorName);
allAuthors.add(value);
return value;
},
);
final Book book = Book(
id: allBooks.length,
title: title,
isPopular: isPopular,
isNew: isNew,
author: author,
);
author.books.add(book);
allBooks.add(book);
}
/// The list of popular books in the library.
List<Book> get popularBooks => <Book>[
...allBooks.where((Book book) => book.isPopular),
];
/// The list of new books in the library.
List<Book> get newBooks => <Book>[
...allBooks.where((Book book) => book.isNew),
];
}

View File

@@ -1,49 +0,0 @@
// 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:go_router/go_router.dart';
import '../data.dart';
import '../widgets/book_list.dart';
/// The author detail screen.
class AuthorDetailsScreen extends StatelessWidget {
/// Creates an author detail screen.
const AuthorDetailsScreen({
required this.author,
super.key,
});
/// The author to be displayed.
final Author? author;
@override
Widget build(BuildContext context) {
if (author == null) {
return const Scaffold(
body: Center(
child: Text('No author found.'),
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(author!.name),
),
body: Center(
child: Column(
children: <Widget>[
Expanded(
child: BookList(
books: author!.books,
onTap: (Book book) => context.go('/book/${book.id}'),
),
),
],
),
),
);
}
}

View File

@@ -1,31 +0,0 @@
// 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:go_router/go_router.dart';
import '../data.dart';
import '../widgets/author_list.dart';
/// A screen that displays a list of authors.
class AuthorsScreen extends StatelessWidget {
/// Creates an [AuthorsScreen].
const AuthorsScreen({super.key});
/// The title of the screen.
static const String title = 'Authors';
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: AuthorList(
authors: libraryInstance.allAuthors,
onTap: (Author author) {
context.go('/author/${author.id}');
},
),
);
}

View File

@@ -1,77 +0,0 @@
// 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:go_router/go_router.dart';
import 'package:url_launcher/link.dart';
import '../data.dart';
import 'author_details.dart';
/// A screen to display book details.
class BookDetailsScreen extends StatelessWidget {
/// Creates a [BookDetailsScreen].
const BookDetailsScreen({
super.key,
this.book,
});
/// The book to be displayed.
final Book? book;
@override
Widget build(BuildContext context) {
if (book == null) {
return const Scaffold(
body: Center(
child: Text('No book found.'),
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(book!.title),
),
body: Center(
child: Column(
children: <Widget>[
Text(
book!.title,
style: Theme.of(context).textTheme.headlineMedium,
),
Text(
book!.author.name,
style: Theme.of(context).textTheme.titleMedium,
),
TextButton(
onPressed: () {
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (BuildContext context) =>
AuthorDetailsScreen(author: book!.author),
),
);
},
child: const Text('View author (navigator.push)'),
),
Link(
uri: Uri.parse('/author/${book!.author.id}'),
builder: (BuildContext context, FollowLink? followLink) =>
TextButton(
onPressed: followLink,
child: const Text('View author (Link)'),
),
),
TextButton(
onPressed: () {
context.push('/author/${book!.author.id}');
},
child: const Text('View author (GoRouter.push)'),
),
],
),
),
);
}
}

View File

@@ -1,118 +0,0 @@
// 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:go_router/go_router.dart';
import '../data.dart';
import '../widgets/book_list.dart';
/// A screen that displays a list of books.
class BooksScreen extends StatefulWidget {
/// Creates a [BooksScreen].
const BooksScreen(this.kind, {super.key});
/// Which tab to display.
final String kind;
@override
State<BooksScreen> createState() => _BooksScreenState();
}
class _BooksScreenState extends State<BooksScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void didUpdateWidget(BooksScreen oldWidget) {
super.didUpdateWidget(oldWidget);
switch (widget.kind) {
case 'popular':
_tabController.index = 0;
break;
case 'new':
_tabController.index = 1;
break;
case 'all':
_tabController.index = 2;
break;
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Books'),
bottom: TabBar(
controller: _tabController,
onTap: _handleTabTapped,
tabs: const <Tab>[
Tab(
text: 'Popular',
icon: Icon(Icons.people),
),
Tab(
text: 'New',
icon: Icon(Icons.new_releases),
),
Tab(
text: 'All',
icon: Icon(Icons.list),
),
],
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
BookList(
books: libraryInstance.popularBooks,
onTap: _handleBookTapped,
),
BookList(
books: libraryInstance.newBooks,
onTap: _handleBookTapped,
),
BookList(
books: libraryInstance.allBooks,
onTap: _handleBookTapped,
),
],
),
);
void _handleBookTapped(Book book) {
context.go('/book/${book.id}');
}
void _handleTabTapped(int index) {
switch (index) {
case 1:
context.go('/books/new');
break;
case 2:
context.go('/books/all');
break;
case 0:
default:
context.go('/books/popular');
break;
}
}
}

View File

@@ -1,71 +0,0 @@
// 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:adaptive_navigation/adaptive_navigation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
/// The enum for scaffold tab.
enum ScaffoldTab {
/// The books tab.
books,
/// The authors tab.
authors,
/// The settings tab.
settings
}
/// The scaffold for the book store.
class BookstoreScaffold extends StatelessWidget {
/// Creates a [BookstoreScaffold].
const BookstoreScaffold({
required this.selectedTab,
required this.child,
super.key,
});
/// Which tab of the scaffold to display.
final ScaffoldTab selectedTab;
/// The scaffold body.
final Widget child;
@override
Widget build(BuildContext context) => Scaffold(
body: AdaptiveNavigationScaffold(
selectedIndex: selectedTab.index,
body: child,
onDestinationSelected: (int idx) {
switch (ScaffoldTab.values[idx]) {
case ScaffoldTab.books:
context.go('/books');
break;
case ScaffoldTab.authors:
context.go('/authors');
break;
case ScaffoldTab.settings:
context.go('/settings');
break;
}
},
destinations: const <AdaptiveScaffoldDestination>[
AdaptiveScaffoldDestination(
title: 'Books',
icon: Icons.book,
),
AdaptiveScaffoldDestination(
title: 'Authors',
icon: Icons.person,
),
AdaptiveScaffoldDestination(
title: 'Settings',
icon: Icons.settings,
),
],
),
);
}

View File

@@ -1,101 +0,0 @@
// 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:go_router/go_router.dart';
import 'package:url_launcher/link.dart';
import '../auth.dart';
/// The settings screen.
class SettingsScreen extends StatefulWidget {
/// Creates a [SettingsScreen].
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
@override
Widget build(BuildContext context) => Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: const Card(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 12),
child: SettingsContent(),
),
),
),
),
),
),
);
}
/// The content of a [SettingsScreen].
class SettingsContent extends StatelessWidget {
/// Creates a [SettingsContent].
const SettingsContent({
super.key,
});
@override
Widget build(BuildContext context) => Column(
children: <Widget>[
...<Widget>[
Text(
'Settings',
style: Theme.of(context).textTheme.headlineMedium,
),
ElevatedButton(
onPressed: () {
BookstoreAuthScope.of(context).signOut();
},
child: const Text('Sign out'),
),
Link(
uri: Uri.parse('/book/0'),
builder: (BuildContext context, FollowLink? followLink) =>
TextButton(
onPressed: followLink,
child: const Text('Go directly to /book/0 (Link)'),
),
),
TextButton(
onPressed: () {
context.go('/book/0');
},
child: const Text('Go directly to /book/0 (GoRouter)'),
),
].map<Widget>((Widget w) =>
Padding(padding: const EdgeInsets.all(8), child: w)),
TextButton(
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Alert!'),
content: const Text('The alert description goes here.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
),
child: const Text('Show Dialog'),
)
],
);
}

View File

@@ -1,77 +0,0 @@
// 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';
/// Credential data class.
class Credentials {
/// Creates a credential data object.
Credentials(this.username, this.password);
/// The username of the credentials.
final String username;
/// The password of the credentials.
final String password;
}
/// The sign-in screen.
class SignInScreen extends StatefulWidget {
/// Creates a sign-in screen.
const SignInScreen({
required this.onSignIn,
super.key,
});
/// Called when users sign in with [Credentials].
final ValueChanged<Credentials> onSignIn;
@override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Card(
child: Container(
constraints: BoxConstraints.loose(const Size(600, 600)),
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Sign in',
style: Theme.of(context).textTheme.headlineMedium),
TextField(
decoration: const InputDecoration(labelText: 'Username'),
controller: _usernameController,
),
TextField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
),
Padding(
padding: const EdgeInsets.all(16),
child: TextButton(
onPressed: () async {
widget.onSignIn(Credentials(
_usernameController.value.text,
_passwordController.value.text));
},
child: const Text('Sign in'),
),
),
],
),
),
),
),
);
}

View File

@@ -1,37 +0,0 @@
// 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 '../data.dart';
/// The author list view.
class AuthorList extends StatelessWidget {
/// Creates an [AuthorList].
const AuthorList({
required this.authors,
this.onTap,
super.key,
});
/// The list of authors to be shown.
final List<Author> authors;
/// Called when the user taps an author.
final ValueChanged<Author>? onTap;
@override
Widget build(BuildContext context) => ListView.builder(
itemCount: authors.length,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(
authors[index].name,
),
subtitle: Text(
'${authors[index].books.length} books',
),
onTap: onTap != null ? () => onTap!(authors[index]) : null,
),
);
}

View File

@@ -1,37 +0,0 @@
// 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 '../data.dart';
/// The book list view.
class BookList extends StatelessWidget {
/// Creates an [BookList].
const BookList({
required this.books,
this.onTap,
super.key,
});
/// The list of books to be displayed.
final List<Book> books;
/// Called when the user taps a book.
final ValueChanged<Book>? onTap;
@override
Widget build(BuildContext context) => ListView.builder(
itemCount: books.length,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(
books[index].title,
),
subtitle: Text(
books[index].author.name,
),
onTap: onTap != null ? () => onTap!(books[index]) : null,
),
);
}

View File

@@ -1,87 +0,0 @@
// 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:go_router/go_router.dart';
/// This sample app shows how to use `GoRouter.onException` to redirect on
/// exception.
///
/// The first route '/' is mapped to [HomeScreen], and the second route
/// '/404' is mapped to [NotFoundScreen].
///
/// Any other unknown route or exception is redirected to `/404`.
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
onException: (_, GoRouterState state, GoRouter router) {
router.go('/404', extra: state.uri.toString());
},
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
),
GoRoute(
path: '/404',
builder: (BuildContext context, GoRouterState state) {
return NotFoundScreen(uri: state.extra as String? ?? '');
},
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
/// The home screen
class HomeScreen extends StatelessWidget {
/// Constructs a [HomeScreen]
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: ElevatedButton(
onPressed: () => context.go('/some-unknown-route'),
child: const Text('Simulates user entering unknown url'),
),
),
);
}
}
/// The not found screen
class NotFoundScreen extends StatelessWidget {
/// Constructs a [HomeScreen]
const NotFoundScreen({super.key, required this.uri});
/// The uri that can not be found.
final String uri;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page Not Found')),
body: Center(
child: Text("Can't find a page for: $uri"),
),
);
}
}

View File

@@ -1,87 +0,0 @@
// 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:go_router/go_router.dart';
/// This sample app shows an app with two screens.
///
/// The first route '/' is mapped to [HomeScreen], and the second route
/// '/details' is mapped to [DetailsScreen].
///
/// The buttons use context.go() to navigate to each destination. On mobile
/// devices, each destination is deep-linkable and on the web, can be navigated
/// to using the address bar.
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
/// The home screen
class HomeScreen extends StatelessWidget {
/// Constructs a [HomeScreen]
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: ElevatedButton(
onPressed: () => context.go('/details'),
child: const Text('Go to the Details screen'),
),
),
);
}
}
/// The details screen
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen]
const DetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go back to the Home screen'),
),
),
);
}
}

View File

@@ -1,181 +0,0 @@
// 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:go_router/go_router.dart';
// This scenario demonstrates how to navigate using named locations instead of
// URLs.
//
// Instead of hardcoding the URI locations , you can also use the named
// locations. To use this API, give a unique name to each GoRoute. The name can
// then be used in context.namedLocation to be translate back to the actual URL
// location.
/// Family data class.
class Family {
/// Create a family.
const Family({required this.name, required this.people});
/// The last name of the family.
final String name;
/// The people in the family.
final Map<String, Person> people;
}
/// Person data class.
class Person {
/// Creates a person.
const Person({required this.name, required this.age});
/// The first name of the person.
final String name;
/// The age of the person.
final int age;
}
const Map<String, Family> _families = <String, Family>{
'f1': Family(
name: 'Doe',
people: <String, Person>{
'p1': Person(name: 'Jane', age: 23),
'p2': Person(name: 'John', age: 6),
},
),
'f2': Family(
name: 'Wong',
people: <String, Person>{
'p1': Person(name: 'June', age: 51),
'p2': Person(name: 'Xin', age: 44),
},
),
};
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Named Routes';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
debugShowCheckedModeBanner: false,
);
late final GoRouter _router = GoRouter(
debugLogDiagnostics: true,
routes: <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(fid: state.pathParameters['fid']!),
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
return PersonScreen(
fid: state.pathParameters['fid']!,
pid: state.pathParameters['pid']!);
},
),
],
),
],
),
],
);
}
/// The home screen that shows a list of families.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(App.title),
),
body: ListView(
children: <Widget>[
for (final MapEntry<String, Family> entry in _families.entries)
ListTile(
title: Text(entry.value.name),
onTap: () => context.go(context.namedLocation('family',
pathParameters: <String, String>{'fid': entry.key})),
)
],
),
);
}
}
/// The screen that shows a list of persons in a family.
class FamilyScreen extends StatelessWidget {
/// Creates a [FamilyScreen].
const FamilyScreen({required this.fid, super.key});
/// The id family to display.
final String fid;
@override
Widget build(BuildContext context) {
final Map<String, Person> people = _families[fid]!.people;
return Scaffold(
appBar: AppBar(title: Text(_families[fid]!.name)),
body: ListView(
children: <Widget>[
for (final MapEntry<String, Person> entry in people.entries)
ListTile(
title: Text(entry.value.name),
onTap: () => context.go(context.namedLocation(
'person',
pathParameters: <String, String>{'fid': fid, 'pid': entry.key},
queryParameters: <String, String>{'qid': 'quid'},
)),
),
],
),
);
}
}
/// The person screen.
class PersonScreen extends StatelessWidget {
/// Creates a [PersonScreen].
const PersonScreen({required this.fid, required this.pid, super.key});
/// The id of family this person belong to.
final String fid;
/// The id of the person to be displayed.
final String pid;
@override
Widget build(BuildContext context) {
final Family family = _families[fid]!;
final Person person = family.people[pid]!;
return Scaffold(
appBar: AppBar(title: Text(person.name)),
body: Text('${person.name} ${family.name} is ${person.age} years old'),
);
}
}

View File

@@ -1,460 +0,0 @@
// 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:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _tabANavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'tabANav');
// This example demonstrates how to setup nested navigation using a
// BottomNavigationBar, where each bar item uses its own persistent navigator,
// i.e. navigation state is maintained separately for each item. This setup also
// enables deep linking into nested pages.
//
// This example also demonstrates how build a nested shell with a custom
// container for the branch Navigators (in this case a TabBarView).
void main() {
runApp(NestedTabNavigationExampleApp());
}
/// An example demonstrating how to use nested navigators
class NestedTabNavigationExampleApp extends StatelessWidget {
/// Creates a NestedTabNavigationExampleApp
NestedTabNavigationExampleApp({super.key});
final GoRouter _router = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/a',
routes: <RouteBase>[
StatefulShellRoute(
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
// This nested StatefulShellRoute demonstrates the use of a
// custom container for the branch Navigators. In this implementation,
// no customization is done in the builder function (navigationShell
// itself is simply used as the Widget for the route). Instead, the
// navigatorContainerBuilder function below is provided to
// customize the container for the branch Navigators.
return navigationShell;
},
navigatorContainerBuilder: (BuildContext context,
StatefulNavigationShell navigationShell, List<Widget> children) {
// Returning a customized container for the branch
// Navigators (i.e. the `List<Widget> children` argument).
//
// See ScaffoldWithNavBar for more details on how the children
// are managed (using AnimatedBranchContainer).
return ScaffoldWithNavBar(
navigationShell: navigationShell, children: children);
},
branches: <StatefulShellBranch>[
// The route branch for the first tab of the bottom navigation bar.
StatefulShellBranch(
navigatorKey: _tabANavigatorKey,
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the first tab of the
// bottom navigation bar.
path: '/a',
builder: (BuildContext context, GoRouterState state) =>
const RootScreenA(),
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) =>
const DetailsScreen(label: 'A'),
),
],
),
],
),
// The route branch for the third tab of the bottom navigation bar.
StatefulShellBranch(
// StatefulShellBranch will automatically use the first descendant
// GoRoute as the initial location of the branch. If another route
// is desired, specify the location of it using the defaultLocation
// parameter.
// defaultLocation: '/c2',
routes: <RouteBase>[
StatefulShellRoute(
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
// Just like with the top level StatefulShellRoute, no
// customization is done in the builder function.
return navigationShell;
},
navigatorContainerBuilder: (BuildContext context,
StatefulNavigationShell navigationShell,
List<Widget> children) {
// Returning a customized container for the branch
// Navigators (i.e. the `List<Widget> children` argument).
//
// See TabbedRootScreen for more details on how the children
// are managed (in a TabBarView).
return TabbedRootScreen(
navigationShell: navigationShell, children: children);
},
// This bottom tab uses a nested shell, wrapping sub routes in a
// top TabBar.
branches: <StatefulShellBranch>[
StatefulShellBranch(routes: <GoRoute>[
GoRoute(
path: '/b1',
builder: (BuildContext context, GoRouterState state) =>
const TabScreen(
label: 'B1', detailsPath: '/b1/details'),
routes: <RouteBase>[
GoRoute(
path: 'details',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B1',
withScaffold: false,
),
),
],
),
]),
StatefulShellBranch(routes: <GoRoute>[
GoRoute(
path: '/b2',
builder: (BuildContext context, GoRouterState state) =>
const TabScreen(
label: 'B2', detailsPath: '/b2/details'),
routes: <RouteBase>[
GoRoute(
path: 'details',
builder:
(BuildContext context, GoRouterState state) =>
const DetailsScreen(
label: 'B2',
withScaffold: false,
),
),
],
),
]),
],
),
],
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerConfig: _router,
);
}
}
/// 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.navigationShell,
required this.children,
Key? key,
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;
/// The children (branch Navigators) to display in a custom container
/// ([AnimatedBranchContainer]).
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBranchContainer(
currentIndex: navigationShell.currentIndex,
children: children,
),
bottomNavigationBar: BottomNavigationBar(
// Here, the items of BottomNavigationBar are hard coded. In a real
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index),
),
);
}
/// Navigate to the current location of the branch at the provided index when
/// tapping an item in the BottomNavigationBar.
void _onTap(BuildContext context, int index) {
// When navigating to a new branch, it's recommended to use the goBranch
// method, as doing so makes sure the last navigation state of the
// Navigator for the branch is restored.
navigationShell.goBranch(
index,
// A common pattern when using bottom navigation bars is to support
// navigating to the initial location when tapping the item that is
// already active. This example demonstrates how to support this behavior,
// using the initialLocation parameter of goBranch.
initialLocation: index == navigationShell.currentIndex,
);
}
}
/// Custom branch Navigator container that provides animated transitions
/// when switching branches.
class AnimatedBranchContainer extends StatelessWidget {
/// Creates a AnimatedBranchContainer
const AnimatedBranchContainer(
{super.key, required this.currentIndex, required this.children});
/// The index (in [children]) of the branch Navigator to display.
final int currentIndex;
/// The children (branch Navigators) to display in this container.
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Stack(
children: children.mapIndexed(
(int index, Widget navigator) {
return AnimatedScale(
scale: index == currentIndex ? 1 : 1.5,
duration: const Duration(milliseconds: 400),
child: AnimatedOpacity(
opacity: index == currentIndex ? 1 : 0,
duration: const Duration(milliseconds: 400),
child: _branchNavigatorWrapper(index, navigator),
),
);
},
).toList());
}
Widget _branchNavigatorWrapper(int index, Widget navigator) => IgnorePointer(
ignoring: index != currentIndex,
child: TickerMode(
enabled: index == currentIndex,
child: navigator,
),
);
}
/// Widget for the root page for the first section of the bottom navigation bar.
class RootScreenA extends StatelessWidget {
/// Creates a RootScreenA
const RootScreenA({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Root of section A'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Screen A', style: Theme.of(context).textTheme.titleLarge),
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () {
GoRouter.of(context).go('/a/details');
},
child: const Text('View details'),
),
],
),
),
);
}
}
/// The details screen for either the A or B screen.
class DetailsScreen extends StatefulWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
this.param,
this.withScaffold = true,
super.key,
});
/// The label to display in the center of the screen.
final String label;
/// Optional param
final String? param;
/// Wrap in scaffold
final bool withScaffold;
@override
State<StatefulWidget> createState() => DetailsScreenState();
}
/// The state for DetailsScreen
class DetailsScreenState extends State<DetailsScreen> {
int _counter = 0;
@override
Widget build(BuildContext context) {
if (widget.withScaffold) {
return Scaffold(
appBar: AppBar(
title: Text('Details Screen - ${widget.label}'),
),
body: _build(context),
);
} else {
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: _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)),
if (!widget.withScaffold) ...<Widget>[
const Padding(padding: EdgeInsets.all(16)),
TextButton(
onPressed: () {
GoRouter.of(context).pop();
},
child: const Text('< Back',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
),
]
],
),
);
}
}
/// Builds a nested shell using a [TabBar] and [TabBarView].
class TabbedRootScreen extends StatefulWidget {
/// Constructs a TabbedRootScreen
const TabbedRootScreen(
{required this.navigationShell, required this.children, super.key});
/// The current state of the parent StatefulShellRoute.
final StatefulNavigationShell navigationShell;
/// The children (branch Navigators) to display in the [TabBarView].
final List<Widget> children;
@override
State<StatefulWidget> createState() => _TabbedRootScreenState();
}
class _TabbedRootScreenState extends State<TabbedRootScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController = TabController(
length: widget.children.length,
vsync: this,
initialIndex: widget.navigationShell.currentIndex);
@override
void didUpdateWidget(covariant TabbedRootScreen oldWidget) {
super.didUpdateWidget(oldWidget);
_tabController.index = widget.navigationShell.currentIndex;
}
@override
Widget build(BuildContext context) {
final List<Tab> tabs = widget.children
.mapIndexed((int i, _) => Tab(text: 'Tab ${i + 1}'))
.toList();
return Scaffold(
appBar: AppBar(
title: const Text('Root of Section B (nested TabBar shell)'),
bottom: TabBar(
controller: _tabController,
tabs: tabs,
onTap: (int tappedIndex) => _onTabTap(context, tappedIndex),
)),
body: TabBarView(
controller: _tabController,
children: widget.children,
),
);
}
void _onTabTap(BuildContext context, int index) {
widget.navigationShell.goBranch(index);
}
}
/// Widget for the pages in the top tab bar.
class TabScreen extends StatelessWidget {
/// Creates a RootScreen
const TabScreen({required this.label, required this.detailsPath, super.key});
/// The label
final String label;
/// The path to the detail page
final String detailsPath;
@override
Widget build(BuildContext context) {
return 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);
},
child: const Text('View details'),
),
],
),
);
}
}

View File

@@ -1,110 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Custom Error Screen';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
GoRoute(
path: '/page2',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
),
],
errorBuilder: (BuildContext context, GoRouterState state) =>
ErrorScreen(state.error!),
);
}
/// The screen of the first page.
class Page1Screen extends StatelessWidget {
/// Creates a [Page1Screen].
const Page1Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2Screen extends StatelessWidget {
/// Creates a [Page2Screen].
const Page2Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}
/// The screen of the error page.
class ErrorScreen extends StatelessWidget {
/// Creates an [ErrorScreen].
const ErrorScreen(this.error, {super.key});
/// The error to display.
final Exception error;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('My "Page Not Found" Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText(error.toString()),
TextButton(
onPressed: () => context.go('/'),
child: const Text('Home'),
),
],
),
),
);
}

View File

@@ -1,133 +0,0 @@
// 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:go_router/go_router.dart';
/// Family data class.
class Family {
/// Create a family.
const Family({required this.name, required this.people});
/// The last name of the family.
final String name;
/// The people in the family.
final Map<String, Person> people;
}
/// Person data class.
class Person {
/// Creates a person.
const Person({required this.name, required this.age});
/// The first name of the person.
final String name;
/// The age of the person.
final int age;
}
const Map<String, Family> _families = <String, Family>{
'f1': Family(
name: 'Doe',
people: <String, Person>{
'p1': Person(name: 'Jane', age: 23),
'p2': Person(name: 'John', age: 6),
},
),
'f2': Family(
name: 'Wong',
people: <String, Person>{
'p1': Person(name: 'June', age: 51),
'p2': Person(name: 'Xin', age: 44),
},
),
};
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Extra Parameter';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
late final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family',
builder: (BuildContext context, GoRouterState state) {
final Map<String, Object> params =
state.extra! as Map<String, String>;
final String fid = params['fid']! as String;
return FamilyScreen(fid: fid);
},
),
],
),
],
);
}
/// The home screen that shows a list of families.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
const HomeScreen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: ListView(
children: <Widget>[
for (final MapEntry<String, Family> entry in _families.entries)
ListTile(
title: Text(entry.value.name),
onTap: () => context.goNamed('family',
extra: <String, String>{'fid': entry.key}),
)
],
),
);
}
/// The screen that shows a list of persons in a family.
class FamilyScreen extends StatelessWidget {
/// Creates a [FamilyScreen].
const FamilyScreen({required this.fid, super.key});
/// The family to display.
final String fid;
@override
Widget build(BuildContext context) {
final Map<String, Person> people = _families[fid]!.people;
return Scaffold(
appBar: AppBar(title: Text(_families[fid]!.name)),
body: ListView(
children: <Widget>[
for (final Person p in people.values)
ListTile(
title: Text(p.name),
),
],
),
);
}
}

View File

@@ -1,110 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Initial Location';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
final GoRouter _router = GoRouter(
initialLocation: '/page3',
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
GoRoute(
path: '/page2',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
),
GoRoute(
path: '/page3',
builder: (BuildContext context, GoRouterState state) =>
const Page3Screen(),
),
],
);
}
/// The screen of the first page.
class Page1Screen extends StatelessWidget {
/// Creates a [Page1Screen].
const Page1Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2Screen extends StatelessWidget {
/// Creates a [Page2Screen].
const Page2Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}
/// The screen of the third page.
class Page3Screen extends StatelessWidget {
/// Creates a [Page3Screen].
const Page3Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
],
),
),
);
}

View File

@@ -1,167 +0,0 @@
// 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:go_router/go_router.dart';
import 'package:logging/logging.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Navigator Observer';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
final GoRouter _router = GoRouter(
observers: <NavigatorObserver>[MyNavObserver()],
routes: <GoRoute>[
GoRoute(
// if there's no name, path will be used as name for observers
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
routes: <GoRoute>[
GoRoute(
name: 'page2',
path: 'page2/:p1',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
routes: <GoRoute>[
GoRoute(
name: 'page3',
path: 'page3',
builder: (BuildContext context, GoRouterState state) =>
const Page3Screen(),
),
],
),
],
),
],
);
}
/// The Navigator observer.
class MyNavObserver extends NavigatorObserver {
/// Creates a [MyNavObserver].
MyNavObserver() {
log.onRecord.listen((LogRecord e) => debugPrint('$e'));
}
/// The logged message.
final Logger log = Logger('MyNavObserver');
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) =>
log.info('didPush: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) =>
log.info('didPop: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) =>
log.info('didRemove: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) =>
log.info('didReplace: new= ${newRoute?.str}, old= ${oldRoute?.str}');
@override
void didStartUserGesture(
Route<dynamic> route,
Route<dynamic>? previousRoute,
) =>
log.info('didStartUserGesture: ${route.str}, '
'previousRoute= ${previousRoute?.str}');
@override
void didStopUserGesture() => log.info('didStopUserGesture');
}
extension on Route<dynamic> {
String get str => 'route(${settings.name}: ${settings.arguments})';
}
/// The screen of the first page.
class Page1Screen extends StatelessWidget {
/// Creates a [Page1Screen].
const Page1Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.goNamed(
'page2',
pathParameters: <String, String>{'p1': 'pv1'},
queryParameters: <String, String>{'q1': 'qv1'},
),
child: const Text('Go to page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2Screen extends StatelessWidget {
/// Creates a [Page2Screen].
const Page2Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.goNamed(
'page3',
pathParameters: <String, String>{'p1': 'pv2'},
),
child: const Text('Go to page 3'),
),
],
),
),
);
}
/// The screen of the third page.
class Page3Screen extends StatelessWidget {
/// Creates a [Page3Screen].
const Page3Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}

View File

@@ -1,101 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Push';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
late final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1ScreenWithPush(),
),
GoRoute(
path: '/page2',
builder: (BuildContext context, GoRouterState state) =>
Page2ScreenWithPush(
int.parse(state.uri.queryParameters['push-count']!),
),
),
],
);
}
/// The screen of the first page.
class Page1ScreenWithPush extends StatelessWidget {
/// Creates a [Page1ScreenWithPush].
const Page1ScreenWithPush({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('${App.title}: page 1')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.push('/page2?push-count=1'),
child: const Text('Push page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2ScreenWithPush extends StatelessWidget {
/// Creates a [Page2ScreenWithPush].
const Page2ScreenWithPush(this.pushCount, {super.key});
/// The push count.
final int pushCount;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text('${App.title}: page 2 w/ push count $pushCount'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () => context.push(
'/page2?push-count=${pushCount + 1}',
),
child: const Text('Push page 2 (again)'),
),
),
],
),
),
);
}

View File

@@ -1,95 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Router neglect';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
final GoRouter _router = GoRouter(
// turn off history tracking in the browser for this navigation
routerNeglect: true,
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
GoRoute(
path: '/page2',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
),
],
);
}
/// The screen of the first page.
class Page1Screen extends StatelessWidget {
/// Creates a [Page1Screen].
const Page1Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
const SizedBox(height: 8),
ElevatedButton(
// turn off history tracking in the browser for this navigation;
// note that this isn't necessary when you've set routerNeglect
// but it does illustrate the technique
onPressed: () => Router.neglect(
context,
() => context.push('/page2'),
),
child: const Text('Push page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2Screen extends StatelessWidget {
/// Creates a [Page2Screen].
const Page2Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}

View File

@@ -1,102 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(
const RootRestorationScope(restorationId: 'root', child: App()),
);
/// The main app.
class App extends StatefulWidget {
/// Creates an [App].
const App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: State Restoration';
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> with RestorationMixin {
@override
String get restorationId => 'wrapper';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// Implement restoreState for your app
}
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: App.title,
restorationScopeId: 'app',
);
final GoRouter _router = GoRouter(
routes: <GoRoute>[
// restorationId set for the route automatically
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const Page1Screen(),
),
// restorationId set for the route automatically
GoRoute(
path: '/page2',
builder: (BuildContext context, GoRouterState state) =>
const Page2Screen(),
),
],
restorationScopeId: 'router',
);
}
/// The screen of the first page.
class Page1Screen extends StatelessWidget {
/// Creates a [Page1Screen].
const Page1Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/page2'),
child: const Text('Go to page 2'),
),
],
),
),
);
}
/// The screen of the second page.
class Page2Screen extends StatelessWidget {
/// Creates a [Page2Screen].
const Page2Screen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}

View File

@@ -1,236 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(RestorableStatefulShellRouteExampleApp());
/// An example demonstrating how to use StatefulShellRoute with state
/// restoration.
class RestorableStatefulShellRouteExampleApp extends StatelessWidget {
/// Creates a NestedTabNavigationExampleApp
RestorableStatefulShellRouteExampleApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: '/a',
restorationScopeId: 'router',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
restorationScopeId: 'shell1',
pageBuilder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
return MaterialPage<void>(
restorationId: 'shellWidget1',
child: ScaffoldWithNavBar(navigationShell: navigationShell));
},
branches: <StatefulShellBranch>[
// The route branch for the first tab of the bottom navigation bar.
StatefulShellBranch(
restorationScopeId: 'branchA',
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the first tab of the
// bottom navigation bar.
path: '/a',
pageBuilder: (BuildContext context, GoRouterState state) =>
const MaterialPage<void>(
restorationId: 'screenA',
child:
RootScreen(label: 'A', detailsPath: '/a/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',
pageBuilder: (BuildContext context, GoRouterState state) =>
const MaterialPage<void>(
restorationId: 'screenADetail',
child: DetailsScreen(label: 'A')),
),
],
),
],
),
// The route branch for the second tab of the bottom navigation bar.
StatefulShellBranch(
restorationScopeId: 'branchB',
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the second tab of the
// bottom navigation bar.
path: '/b',
pageBuilder: (BuildContext context, GoRouterState state) =>
const MaterialPage<void>(
restorationId: 'screenB',
child:
RootScreen(label: 'B', detailsPath: '/b/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',
pageBuilder: (BuildContext context, GoRouterState state) =>
const MaterialPage<void>(
restorationId: 'screenBDetail',
child: DetailsScreen(label: 'B')),
),
],
),
],
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
restorationScopeId: 'app',
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerConfig: _router,
);
}
}
/// 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.navigationShell,
Key? key,
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
],
currentIndex: navigationShell.currentIndex,
onTap: (int tappedIndex) => navigationShell.goBranch(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,
super.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('Root of section $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);
},
child: const Text('View details'),
),
],
),
),
);
}
}
/// The details screen for either the A or B screen.
class DetailsScreen extends StatefulWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
super.key,
});
/// The label to display in the center of the screen.
final String label;
@override
State<StatefulWidget> createState() => DetailsScreenState();
}
/// The state for DetailsScreen
class DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
final RestorableInt _counter = RestorableInt(0);
@override
String? get restorationId => 'DetailsScreen-${widget.label}';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_counter, 'counter');
}
@override
void dispose() {
super.dispose();
_counter.dispose();
}
@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.value}',
style: Theme.of(context).textTheme.titleLarge),
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () {
setState(() {
_counter.value++;
});
},
child: const Text('Increment counter'),
),
const Padding(padding: EdgeInsets.all(8)),
],
),
);
}
}

View File

@@ -1,163 +0,0 @@
// 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:go_router/go_router.dart';
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Custom Transitions';
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
);
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
redirect: (_, __) => '/none',
),
GoRoute(
path: '/fade',
pageBuilder: (BuildContext context, GoRouterState state) =>
CustomTransitionPage<void>(
key: state.pageKey,
child: const ExampleTransitionsScreen(
kind: 'fade',
color: Colors.red,
),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
FadeTransition(opacity: animation, child: child),
),
),
GoRoute(
path: '/scale',
pageBuilder: (BuildContext context, GoRouterState state) =>
CustomTransitionPage<void>(
key: state.pageKey,
child: const ExampleTransitionsScreen(
kind: 'scale',
color: Colors.green,
),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
ScaleTransition(scale: animation, child: child),
),
),
GoRoute(
path: '/slide',
pageBuilder: (BuildContext context, GoRouterState state) =>
CustomTransitionPage<void>(
key: state.pageKey,
child: const ExampleTransitionsScreen(
kind: 'slide',
color: Colors.yellow,
),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
SlideTransition(
position: animation.drive(
Tween<Offset>(
begin: const Offset(0.25, 0.25),
end: Offset.zero,
).chain(CurveTween(curve: Curves.easeIn)),
),
child: child,
),
),
),
GoRoute(
path: '/rotation',
pageBuilder: (BuildContext context, GoRouterState state) =>
CustomTransitionPage<void>(
key: state.pageKey,
child: const ExampleTransitionsScreen(
kind: 'rotation',
color: Colors.purple,
),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
RotationTransition(turns: animation, child: child),
),
),
GoRoute(
path: '/none',
pageBuilder: (BuildContext context, GoRouterState state) =>
NoTransitionPage<void>(
key: state.pageKey,
child: const ExampleTransitionsScreen(
kind: 'none',
color: Colors.white,
),
),
),
],
);
}
/// An Example transitions screen.
class ExampleTransitionsScreen extends StatelessWidget {
/// Creates an [ExampleTransitionsScreen].
const ExampleTransitionsScreen({
required this.color,
required this.kind,
super.key,
});
/// The available transition kinds.
static final List<String> kinds = <String>[
'fade',
'scale',
'slide',
'rotation',
'none'
];
/// The color of the container.
final Color color;
/// The transition kind.
final String kind;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text('${App.title}: $kind')),
body: Container(
color: color,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (final String kind in kinds)
Padding(
padding: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () => context.go('/$kind'),
child: Text('$kind transition'),
),
)
],
),
),
),
);
}

View File

@@ -1,169 +0,0 @@
// 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:go_router/go_router.dart';
// This scenario demonstrates how to use path parameters and query parameters.
//
// The route segments that start with ':' are treated as path parameters when
// defining GoRoute[s]. The parameter values can be accessed through
// GoRouterState.pathParameters.
//
// The query parameters are automatically stored in GoRouterState.queryParameters.
/// Family data class.
class Family {
/// Create a family.
const Family({required this.name, required this.people});
/// The last name of the family.
final String name;
/// The people in the family.
final Map<String, Person> people;
}
/// Person data class.
class Person {
/// Creates a person.
const Person({required this.name, required this.age});
/// The first name of the person.
final String name;
/// The age of the person.
final int age;
}
const Map<String, Family> _families = <String, Family>{
'f1': Family(
name: 'Doe',
people: <String, Person>{
'p1': Person(name: 'Jane', age: 23),
'p2': Person(name: 'John', age: 6),
},
),
'f2': Family(
name: 'Wong',
people: <String, Person>{
'p1': Person(name: 'June', age: 51),
'p2': Person(name: 'Xin', age: 44),
},
),
};
void main() => runApp(App());
/// The main app.
class App extends StatelessWidget {
/// Creates an [App].
App({super.key});
/// The title of the app.
static const String title = 'GoRouter Example: Query Parameters';
// add the login info into the tree as app state that can change over time
@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
title: title,
debugShowCheckedModeBanner: false,
);
late final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) {
return FamilyScreen(
fid: state.pathParameters['fid']!,
asc: state.uri.queryParameters['sort'] == 'asc',
);
}),
],
),
],
);
}
/// The home screen that shows a list of families.
class HomeScreen extends StatelessWidget {
/// Creates a [HomeScreen].
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(App.title),
),
body: ListView(
children: <Widget>[
for (final MapEntry<String, Family> entry in _families.entries)
ListTile(
title: Text(entry.value.name),
onTap: () => context.go('/family/${entry.key}'),
)
],
),
);
}
}
/// The screen that shows a list of persons in a family.
class FamilyScreen extends StatelessWidget {
/// Creates a [FamilyScreen].
const FamilyScreen({required this.fid, required this.asc, super.key});
/// The family to display.
final String fid;
/// Whether to sort the name in ascending order.
final bool asc;
@override
Widget build(BuildContext context) {
final Map<String, String> newQueries;
final List<String> names = _families[fid]!
.people
.values
.map<String>((Person p) => p.name)
.toList();
names.sort();
if (asc) {
newQueries = const <String, String>{'sort': 'desc'};
} else {
newQueries = const <String, String>{'sort': 'asc'};
}
return Scaffold(
appBar: AppBar(
title: Text(_families[fid]!.name),
actions: <Widget>[
IconButton(
onPressed: () => context.goNamed('family',
pathParameters: <String, String>{'fid': fid},
queryParameters: newQueries),
tooltip: 'sort ascending or descending',
icon: const Icon(Icons.sort),
)
],
),
body: ListView(
children: <Widget>[
for (final String name in asc ? names : names.reversed)
ListTile(
title: Text(name),
),
],
),
);
}
}

View File

@@ -1,27 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
// import 'package:window_size/window_size.dart';
import 'src/app.dart';
void main() {
// Use package:url_strategy until this pull request is released:
// https://github.com/flutter/flutter/pull/77103
// Use to setHashUrlStrategy() to use "/#/" in the address bar (default). Use
// setPathUrlStrategy() to use the path. You may need to configure your web
// server to redirect all paths to index.html.
//
// On mobile platforms, both functions are no-ops.
setHashUrlStrategy();
// setPathUrlStrategy();
runApp(const Bookstore());
}

View File

@@ -1,81 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. 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 'auth.dart';
import 'routing.dart';
import 'screens/navigator.dart';
class Bookstore extends StatefulWidget {
const Bookstore({super.key});
@override
State<Bookstore> createState() => _BookstoreState();
}
class _BookstoreState extends State<Bookstore> {
late final RouteState _routeState;
late final SimpleRouterDelegate _routerDelegate;
late final TemplateRouteParser _routeParser;
@override
void initState() {
/// Configure the parser with all of the app's allowed path templates.
_routeParser = TemplateRouteParser(
allowedPaths: [
'/signin',
'/authors',
'/settings',
'/books/new',
'/books/all',
'/books/popular',
'/book/:bookId',
'/author/:authorId',
],
initialRoute: '/signin',
);
_routeState = RouteState(_routeParser);
_routerDelegate = SimpleRouterDelegate(
routeState: _routeState,
);
super.initState();
}
@override
Widget build(BuildContext context) => RouteStateScope(
notifier: _routeState,
child: MaterialApp.router(
routerDelegate: _routerDelegate,
routeInformationParser: _routeParser,
// Revert back to pre-Flutter-2.5 transition behavior:
// https://github.com/flutter/flutter/issues/82053
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
},
),
),
),
);
@override
void dispose() {
_routeState.dispose();
_routerDelegate.dispose();
super.dispose();
}
}

View File

@@ -1,47 +0,0 @@
// // Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// // for details. 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/widgets.dart';
//
// /// A mock authentication service
// class BookstoreAuth extends ChangeNotifier {
// bool _signedIn = false;
//
// bool get signedIn => _signedIn;
//
// Future<void> signOut() async {
// await Future<void>.delayed(const Duration(milliseconds: 200));
// // Sign out.
// _signedIn = false;
// notifyListeners();
// }
//
// Future<bool> signIn(String username, String password) async {
// await Future<void>.delayed(const Duration(milliseconds: 200));
//
// // Sign in. Allow any password.
// _signedIn = true;
// notifyListeners();
// return _signedIn;
// }
//
// @override
// bool operator ==(Object other) =>
// other is BookstoreAuth && other._signedIn == _signedIn;
//
// @override
// int get hashCode => _signedIn.hashCode;
// }
//
// class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
// const BookstoreAuthScope({
// required super.notifier,
// required super.child,
// super.key,
// });
//
// static BookstoreAuth of(BuildContext context) => context
// .dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()!
// .notifier!;
// }

View File

@@ -1,7 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
export 'data/author.dart';
export 'data/book.dart';
export 'data/library.dart';

View File

@@ -1,13 +0,0 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'book.dart';
class Author {
final int id;
final String name;
final books = <Book>[];
Author(this.id, this.name);
}

Some files were not shown because too many files have changed in this diff Show More