This commit is contained in:
toly
2023-12-10 15:25:12 +08:00
parent 2ceed8b9b5
commit 7b16b52761
86 changed files with 3635 additions and 930 deletions

View File

@@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
Map<String, String> kRouteLabelMap = {
'': '',
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/sort': '排序算法',
'/sort/player': '',
'/sort/settings': '排序配置',
'/user': '我的',
'/settings': '系统设置',
};
String calcRouteName(BuildContext context,String path){
String? result = kRouteLabelMap[path];
if(result !=null) return result;
if(path.startsWith('/sort/player/')){
return path.split('/sort/player/')[1];
}
return '未知路由';
}

View File

@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class RouterHistoryScope extends InheritedNotifier<RouterHistory> {
const RouterHistoryScope({super.key, required super.child, super.notifier});
static RouterHistory of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<RouterHistoryScope>()!
.notifier!;
}
static RouterHistory read(BuildContext context) {
return context
.getInheritedWidgetOfExactType<RouterHistoryScope>()!
.notifier!;
}
}
class RouterHistory with ChangeNotifier {
final List<String> exclusives;
final GoRouterDelegate delegate;
final List<RouteMatchList> _histories = [];
final List<RouteMatchList> _backHistories = [];
List<RouteMatchList> get histories => _histories;
RouterHistory(this.delegate, {this.exclusives = const []}) {
delegate.addListener(_onRouteChange);
}
/// 用于记录当前历史记录
/// 清空历史之后,切换时,先记录 _current
RouteMatchList? _current;
void _onRouteChange() {
/// 当没有历史,且 _current 非空
if (_histories.isEmpty && _current != null) {
_histories.add(_current!);
}
RouteMatchList matchList = delegate.currentConfiguration;
if (_histories.isNotEmpty && matchList == _histories.last
|| matchList.isEmpty
) return;
String uri = matchList.last.matchedLocation;
if (exclusives.contains(uri)) {
return;
}
_recode(matchList);
}
/// 将 [history] 加入历史记录
void _recode(RouteMatchList history) {
_current = history;
_histories.add(history);
if (hasHistory) {
notifyListeners();
}
}
bool get hasHistory => _histories.length > 1;
bool get hasBackHistory => _backHistories.isNotEmpty;
/// 历史回退操作
/// 将当前顶层移除,并加入 [_backHistories] 撤销列表
/// 并转到前一路径 [_histories.last]
void back() {
if (!hasHistory) {
return;
}
RouteMatchList top = _histories.removeLast();
_backHistories.add(top);
if (_histories.isNotEmpty) {
delegate.setNewRoutePath(_histories.last);
notifyListeners();
}
}
/// 撤销回退操作
/// 取出回退列表的最后元素,跳转到该路径
void revocation() {
RouteMatchList target = _backHistories.removeLast();
delegate.setNewRoutePath(target);
notifyListeners();
}
void close(RouteMatchList history) {
_histories.remove(history);
notifyListeners();
}
void clear() {
_histories.clear();
notifyListeners();
}
void select(RouteMatchList history) {
_histories.remove(history);
delegate.setNewRoutePath(history);
}
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/empty/empty_panel.dart';
import '../views/app_navigation.dart';
import 'color.dart';
import 'sort.dart';
final RouteBase appRoute = ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return AppNavigation(navigator: child);
},
routes: <RouteBase>[
colorRouters,
GoRoute(
path: 'counter',
builder: (BuildContext context, GoRouterState state) {
return const CounterPage();
}),
sortRouters,
GoRoute(
path: 'user',
builder: (BuildContext context, GoRouterState state) {
return const UserPage();
},
),
GoRoute(
path: 'settings',
builder: (BuildContext context, GoRouterState state) {
return const SettingPage();
},
),
GoRoute(
path: '404',
builder: (BuildContext context, GoRouterState state) {
String msg = '无法访问: ${state.extra}';
return EmptyPanel(msg: msg);
},
)
],
);

View File

