This commit is contained in:
toly
2023-11-08 09:35:29 +08:00
parent 88cd6fb3b4
commit 8fb4bf57d6
78 changed files with 4344 additions and 544 deletions

View File

@@ -6,17 +6,27 @@ import 'route_history_manager.dart';
import 'routes.dart';
import 'views/not_find_view.dart';
AppRouterDelegate router = AppRouterDelegate();
AppRouterDelegate router = AppRouterDelegate(
initial: IRouteConfig(uri: Uri.parse('/color')),
);
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
/// 核心数据,路由配置数据列表
final List<IRouteConfig> _configs = [];
String get path => current.uri.path;
IRouteConfig get current => _configs.last;
final IRoutePageBuilder? notFindPageBuilder;
AppRouterDelegate({this.notFindPageBuilder}) {
_historyManager.recode(IRouteConfig(uri: Uri.parse(path)));
AppRouterDelegate({
this.notFindPageBuilder,
required IRouteConfig initial,
}) {
_configs.add(initial);
_historyManager.recode(initial);
}
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
@@ -45,48 +55,60 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
notifyListeners();
}
// final List<IRouteConfig> _pathStack = [];
bool get canPop => _configs.where((e) => e.routeStyle==RouteStyle.push).isNotEmpty;
final Map<String, Completer<dynamic>> _completerMap = {};
final Map<String, dynamic> _pathExtraMap = {};
final List<String> keepAlivePath = [];
FutureOr<dynamic> changeRoute(IRouteConfig config) {
String value = config.uri.path;
if (_path == value) null;
if (current == config) null;
_handleChangeStyle(config);
if (config.forResult) {
_completerMap[value] = Completer();
}
if (config.keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (config.extra != null) {
_pathExtraMap[value] = config.extra;
}
if (config.recordHistory) {
_historyManager.recode(config);
}
_path = value;
notifyListeners();
if (config.forResult) {
return _completerMap[value]!.future;
}
}
void _handleChangeStyle(IRouteConfig config){
switch (config.routeStyle) {
case RouteStyle.push:
if (_configs.contains(config)) {
_configs.remove(config);
}
_configs.add(config);
break;
case RouteStyle.replace:
List<IRouteConfig> liveRoutes = _configs.where((e) => e.keepAlive&&e!=config).toList();
_configs.clear();
_configs.addAll([...liveRoutes,config]);
break;
}
}
FutureOr<dynamic> changePath(
String value, {
bool forResult = false,
Object? extra,
bool keepAlive = false,
bool recordHistory = true,
RouteStyle style = RouteStyle.replace,
}) {
return changeRoute(IRouteConfig(
uri: Uri.parse(value),
forResult: forResult,
extra: extra,
routeStyle: style,
keepAlive: keepAlive,
recordHistory: recordHistory,
));
@@ -96,46 +118,47 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPages(context, path),
pages: _buildPages(context, _configs),
);
}
List<Page> _buildPages(BuildContext context, String path) {
List<Page> _buildPages(BuildContext context, List<IRouteConfig> configs) {
IRouteConfig top = configs.last;
List<IRouteConfig> bottoms = _configs.sublist(0,_configs.length-1).toList();
List<Page> pages = [];
List<Page> topPages = _buildPageByPathFromTree(context, path);
if (keepAlivePath.isNotEmpty) {
for (String alivePath in keepAlivePath) {
if (alivePath != path) {
pages.addAll(_buildPageByPathFromTree(context, alivePath));
}
}
/// 去除和 topPages 中重复的界面
pages.removeWhere(
(element) => topPages.map((e) => e.key).contains(element.key));
}
List<Page> topPages = _buildPageByPathFromTree(context, top);
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
pages.addAll(topPages);
return pages;
}
List<Page> _buildPageByPathFromTree(BuildContext context, String path) {
List<Page> _buildLivePageByPathList(
BuildContext context,
List<IRouteConfig> paths,
IRouteConfig curConfig,
List<Page> curPages,
) {
List<Page> pages = [];
if (paths.isNotEmpty) {
for (IRouteConfig path in paths) {
if (path != curConfig) {
pages.addAll(_buildPageByPathFromTree(context, path));
}
}
/// 去除和 curPages 中重复的界面
pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key));
}
return pages;
}
List<Page> _buildPageByPathFromTree(
BuildContext context, IRouteConfig config) {
List<Page> result = [];
List<IRouteNode> iRoutes = rootRoute.find(path);
List<IRouteNode> iRoutes = rootRoute.find(config.path);
if (iRoutes.isNotEmpty) {
for (int i = 0; i < iRoutes.length; i++) {
IRouteNode iroute = iRoutes[i];
String path = iroute.path;
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
IRouteConfig config = IRouteConfig(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
);
config = config.copyWith(path: iroute.path);
Page? page;
if (iroute is NotFindNode) {
page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config);
@@ -145,6 +168,9 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
if (page != null) {
result.add(page);
}
if(iroute is CellIRoute){
break;
}
}
}
return result;
@@ -156,12 +182,29 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
return true;
}
void backStack() {
if (_configs.isNotEmpty) {
_configs.removeLast();
if (_configs.isNotEmpty) {
changeRoute(_configs.last);
} else {
changeRoute(current);
}
}
}
bool _onPopPage(Route route, result) {
if (_completerMap.containsKey(path)) {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
changePath(backPath(path), recordHistory: false);
if (_configs.isNotEmpty) {
_configs.removeLast();
notifyListeners();
} else {
changePath(backPath(path), recordHistory: false);
}
return route.didPop(result);
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'iroute_config.dart';
typedef IRoutePageBuilder = Page? Function(
@@ -24,7 +23,9 @@ abstract class IRouteNode {
Page? createPage(BuildContext context, IRouteConfig config);
List<IRouteNode> find(String input,) {
List<IRouteNode> find(
String input,
) {
return findNodes(this, Uri.parse(input), 0, '/', []);
}
@@ -41,15 +42,16 @@ abstract class IRouteNode {
}
String target = parts[deep];
if (node.children.isNotEmpty) {
target = prefix + target;
List<IRouteNode> nodes = node.children.where((e) => e.path == target).toList();
target = prefix + target;
List<IRouteNode> nodes =
node.children.where((e) => e.path == target).toList();
bool match = nodes.isNotEmpty;
if (match) {
IRouteNode matched = nodes.first;
result.add(matched);
String nextPrefix = '${matched.path}/';
findNodes(matched, uri, ++deep, nextPrefix, result);
}else{
} else {
result.add(NotFindNode(path: target));
return result;
}
@@ -92,8 +94,8 @@ class IRoute extends IRouteNode {
}
/// 未知路由
class NotFindNode extends IRouteNode{
NotFindNode({required super.path, super.children= const[]});
class NotFindNode extends IRouteNode {
NotFindNode({required super.path, super.children = const []});
@override
Page? createPage(BuildContext context, IRouteConfig config) {
@@ -101,15 +103,14 @@ class NotFindNode extends IRouteNode{
}
}
class CellIRouter extends IRoute {
final CellBuilder cellBuilder;
CellIRouter(
{required super.path,
super.pageBuilder,
super.children,
required this.cellBuilder});
class CellIRoute extends IRoute {
const CellIRoute({
required super.path,
super.pageBuilder,
super.children,
super.widget,
});
}
typedef CellBuilder = Widget Function(BuildContext context, Widget child);
typedef CellBuilder = Widget Function(BuildContext context,IRouteConfig config, CellIRoute cell);

View File

@@ -1,10 +1,17 @@
import 'package:flutter/material.dart';
enum RouteStyle{
push,
replace,
}
class IRouteConfig {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
final RouteStyle routeStyle;
final bool recordHistory;
const IRouteConfig({
@@ -12,6 +19,7 @@ class IRouteConfig {
required this.uri,
this.forResult = false,
this.keepAlive = false,
this.routeStyle = RouteStyle.replace,
this.recordHistory = false,
});
@@ -22,14 +30,42 @@ class IRouteConfig {
bool? forResult,
bool? keepAlive,
bool? recordHistory,
String? path,
}) =>
IRouteConfig(
extra: extra ?? this.extra,
forResult: forResult ?? this.forResult,
keepAlive: keepAlive ?? this.keepAlive,
recordHistory: recordHistory ?? this.recordHistory,
uri: uri,
uri: path!=null?Uri.parse(path):uri,
);
ValueKey get pageKey => ValueKey(path);
ValueKey get pageKey => ValueKey(hashCode);
@override
String toString() {
return 'IRouteConfig{extra: $extra, forResult: $forResult, uri: $uri, keepAlive: $keepAlive, routeStyle: $routeStyle, recordHistory: $recordHistory}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is IRouteConfig &&
runtimeType == other.runtimeType &&
extra == other.extra &&
forResult == other.forResult &&
uri == other.uri &&
keepAlive == other.keepAlive &&
routeStyle == other.routeStyle &&
recordHistory == other.recordHistory;
@override
int get hashCode =>
extra.hashCode ^
forResult.hashCode ^
uri.hashCode ^
keepAlive.hashCode ^
routeStyle.hashCode ^
recordHistory.hashCode;
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../pages/sort/views/player/sort_player.dart';
import 'iroute_config.dart';
import 'iroute.dart';
import '../../../pages/color/color_add_page.dart';
@@ -7,8 +8,8 @@ import '../../../pages/color/color_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/sort/views/sort_page.dart';
import '../../../pages/sort/views/sort_page/sort_page.dart';
import '../../../pages/sort/views/settings/sort_setting.dart';
IRoute rootRoute = const IRoute(
path: 'root',
@@ -22,7 +23,20 @@ IRoute rootRoute = const IRoute(
],
),
IRoute(path: '/counter', widget: CounterPage()),
IRoute(path: '/sort', widget: SortPage()),
CellIRoute(
path: '/sort',
widget: SortPage(),
children: [
IRoute(
path: '/sort/settings',
widget: SortSettings(),
),
IRoute(
path: '/sort/player',
widget: SortPlayer(),
),
],
),
IRoute(path: '/user', widget: UserPage()),
IRoute(path: '/settings', widget: SettingPage()),
],

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import '../app_router_delegate.dart';
class RouteBackIndicator extends StatefulWidget {
const RouteBackIndicator({super.key});
@override
State<RouteBackIndicator> createState() => _RouteBackIndicatorState();
}
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
@override
void initState() {
super.initState();
router.addListener(_onChange);
}
@override
void dispose() {
router.removeListener(_onChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
if(router.canPop){
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: router.backStack,
child: Container(
width: 26,
height: 26,
margin: EdgeInsets.only(right: 8),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color(0xffE3E5E7),
borderRadius: BorderRadius.circular(6)
),
child: Icon(Icons.arrow_back_ios_new,size: 14,)),
),
);
}
return SizedBox();
}
void _onChange() {
setState(() {
});
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
import '../router/iroute_config.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@@ -10,13 +11,12 @@ class AppNavigationRail extends StatefulWidget {
}
class _AppNavigationRailState extends State<AppNavigationRail> {
final List<MenuMeta> deskNavBarMenus = const [
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined,path: '/color'),
MenuMeta(label: '计数器', icon: Icons.add_chart,path: '/counter'),
MenuMeta(label: '排序', icon: Icons.sort,path: '/sort'),
MenuMeta(label: '我的', icon: Icons.person,path: '/user'),
MenuMeta(label: '设置', icon: Icons.settings,path: '/settings'),
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'),
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'),
MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'),
MenuMeta(label: '我的', icon: Icons.person, path: '/user'),
MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'),
];
@override
@@ -42,7 +42,10 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.7',style: TextStyle(color: Colors.white,fontSize: 12),),
child: Text(
'V0.0.8',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
@@ -53,21 +56,26 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
RegExp _segReg = RegExp(r'/\w+');
int? get activeIndex{
int? get activeIndex {
String path = router.path;
RegExpMatch? match = _segReg.firstMatch(path);
if(match==null) return null;
if (match == null) return null;
String? target = match.group(0);
int index = deskNavBarMenus.indexWhere((menu) => menu.path==target);
if(index==-1) return null;
int index = deskNavBarMenus.indexWhere((menu) => menu.path == target);
if (index == -1) return null;
return index;
}
}
void _onDestinationSelected(int index) {
String path = deskNavBarMenus[index].path!;
if(index==1){
router.changePath(path,keepAlive: true);
}else{
if (index == 1) {
router.changePath(path, keepAlive: true);
return;
}
if (index == 4) {
router.changePath(path, style: RouteStyle.push);
return;
} else {
router.changePath(path);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../router/app_router_delegate.dart';
import '../../router/views/route_back_indicator.dart';
import 'app_router_editor.dart';
import 'history_view_icon.dart';
import 'route_history_button.dart';
@@ -17,6 +18,7 @@ class AppTopBar extends StatelessWidget {
child: Row(
children: [
const SizedBox(width: 16),
const RouteBackIndicator(),
const RouterIndicator(),
Expanded(
child: Row(children: [
@@ -58,6 +60,7 @@ Map<String, String> kRouteLabelMap = {
'/color/detail': '颜色详情',
'/counter': '计数器',
'/sort': '可视化排序算法',
'/sort/settings': '排序配置',
'/user': '我的',
'/settings': '系统设置',
};