diff --git a/lib/main.dart b/lib/main.dart index b0e45aa..6ae6bc5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,14 +2,16 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:window_manager/window_manager.dart'; -import 'v11/app.dart'; +import 'v12/app.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); setSize(); - runApp(const UnitApp()); + GoRouter.optionURLReflectsImperativeAPIs = true; + runApp(UnitApp()); } diff --git a/lib/v11/app/navigation/helper/function.dart b/lib/v11/app/navigation/helper/function.dart new file mode 100644 index 0000000..526be07 --- /dev/null +++ b/lib/v11/app/navigation/helper/function.dart @@ -0,0 +1,25 @@ +import 'package:flutter/cupertino.dart'; + +Map 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 '未知路由'; +} \ No newline at end of file diff --git a/lib/v11/app/navigation/helper/router_history.dart b/lib/v11/app/navigation/helper/router_history.dart new file mode 100644 index 0000000..19deda0 --- /dev/null +++ b/lib/v11/app/navigation/helper/router_history.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class RouterHistoryScope extends InheritedNotifier { + const RouterHistoryScope({super.key, required super.child, super.notifier}); + + static RouterHistory of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType()! + .notifier!; + } + + static RouterHistory read(BuildContext context) { + return context + .getInheritedWidgetOfExactType()! + .notifier!; + } +} + +class RouterHistory with ChangeNotifier { + final List exclusives; + + final GoRouterDelegate delegate; + + final List _histories = []; + final List _backHistories = []; + + List 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); + } +} diff --git a/lib/v11/app/navigation/router/app.dart b/lib/v11/app/navigation/router/app.dart new file mode 100644 index 0000000..c9719e7 --- /dev/null +++ b/lib/v11/app/navigation/router/app.dart @@ -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: [ + 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); + }, + ) + ], +); diff --git a/lib/v11/app/navigation/router/app_router_delegate.dart b/lib/v11/app/navigation/router/app_router_delegate.dart deleted file mode 100644 index bc85d42..0000000 --- a/lib/v11/app/navigation/router/app_router_delegate.dart +++ /dev/null @@ -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 - with ChangeNotifier { - /// 核心数据,路由配置数据列表 - final List _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 _pathStack = []; - - bool get canPop => _configs.where((e) => e.routeStyle == RouteStyle.push).isNotEmpty; - - final Map> _completerMap = {}; - - FutureOr 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 liveRoutes = - _configs.where((e) => e.keepAlive && e != config).toList(); - _configs.clear(); - _configs.addAll([...liveRoutes, config]); - break; - } - } - - FutureOr 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 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 parts = List.of(uri.pathSegments)..removeLast(); - return '/${parts.join('/')}'; - } - - @override - Future setNewRoutePath(IRouteConfig configuration) async{ - changeRoute(configuration); - } - - @override - Future setInitialRoutePath(IRouteConfig configuration) { - _configs.add(configuration); - _historyManager.recode(configuration); - return super.setInitialRoutePath(configuration); - } -} diff --git a/lib/v11/app/navigation/router/app_router_parser.dart b/lib/v11/app/navigation/router/app_router_parser.dart deleted file mode 100644 index 3b40f88..0000000 --- a/lib/v11/app/navigation/router/app_router_parser.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'iroute_config.dart'; - -class AppRouterParser extends RouteInformationParser { - @override - RouteInformation restoreRouteInformation(IRouteConfig configuration) { - return RouteInformation(uri: configuration.uri); - } - - @override - Future parseRouteInformationWithDependencies( - RouteInformation routeInformation, - BuildContext context, - ) { - if(routeInformation.state is IRouteConfig){ - return SynchronousFuture(routeInformation.state as IRouteConfig); - } - return SynchronousFuture(IRouteConfig(uri: routeInformation.uri)); - } -} diff --git a/lib/v11/app/navigation/router/color.dart b/lib/v11/app/navigation/router/color.dart new file mode 100644 index 0000000..6a2052a --- /dev/null +++ b/lib/v11/app/navigation/router/color.dart @@ -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: [ + 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(); + }, + ), + ], +); + diff --git a/lib/v11/app/navigation/router/iroute.dart b/lib/v11/app/navigation/router/iroute.dart deleted file mode 100644 index c01cf88..0000000 --- a/lib/v11/app/navigation/router/iroute.dart +++ /dev/null @@ -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 children; - - const IRouteNode({ - required this.path, - required this.children, - }); - - Page? createPage(BuildContext context, IRouteConfig config); - - List 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 findNodes( - IRouteNode node, - Uri uri, - int deep, - String prefix, - List result, - ) { - List parts = uri.pathSegments; - if (deep > parts.length - 1) { - return result; - } - String target = parts[deep]; - if (node.children.isNotEmpty) { - target = prefix + target; - - List 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, - ); - } -} \ No newline at end of file diff --git a/lib/v11/app/navigation/router/iroute_config.dart b/lib/v11/app/navigation/router/iroute_config.dart deleted file mode 100644 index 3555b27..0000000 --- a/lib/v11/app/navigation/router/iroute_config.dart +++ /dev/null @@ -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? 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; -} diff --git a/lib/v11/app/navigation/router/root.dart b/lib/v11/app/navigation/router/root.dart new file mode 100644 index 0000000..9eea0a3 --- /dev/null +++ b/lib/v11/app/navigation/router/root.dart @@ -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 _redirect(BuildContext context, GoRouterState state) { + if(state.fullPath=='/'){ + return '/color'; + } + return null; +} diff --git a/lib/v11/app/navigation/router/route_history_manager.dart b/lib/v11/app/navigation/router/route_history_manager.dart deleted file mode 100644 index 8844898..0000000 --- a/lib/v11/app/navigation/router/route_history_manager.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'iroute_config.dart'; - -typedef OnRouteChange = void Function(IRouteConfig config); - -class RouteHistoryManager{ - final List _histories = []; - final List _backHistories = []; - - List 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(); - } -} \ No newline at end of file diff --git a/lib/v11/app/navigation/router/routes.dart b/lib/v11/app/navigation/router/routes.dart deleted file mode 100644 index 60bde28..0000000 --- a/lib/v11/app/navigation/router/routes.dart +++ /dev/null @@ -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 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 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 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'), -]; diff --git a/lib/v11/app/navigation/router/sort.dart b/lib/v11/app/navigation/router/sort.dart new file mode 100644 index 0000000..e6d6a84 --- /dev/null +++ b/lib/v11/app/navigation/router/sort.dart @@ -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 _redirectSort(BuildContext context, GoRouterState state) { + if (state.fullPath == '/sort') { + String name = SortStateScope.read(context).config.name; + return '/sort/player/$name'; + } + return null; +} diff --git a/lib/v11/app/navigation/router/utils/path_utils.dart b/lib/v11/app/navigation/router/utils/path_utils.dart deleted file mode 100644 index dff7feb..0000000 --- a/lib/v11/app/navigation/router/utils/path_utils.dart +++ /dev/null @@ -1,37 +0,0 @@ -final RegExp _parameterRegExp = RegExp(r':(\w+)(\((?:\\.|[^\\()])+\))?'); - -RegExp patternToRegExp(String pattern, List 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; -} \ No newline at end of file diff --git a/lib/v11/app/navigation/router/views/navigator_scope.dart b/lib/v11/app/navigation/router/views/navigator_scope.dart deleted file mode 100644 index be7b1d8..0000000 --- a/lib/v11/app/navigation/router/views/navigator_scope.dart +++ /dev/null @@ -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 configs; - final IRoutePageBuilder notFindPageBuilder; - - const NavigatorScope({ - super.key, - required this.node, - required this.onPopPage, - required this.configs, - required this.notFindPageBuilder, - }); - - @override - State createState() => _NavigatorScopeState(); -} - -class _NavigatorScopeState extends State { - @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 _buildPages(BuildContext context, List configs) { - IRouteConfig top = configs.last; - List bottoms = - configs.sublist(0, configs.length - 1).toList(); - List pages = []; - List topPages = _buildPageByPathFromTree(context, top); - pages = _buildLivePageByPathList(context, bottoms, top, topPages); - pages.addAll(topPages); - return pages; - } - - List _buildLivePageByPathList( - BuildContext context, - List paths, - IRouteConfig curConfig, - List curPages, - ) { - List 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 _buildPageByPathFromTree( - BuildContext context, IRouteConfig config) { - List result = []; - List 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; - } -} diff --git a/lib/v11/app/navigation/views/app_navigation.dart b/lib/v11/app/navigation/views/app_navigation.dart index 9360abf..bcad815 100644 --- a/lib/v11/app/navigation/views/app_navigation.dart +++ b/lib/v11/app/navigation/views/app_navigation.dart @@ -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'; diff --git a/lib/v11/app/navigation/views/app_navigation_rail.dart b/lib/v11/app/navigation/views/app_navigation_rail.dart index 2ddb93a..654650c 100644 --- a/lib/v11/app/navigation/views/app_navigation_rail.dart +++ b/lib/v11/app/navigation/views/app_navigation_rail.dart @@ -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 { + final List 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 { 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 { ); } - 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 { 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(() {}); } } diff --git a/lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart b/lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart index 40516a7..ea7c2da 100644 --- a/lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart +++ b/lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart @@ -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? onSubmit; @@ -14,23 +15,34 @@ class AppRouterEditor extends StatefulWidget { class _AppRouterEditorState extends State { 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 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(); } diff --git a/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart b/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart index b806eb5..c43932a 100644 --- a/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart +++ b/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart @@ -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 createState() => _RouterIndicatorState(); } - class _RouterIndicatorState extends State { + 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 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 { 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 pathToBreadcrumbItems(String path) { + List pathToBreadcrumbItems( + BuildContext context, String path) { Uri uri = Uri.parse(path); List result = []; String to = ''; @@ -106,16 +113,25 @@ class _RouterIndicatorState extends State { 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 kRouteLabelMap = { + '': '', + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/sort': '排序算法', + '/sort/player': '演示', + '/sort/settings': '排序配置', + '/user': '我的', + '/settings': '系统设置', +}; diff --git a/lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart b/lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart index e2ad492..1bb6749 100644 --- a/lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart +++ b/lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart @@ -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 { @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 { 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 { + 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 histories = router.historyManager.histories; + List histories = _history.histories.reversed.toList(); if(histories.isEmpty){ return const Center( child: Text( @@ -141,12 +139,12 @@ class _HistoryPanelState extends State { 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]), ); diff --git a/lib/v11/app/navigation/views/app_top_bar/route_history_button.dart b/lib/v11/app/navigation/views/app_top_bar/route_history_button.dart index 3ee6513..d94a2ef 100644 --- a/lib/v11/app/navigation/views/app_top_bar/route_history_button.dart +++ b/lib/v11/app/navigation/views/app_top_bar/route_history_button.dart @@ -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 { - @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 { 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 { hoverColor: backHistoryColor, defaultColor: backHistoryColor, icon: CupertinoIcons.arrow_right_circle, - onPressed: hasBackHistory?router.revocation:null, + onPressed: hasBackHistory?_history.revocation:null, ), ], ); } - - void _onChange() { - setState(() {}); - } } diff --git a/lib/v11/app/navigation/router/views/not_find_view.dart b/lib/v11/app/navigation/views/not_find_view.dart similarity index 100% rename from lib/v11/app/navigation/router/views/not_find_view.dart rename to lib/v11/app/navigation/views/not_find_view.dart diff --git a/lib/v11/app/navigation/router/views/route_back_indicator.dart b/lib/v11/app/navigation/views/route_back_indicator.dart similarity index 73% rename from lib/v11/app/navigation/router/views/route_back_indicator.dart rename to lib/v11/app/navigation/views/route_back_indicator.dart index 5be781a..2142daa 100644 --- a/lib/v11/app/navigation/router/views/route_back_indicator.dart +++ b/lib/v11/app/navigation/views/route_back_indicator.dart @@ -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 { + 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().isNotEmpty; + if(hasPush){ return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( - onTap: router.backStack, + onTap: context.pop, child: Container( width: 26, height: 26, diff --git a/lib/v11/app/unit_app.dart b/lib/v11/app/unit_app.dart index 7e4acb0..d17c59c 100644 --- a/lib/v11/app/unit_app.dart +++ b/lib/v11/app/unit_app.dart @@ -1,42 +1,53 @@ import 'package:flutter/material.dart'; -import 'navigation/router/app_router_parser.dart'; -import 'navigation/router/app_router_delegate.dart'; +import 'package:go_router/go_router.dart'; +import '../pages/empty/empty_page.dart'; +import 'navigation/router/root.dart'; import '../pages/sort/provider/state.dart'; +import 'navigation/helper/router_history.dart'; import 'navigation/transition/fade_page_transitions_builder.dart'; class UnitApp extends StatelessWidget { - const UnitApp({super.key}); + UnitApp({super.key}); + + final GoRouter _router = GoRouter( + routes: [rootRoute], + onException: (BuildContext ctx,GoRouterState state, GoRouter router){ + router.go('/404', extra: state.uri.toString()); + }, + // errorBuilder: (_,state) { + // return EmptyPage(msg: '无法访问: ${state.matchedLocation}',); + // } + ); @override Widget build(BuildContext context) { return SortStateScope( notifier: SortState(), - child: MaterialApp.router( - routerDelegate: router, - routeInformationParser: AppRouterParser(), - routeInformationProvider: PlatformRouteInformationProvider( - initialRouteInformation: RouteInformation(uri: Uri.parse('/app/color')), + child: RouterHistoryScope( + notifier: RouterHistory(_router.routerDelegate, exclusives: ['/login']), + child: MaterialApp.router( + routerConfig: _router, + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + pageTransitionsTheme: const PageTransitionsTheme(builders: { + TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.macOS: FadePageTransitionsBuilder(), + TargetPlatform.windows: FadePageTransitionsBuilder(), + TargetPlatform.linux: FadePageTransitionsBuilder(), + }), + appBarTheme: const AppBarTheme( + elevation: 0, + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ))), + debugShowCheckedModeBanner: false, + // home: AppNavigation() ), - theme: ThemeData( - fontFamily: "宋体", - scaffoldBackgroundColor: Colors.white, - pageTransitionsTheme: const PageTransitionsTheme(builders: { - TargetPlatform.android: ZoomPageTransitionsBuilder(), - TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), - TargetPlatform.macOS: FadePageTransitionsBuilder(), - TargetPlatform.windows: FadePageTransitionsBuilder(), - TargetPlatform.linux: FadePageTransitionsBuilder(), - }), - appBarTheme: const AppBarTheme( - elevation: 0, - iconTheme: IconThemeData(color: Colors.black), - titleTextStyle: TextStyle( - color: Colors.black, - fontSize: 18, - fontWeight: FontWeight.bold, - ))), - debugShowCheckedModeBanner: false, - // home: AppNavigation() ), ); } diff --git a/lib/v11/pages/color/color_page.dart b/lib/v11/pages/color/color_page.dart index 60d18c6..9d9c15c 100644 --- a/lib/v11/pages/color/color_page.dart +++ b/lib/v11/pages/color/color_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:iroute/components/project/colors_panel.dart'; -import '../../app/navigation/router/app_router_delegate.dart'; class ColorPage extends StatefulWidget { const ColorPage({super.key}); @@ -11,10 +11,26 @@ class ColorPage extends StatefulWidget { class _ColorPageState extends State { final List _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, + 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 @@ -47,20 +63,19 @@ class _ColorPageState extends State { ); } - void _selectColor(Color color){ + void _selectColor(Color color) { String value = color.value.toRadixString(16); - String path = '/app/color/detail?color=$value'; - // router.changePath('/app/color/detail',extra: color); - router.changePath(path,recordHistory: true); - + context.push('/color/detail?color=$value'); + // GoRouter.of(context) .pushNamed('colorDetail', queryParameters: {'color': value}); } void _toAddPage() async { - Color? color = await router.changePath('/app/color/add',forResult: true,recordHistory: false); - if (color != null) { - setState(() { - _colors.add(color); - }); - } + Color? color = await context.push('/color/add'); + + if (color != null) { + setState(() { + _colors.add(color); + }); + } } -} \ No newline at end of file +} diff --git a/lib/v11/pages/empty/empty_page.dart b/lib/v11/pages/empty/empty_page.dart index b05f56f..008d529 100644 --- a/lib/v11/pages/empty/empty_page.dart +++ b/lib/v11/pages/empty/empty_page.dart @@ -1,30 +1,48 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; class EmptyPage extends StatelessWidget { - const EmptyPage({super.key}); + final String msg; + const EmptyPage({super.key, required this.msg}); @override Widget build(BuildContext context) { - return Scaffold( - // appBar: AppBar( - // title: Text('界面走丢了'), - // ), - body: Scaffold( - body: Center( - child: Wrap( - spacing: 16, - crossAxisAlignment: WrapCrossAlignment.center, - direction: Axis.vertical, + return DragToMoveWrap( + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(46), + child: Row( children: [ - Icon(Icons.nearby_error,size: 64, color: Colors.grey), + const SizedBox(width: 20,), Text( '404 界面丢失', - style: TextStyle(fontSize: 24, color: Colors.grey), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), + Spacer(), + const WindowButtons() ], ), ), + body: Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + msg, + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ElevatedButton(onPressed: (){ + context.go('/'); + }, child: Text('返回首页')) + ], + ), + ), ), ); } } + diff --git a/lib/v11/pages/empty/empty_panel.dart b/lib/v11/pages/empty/empty_panel.dart new file mode 100644 index 0000000..1e99155 --- /dev/null +++ b/lib/v11/pages/empty/empty_panel.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; + +class EmptyPanel extends StatelessWidget { + final String msg; + const EmptyPanel({super.key, required this.msg}); + + @override + Widget build(BuildContext context) { + return Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + msg, + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ElevatedButton(onPressed: (){ + context.go('/'); + }, child: Text('返回首页')) + ], + ), + ); + } +} + diff --git a/lib/v11/pages/login/login.dart b/lib/v11/pages/login/login.dart index af1568a..3177a76 100644 --- a/lib/v11/pages/login/login.dart +++ b/lib/v11/pages/login/login.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import '../../app/navigation/router/app_router_delegate.dart'; +import 'package:go_router/go_router.dart'; + class LoginPage extends StatelessWidget { const LoginPage({super.key}); @@ -14,7 +15,8 @@ class LoginPage extends StatelessWidget { Text('Login Page',style: TextStyle(fontSize: 24),), const SizedBox(height: 20,), ElevatedButton(onPressed: (){ - router.changePath('/app/color'); + GoRouter.of(context).go('/color'); + // router.changePath('/app/color'); }, child: Text('点击进入')) ], ), diff --git a/lib/v11/pages/sort/provider/state.dart b/lib/v11/pages/sort/provider/state.dart index b695b83..b594b1e 100644 --- a/lib/v11/pages/sort/provider/state.dart +++ b/lib/v11/pages/sort/provider/state.dart @@ -99,6 +99,7 @@ class SortStateScope extends InheritedNotifier { static SortState of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.notifier!; - static SortState read(BuildContext context) => - context.getInheritedWidgetOfExactType()!.notifier!; + static SortState read(BuildContext context) { + return context.getInheritedWidgetOfExactType()!.notifier!; + } } \ No newline at end of file diff --git a/lib/v11/pages/sort/views/settings/sort_setting.dart b/lib/v11/pages/sort/views/settings/sort_setting.dart index 84f5f52..fe86289 100644 --- a/lib/v11/pages/sort/views/settings/sort_setting.dart +++ b/lib/v11/pages/sort/views/settings/sort_setting.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import '../../provider/state.dart'; import 'color_picker.dart'; class SortSettings extends StatefulWidget { @@ -66,8 +67,9 @@ class _SortSettingsState extends State { child: IconButton( splashRadius: 20, onPressed: (){ - SortState state = SortStateScope.of(context); - state.config =state.config.copyWith( + + SortState state = SortStateScope.read(context); + state.config = state.config.copyWith( count: int.parse(_count.text), duration: Duration( microseconds: int.parse(_duration.text), @@ -75,7 +77,8 @@ class _SortSettingsState extends State { seed: int.parse(_seed.text), colorIndex: _colorIndex ); - Navigator.of(context).pop(); + GoRouter.of(context).pop(); + // Navigator.of(context).pop(); }, icon: Icon(Icons.check)), )], iconTheme: IconThemeData(color: Colors.black), diff --git a/lib/v11/pages/sort/views/sort_page/sort_page.dart b/lib/v11/pages/sort/views/sort_page/sort_page.dart index 56beba3..6631bff 100644 --- a/lib/v11/pages/sort/views/sort_page/sort_page.dart +++ b/lib/v11/pages/sort/views/sort_page/sort_page.dart @@ -1,8 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../../app/navigation/router/iroute_config.dart'; -import '../../../../app/navigation/router/app_router_delegate.dart'; +import 'package:go_router/go_router.dart'; import 'sort_button.dart'; import '../../functions.dart'; @@ -62,7 +61,8 @@ class SortRailPanel extends StatelessWidget { cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { - router.changePath('/app/sort/settings',style: RouteStyle.push); + GoRouter.of(context).push('/sort/settings'); + // router.changePath('/app/sort/settings',style: RouteStyle.push); }, child: const Icon( CupertinoIcons.settings, @@ -81,7 +81,7 @@ class SortRailPanel extends StatelessWidget { options: sortNameMap.values.toList(), onSelected: (name) { state.selectName(name); - router.changePath('/app/sort/${name}',recordHistory: true); + GoRouter.of(context).go('/sort/player/$name'); }, ), ), diff --git a/lib/v12/app.dart b/lib/v12/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v12/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v12/app/authentication/auth_scope.dart b/lib/v12/app/authentication/auth_scope.dart new file mode 100644 index 0000000..a9f37e0 --- /dev/null +++ b/lib/v12/app/authentication/auth_scope.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +const Map _kTestUserMap = { + 'toly': '123456', + '张风捷特烈': '111111', + 'test1': '111111', +}; + +class AuthScope extends InheritedNotifier { + const AuthScope({super.key, required super.child, super.notifier}); + + static AuthResult of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType()! + .notifier!; + } + + static AuthResult read(BuildContext context) { + return context + .getInheritedWidgetOfExactType()! + .notifier!; + } +} + +enum AuthStatus{ + none, + success, + failed, + waitingLogin, + waitingLoginOut, +} + +class AuthResult with ChangeNotifier { + String? name; + int coin = 0; + String? token; + AuthStatus status = AuthStatus.none; + + AuthResult(); + + Future<(bool,String)> login(String username,String pwd) async{ + /// 模拟校验 与 模拟延时 + bool allow = _kTestUserMap[username] == pwd; + status = AuthStatus.waitingLogin; + notifyListeners(); + await Future.delayed(const Duration(seconds: 1)); + if(allow){ + name = username; + coin = 1994328; + token = 'testToken======'; + status = AuthStatus.success; + notifyListeners(); + return (true,'登陆成功!'); + }else{ + /// 可以返回一写其他错误信息 + status = AuthStatus.failed; + notifyListeners(); + return (false,'登陆失败!'); + } + } + + Future logout() async{ + status = AuthStatus.waitingLoginOut; + notifyListeners(); + /// 模拟退出登录请求耗时 + await Future.delayed(const Duration(seconds: 1)); + name = null; + coin = 0; + token = null; + status = AuthStatus.none; + notifyListeners(); + return true; + } +} + diff --git a/lib/v12/app/navigation/helper/function.dart b/lib/v12/app/navigation/helper/function.dart new file mode 100644 index 0000000..526be07 --- /dev/null +++ b/lib/v12/app/navigation/helper/function.dart @@ -0,0 +1,25 @@ +import 'package:flutter/cupertino.dart'; + +Map 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 '未知路由'; +} \ No newline at end of file diff --git a/lib/v12/app/navigation/helper/router_history.dart b/lib/v12/app/navigation/helper/router_history.dart new file mode 100644 index 0000000..19deda0 --- /dev/null +++ b/lib/v12/app/navigation/helper/router_history.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class RouterHistoryScope extends InheritedNotifier { + const RouterHistoryScope({super.key, required super.child, super.notifier}); + + static RouterHistory of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType()! + .notifier!; + } + + static RouterHistory read(BuildContext context) { + return context + .getInheritedWidgetOfExactType()! + .notifier!; + } +} + +class RouterHistory with ChangeNotifier { + final List exclusives; + + final GoRouterDelegate delegate; + + final List _histories = []; + final List _backHistories = []; + + List 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); + } +} diff --git a/lib/v12/app/navigation/router/app.dart b/lib/v12/app/navigation/router/app.dart new file mode 100644 index 0000000..c9719e7 --- /dev/null +++ b/lib/v12/app/navigation/router/app.dart @@ -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: [ + 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); + }, + ) + ], +); diff --git a/lib/v12/app/navigation/router/color.dart b/lib/v12/app/navigation/router/color.dart new file mode 100644 index 0000000..6a2052a --- /dev/null +++ b/lib/v12/app/navigation/router/color.dart @@ -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: [ + 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(); + }, + ), + ], +); + diff --git a/lib/v12/app/navigation/router/root.dart b/lib/v12/app/navigation/router/root.dart new file mode 100644 index 0000000..9eea0a3 --- /dev/null +++ b/lib/v12/app/navigation/router/root.dart @@ -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 _redirect(BuildContext context, GoRouterState state) { + if(state.fullPath=='/'){ + return '/color'; + } + return null; +} diff --git a/lib/v12/app/navigation/router/sort.dart b/lib/v12/app/navigation/router/sort.dart new file mode 100644 index 0000000..e6d6a84 --- /dev/null +++ b/lib/v12/app/navigation/router/sort.dart @@ -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 _redirectSort(BuildContext context, GoRouterState state) { + if (state.fullPath == '/sort') { + String name = SortStateScope.read(context).config.name; + return '/sort/player/$name'; + } + return null; +} diff --git a/lib/v12/app/navigation/transition/fade_page_transitions_builder.dart b/lib/v12/app/navigation/transition/fade_page_transitions_builder.dart new file mode 100644 index 0000000..0587ecc --- /dev/null +++ b/lib/v12/app/navigation/transition/fade_page_transitions_builder.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class FadePageTransitionsBuilder extends PageTransitionsBuilder { + + const FadePageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute? route, + BuildContext? context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return _FadePagePageTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + } +} + +class _FadePagePageTransition extends StatelessWidget { + + const _FadePagePageTransition({ + required this.animation, + required this.secondaryAnimation, + required this.child, + }); + + final Animation animation; + final Animation secondaryAnimation; + final Widget child; + + @override + Widget build(BuildContext context) { + var curveTween = CurveTween(curve: Curves.easeIn); + return FadeTransition( + opacity: animation.drive(curveTween), + child: child, + ); + } +} \ No newline at end of file diff --git a/lib/v12/app/navigation/transition/fade_transition_page.dart b/lib/v12/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..fc4e6e6 --- /dev/null +++ b/lib/v12/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,52 @@ +// 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'; + +class FadeTransitionPage extends Page { + final Widget child; + final Duration duration; + + const FadeTransitionPage({ + super.key, + required this.child, + this.duration = const Duration(milliseconds: 300), + }); + + @override + Route createRoute(BuildContext context) => PageBasedFadeTransitionRoute(this); +} + +class PageBasedFadeTransitionRoute extends PageRoute { + final FadeTransitionPage _page; + + PageBasedFadeTransitionRoute(this._page) : super(settings: _page); + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + Duration get transitionDuration => _page.duration; + + @override + bool get maintainState => true; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + var curveTween = CurveTween(curve: Curves.easeIn); + return FadeTransition( + opacity: animation.drive(curveTween), + child: (settings as FadeTransitionPage).child, + ); + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) => + child; +} diff --git a/lib/v12/app/navigation/transition/no_transition_page.dart b/lib/v12/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v12/app/navigation/transition/no_transition_page.dart @@ -0,0 +1,47 @@ +// 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'; + +class NoTransitionPage extends Page { + final Widget child; + + const NoTransitionPage({ + super.key, + required this.child, + }); + + @override + Route createRoute(BuildContext context) => NoTransitionRoute(this); +} + +class NoTransitionRoute extends PageRoute { + + final NoTransitionPage _page; + + NoTransitionRoute(this._page) : super(settings: _page); + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + Duration get transitionDuration => const Duration(milliseconds: 0); + + @override + bool get maintainState => true; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return (settings as NoTransitionPage).child; + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) => + child; +} diff --git a/lib/v12/app/navigation/views/app_navigation.dart b/lib/v12/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..bcad815 --- /dev/null +++ b/lib/v12/app/navigation/views/app_navigation.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'app_navigation_rail.dart'; +import 'app_top_bar/app_top_bar.dart'; + +class AppNavigation extends StatefulWidget { + final Widget navigator; + + const AppNavigation({super.key,required this.navigator}); + + @override + State createState() => _AppNavigationState(); +} + +class _AppNavigationState extends State { + + @override + void initState() { + print('======_AppNavigationState#initState=============='); + super.initState(); + } + + @override + void dispose() { + print('======_AppNavigationState#dispose=============='); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + double px1 = 1/View.of(context).devicePixelRatio; + return Scaffold( + body: Row( + children: [ + const AppNavigationRail(), + Expanded( + child: Column( + children: [ + const AppTopBar(), + Divider(height: px1,), + Expanded( + child: widget.navigator, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/v12/app/navigation/views/app_navigation_rail.dart b/lib/v12/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..654650c --- /dev/null +++ b/lib/v12/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + final List 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 + Widget build(BuildContext context) { + return DragToMoveWrap( + child: TolyNavigationRail( + items: deskNavBarMenus, + leading: const Padding( + padding: EdgeInsets.symmetric(vertical: 18.0), + child: FlutterLogo(), + ), + tail: Padding( + padding: const EdgeInsets.only(bottom: 6.0), + child: Text( + 'V0.1.1', + style: TextStyle(color: Colors.white, fontSize: 12), + ), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: activeIndex, + ), + ); + } + + final RegExp _segReg = RegExp(r'/\w+'); + + int? get activeIndex { + final String path = GoRouterState.of(context).uri.toString(); + RegExpMatch? match = _segReg.firstMatch(path); + if (match == null) return null; + String? target = match.group(0); + int index = deskNavBarMenus.indexWhere((menu) => menu.path!.contains(target??'')); + if (index == -1) return null; + return index; + } + + void _onDestinationSelected(int index) { + String path = deskNavBarMenus[index].path!; + if(index==4){ + GoRouter.of(context).push(path); + }else{ + GoRouter.of(context).go(path); + } + } +} diff --git a/lib/v12/app/navigation/views/app_top_bar/app_router_editor.dart b/lib/v12/app/navigation/views/app_top_bar/app_router_editor.dart new file mode 100644 index 0000000..ea7c2da --- /dev/null +++ b/lib/v12/app/navigation/views/app_top_bar/app_router_editor.dart @@ -0,0 +1,76 @@ +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'; + +class AppRouterEditor extends StatefulWidget { + final ValueChanged? onSubmit; + const AppRouterEditor({super.key, this.onSubmit}); + + @override + State createState() => _AppRouterEditorState(); +} + +class _AppRouterEditorState extends State { + + final TextEditingController _controller = TextEditingController(); + late GoRouterDelegate _delegate ; + + + @override + void initState() { + super.initState(); + _delegate = GoRouter.of(context).routerDelegate; + + _onRouteChange(); + _delegate.addListener(_onRouteChange); + } + + void _onRouteChange() { + + List 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(); + _delegate.removeListener(_onRouteChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.centerRight, + children: [ + SizedBox( + child: CupertinoTextField( + controller: _controller, + style: TextStyle(fontSize: 14), + padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32), + placeholder: '输入路由地址导航', + onSubmitted: widget.onSubmit, + decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: HoverIconButton( + icon: Icons.directions_outlined, + defaultColor: Color(0xff68696B), + onPressed:()=>widget.onSubmit?.call(_controller.text), + size: 20 + ), + ) + ], + ); + } +} diff --git a/lib/v12/app/navigation/views/app_top_bar/app_top_bar.dart b/lib/v12/app/navigation/views/app_top_bar/app_top_bar.dart new file mode 100644 index 0000000..c43932a --- /dev/null +++ b/lib/v12/app/navigation/views/app_top_bar/app_top_bar.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; +import '../../helper/function.dart'; +import '../route_back_indicator.dart'; +import 'app_router_editor.dart'; +import 'route_history_button.dart'; +import 'history_view_icon.dart'; + +class AppTopBar extends StatelessWidget { + const AppTopBar({super.key}); + + @override + Widget build(BuildContext context) { + return DragToMoveWrap( + child: Container( + // alignment: Alignment.center, + height: 46, + child: Row( + children: [ + const SizedBox(width: 16), + const RouteBackIndicator(), + const RouterIndicator(), + // Spacer(), + Expanded( + 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() + ], + ), + ), + ); + } +} + +class RouterIndicator extends StatefulWidget { + const RouterIndicator({super.key}); + + @override + State createState() => _RouterIndicatorState(); +} + +class _RouterIndicatorState extends State { + late GoRouterDelegate _delegate; + @override + void initState() { + super.initState(); + _delegate = GoRouter.of(context).routerDelegate; + _delegate.addListener(_onRouterChange); + } + + @override + void dispose() { + _delegate.removeListener(_onRouterChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List 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(context, match.matchedLocation), + onTapItem: (item) { + if (item.to != null) { + GoRouter.of(context).go(item.to!); + } + }, + ); + } + + void _onRouterChange() { + setState(() {}); + } + + List pathToBreadcrumbItems( + BuildContext context, String path) { + Uri uri = Uri.parse(path); + List result = []; + String to = ''; + + String distPath = ''; + for (String segment in uri.pathSegments) { + distPath += '/$segment'; + } + + for (String segment in uri.pathSegments) { + to += '/$segment'; + String label = calcRouteName(context, to); + if (label.isNotEmpty) { + result + .add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + } + return result; + } +} + +Map kRouteLabelMap = { + '': '', + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/sort': '排序算法', + '/sort/player': '演示', + '/sort/settings': '排序配置', + '/user': '我的', + '/settings': '系统设置', +}; diff --git a/lib/v12/app/navigation/views/app_top_bar/history_view_icon.dart b/lib/v12/app/navigation/views/app_top_bar/history_view_icon.dart new file mode 100644 index 0000000..1bb6749 --- /dev/null +++ b/lib/v12/app/navigation/views/app_top_bar/history_view_icon.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; +import 'package:iroute/v11/app/navigation/helper/function.dart'; +import '../../helper/router_history.dart'; +import 'app_top_bar.dart'; + +class HistoryViewIcon extends StatelessWidget{ + const HistoryViewIcon({super.key}); + + @override + Widget build(BuildContext context) { + RouterHistory routerHistory = RouterHistoryScope.read(context); + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: PopPanel( + offset: const Offset(0, 10), + panel: SizedBox( + height: 350, + child: Column( + children: [ + _buildTopBar(routerHistory), + const Expanded( + child:HistoryPanel(), + ), + ], + ), + ), + child: const Icon( + Icons.history, + size: 20, + ), + ), + ); + } + + Widget _buildTopBar( RouterHistory routerHistory) { + return Container( + decoration: BoxDecoration( + color: const Color(0xffFAFAFC), + borderRadius: BorderRadius.circular(6), + ), + padding: + const EdgeInsets.only(top: 10, left: 12, right: 12, bottom: 8), + child: Row( + children: [ + const Text( + '浏览历史', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const Spacer(), + TextButton(onPressed: routerHistory.clear, child: const Text('清空历史')) + ], + )); + } +} + +class HistoryItem extends StatefulWidget { + final RouteMatchList history; + final VoidCallback onPressed; + final VoidCallback onDelete; + + const HistoryItem({super.key, required this.history, required this.onPressed, required this.onDelete}); + + @override + State createState() => _HistoryItemState(); +} + +class _HistoryItemState extends State { + @override + Widget build(BuildContext context) { + String path = widget.history.matches.last.matchedLocation; + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onPressed, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(path), + const SizedBox( + height: 2, + ), + Text(calcRouteName(context, path)), + ], + )), + GestureDetector( + onTap: widget.onDelete, + child: const Icon( + Icons.close, + size: 18, + color: Color(0xff8E92A9), + ), + ), + ], + ), + ), + ); + } +} + +class HistoryPanel extends StatefulWidget { + const HistoryPanel({super.key}); + + @override + State createState() => _HistoryPanelState(); +} + +class _HistoryPanelState extends State { + late RouterHistory _history ; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _history = RouterHistoryScope.of(context); + } + + + @override + Widget build(BuildContext context) { + List histories = _history.histories.reversed.toList(); + if(histories.isEmpty){ + return const Center( + child: Text( + '暂无浏览历史记录', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ); + } + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + itemExtent: 46, + itemCount: histories.length, + itemBuilder: (_, index) => + HistoryItem( + onDelete: (){ + _history.close(histories[index]); + }, + onPressed: ()async { + Navigator.of(context).pop(); + _history.select(histories[index]); + // router.changeRoute(histories[index].copyWith(recordHistory: false)); + }, + history: histories[index]), + ); + } + + void _onChange() { + setState(() {}); + } +} diff --git a/lib/v12/app/navigation/views/app_top_bar/route_history_button.dart b/lib/v12/app/navigation/views/app_top_bar/route_history_button.dart new file mode 100644 index 0000000..d94a2ef --- /dev/null +++ b/lib/v12/app/navigation/views/app_top_bar/route_history_button.dart @@ -0,0 +1,52 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:iroute/components/toly_ui/button/hover_icon_button.dart'; + +import '../../helper/router_history.dart'; + +class RouteHistoryButton extends StatefulWidget { + const RouteHistoryButton({super.key}); + + @override + State createState() => _RouteHistoryButtonState(); +} + +class _RouteHistoryButtonState extends State { + + late RouterHistory _history ; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _history = RouterHistoryScope.of(context); + } + + @override + Widget build(BuildContext context) { + bool hasHistory = _history.hasHistory; + bool hasBackHistory = _history.hasBackHistory; + Color activeColor = const Color(0xff9195AC); + Color inActiveColor = const Color(0xffC7CAD5); + Color historyColor = hasHistory?activeColor:inActiveColor; + Color backHistoryColor = hasBackHistory?activeColor:inActiveColor; + return Wrap( + children: [ + HoverIconButton( + size: 20, + hoverColor: historyColor, + defaultColor: historyColor, + icon: CupertinoIcons.arrow_left_circle, + onPressed: hasHistory?_history.back:null, + ), + const SizedBox(width: 8,), + HoverIconButton( + size: 20, + hoverColor: backHistoryColor, + defaultColor: backHistoryColor, + icon: CupertinoIcons.arrow_right_circle, + onPressed: hasBackHistory?_history.revocation:null, + ), + ], + ); + } +} diff --git a/lib/v12/app/navigation/views/not_find_view.dart b/lib/v12/app/navigation/views/not_find_view.dart new file mode 100644 index 0000000..ccefa16 --- /dev/null +++ b/lib/v12/app/navigation/views/not_find_view.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class NotFindPage extends StatelessWidget { + const NotFindPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Material( + child: Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + '404 Page Not Find', + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ], + ), + ), + ); + } +} diff --git a/lib/v12/app/navigation/views/route_back_indicator.dart b/lib/v12/app/navigation/views/route_back_indicator.dart new file mode 100644 index 0000000..2142daa --- /dev/null +++ b/lib/v12/app/navigation/views/route_back_indicator.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class RouteBackIndicator extends StatefulWidget { + const RouteBackIndicator({super.key}); + + @override + State createState() => _RouteBackIndicatorState(); +} + +class _RouteBackIndicatorState extends State { + + late GoRouterDelegate _delegate ; + + @override + void initState() { + super.initState(); + _delegate = GoRouter.of(context).routerDelegate; + _delegate.addListener(_onChange); + } + + @override + void dispose() { + _delegate.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool hasPush = _delegate.currentConfiguration.matches + .whereType().isNotEmpty; + if(hasPush){ + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: context.pop, + 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(() { + + }); + } +} diff --git a/lib/v12/app/unit_app.dart b/lib/v12/app/unit_app.dart new file mode 100644 index 0000000..551d8d3 --- /dev/null +++ b/lib/v12/app/unit_app.dart @@ -0,0 +1,77 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../app/authentication/auth_scope.dart'; +import 'navigation/router/root.dart'; +import '../pages/sort/provider/state.dart'; +import 'navigation/helper/router_history.dart'; +import 'navigation/transition/fade_page_transitions_builder.dart'; + + + + +class UnitApp extends StatelessWidget { + UnitApp({super.key}); + + final GoRouter _router = GoRouter( + routes: [rootRoute], + onException: (BuildContext ctx, GoRouterState state, GoRouter router) { + router.go('/404', extra: state.uri.toString()); + }, + redirect: _authRedirect + ); + + late final RouterHistory _routerHistory = RouterHistory( + _router.routerDelegate, + exclusives: ['/login'], + ); + final AuthResult _result = AuthResult(); + + @override + Widget build(BuildContext context) { + return AuthScope( + notifier: _result, + child: SortStateScope( + notifier: SortState(), + child: RouterHistoryScope( + notifier: _routerHistory, + child: MaterialApp.router( + routerConfig: _router, + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + pageTransitionsTheme: const PageTransitionsTheme(builders: { + TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.macOS: FadePageTransitionsBuilder(), + TargetPlatform.windows: FadePageTransitionsBuilder(), + TargetPlatform.linux: FadePageTransitionsBuilder(), + }), + appBarTheme: const AppBarTheme( + elevation: 0, + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ))), + debugShowCheckedModeBanner: false, + // home: AppNavigation() + ), + ), + + ), + ); + } + + +} + +FutureOr _authRedirect(BuildContext context, GoRouterState state) async{ + bool loggedIn = AuthScope.read(context).status==AuthStatus.success; + if (!loggedIn) { + return '/login'; + } + return null; +} \ No newline at end of file diff --git a/lib/v12/pages/color/color_add_page.dart b/lib/v12/pages/color/color_add_page.dart new file mode 100644 index 0000000..48e6dc6 --- /dev/null +++ b/lib/v12/pages/color/color_add_page.dart @@ -0,0 +1,99 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; + +class ColorAddPage extends StatefulWidget { + const ColorAddPage({super.key}); + + @override + State createState() => _ColorAddPageState(); +} + +class _ColorAddPageState extends State { + late Color _color; + + @override + void initState() { + super.initState(); + _color = randomColor; + } + + @override + Widget build(BuildContext context) { + String text = '# ${_color.value.toRadixString(16)}'; + return Scaffold( + bottomNavigationBar: Container( + margin: EdgeInsets.only(right:20,bottom: 20), + // color: Colors.redAccent, + child: Row( + textDirection:TextDirection.rtl, + children: [ + ElevatedButton(onPressed: (){ + Navigator.of(context).pop(_color); + + }, child: Text('添加')), + SizedBox(width: 12,), + OutlinedButton(onPressed: (){ + Navigator.of(context).pop(); + }, child: Text('取消')), + ], + ), + ), + body: Column( + // mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 16), + 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), + ), + ), + ], + ), + ), + ColorPicker( + colorPickerWidth:200, + // enableAlpha: false, + displayThumbColor:true, + pickerColor: _color, + paletteType: PaletteType.hueWheel, + onColorChanged: changeColor, + + ), + ], + ), + ); + } + + Random _random = Random(); + + Color get randomColor { + return Color.fromARGB( + 255, + _random.nextInt(256), + _random.nextInt(256), + _random.nextInt(256), + ); + } + + + void changeColor(Color value) { + _color = value; + setState(() { + + }); + } +} diff --git a/lib/v12/pages/color/color_detail_page.dart b/lib/v12/pages/color/color_detail_page.dart new file mode 100644 index 0000000..7dfed86 --- /dev/null +++ b/lib/v12/pages/color/color_detail_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ColorDetailPage extends StatelessWidget { + final Color color; + const ColorDetailPage({super.key, required this.color}); + + @override + Widget build(BuildContext context) { + + const TextStyle style = TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.white + ); + String text = '# ${color.value.toRadixString(16)}'; + return Scaffold( + body: Container( + alignment: Alignment.center, + color: color, + child: Text(text ,style: style,), + ), + ); + } +} diff --git a/lib/v12/pages/color/color_page.dart b/lib/v12/pages/color/color_page.dart new file mode 100644 index 0000000..9d9c15c --- /dev/null +++ b/lib/v12/pages/color/color_page.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/project/colors_panel.dart'; + +class ColorPage extends StatefulWidget { + const ColorPage({super.key}); + + @override + State createState() => _ColorPageState(); +} + +class _ColorPageState extends State { + final List _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 + void initState() { + print('======_ColorPageState#initState=============='); + super.initState(); + } + + @override + void dispose() { + print('======_ColorPageState#dispose=============='); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar(title:const Text('颜色主页')), + floatingActionButton: FloatingActionButton( + onPressed: _toAddPage, + child: const Icon(Icons.add), + ), + body: Align( + alignment: Alignment.topCenter, + child: ColorsPanel( + colors: _colors, + onSelect: _selectColor, + ), + ), + ); + } + + void _selectColor(Color color) { + String value = color.value.toRadixString(16); + context.push('/color/detail?color=$value'); + // GoRouter.of(context) .pushNamed('colorDetail', queryParameters: {'color': value}); + } + + void _toAddPage() async { + Color? color = await context.push('/color/add'); + + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} diff --git a/lib/v12/pages/counter/counter_page.dart b/lib/v12/pages/counter/counter_page.dart new file mode 100644 index 0000000..b74a199 --- /dev/null +++ b/lib/v12/pages/counter/counter_page.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +class CounterPage extends StatefulWidget { + const CounterPage({super.key}); + + @override + State createState() => _CounterPageState(); +} + +class _CounterPageState extends State { + int _counter = 0; + + @override + void initState() { + print('======_CounterPageState#initState=============='); + super.initState(); + } + + @override + void dispose() { + print('======_CounterPageState#dispose=============='); + super.dispose(); + } + + void _incrementCounter() { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} \ No newline at end of file diff --git a/lib/v12/pages/empty/empty_page.dart b/lib/v12/pages/empty/empty_page.dart new file mode 100644 index 0000000..008d529 --- /dev/null +++ b/lib/v12/pages/empty/empty_page.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; + +class EmptyPage extends StatelessWidget { + final String msg; + const EmptyPage({super.key, required this.msg}); + + @override + Widget build(BuildContext context) { + return DragToMoveWrap( + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(46), + child: Row( + children: [ + const SizedBox(width: 20,), + Text( + '404 界面丢失', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + Spacer(), + const WindowButtons() + ], + ), + ), + body: Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + msg, + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ElevatedButton(onPressed: (){ + context.go('/'); + }, child: Text('返回首页')) + ], + ), + ), + ), + ); + } +} + diff --git a/lib/v12/pages/empty/empty_panel.dart b/lib/v12/pages/empty/empty_panel.dart new file mode 100644 index 0000000..1e99155 --- /dev/null +++ b/lib/v12/pages/empty/empty_panel.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/components/components.dart'; + +class EmptyPanel extends StatelessWidget { + final String msg; + const EmptyPanel({super.key, required this.msg}); + + @override + Widget build(BuildContext context) { + return Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + msg, + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ElevatedButton(onPressed: (){ + context.go('/'); + }, child: Text('返回首页')) + ], + ), + ); + } +} + diff --git a/lib/v12/pages/login/login.dart b/lib/v12/pages/login/login.dart new file mode 100644 index 0000000..b676b80 --- /dev/null +++ b/lib/v12/pages/login/login.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../components/components.dart'; +import '../../app/authentication/auth_scope.dart'; +import 'login_button.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const PreferredSize( + preferredSize: Size.fromHeight(46), + child: DragToMoveWrap( + child: Row( + children: [ + SizedBox( + width: 20, + ), + Text( + '欢迎登录', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + Spacer(), + WindowButtons() + ], + ), + ), + ), + body: Center( + child: SizedBox( + width: 360, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Login Page', + style: TextStyle(fontSize: 24), + ), + const SizedBox( + height: 20, + ), + buildUsernameInput(), + Stack( + alignment: const Alignment(.8, 0), + children: [ + buildPasswordInput(), + GestureDetector( + onTap: () => setState(() => _showPwd = !_showPwd), + child: Icon( + _showPwd ? Icons.remove_red_eye : Icons.hide_source)) + ], + ), + const SizedBox( + height: 20, + ), + LoginButton( + onLogin: () async { + String username = _usernameController.text; + String pwd = _passwordController.text; + (bool, String) result = await AuthScope.read(context).login(username, pwd); + if (result.$1) { + context.go('/'); + }else{ + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + backgroundColor: Colors.redAccent, + content: Text(result.$2))); + } + }, + ) + ], + ), + ), + ), + ); + } + + final _usernameController = TextEditingController(text: '张风捷特烈'); + + final _passwordController = TextEditingController(text: '111111'); + + Widget buildUsernameInput() { + return Column( + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.withOpacity(0.5), + width: 1.0, + ), + borderRadius: BorderRadius.circular(15.0), + ), + margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + child: Row( + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + child: Icon( + Icons.person_outline, + color: Colors.grey, + ), + ), + Container( + height: 20.0, + width: 1.0, + color: Colors.grey.withOpacity(0.5), + margin: const EdgeInsets.only(left: 00.0, right: 10.0), + ), + Expanded( + child: TextField( + controller: _usernameController, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: '请输入用户名...', + hintStyle: TextStyle(color: Colors.grey), + ), + ), + ) + ], + ), + ) + ], + ); + } + + bool _showPwd = false; + Widget buildPasswordInput() { + return Column( + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.withOpacity(0.5), + width: 1.0, + ), + borderRadius: BorderRadius.circular(15.0), + ), + margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + child: Row( + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + child: Icon( + Icons.lock_outline, + color: Colors.grey, + ), + ), + Container( + height: 30.0, + width: 1.0, + color: Colors.grey.withOpacity(0.5), + margin: const EdgeInsets.only(left: 00.0, right: 10.0), + ), + Expanded( + child: TextField( + obscureText: !_showPwd, + controller: _passwordController, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: '请输入密码...', + hintStyle: TextStyle(color: Colors.grey), + ), + ), + ) + ], + ), + ) + ], + ); + } +} diff --git a/lib/v12/pages/login/login_button.dart b/lib/v12/pages/login/login_button.dart new file mode 100644 index 0000000..d011f01 --- /dev/null +++ b/lib/v12/pages/login/login_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../../app/authentication/auth_scope.dart'; + +class LoginButton extends StatelessWidget { + final VoidCallback onLogin; + const LoginButton({super.key, required this.onLogin}); + + @override + Widget build(BuildContext context) { + const TextStyle style = TextStyle(fontSize: 16, fontWeight: FontWeight.bold); + AuthResult authResult = AuthScope.of(context); + VoidCallback? action; + Widget child; + switch (authResult.status) { + case AuthStatus.none: + case AuthStatus.success: + case AuthStatus.failed: + case AuthStatus.waitingLoginOut: + action = onLogin; + child = const Text('登 录', style: style); + break; + case AuthStatus.waitingLogin: + action = null; + child = const Wrap( + spacing: 8, + children: [ + CupertinoActivityIndicator(radius: 10), + Text('登录中...', style: style) + ], + ); + } + + return ElevatedButton( + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), minimumSize: const Size(320, 52)), + onPressed: action, + child: child); + } +} diff --git a/lib/v12/pages/login/logout_button.dart b/lib/v12/pages/login/logout_button.dart new file mode 100644 index 0000000..8cef75f --- /dev/null +++ b/lib/v12/pages/login/logout_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:iroute/v12/app/authentication/auth_scope.dart'; + +class LogoutButton extends StatelessWidget { + final VoidCallback onLogout; + const LogoutButton({super.key, required this.onLogout}); + + @override + Widget build(BuildContext context) { + AuthResult authResult = AuthScope.of(context); + VoidCallback? action = onLogout; + Widget child = const Icon(Icons.logout,size: 20,); + switch (authResult.status) { + case AuthStatus.none: + case AuthStatus.success: + case AuthStatus.failed: + case AuthStatus.waitingLogin: + break; + case AuthStatus.waitingLoginOut: + action = null; + child = CupertinoActivityIndicator(radius: 10,); + } + + return IconButton( + splashRadius: 20, + onPressed: action, + tooltip: '退出登录', + icon: child, + ); + } +} diff --git a/lib/v12/pages/settings/settings_page.dart b/lib/v12/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v12/pages/settings/settings_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +class SettingPage extends StatelessWidget { + const SettingPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body:Center(child: Text('SettingPage'))); + } +} diff --git a/lib/v12/pages/sort/functions.dart b/lib/v12/pages/sort/functions.dart new file mode 100644 index 0000000..6ae1176 --- /dev/null +++ b/lib/v12/pages/sort/functions.dart @@ -0,0 +1,46 @@ +import 'functions/bubble.dart'; +import 'functions/cocktail.dart'; +import 'functions/comb.dart'; +import 'functions/cycle.dart'; +import 'functions/gnome.dart'; +import 'functions/heap.dart'; +import 'functions/insertion.dart'; +import 'functions/merage.dart'; +import 'functions/pigeonHole.dart'; +import 'functions/quick.dart'; +import 'functions/selection.dart'; +import 'functions/shell.dart'; + +typedef SortFunction = Future Function(List src, SortCallback callback); +typedef SortCallback = Future Function(List dist); + +Map sortFunctionMap = { + 'insertion': insertionSort, + 'bubble': bubbleSort, + 'cocktail': cocktailSort, + 'comb': combSort, + 'pigeonHole': pigeonHoleSort, + 'shell': shellSort, + 'selection': selectionSort, + 'gnome': gnomeSort, + 'cycle': cycleSort, + 'heap': heapSort, + 'quick': quickSort, + 'mergeSort': mergeSort, +}; + +Map sortNameMap = { + 'insertion': '插入排序', + 'bubble': '冒泡排序', + 'cocktail': '鸡尾酒排序(双向冒泡排序)', + 'comb': '梳排序', + 'pigeonHole': '鸽巢排序', + 'shell': '希尔排序', + 'selection': '选择排序', + 'gnome': '侏儒排序', + 'cycle': '循环排序', + 'heap': '堆排序', + 'quick': '快速排序', + 'mergeSort': '归并排序', +}; + diff --git a/lib/v12/pages/sort/functions/bubble.dart b/lib/v12/pages/sort/functions/bubble.dart new file mode 100644 index 0000000..3cf3c99 --- /dev/null +++ b/lib/v12/pages/sort/functions/bubble.dart @@ -0,0 +1,19 @@ +import '../functions.dart'; + +///冒泡排序 +Future bubbleSort(List src, SortCallback callback ) async{ + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < src.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < src.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (src[j] > src[j + 1]) { + int temp = src[j]; + src[j] = src[j + 1]; + src[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/cocktail.dart b/lib/v12/pages/sort/functions/cocktail.dart new file mode 100644 index 0000000..8c2d18c --- /dev/null +++ b/lib/v12/pages/sort/functions/cocktail.dart @@ -0,0 +1,52 @@ +import '../functions.dart'; + +///鸡尾酒排序(双向冒泡排序) +Future cocktailSort(List src, SortCallback callback ) async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = src.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (src[i] > src[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await callback(src); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (src[i] > src[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await callback(src); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/comb.dart b/lib/v12/pages/sort/functions/comb.dart new file mode 100644 index 0000000..821f4a9 --- /dev/null +++ b/lib/v12/pages/sort/functions/comb.dart @@ -0,0 +1,34 @@ +import '../functions.dart'; + +///梳排序(Comb Sort) +Future combSort(List src, SortCallback callback) async{ + int gap = src.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < src.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (src[i] > src[i + gap]) { + int temp = src[i]; + src[i] = src[i + gap]; + src[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await callback(src); + } + } +} + +int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/cycle.dart b/lib/v12/pages/sort/functions/cycle.dart new file mode 100644 index 0000000..4bef6eb --- /dev/null +++ b/lib/v12/pages/sort/functions/cycle.dart @@ -0,0 +1,54 @@ +import '../functions.dart'; + +///循环排序 +Future cycleSort(List src, SortCallback callback) async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= src.length - 2; cycleStart++) { + int item = src[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < src.length; i++) { + if (src[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == src[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = src[pos]; + src[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < src.length; i++) { + if (src[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == src[pos]) { + pos += 1; + } + if (item != src[pos]) { + int temp = item; + item = src[pos]; + src[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/gnome.dart b/lib/v12/pages/sort/functions/gnome.dart new file mode 100644 index 0000000..5e08fc3 --- /dev/null +++ b/lib/v12/pages/sort/functions/gnome.dart @@ -0,0 +1,22 @@ +import '../functions.dart'; + +///地精排序 (侏儒排序) +Future gnomeSort(List src, SortCallback callback) async { + int index = 0; + while (index < src.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (src[index] >= src[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = src[index]; + src[index] = src[index - 1]; + src[index - 1] = temp; + index--; + } + await callback(src); + } + return; +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/heap.dart b/lib/v12/pages/sort/functions/heap.dart new file mode 100644 index 0000000..9f5410b --- /dev/null +++ b/lib/v12/pages/sort/functions/heap.dart @@ -0,0 +1,38 @@ +import '../functions.dart'; + +///堆排序 +Future heapSort(List src, SortCallback callback) async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = src.length ~/ 2; i >= 0; i--) { + await heapify(src,callback, src.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = src.length - 1; i >= 0; i--) { + int temp = src[0]; + src[0] = src[i]; + src[i] = temp; + await heapify(src, callback,i, 0); + } +} + +Future heapify(List src, SortCallback callback, int n, int i) async{ + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && src[l] > src[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && src[r] > src[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = src[i]; + src[i] = src[largest]; + src[largest] = temp; + heapify(src,callback, n, largest); + } + await callback(src); +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/insertion.dart b/lib/v12/pages/sort/functions/insertion.dart new file mode 100644 index 0000000..b1c7814 --- /dev/null +++ b/lib/v12/pages/sort/functions/insertion.dart @@ -0,0 +1,19 @@ +import '../functions.dart'; + +///插入排序 +Future insertionSort(List src, SortCallback callback) async { + for (int i = 1; i < src.length; i++) { + int temp = src[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < src[j]) { + src[j + 1] = src[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + // 更新排序结果回调 + await callback(src); + } + src[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await callback(src); + } +} diff --git a/lib/v12/pages/sort/functions/merage.dart b/lib/v12/pages/sort/functions/merage.dart new file mode 100644 index 0000000..12135b4 --- /dev/null +++ b/lib/v12/pages/sort/functions/merage.dart @@ -0,0 +1,79 @@ +import '../functions.dart'; + +//快速排序 +Future mergeSort(List src, SortCallback callback) async { + await _mergeSort(src,callback,0,src.length-1); +} + +///归并排序 +Future _mergeSort(List src, SortCallback callback,int leftIndex, int rightIndex) async { + // 定义一个名为 merge 的异步函数,用于合并两个有序子数组 + Future merge(int leftIndex, int middleIndex, int rightIndex) async { + // 计算左侧子数组和右侧子数组的大小 + int leftSize = middleIndex - leftIndex + 1; + int rightSize = rightIndex - middleIndex; + + // 创建左侧子数组和右侧子数组 + List leftList = List.generate(leftSize, (index) => 0); + List rightList = List.generate(rightSize, (index) => 0); + + // 将原始数组中的元素分别复制到左侧子数组和右侧子数组中 + for (int i = 0; i < leftSize; i++) { + leftList[i] = src[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = src[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + src[k] = leftList[i]; + i++; + } else { + src[k] = rightList[j]; + j++; + } + + await callback(src); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + src[k] = leftList[i]; + i++; + k++; + + await callback(src); + } + + while (j < rightSize) { + src[k] = rightList[j]; + j++; + k++; + + await callback(src); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await _mergeSort(src,callback,leftIndex, middleIndex); + await _mergeSort(src,callback,middleIndex + 1, rightIndex); + + await callback(src); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/oddEven.dart b/lib/v12/pages/sort/functions/oddEven.dart new file mode 100644 index 0000000..bd6f548 --- /dev/null +++ b/lib/v12/pages/sort/functions/oddEven.dart @@ -0,0 +1,37 @@ +import '../functions.dart'; + +///奇偶排序(Odd-Even Sort) +Future oddEvenSort(List src, SortCallback callback) async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= src.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (src[i] > src[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await callback(src); + } + } + + for (int i = 0; i <= src.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (src[i] > src[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + isSorted = false; + await callback(src); + } + } + } + + return; +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/pigeonHole.dart b/lib/v12/pages/sort/functions/pigeonHole.dart new file mode 100644 index 0000000..83bbce1 --- /dev/null +++ b/lib/v12/pages/sort/functions/pigeonHole.dart @@ -0,0 +1,33 @@ +import '../functions.dart'; + +///鸽巢排序 +Future pigeonHoleSort(List src, SortCallback callback ) async{ + int min = src[0]; + int max = src[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < src.length; a++) { + if (src[a] > max) max = src[a]; + if (src[a] < min) min = src[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < src.length; i++) { + p[src[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + src[index++] = j + min; + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/quick.dart b/lib/v12/pages/sort/functions/quick.dart new file mode 100644 index 0000000..25b9685 --- /dev/null +++ b/lib/v12/pages/sort/functions/quick.dart @@ -0,0 +1,65 @@ +import '../functions.dart'; + +//快速排序 +Future quickSort(List src, SortCallback callback) async { + await _quickSort(src,callback,0,src.length-1); +} + +///快速排序 +Future _quickSort(List src, SortCallback callback,int leftIndex,int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async { + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = src[p]; + src[p] = src[right]; + src[right] = temp; + await callback(src); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(src[i], src[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = src[i]; + src[i] = src[cursor]; + src[cursor] = temp; + cursor++; + await callback(src); + } + } + + // 将基准元素放置在游标位置 + temp = src[right]; + src[right] = src[cursor]; + src[cursor] = temp; + + await callback(src); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await _quickSort(src,callback,leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await _quickSort(src,callback, p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序 + } +} + +// 比较函数,用于判断两个元素的大小关系 +cf(int a, int b) { + if (a < b) { + return -1; // 若 a 小于 b,则返回 -1 + } else if (a > b) { + return 1; // 若 a 大于 b,则返回 1 + } else { + return 0; // 若 a 等于 b,则返回 0 + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/selection.dart b/lib/v12/pages/sort/functions/selection.dart new file mode 100644 index 0000000..185dae2 --- /dev/null +++ b/lib/v12/pages/sort/functions/selection.dart @@ -0,0 +1,18 @@ +import '../functions.dart'; + +///选择排序 +Future selectionSort(List src, SortCallback callback ) async { + for (int i = 0; i < src.length; i++) { + for (int j = i + 1; j < src.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (src[i] > src[j]) { + // 判断当前元素是否比后续元素小 + int temp = src[j]; + // 交换当前元素和后续较小的元素 + src[j] = src[i]; + src[i] = temp; + } + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/functions/shell.dart b/lib/v12/pages/sort/functions/shell.dart new file mode 100644 index 0000000..232f872 --- /dev/null +++ b/lib/v12/pages/sort/functions/shell.dart @@ -0,0 +1,21 @@ +import '../functions.dart'; + +///希尔排序 +Future shellSort(List src, SortCallback callback) async{ + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = src.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < src.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = src[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && src[j - gap] > temp; j -= gap) { + src[j] = src[j - gap]; + } + src[j] = temp; + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/provider/sort_config.dart b/lib/v12/pages/sort/provider/sort_config.dart new file mode 100644 index 0000000..1fc3790 --- /dev/null +++ b/lib/v12/pages/sort/provider/sort_config.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class SortConfig { + final int count; + final int seed; + final Duration duration; + final String name; + final int colorIndex; + + SortConfig({ + this.count = 100, + this.duration = const Duration(microseconds: 1500), + this.seed = -1, + this.colorIndex = 0, + this.name = 'insertion', + }); + + SortConfig copyWith({ + int? count, + int? seed, + int? colorIndex, + Duration? duration, + String? name, + }) => + SortConfig( + count:count??this.count, + seed:seed??this.seed, + duration:duration??this.duration, + name:name??this.name, + colorIndex:colorIndex??this.colorIndex, + ); +} + diff --git a/lib/v12/pages/sort/provider/state.dart b/lib/v12/pages/sort/provider/state.dart new file mode 100644 index 0000000..b594b1e --- /dev/null +++ b/lib/v12/pages/sort/provider/state.dart @@ -0,0 +1,105 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../functions.dart'; +import 'sort_config.dart'; + +enum SortStatus{ + none, // 未操作 + sorting, // 排序中 + sorted, // 排序完成 +} + +List kColorSupport = [ + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.red, + Colors.pink, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.indigo, + Colors.purple, + Colors.deepPurple, +]; + +class SortState with ChangeNotifier{ + + SortState(){ + reset(); + } + + SortStatus status = SortStatus.none; + + List data = []; + List stepData = []; + + SortConfig _config = SortConfig(); + SortConfig get config => _config; + Random random = Random(); + + set config(SortConfig config){ + _config = config; + reset(); + notifyListeners(); + } + + void selectName(String name){ + if(name==config.name) return; + config = config.copyWith(name: name); + } + + void selectColor(int colorIndex){ + if(colorIndex==config.colorIndex) return; + config = config.copyWith(colorIndex: colorIndex); + } + + void reset(){ + data.clear(); + status = SortStatus.none; + notifyListeners(); + int count = config.count; + if(config.seed!=-1){ + random = Random(config.seed); + } + for (int i = 0; i < count; i++) { + //随机往数组中填值 + data.add(random.nextInt(1000)); + } + } + + void sort() async{ + status = SortStatus.sorting; + notifyListeners(); + Stopwatch stopwatch = Stopwatch()..start(); + SortFunction? sortFunction = sortFunctionMap[config.name]; + if(sortFunction!=null){ + await sortFunction(data,(arr) async { + await Future.delayed(config.duration); + notifyListeners(); + }); + } + status = SortStatus.sorted; + notifyListeners(); + stopwatch.stop(); + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + } +} + +/// Provides the current [SortState] to descendant widgets in the tree. +class SortStateScope extends InheritedNotifier { + const SortStateScope({ + required super.notifier, + required super.child, + super.key, + }); + + static SortState of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!.notifier!; + + static SortState read(BuildContext context) { + return context.getInheritedWidgetOfExactType()!.notifier!; + } +} \ No newline at end of file diff --git a/lib/v12/pages/sort/views/code_page/code_page.dart b/lib/v12/pages/sort/views/code_page/code_page.dart new file mode 100644 index 0000000..7d8d4c9 --- /dev/null +++ b/lib/v12/pages/sort/views/code_page/code_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import '../../functions.dart'; +import '../../provider/state.dart'; + +class CodePage extends StatelessWidget { + const CodePage({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + leading: BackButton(), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + centerTitle: true, + title: Text(sortNameMap[state.config.name]!+'代码实现'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text('代码'*1000), + )); + } +} diff --git a/lib/v12/pages/sort/views/player/data_painter.dart b/lib/v12/pages/sort/views/player/data_painter.dart new file mode 100644 index 0000000..392a9e8 --- /dev/null +++ b/lib/v12/pages/sort/views/player/data_painter.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + + + +class DataPainter extends CustomPainter{ + + final List data; + final MaterialColor color; + + DataPainter( {required this.data,required this.color,}); + + @override + void paint(Canvas canvas, Size size) { + double itemWidth = size.width/data.length; + double height = size.height; + + Paint paint = Paint(); + paint.strokeWidth = itemWidth; + paint.strokeCap = StrokeCap.round; + + + for(int i=0;i numbers = state.data; + MaterialColor color = kColorSupport[state.config.colorIndex]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomPaint( + painter: DataPainter(data: numbers,color: color), + child: ConstrainedBox(constraints: const BoxConstraints.expand()), + ), + ); + } +} diff --git a/lib/v12/pages/sort/views/settings/color_picker.dart b/lib/v12/pages/sort/views/settings/color_picker.dart new file mode 100644 index 0000000..b0bb469 --- /dev/null +++ b/lib/v12/pages/sort/views/settings/color_picker.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class ColorPicker extends StatelessWidget { + final List colors; + final ValueChanged onSelected; + final int activeIndex; + + const ColorPicker({ + super.key, + required this.colors, + required this.activeIndex, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Wrap( + children: colors + .asMap() + .keys + .map((int index) => MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: ()=>onSelected(index), + child: Container( + width: 32, + height: 32, + color: colors[index], + child: activeIndex == index + ? const Icon( + Icons.check, + color: Colors.white, + ) + : null, + ), + ), + )) + .toList(), + ); + } +} diff --git a/lib/v12/pages/sort/views/settings/sort_setting.dart b/lib/v12/pages/sort/views/settings/sort_setting.dart new file mode 100644 index 0000000..fe86289 --- /dev/null +++ b/lib/v12/pages/sort/views/settings/sort_setting.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../../provider/state.dart'; +import 'color_picker.dart'; +class SortSettings extends StatefulWidget { + + const SortSettings({super.key,}); + + @override + State createState() => _SortSettingsState(); +} + +class _SortSettingsState extends State { + final TextEditingController _count = TextEditingController(); + final TextEditingController _duration = TextEditingController(); + final TextEditingController _seed = TextEditingController(); + int _colorIndex = 0; + + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + SortState state = SortStateScope.of(context); + _count.text = state.config.count.toString(); + _duration.text = state.config.duration.inMicroseconds.toString(); + _seed.text = state.config.seed.toString(); + _colorIndex = state.config.colorIndex; + } + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + automaticallyImplyLeading: false, + // leading: Align( + // child: MouseRegion( + // cursor: SystemMouseCursors.click, + // child: GestureDetector( + // onTap: (){ + // Navigator.of(context).pop(); + // }, + // child: Container( + // width: 28, + // height: 28, + // margin: EdgeInsets.only(right: 8,left: 8), + // alignment: Alignment.center, + // decoration: BoxDecoration( + // color: Color(0xffE3E5E7), + // borderRadius: BorderRadius.circular(6) + // ), + // child: Icon(Icons.arrow_back_ios_new,size: 18,)), + // ), + // ), + // ), + // leading: BackButton(), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + splashRadius: 20, + onPressed: (){ + + SortState state = SortStateScope.read(context); + state.config = state.config.copyWith( + count: int.parse(_count.text), + duration: Duration( + microseconds: int.parse(_duration.text), + ), + seed: int.parse(_seed.text), + colorIndex: _colorIndex + ); + GoRouter.of(context).pop(); + // Navigator.of(context).pop(); + }, icon: Icon(Icons.check)), + )], + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + centerTitle: true, + title: Text('排序算法配置'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + Row( + children: [ + Text('数据数量(个数):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _count, + )), + ], + ), + Row( + children: [ + Text('时间间隔(微秒):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _duration, + )), + ], + ), + Row( + children: [ + Text('随机种子:'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _seed, + )), + ], + ), + const SizedBox(height: 20,), + + Row( + children: [ + Text('选择颜色:'), + const SizedBox( + width: 20, + ), + Expanded( + child: ColorPicker( + colors: kColorSupport, + onSelected: (index){ + setState(() { + _colorIndex = index; + }); + }, + activeIndex: _colorIndex, + ),), + ], + ), + + Spacer(), + // ElevatedButton( + // onPressed: () { + // SortState state = SortStateScope.of(context); + // state.config =state.config.copyWith( + // count: int.parse(_count.text), + // duration: Duration( + // microseconds: int.parse(_duration.text), + // ), + // seed: int.parse(_seed.text) + // ); + // Navigator.of(context).pop(); + // }, + // child: Text('确定设置')) + ], + ), + ), + ); + } +} diff --git a/lib/v12/pages/sort/views/sort_page/sort_button.dart b/lib/v12/pages/sort/views/sort_page/sort_button.dart new file mode 100644 index 0000000..93df9d9 --- /dev/null +++ b/lib/v12/pages/sort/views/sort_page/sort_button.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../provider/state.dart'; + +class SortButton extends StatelessWidget { + const SortButton({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + VoidCallback? action; + IconData icon; + String text = ''; + Color color; + switch (state.status) { + case SortStatus.none: + icon = Icons.not_started_outlined; + color = Colors.green; + action = state.sort; + text = '点击启动'; + break; + case SortStatus.sorting: + icon = Icons.stop_circle_outlined; + color = Colors.grey; + action = null; + text = '排序中...'; + break; + case SortStatus.sorted: + icon = CupertinoIcons.repeat; + color = Colors.black; + action = state.reset; + text = '点击重置'; + break; + } + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: action, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon( + icon, + color: color, + size: 18, + ), + const SizedBox(width: 4,), + Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),), + ], + ), + ), + ); + } +} diff --git a/lib/v12/pages/sort/views/sort_page/sort_page.dart b/lib/v12/pages/sort/views/sort_page/sort_page.dart new file mode 100644 index 0000000..6631bff --- /dev/null +++ b/lib/v12/pages/sort/views/sort_page/sort_page.dart @@ -0,0 +1,166 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'sort_button.dart'; + +import '../../functions.dart'; +import '../../provider/state.dart'; + +class SortNavigation extends StatelessWidget { + final Widget navigator; + const SortNavigation({super.key, required this.navigator}); + + @override + Widget build(BuildContext context) { + return Material( + child: Row( + children: [ + SizedBox( + width: 220, + child: SortRailPanel(), + ), + VerticalDivider( + width: 1, + ), + Expanded( + child: navigator, + ) + ], + ), + ); + } +} + +class SortRailPanel extends StatelessWidget { + const SortRailPanel({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + const SortButton(), + const Spacer(), + const MouseRegion( + cursor: SystemMouseCursors.click, + child: Icon( + CupertinoIcons.chevron_left_slash_chevron_right, + size: 18, + ), + ), + const SizedBox( + width: 8, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + GoRouter.of(context).push('/sort/settings'); + // router.changePath('/app/sort/settings',style: RouteStyle.push); + }, + child: const Icon( + CupertinoIcons.settings, + size: 18, + )), + ), + ], + ), + ), + const Divider( + height: 1, + ), + Expanded( + child: SortSelectorPanel( + active: state.config.name, + options: sortNameMap.values.toList(), + onSelected: (name) { + state.selectName(name); + GoRouter.of(context).go('/sort/player/$name'); + }, + ), + ), + ], + ); + } +} + +class SortSelectorPanel extends StatelessWidget { + final String active; + final ValueChanged onSelected; + final List options; + + const SortSelectorPanel( + {super.key, + required this.active, + required this.options, + required this.onSelected}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemExtent: 46, + itemCount: sortNameMap.length, + itemBuilder: _buildByIndex, + ); + } + + Widget? _buildByIndex(BuildContext context, int index) { + String key = sortNameMap.keys.toList()[index]; + bool selected = sortNameMap.keys.toList()[index] == active; + return SortItemTile( + selected: selected, + onTap: () => onSelected(key), + title: options[index], + ); + } +} + +class SortItemTile extends StatefulWidget { + final String title; + final VoidCallback onTap; + final bool selected; + + const SortItemTile( + {super.key, + required this.title, + required this.selected, + required this.onTap}); + + @override + State createState() => _SortItemTileState(); +} + +class _SortItemTileState extends State { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: widget.selected ? const Color(0xffE6F0FF) : null), + padding: const EdgeInsets.only(left: 12), + alignment: Alignment.centerLeft, + child: Text( + widget.title, + style: TextStyle( + fontSize: 14, + fontWeight: widget.selected ? FontWeight.bold : null), + ), + ), + ), + ), + ); + } +} diff --git a/lib/v12/pages/user/user_page.dart b/lib/v12/pages/user/user_page.dart new file mode 100644 index 0000000..e6cfbec --- /dev/null +++ b/lib/v12/pages/user/user_page.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:iroute/v12/app/authentication/auth_scope.dart'; + +import '../login/logout_button.dart'; + +class UserPage extends StatelessWidget { + const UserPage({super.key}); + + @override + Widget build(BuildContext context) { + String? name = AuthScope.of(context).name; + int? coin = AuthScope.of(context).coin; + return Scaffold( + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0,vertical: 12), + child: Row( + children: [ + CircleAvatar( + child: FlutterLogo(), + ), + const SizedBox(width: 12,), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('$name',style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold),), + const SizedBox(height: 4,), + Text('金币: ${coin}',style: TextStyle(fontSize: 12,color: Colors.grey,),) + ], + ), + Spacer(), + LogoutButton( + onLogout: () async { + bool success = await AuthScope.of(context).logout(); + if(success){ + context.go('/'); + } + }, + ) + ], + ), + ) + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 947280d..136624c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: flutter_colorpicker: ^1.0.3 window_manager: ^0.3.7 adaptive_navigation: ^0.0.4 - go_router: ^12.0.0 + go_router: ^12.1.1 provider: 6.0.5 url_launcher: ^6.0.7 equatable: ^2.0.5