@@ -1,184 +0,0 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'views/navigator_scope.dart';
import 'iroute.dart';
import 'iroute_config.dart';
import 'route_history_manager.dart';
import 'routes.dart';
import 'views/not_find_view.dart';
AppRouterDelegate router = AppRouterDelegate(node: rootRoute);
class AppRouterDelegate extends RouterDelegate<IRouteConfig>
with ChangeNotifier {
/// 核心数据,路由配置数据列表
final List<IRouteConfig> _configs = [];
String get path => current.uri.toString();
IRouteConfig get current => _configs.last;
final IRoutePageBuilder? notFindPageBuilder;
final IRouteNode node;
@override
IRouteConfig? get currentConfiguration {
if(_configs.isEmpty) return null;
return current;
}
AppRouterDelegate({
this.notFindPageBuilder,
required this.node,
});
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
child: Material(child: NotFindPage()),
);
final RouteHistoryManager _historyManager = RouteHistoryManager();
RouteHistoryManager get historyManager => _historyManager;
/// 历史回退操作
/// 详见: [RouteHistoryManager.back]
void back() => _historyManager.back(changeRoute);
/// 撤销回退操作
/// 详见: [RouteHistoryManager.revocation]
void revocation() => _historyManager.revocation(changeRoute);
void closeHistory(int index) {
_historyManager.close(index);
notifyListeners();
}
void clearHistory() {
_historyManager.clear();
notifyListeners();
}
// final List<IRouteConfig> _pathStack = [];
bool get canPop => _configs.where((e) => e.routeStyle == RouteStyle.push).isNotEmpty;
final Map<String, Completer<dynamic>> _completerMap = {};
FutureOr<dynamic> changeRoute(IRouteConfig config) {
String value = config.uri.path;
if (current == config) null;
_handleChangeStyle(config);
if (config.forResult) {
_completerMap[value] = Completer();
}
if (config.recordHistory) {
_historyManager.recode(config);
}
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 = false,
RouteStyle style = RouteStyle.replace,
}) {
return changeRoute(IRouteConfig(
uri: Uri.parse(value),
forResult: forResult,
extra: extra,
routeStyle: style,
keepAlive: keepAlive,
recordHistory: recordHistory,
));
}
@override
Widget build(BuildContext context) {
return NavigatorScope(
node: node,
onPopPage: _onPopPage,
configs: _configs,
notFindPageBuilder: (notFindPageBuilder ?? _defaultNotFindPageBuilder),
);
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
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);
}
if (canPop) {
_configs.removeLast();
notifyListeners();
} else {
changePath(backPath(path), recordHistory: false);
}
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(IRouteConfig configuration) async{
changeRoute(configuration);
}
@override
Future<void> setInitialRoutePath(IRouteConfig configuration) {
_configs.add(configuration);
_historyManager.recode(configuration);
return super.setInitialRoutePath(configuration);
}
}

View File

@@ -1,22 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'iroute_config.dart';
class AppRouterParser extends RouteInformationParser<IRouteConfig> {
@override
RouteInformation restoreRouteInformation(IRouteConfig configuration) {
return RouteInformation(uri: configuration.uri);
}
@override
Future<IRouteConfig> parseRouteInformationWithDependencies(
RouteInformation routeInformation,
BuildContext context,
) {
if(routeInformation.state is IRouteConfig){
return SynchronousFuture(routeInformation.state as IRouteConfig);
}
return SynchronousFuture(IRouteConfig(uri: routeInformation.uri));
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
final RouteBase colorRouters = GoRoute(
path: 'color',
builder: (BuildContext context, GoRouterState state) {
return const ColorPage();
},
routes: <RouteBase>[
GoRoute(
path: 'detail',
name: 'colorDetail',
builder: (BuildContext context, GoRouterState state) {
String? selectedColor = state.uri.queryParameters['color'];
Color color = Colors.black;
if (selectedColor != null) {
color = Color(int.parse(selectedColor, radix: 16));
}
return ColorDetailPage(color: color);
},
),
GoRoute(
path: 'add',
builder: (BuildContext context, GoRouterState state) {
return const ColorAddPage();
},
),
],
);

View File

@@ -1,164 +0,0 @@
import 'package:flutter/material.dart';
import 'package:iroute/v9/app/navigation/router/views/navigator_scope.dart';
import 'iroute_config.dart';
import 'utils/path_utils.dart';
typedef IRoutePageBuilder = Page? Function(
BuildContext context,
IRouteConfig data,
);
typedef IRouteWidgetBuilder = Widget? Function(
BuildContext context,
IRouteConfig data,
);
abstract class IRouteNode {
final String path;
final List<IRouteNode> children;
const IRouteNode({
required this.path,
required this.children,
});
Page? createPage(BuildContext context, IRouteConfig config);
List<IRouteNode> find(
String input,
) {
String prefix = '/';
if (this is CellIRoute) {
input = input.replaceFirst(path, '');
if (path != '/') {
prefix = path + "/";
}
}
return findNodes(this, Uri.parse(input), 0, prefix, []);
}
List<IRouteNode> findNodes(
IRouteNode node,
Uri uri,
int deep,
String prefix,
List<IRouteNode> result,
) {
List<String> parts = uri.pathSegments;
if (deep > parts.length - 1) {
return result;
}
String target = parts[deep];
if (node.children.isNotEmpty) {
target = prefix + target;
List<IRouteNode> nodes = node.children.where((e) => _match(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 {
result.add(NotFindNode(path: target));
return result;
}
}
return result;
}
bool _match(String path ,String target){
if(!path.contains(':')){
return path == target;
}
return patternToRegExp(path,[]).hasMatch(target);
}
}
/// 优先调用 [pageBuilder] 构建 Page
/// 没有 [pageBuilder] 时, 使用 [widgetBuilder] 构建组件
/// 没有 [pageBuilder] 和 [widgetBuilder] 时, 使用 [widget] 构建组件
class IRoute extends IRouteNode {
final IRoutePageBuilder? pageBuilder;
final IRouteWidgetBuilder? widgetBuilder;
final Widget? widget;
const IRoute({
required super.path,
super.children = const [],
this.widget,
this.pageBuilder,
this.widgetBuilder,
});
@override
Page? createPage(BuildContext context, IRouteConfig config) {
if (pageBuilder != null) {
return pageBuilder!(context, config);
}
Widget? child;
if (widgetBuilder != null) {
child = widgetBuilder!(context, config);
}
child ??= widget;
if (child != null) {
return MaterialPage(child: child, key: config.pageKey);
}
return null;
}
}
/// 未知路由
class NotFindNode extends IRouteNode {
NotFindNode({required super.path, super.children = const []});
@override
Page? createPage(BuildContext context, IRouteConfig config) {
return null;
}
}
typedef CellBuilder = Widget Function(
BuildContext context,
IRouteConfig config,
Widget navigator,
);
typedef CellIRoutePageBuilder = Page? Function(
BuildContext context,
IRouteConfig data,
Widget child,
);
class CellIRoute extends IRouteNode {
final CellBuilder cellBuilder;
final CellIRoutePageBuilder? pageBuilder;
const CellIRoute({
required this.cellBuilder,
this.pageBuilder,
required super.path,
required super.children,
});
@override
Page? createPage(BuildContext context, IRouteConfig config) {
return null;
}
Page? createCellPage(BuildContext context, IRouteConfig config,
Widget child) {
if (pageBuilder != null) {
return pageBuilder!(context, config, child);
}
return MaterialPage(
child: child,
key: config.pageKey,
);
}
}

View File

@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
enum RouteStyle{
push,
replace,
}
class IRouteConfig {
final Object? extra;
final bool forResult;
final Uri uri;
final Map<String,String>? pathParams;
final bool keepAlive;
final RouteStyle routeStyle;
final bool recordHistory;
const IRouteConfig({
this.extra,
required this.uri,
this.forResult = false,
this.pathParams,
this.keepAlive = false,
this.routeStyle = RouteStyle.replace,
this.recordHistory = false,
});
String get path => uri.toString();
IRouteConfig copyWith({
Object? extra,
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: path!=null?Uri.parse(path):uri,
);
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

@@ -0,0 +1,27 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../pages/login/login.dart';
import 'app.dart';
final RouteBase rootRoute = GoRoute(
path: '/',
redirect: _redirect,
routes: [
appRoute,
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) {
return const LoginPage();
},
),
],
);
FutureOr<String?> _redirect(BuildContext context, GoRouterState state) {
if(state.fullPath=='/'){
return '/color';
}
return null;
}

View File

@@ -1,47 +0,0 @@
import 'iroute_config.dart';
typedef OnRouteChange = void Function(IRouteConfig config);
class RouteHistoryManager{
final List<IRouteConfig> _histories = [];
final List<IRouteConfig> _backHistories = [];
List<IRouteConfig> get histories => _histories.reversed.toList();
bool get hasHistory => _histories.length > 1;
bool get hasBackHistory => _backHistories.isNotEmpty;
/// 将 [config] 加入历史记录
void recode(IRouteConfig config){
if (_histories.isNotEmpty && config.path == _histories.last.path) return;
_histories.add(config);
}
/// 历史回退操作
/// 将当前顶层移除,并加入 [_backHistories] 撤销列表
/// 并转到前一路径 [_histories.last]
void back(OnRouteChange callback) {
if (!hasHistory) return;
IRouteConfig top = _histories.removeLast();
_backHistories.add(top);
if (_histories.isNotEmpty) {
callback(_histories.last);
}
}
/// 撤销回退操作
/// 取出回退列表的最后元素,跳转到该路径
void revocation(OnRouteChange callback) {
IRouteConfig target = _backHistories.removeLast();
callback(target);
}
void close(int index) {
_histories.removeAt(index);
}
void clear() {
_histories.clear();
}
}

View File

@@ -1,95 +0,0 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../../pages/login/login.dart';
import '../transition/no_transition_page.dart';
import '../../../pages/sort/views/player/sort_player.dart';
import 'iroute_config.dart';
import '../views/app_navigation.dart';
import 'iroute.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
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/sort_page.dart';
import '../../../pages/sort/views/settings/sort_setting.dart';
CellIRoute appRoute = CellIRoute(
cellBuilder: (_, __, navigator) => AppNavigation(
navigator: navigator,
),
path: '/app',
children: [
IRoute(
path: '/app/color',
widget: ColorPage(),
children: [
IRoute(path: '/app/color/detail', widgetBuilder: _buildColorDetail),
IRoute(path: '/app/color/add', widget: ColorAddPage()),
],
),
const IRoute(path: '/app/counter', widget: CounterPage()),
CellIRoute(
cellBuilder: (_, __, navigator) => SortNavigation(navigator: navigator),
// pageBuilder: (_,config,child)=> NoTransitionPage(
// child: child,
// key: config.pageKey
// ),
path: '/app/sort',
children: [
const IRoute(
path: '/app/sort/settings',
widget: SortSettings(),
),
const IRoute(
path: '/app/sort/player/:name',
widget: SortPlayer(),
),
],
),
const IRoute(path: '/app/user', widget: UserPage()),
const IRoute(path: '/app/settings', widget: SettingPage()),
],
);
IRoute rootRoute = IRoute(path: '/', children: [
appRoute,
const IRoute(
path: '/login',
widget: LoginPage()
)
]);
Widget? _buildColorDetail(BuildContext context, IRouteConfig data) {
final Map<String, String> queryParams = data.uri.queryParameters;
String? selectedColor = queryParams['color'];
Color color = Colors.black;
if (selectedColor != null) {
color = Color(int.parse(selectedColor, radix: 16));
} else if (data.extra is Color) {
color = data.extra as Color;
}
return ColorDetailPage(color: color);
}
Map<String, String> kRouteLabelMap = {
'/app': '',
'/app/color': '颜色板',
'/app/color/add': '添加颜色',
'/app/color/detail': '颜色详情',
'/app/counter': '计数器',
'/app/sort': '排序算法',
'/app/sort/:name': '',
'/app/sort/settings': '排序配置',
'/app/user': '我的',
'/app/settings': '系统设置',
};
const List<MenuMeta> deskNavBarMenus = [
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/app/color'),
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/app/counter'),
MenuMeta(label: '排序', icon: Icons.sort, path: '/app/sort/player'),
MenuMeta(label: '我的', icon: Icons.person, path: '/app/user'),
MenuMeta(label: '设置', icon: Icons.settings, path: '/app/settings'),
];

View File

@@ -0,0 +1,44 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../pages/sort/provider/state.dart';
import '../../../pages/sort/views/player/sort_player.dart';
import '../../../pages/sort/views/settings/sort_setting.dart';
import '../../../pages/sort/views/sort_page/sort_page.dart';
final RouteBase sortRouters = ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return SortNavigation(navigator: child);
},
routes: [
GoRoute(
path: 'sort',
redirect: _redirectSort,
routes: [
GoRoute(
path: 'player/:name',
builder: (BuildContext context, GoRouterState state) {
print(state.pathParameters);// 获取路径参数
return const SortPlayer();
},
),
GoRoute(
path: 'settings',
builder: (BuildContext context, GoRouterState state) {
return const SortSettings();
},
),
],
),
],
);
FutureOr<String?> _redirectSort(BuildContext context, GoRouterState state) {
if (state.fullPath == '/sort') {
String name = SortStateScope.read(context).config.name;
return '/sort/player/$name';
}
return null;
}

View File

@@ -1,37 +0,0 @@
final RegExp _parameterRegExp = RegExp(r':(\w+)(\((?:\\.|[^\\()])+\))?');
RegExp patternToRegExp(String pattern, List<String> parameters) {
final StringBuffer buffer = StringBuffer('^');
int start = 0;
for (final RegExpMatch match in _parameterRegExp.allMatches(pattern)) {
if (match.start > start) {
buffer.write(RegExp.escape(pattern.substring(start, match.start)));
}
final String name = match[1]!;
final String? optionalPattern = match[2];
final String regex = optionalPattern != null
? _escapeGroup(optionalPattern, name)
: '(?<$name>[^/]+)';
buffer.write(regex);
parameters.add(name);
start = match.end;
}
if (start < pattern.length) {
buffer.write(RegExp.escape(pattern.substring(start)));
}
if (!pattern.endsWith('/')) {
buffer.write(r'(?=/|$)');
}
return RegExp(buffer.toString(), caseSensitive: false);
}
String _escapeGroup(String group, [String? name]) {
final String escapedGroup = group.replaceFirstMapped(
RegExp(r'[:=!]'), (Match match) => '\\${match[0]}');
if (name != null) {
return '(?<$name>$escapedGroup)';
}
return escapedGroup;
}

View File

@@ -1,109 +0,0 @@
import 'package:flutter/material.dart';
import '../iroute.dart';
import '../iroute_config.dart';
class NavigatorScope extends StatefulWidget {
final IRouteNode node;
final PopPageCallback onPopPage;
final List<IRouteConfig> configs;
final IRoutePageBuilder notFindPageBuilder;
const NavigatorScope({
super.key,
required this.node,
required this.onPopPage,
required this.configs,
required this.notFindPageBuilder,
});
@override
State<NavigatorScope> createState() => _NavigatorScopeState();
}
class _NavigatorScopeState extends State<NavigatorScope> {
@override
Widget build(BuildContext context) {
Widget content = Navigator(
onPopPage: widget.onPopPage,
pages: _buildPages(context, widget.configs),
);
if(widget.node is CellIRoute){
content = (widget.node as CellIRoute).cellBuilder(context,widget.configs.last,content);
}
return HeroControllerScope(
controller: MaterialApp.createMaterialHeroController(),
child: content,
);
}
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, top);
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
pages.addAll(topPages);
return pages;
}
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 = widget.node.find(config.path);
if (iRoutes.isNotEmpty) {
for (int i = 0; i < iRoutes.length; i++) {
IRouteNode iroute = iRoutes[i];
IRouteConfig fixConfig = config;
if(iroute.path!=config.uri.path){
fixConfig = IRouteConfig(uri: Uri.parse(iroute.path));
}
Page? page;
if (iroute is NotFindNode) {
page = widget.notFindPageBuilder(context, config);
} else if (iroute is CellIRoute) {
Widget scope = NavigatorScope(
node: iroute,
onPopPage: widget.onPopPage,
configs: widget.configs,
notFindPageBuilder: widget.notFindPageBuilder,
);
page = iroute.createCellPage(context, fixConfig, scope);
} else {
page = iroute.createPage(context, fixConfig);
}
if (page != null) {
result.add(page);
}
if (iroute is CellIRoute) {
break;
}
}
}
return result;
}
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar/app_top_bar.dart';

View File

@@ -1,10 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:iroute/components/components.dart';
import 'package:provider/provider.dart';
import '../../../pages/sort/provider/state.dart';
import '../router/app_router_delegate.dart';
import '../router/iroute_config.dart';
import '../router/routes.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@@ -14,18 +10,14 @@ 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'),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
@@ -39,7 +31,7 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text(
'V0.0.11',
'V0.1.1',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
@@ -50,10 +42,10 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
);
}
RegExp _segReg = RegExp(r'/app/\w+');
final RegExp _segReg = RegExp(r'/\w+');
int? get activeIndex {
String path = router.path;
final String path = GoRouterState.of(context).uri.toString();
RegExpMatch? match = _segReg.firstMatch(path);
if (match == null) return null;
String? target = match.group(0);
@@ -64,25 +56,10 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
void _onDestinationSelected(int index) {
String path = deskNavBarMenus[index].path!;
if (index == 1) {
router.changePath(path, keepAlive: true,recordHistory: true);
return;
if(index==4){
GoRouter.of(context).push(path);
}else{
GoRouter.of(context).go(path);
}
if(index ==2){
SortState state = SortStateScope.read(context);
String name = state.config.name;
router.changePath('/app/sort/$name',recordHistory: true);
return;
}
if (index == 4) {
router.changePath(path, style: RouteStyle.push,recordHistory: true);
return;
} else {
router.changePath(path,recordHistory: true);
}
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -1,7 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import '../../router/app_router_delegate.dart';
// import '../../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;
@@ -14,23 +15,34 @@ class AppRouterEditor extends StatefulWidget {
class _AppRouterEditorState extends State<AppRouterEditor> {
final TextEditingController _controller = TextEditingController();
late GoRouterDelegate _delegate ;
@override
void initState() {
super.initState();
_delegate = GoRouter.of(context).routerDelegate;
_onRouteChange();
router.addListener(_onRouteChange);
_delegate.addListener(_onRouteChange);
}
void _onRouteChange() {
_controller.text=router.path;
List<RouteMatch> matches = _delegate.currentConfiguration.matches;
if(matches.isEmpty) return;
RouteMatch match = matches.last;
if(match is ImperativeRouteMatch){
_controller.text = match.matches.uri.toString();
}else{
_controller.text = match.matchedLocation;
}
}
@override
void dispose() {
_controller.dispose();
router.removeListener(_onRouteChange);
_delegate.removeListener(_onRouteChange);
super.dispose();
}

View File

@@ -1,13 +1,11 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:iroute/components/components.dart';
import '../../../../pages/sort/provider/state.dart';
import '../../router/app_router_delegate.dart';
import '../../router/routes.dart';
import '../../router/utils/path_utils.dart';
import '../../router/views/route_back_indicator.dart';
import '../../helper/function.dart';
import '../route_back_indicator.dart';
import 'app_router_editor.dart';
import 'history_view_icon.dart';
import 'route_history_button.dart';
import 'history_view_icon.dart';
class AppTopBar extends StatelessWidget {
const AppTopBar({super.key});
@@ -16,31 +14,38 @@ class AppTopBar extends StatelessWidget {
Widget build(BuildContext context) {
return DragToMoveWrap(
child: Container(
alignment: Alignment.center,
// alignment: Alignment.center,
height: 46,
child: Row(
children: [
const SizedBox(width: 16),
const RouteBackIndicator(),
const RouterIndicator(),
// Spacer(),
Expanded(
child: Row(children: [
const Spacer(),
RouteHistoryButton(),
const SizedBox(width: 12,),
SizedBox(
width: 250,
child: AppRouterEditor(
onSubmit: (path) => router.changePath(path),
)),
const SizedBox(width: 12,),
HistoryViewIcon(),
child: Row(textDirection: TextDirection.rtl, children: [
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: VerticalDivider(
width: 32,
),
)
),
HistoryViewIcon(),
const SizedBox(
width: 12,
),
SizedBox(
width: 250,
child: AppRouterEditor(
onSubmit: (path) {
GoRouter.of(context).go(path);
// => router.changePath(path)
},
)),
const SizedBox(
width: 12,
),
RouteHistoryButton(),
])),
const WindowButtons()
],
@@ -57,27 +62,35 @@ class RouterIndicator extends StatefulWidget {
State<RouterIndicator> createState() => _RouterIndicatorState();
}
class _RouterIndicatorState extends State<RouterIndicator> {
late GoRouterDelegate _delegate;
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
_delegate = GoRouter.of(context).routerDelegate;
_delegate.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
_delegate.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
List<RouteMatch> matches = _delegate.currentConfiguration.matches;
if(matches.isEmpty) return const SizedBox();
RouteMatch match = _delegate.currentConfiguration.matches.last;
print(
"=========_RouterIndicatorState:build==${match.matchedLocation}========");
return TolyBreadcrumb(
items: pathToBreadcrumbItems(router.path),
items: pathToBreadcrumbItems(context, match.matchedLocation),
onTapItem: (item) {
if (item.to != null) {
router.changePath(item.to!);
GoRouter.of(context).go(item.to!);
}
},
);
@@ -85,16 +98,10 @@ class _RouterIndicatorState extends State<RouterIndicator> {
void _onRouterChange() {
setState(() {});
if(router.path.startsWith('/app/sort/')){
SortState state = SortStateScope.of(context);
String name = router.path.replaceAll('/app/sort/', '');
if(name!='settings'){
state.selectName(name);
}
}
}
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
List<BreadcrumbItem> pathToBreadcrumbItems(
BuildContext context, String path) {
Uri uri = Uri.parse(path);
List<BreadcrumbItem> result = [];
String to = '';
@@ -106,16 +113,25 @@ class _RouterIndicatorState extends State<RouterIndicator> {
for (String segment in uri.pathSegments) {
to += '/$segment';
String label = '';
if(to.startsWith('/app/sort/')){
label = to.replaceAll('/app/sort/', '');
}else{
label = kRouteLabelMap[to] ?? '未知路由';
}
if(label.isNotEmpty){
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
String label = calcRouteName(context, to);
if (label.isNotEmpty) {
result
.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
}
}
return result;
}
}
Map<String, String> kRouteLabelMap = {
'': '',
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/sort': '排序算法',
'/sort/player': '演示',
'/sort/settings': '排序配置',
'/user': '我的',
'/settings': '系统设置',
};

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:iroute/components/components.dart';
import '../../router/app_router_delegate.dart';
import '../../router/routes.dart';
import '../../router/iroute_config.dart';
import 'package:iroute/v11/app/navigation/helper/function.dart';
import '../../helper/router_history.dart';
import 'app_top_bar.dart';
class HistoryViewIcon extends StatelessWidget{
@@ -10,6 +10,7 @@ class HistoryViewIcon extends StatelessWidget{
@override
Widget build(BuildContext context) {
RouterHistory routerHistory = RouterHistoryScope.read(context);
return MouseRegion(
cursor: SystemMouseCursors.click,
@@ -19,7 +20,7 @@ class HistoryViewIcon extends StatelessWidget{
height: 350,
child: Column(
children: [
_buildTopBar(),
_buildTopBar(routerHistory),
const Expanded(
child:HistoryPanel(),
),
@@ -34,7 +35,7 @@ class HistoryViewIcon extends StatelessWidget{
);
}
Widget _buildTopBar() {
Widget _buildTopBar( RouterHistory routerHistory) {
return Container(
decoration: BoxDecoration(
color: const Color(0xffFAFAFC),
@@ -49,14 +50,14 @@ class HistoryViewIcon extends StatelessWidget{
style: TextStyle(fontWeight: FontWeight.bold),
),
const Spacer(),
TextButton(onPressed: router.clearHistory, child: const Text('清空历史'))
TextButton(onPressed: routerHistory.clear, child: const Text('清空历史'))
],
));
}
}
class HistoryItem extends StatefulWidget {
final IRouteConfig history;
final RouteMatchList history;
final VoidCallback onPressed;
final VoidCallback onDelete;
@@ -69,6 +70,7 @@ class HistoryItem extends StatefulWidget {
class _HistoryItemState extends State<HistoryItem> {
@override
Widget build(BuildContext context) {
String path = widget.history.matches.last.matchedLocation;
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
@@ -80,11 +82,11 @@ class _HistoryItemState extends State<HistoryItem> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.history.path),
Text(path),
const SizedBox(
height: 2,
),
Text(kRouteLabelMap[widget.history.path]??'未知路由'),
Text(calcRouteName(context, path)),
],
)),
GestureDetector(
@@ -110,22 +112,18 @@ class HistoryPanel extends StatefulWidget {
}
class _HistoryPanelState extends State<HistoryPanel> {
late RouterHistory _history ;
@override
void initState() {
super.initState();
router.addListener(_onChange);
void didChangeDependencies() {
super.didChangeDependencies();
_history = RouterHistoryScope.of(context);
}
@override
void dispose() {
router.removeListener(_onChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
List<IRouteConfig> histories = router.historyManager.histories;
List<RouteMatchList> histories = _history.histories.reversed.toList();
if(histories.isEmpty){
return const Center(
child: Text(
@@ -141,12 +139,12 @@ class _HistoryPanelState extends State<HistoryPanel> {
itemBuilder: (_, index) =>
HistoryItem(
onDelete: (){
int fixIndex = histories.length - 1 - index;
router.closeHistory(fixIndex);
_history.close(histories[index]);
},
onPressed: (){
router.changeRoute(histories[index].copyWith(recordHistory: false));
onPressed: ()async {
Navigator.of(context).pop();
_history.select(histories[index]);
// router.changeRoute(histories[index].copyWith(recordHistory: false));
},
history: histories[index]),
);

View File

@@ -1,7 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import '../../router/app_router_delegate.dart';
import '../../helper/router_history.dart';
class RouteHistoryButton extends StatefulWidget {
const RouteHistoryButton({super.key});
@@ -11,22 +12,19 @@ class RouteHistoryButton extends StatefulWidget {
}
class _RouteHistoryButtonState extends State<RouteHistoryButton> {
@override
void initState() {
super.initState();
router.addListener(_onChange);
}
late RouterHistory _history ;
@override
void dispose() {
router.removeListener(_onChange);
super.dispose();
void didChangeDependencies() {
super.didChangeDependencies();
_history = RouterHistoryScope.of(context);
}
@override
Widget build(BuildContext context) {
bool hasHistory = router.historyManager.hasHistory;
bool hasBackHistory = router.historyManager.hasBackHistory;
bool hasHistory = _history.hasHistory;
bool hasBackHistory = _history.hasBackHistory;
Color activeColor = const Color(0xff9195AC);
Color inActiveColor = const Color(0xffC7CAD5);
Color historyColor = hasHistory?activeColor:inActiveColor;
@@ -38,7 +36,7 @@ class _RouteHistoryButtonState extends State<RouteHistoryButton> {
hoverColor: historyColor,
defaultColor: historyColor,
icon: CupertinoIcons.arrow_left_circle,
onPressed: hasHistory?router.back:null,
onPressed: hasHistory?_history.back:null,
),
const SizedBox(width: 8,),
HoverIconButton(
@@ -46,13 +44,9 @@ class _RouteHistoryButtonState extends State<RouteHistoryButton> {
hoverColor: backHistoryColor,
defaultColor: backHistoryColor,
icon: CupertinoIcons.arrow_right_circle,
onPressed: hasBackHistory?router.revocation:null,
onPressed: hasBackHistory?_history.revocation:null,
),
],
);
}
void _onChange() {
setState(() {});
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../app_router_delegate.dart';
import 'package:go_router/go_router.dart';
class RouteBackIndicator extends StatefulWidget {
const RouteBackIndicator({super.key});
@@ -9,25 +10,30 @@ class RouteBackIndicator extends StatefulWidget {
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
late GoRouterDelegate _delegate ;
@override
void initState() {
super.initState();
router.addListener(_onChange);
_delegate = GoRouter.of(context).routerDelegate;
_delegate.addListener(_onChange);
}
@override
void dispose() {
router.removeListener(_onChange);
_delegate.removeListener(_onChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
if(router.canPop){
bool hasPush = _delegate.currentConfiguration.matches
.whereType<ImperativeRouteMatch>().isNotEmpty;
if(hasPush){
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: router.backStack,
onTap: context.pop,
child: Container(
width: 26,
height: 26,