From 6e8d9266938fe356097cae201584ece847b9d4f4 Mon Sep 17 00:00:00 2001 From: toly <1981462002@qq.com> Date: Mon, 20 Nov 2023 20:05:05 +0800 Subject: [PATCH] v10 --- lib/components/windows/drag_to_move_area.dart | 2 + lib/main.dart | 7 +- .../router/app_router_delegate.dart | 34 ++-- .../navigation/router/app_router_parser.dart | 19 +- lib/v10/app/navigation/router/iroute.dart | 3 +- .../navigation/views/app_navigation_rail.dart | 8 +- lib/v10/app/unit_app.dart | 51 +++-- lib/v11/app.dart | 1 + .../router/app_router_delegate.dart | 184 ++++++++++++++++++ .../navigation/router/app_router_parser.dart | 22 +++ lib/v11/app/navigation/router/iroute.dart | 156 +++++++++++++++ .../app/navigation/router/iroute_config.dart | 71 +++++++ .../router/route_history_manager.dart | 47 +++++ lib/v11/app/navigation/router/routes.dart | 95 +++++++++ .../router/views/navigator_scope.dart | 109 +++++++++++ .../router/views/not_find_view.dart | 25 +++ .../router/views/route_back_indicator.dart | 52 +++++ .../fade_page_transitions_builder.dart | 43 ++++ .../transition/fade_transition_page.dart | 52 +++++ .../transition/no_transition_page.dart | 47 +++++ .../app/navigation/views/app_navigation.dart | 51 +++++ .../navigation/views/app_navigation_rail.dart | 80 ++++++++ .../views/app_top_bar/app_router_editor.dart | 64 ++++++ .../views/app_top_bar/app_top_bar.dart | 107 ++++++++++ .../views/app_top_bar/history_view_icon.dart | 158 +++++++++++++++ .../app_top_bar/route_history_button.dart | 58 ++++++ lib/v11/app/unit_app.dart | 43 ++++ lib/v11/pages/color/color_add_page.dart | 99 ++++++++++ lib/v11/pages/color/color_detail_page.dart | 25 +++ lib/v11/pages/color/color_page.dart | 65 +++++++ lib/v11/pages/counter/counter_page.dart | 55 ++++++ lib/v11/pages/empty/empty_page.dart | 30 +++ lib/v11/pages/login/login.dart | 24 +++ lib/v11/pages/settings/settings_page.dart | 11 ++ lib/v11/pages/sort/functions.dart | 46 +++++ lib/v11/pages/sort/functions/bubble.dart | 19 ++ lib/v11/pages/sort/functions/cocktail.dart | 52 +++++ lib/v11/pages/sort/functions/comb.dart | 34 ++++ lib/v11/pages/sort/functions/cycle.dart | 54 +++++ lib/v11/pages/sort/functions/gnome.dart | 22 +++ lib/v11/pages/sort/functions/heap.dart | 38 ++++ lib/v11/pages/sort/functions/insertion.dart | 19 ++ lib/v11/pages/sort/functions/merage.dart | 79 ++++++++ lib/v11/pages/sort/functions/oddEven.dart | 37 ++++ lib/v11/pages/sort/functions/pigeonHole.dart | 33 ++++ lib/v11/pages/sort/functions/quick.dart | 65 +++++++ lib/v11/pages/sort/functions/selection.dart | 18 ++ lib/v11/pages/sort/functions/shell.dart | 21 ++ lib/v11/pages/sort/provider/sort_config.dart | 35 ++++ lib/v11/pages/sort/provider/state.dart | 101 ++++++++++ .../pages/sort/views/code_page/code_page.dart | 29 +++ .../pages/sort/views/player/data_painter.dart | 62 ++++++ .../pages/sort/views/player/sort_player.dart | 22 +++ .../sort/views/settings/color_picker.dart | 41 ++++ .../sort/views/settings/sort_setting.dart | 170 ++++++++++++++++ .../sort/views/sort_page/sort_button.dart | 57 ++++++ .../pages/sort/views/sort_page/sort_page.dart | 166 ++++++++++++++++ lib/v11/pages/user/user_page.dart | 11 ++ pubspec.lock | 4 +- test/tree/meter_topology_result_1.json | 16 ++ test/tree/node.dart | 23 +++ 61 files changed, 3107 insertions(+), 65 deletions(-) create mode 100644 lib/v11/app.dart create mode 100644 lib/v11/app/navigation/router/app_router_delegate.dart create mode 100644 lib/v11/app/navigation/router/app_router_parser.dart create mode 100644 lib/v11/app/navigation/router/iroute.dart create mode 100644 lib/v11/app/navigation/router/iroute_config.dart create mode 100644 lib/v11/app/navigation/router/route_history_manager.dart create mode 100644 lib/v11/app/navigation/router/routes.dart create mode 100644 lib/v11/app/navigation/router/views/navigator_scope.dart create mode 100644 lib/v11/app/navigation/router/views/not_find_view.dart create mode 100644 lib/v11/app/navigation/router/views/route_back_indicator.dart create mode 100644 lib/v11/app/navigation/transition/fade_page_transitions_builder.dart create mode 100644 lib/v11/app/navigation/transition/fade_transition_page.dart create mode 100644 lib/v11/app/navigation/transition/no_transition_page.dart create mode 100644 lib/v11/app/navigation/views/app_navigation.dart create mode 100644 lib/v11/app/navigation/views/app_navigation_rail.dart create mode 100644 lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart create mode 100644 lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart create mode 100644 lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart create mode 100644 lib/v11/app/navigation/views/app_top_bar/route_history_button.dart create mode 100644 lib/v11/app/unit_app.dart create mode 100644 lib/v11/pages/color/color_add_page.dart create mode 100644 lib/v11/pages/color/color_detail_page.dart create mode 100644 lib/v11/pages/color/color_page.dart create mode 100644 lib/v11/pages/counter/counter_page.dart create mode 100644 lib/v11/pages/empty/empty_page.dart create mode 100644 lib/v11/pages/login/login.dart create mode 100644 lib/v11/pages/settings/settings_page.dart create mode 100644 lib/v11/pages/sort/functions.dart create mode 100644 lib/v11/pages/sort/functions/bubble.dart create mode 100644 lib/v11/pages/sort/functions/cocktail.dart create mode 100644 lib/v11/pages/sort/functions/comb.dart create mode 100644 lib/v11/pages/sort/functions/cycle.dart create mode 100644 lib/v11/pages/sort/functions/gnome.dart create mode 100644 lib/v11/pages/sort/functions/heap.dart create mode 100644 lib/v11/pages/sort/functions/insertion.dart create mode 100644 lib/v11/pages/sort/functions/merage.dart create mode 100644 lib/v11/pages/sort/functions/oddEven.dart create mode 100644 lib/v11/pages/sort/functions/pigeonHole.dart create mode 100644 lib/v11/pages/sort/functions/quick.dart create mode 100644 lib/v11/pages/sort/functions/selection.dart create mode 100644 lib/v11/pages/sort/functions/shell.dart create mode 100644 lib/v11/pages/sort/provider/sort_config.dart create mode 100644 lib/v11/pages/sort/provider/state.dart create mode 100644 lib/v11/pages/sort/views/code_page/code_page.dart create mode 100644 lib/v11/pages/sort/views/player/data_painter.dart create mode 100644 lib/v11/pages/sort/views/player/sort_player.dart create mode 100644 lib/v11/pages/sort/views/settings/color_picker.dart create mode 100644 lib/v11/pages/sort/views/settings/sort_setting.dart create mode 100644 lib/v11/pages/sort/views/sort_page/sort_button.dart create mode 100644 lib/v11/pages/sort/views/sort_page/sort_page.dart create mode 100644 lib/v11/pages/user/user_page.dart create mode 100644 test/tree/meter_topology_result_1.json diff --git a/lib/components/windows/drag_to_move_area.dart b/lib/components/windows/drag_to_move_area.dart index 01ab98d..e04f755 100644 --- a/lib/components/windows/drag_to_move_area.dart +++ b/lib/components/windows/drag_to_move_area.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; @@ -11,6 +12,7 @@ class DragToMoveWrap extends StatelessWidget { @override Widget build(BuildContext context) { + if(kIsWeb) return child; return GestureDetector( behavior: HitTestBehavior.translucent, onPanStart: (details) { diff --git a/lib/main.dart b/lib/main.dart index e4d67e9..38ea808 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; -import 'v8/app.dart'; +import 'v10/app.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -14,8 +14,7 @@ void main() { void setSize() async{ - - if(Platform.isWindows||Platform.isWindows||Platform.isLinux){ + if(kIsWeb||Platform.isAndroid||Platform.isIOS) return; await windowManager.ensureInitialized(); WindowOptions windowOptions = const WindowOptions( size: Size(800, 540), @@ -29,6 +28,4 @@ void setSize() async{ await windowManager.show(); await windowManager.focus(); }); - } - } diff --git a/lib/v10/app/navigation/router/app_router_delegate.dart b/lib/v10/app/navigation/router/app_router_delegate.dart index 79e979e..cdc38e0 100644 --- a/lib/v10/app/navigation/router/app_router_delegate.dart +++ b/lib/v10/app/navigation/router/app_router_delegate.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'views/navigator_scope.dart'; import 'iroute.dart'; @@ -7,13 +8,10 @@ import 'route_history_manager.dart'; import 'routes.dart'; import 'views/not_find_view.dart'; -AppRouterDelegate router = AppRouterDelegate( - initial: IRouteConfig( - uri: Uri.parse('/app/color'), - ), - node: appRoute); +AppRouterDelegate router = AppRouterDelegate(node: rootRoute); -class AppRouterDelegate extends RouterDelegate with ChangeNotifier { +class AppRouterDelegate extends RouterDelegate + with ChangeNotifier { /// 核心数据,路由配置数据列表 final List _configs = []; @@ -25,18 +23,16 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier final IRouteNode node; - @override - IRouteConfig get currentConfiguration => current; + IRouteConfig? get currentConfiguration { + if(_configs.isEmpty) return null; + return current; + } AppRouterDelegate({ this.notFindPageBuilder, required this.node, - required IRouteConfig initial, - }) { - _configs.add(initial); - _historyManager.recode(initial); - } + }); Page _defaultNotFindPageBuilder(_, __) => const MaterialPage( child: Material(child: NotFindPage()), @@ -84,6 +80,7 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier } notifyListeners(); + if (config.forResult) { return _completerMap[value]!.future; } @@ -174,5 +171,14 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier } @override - Future setNewRoutePath(configuration) async {} + 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/v10/app/navigation/router/app_router_parser.dart b/lib/v10/app/navigation/router/app_router_parser.dart index bae668e..a2f11db 100644 --- a/lib/v10/app/navigation/router/app_router_parser.dart +++ b/lib/v10/app/navigation/router/app_router_parser.dart @@ -1,22 +1,19 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'iroute_config.dart'; -class AppRouterParser extends RouteInformationParser{ - - - - +class AppRouterParser extends RouteInformationParser { @override RouteInformation restoreRouteInformation(IRouteConfig configuration) { - return RouteInformation( - uri: configuration.uri - ); + return RouteInformation(uri: configuration.uri); } @override Future parseRouteInformationWithDependencies( - RouteInformation routeInformation, BuildContext context) async{ - return IRouteConfig(uri: routeInformation.uri); + RouteInformation routeInformation, + BuildContext context, + ) { + return SynchronousFuture(IRouteConfig(uri: routeInformation.uri)); } -} \ No newline at end of file +} diff --git a/lib/v10/app/navigation/router/iroute.dart b/lib/v10/app/navigation/router/iroute.dart index dffa34b..ddfc8c3 100644 --- a/lib/v10/app/navigation/router/iroute.dart +++ b/lib/v10/app/navigation/router/iroute.dart @@ -55,8 +55,7 @@ abstract class IRouteNode { if (node.children.isNotEmpty) { target = prefix + target; - List nodes = - node.children.where((e) => e.path == target).toList(); + List nodes = node.children.where((e) => e.path == target).toList(); bool match = nodes.isNotEmpty; if (match) { IRouteNode matched = nodes.first; diff --git a/lib/v10/app/navigation/views/app_navigation_rail.dart b/lib/v10/app/navigation/views/app_navigation_rail.dart index 09b54e8..29eec05 100644 --- a/lib/v10/app/navigation/views/app_navigation_rail.dart +++ b/lib/v10/app/navigation/views/app_navigation_rail.dart @@ -37,7 +37,7 @@ class _AppNavigationRailState extends State { tail: Padding( padding: const EdgeInsets.only(bottom: 6.0), child: Text( - 'V0.0.9', + 'V0.0.10', style: TextStyle(color: Colors.white, fontSize: 12), ), ), @@ -63,14 +63,14 @@ class _AppNavigationRailState extends State { void _onDestinationSelected(int index) { String path = deskNavBarMenus[index].path!; if (index == 1) { - router.changePath(path, keepAlive: true); + router.changePath(path, keepAlive: true,recordHistory: true); return; } if (index == 4) { - router.changePath(path, style: RouteStyle.push); + router.changePath(path, style: RouteStyle.push,recordHistory: true); return; } else { - router.changePath(path); + router.changePath(path,recordHistory: true); } } diff --git a/lib/v10/app/unit_app.dart b/lib/v10/app/unit_app.dart index 779f182..7e4acb0 100644 --- a/lib/v10/app/unit_app.dart +++ b/lib/v10/app/unit_app.dart @@ -4,43 +4,40 @@ import 'navigation/router/app_router_delegate.dart'; import '../pages/sort/provider/state.dart'; import 'navigation/transition/fade_page_transitions_builder.dart'; - class UnitApp extends StatelessWidget { const UnitApp({super.key}); @override Widget build(BuildContext context) { - return SortStateScope( notifier: SortState(), child: MaterialApp.router( - routerDelegate: router, - routeInformationParser: AppRouterParser(), - theme: ThemeData( + routerDelegate: router, + routeInformationParser: AppRouterParser(), + routeInformationProvider: PlatformRouteInformationProvider( + initialRouteInformation: RouteInformation(uri: Uri.parse('/app/color')), + ), + 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() + 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/app.dart b/lib/v11/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v11/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v11/app/navigation/router/app_router_delegate.dart b/lib/v11/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..cdc38e0 --- /dev/null +++ b/lib/v11/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,184 @@ +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.path; + + 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 new file mode 100644 index 0000000..3b40f88 --- /dev/null +++ b/lib/v11/app/navigation/router/app_router_parser.dart @@ -0,0 +1,22 @@ +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/iroute.dart b/lib/v11/app/navigation/router/iroute.dart new file mode 100644 index 0000000..ddfc8c3 --- /dev/null +++ b/lib/v11/app/navigation/router/iroute.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/v9/app/navigation/router/views/navigator_scope.dart'; + +import 'iroute_config.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) => 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; + } +} + +/// 优先调用 [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); + } + print("======CellIRoute#createCellPage${config.pageKey}================="); + 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 new file mode 100644 index 0000000..f526e71 --- /dev/null +++ b/lib/v11/app/navigation/router/iroute_config.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +enum RouteStyle{ + push, + replace, +} + + +class IRouteConfig { + final Object? extra; + final bool forResult; + final Uri uri; + final bool keepAlive; + final RouteStyle routeStyle; + final bool recordHistory; + + const IRouteConfig({ + this.extra, + required this.uri, + this.forResult = false, + this.keepAlive = false, + this.routeStyle = RouteStyle.replace, + this.recordHistory = false, + }); + + String get path => uri.path; + + 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/route_history_manager.dart b/lib/v11/app/navigation/router/route_history_manager.dart new file mode 100644 index 0000000..8844898 --- /dev/null +++ b/lib/v11/app/navigation/router/route_history_manager.dart @@ -0,0 +1,47 @@ +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 new file mode 100644 index 0000000..356f8b2 --- /dev/null +++ b/lib/v11/app/navigation/router/routes.dart @@ -0,0 +1,95 @@ +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', + 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/player': '演示', + '/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/views/navigator_scope.dart b/lib/v11/app/navigation/router/views/navigator_scope.dart new file mode 100644 index 0000000..be7b1d8 --- /dev/null +++ b/lib/v11/app/navigation/router/views/navigator_scope.dart @@ -0,0 +1,109 @@ +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/router/views/not_find_view.dart b/lib/v11/app/navigation/router/views/not_find_view.dart new file mode 100644 index 0000000..ccefa16 --- /dev/null +++ b/lib/v11/app/navigation/router/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/v11/app/navigation/router/views/route_back_indicator.dart b/lib/v11/app/navigation/router/views/route_back_indicator.dart new file mode 100644 index 0000000..5be781a --- /dev/null +++ b/lib/v11/app/navigation/router/views/route_back_indicator.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import '../app_router_delegate.dart'; +class RouteBackIndicator extends StatefulWidget { + const RouteBackIndicator({super.key}); + + @override + State createState() => _RouteBackIndicatorState(); +} + +class _RouteBackIndicatorState extends State { + + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if(router.canPop){ + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: router.backStack, + child: Container( + width: 26, + height: 26, + margin: EdgeInsets.only(right: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffE3E5E7), + borderRadius: BorderRadius.circular(6) + ), + child: Icon(Icons.arrow_back_ios_new,size: 14,)), + ), + ); + } + return SizedBox(); + } + + void _onChange() { + setState(() { + + }); + } +} diff --git a/lib/v11/app/navigation/transition/fade_page_transitions_builder.dart b/lib/v11/app/navigation/transition/fade_page_transitions_builder.dart new file mode 100644 index 0000000..0587ecc --- /dev/null +++ b/lib/v11/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/v11/app/navigation/transition/fade_transition_page.dart b/lib/v11/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..fc4e6e6 --- /dev/null +++ b/lib/v11/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/v11/app/navigation/transition/no_transition_page.dart b/lib/v11/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v11/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/v11/app/navigation/views/app_navigation.dart b/lib/v11/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..9360abf --- /dev/null +++ b/lib/v11/app/navigation/views/app_navigation.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import '../router/app_router_delegate.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/v11/app/navigation/views/app_navigation_rail.dart b/lib/v11/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..29eec05 --- /dev/null +++ b/lib/v11/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; +import '../router/iroute_config.dart'; +import '../router/routes.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + + @override + void initState() { + super.initState(); + router.addListener(_onRouterChange); + } + + @override + void dispose() { + router.removeListener(_onRouterChange); + super.dispose(); + } + + @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.0.10', + style: TextStyle(color: Colors.white, fontSize: 12), + ), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: activeIndex, + ), + ); + } + + RegExp _segReg = RegExp(r'/app/\w+'); + + int? get activeIndex { + String path = router.path; + 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 == 1) { + router.changePath(path, keepAlive: true,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 new file mode 100644 index 0000000..40516a7 --- /dev/null +++ b/lib/v11/app/navigation/views/app_top_bar/app_router_editor.dart @@ -0,0 +1,64 @@ +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'; + +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(); + + + @override + void initState() { + super.initState(); + _onRouteChange(); + router.addListener(_onRouteChange); + } + + void _onRouteChange() { + _controller.text=router.path; + } + + @override + void dispose() { + _controller.dispose(); + router.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/v11/app/navigation/views/app_top_bar/app_top_bar.dart b/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart new file mode 100644 index 0000000..150ac6b --- /dev/null +++ b/lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../../router/app_router_delegate.dart'; +import '../../router/routes.dart'; +import '../../router/views/route_back_indicator.dart'; +import 'app_router_editor.dart'; +import 'history_view_icon.dart'; +import 'route_history_button.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(), + 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(), + const Padding( + padding: EdgeInsets.symmetric(vertical: 12.0), + child: VerticalDivider( + width: 32, + ), + ) + ])), + const WindowButtons() + ], + ), + ), + ); + } +} + +class RouterIndicator extends StatefulWidget { + const RouterIndicator({super.key}); + + @override + State createState() => _RouterIndicatorState(); +} + + +class _RouterIndicatorState extends State { + @override + void initState() { + super.initState(); + router.addListener(_onRouterChange); + } + + @override + void dispose() { + router.removeListener(_onRouterChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TolyBreadcrumb( + items: pathToBreadcrumbItems(router.path), + onTapItem: (item) { + if (item.to != null) { + router.changePath(item.to!); + } + }, + ); + } + + void _onRouterChange() { + setState(() {}); + } + + List pathToBreadcrumbItems(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 = kRouteLabelMap[to] ?? '未知路由'; + if(label.isNotEmpty){ + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + } + return result; + } +} 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 new file mode 100644 index 0000000..e2ad492 --- /dev/null +++ b/lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../../router/app_router_delegate.dart'; +import '../../router/routes.dart'; +import '../../router/iroute_config.dart'; +import 'app_top_bar.dart'; + +class HistoryViewIcon extends StatelessWidget{ + const HistoryViewIcon({super.key}); + + @override + Widget build(BuildContext context) { + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: PopPanel( + offset: const Offset(0, 10), + panel: SizedBox( + height: 350, + child: Column( + children: [ + _buildTopBar(), + const Expanded( + child:HistoryPanel(), + ), + ], + ), + ), + child: const Icon( + Icons.history, + size: 20, + ), + ), + ); + } + + Widget _buildTopBar() { + 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: router.clearHistory, child: const Text('清空历史')) + ], + )); + } +} + +class HistoryItem extends StatefulWidget { + final IRouteConfig 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) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onPressed, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.history.path), + const SizedBox( + height: 2, + ), + Text(kRouteLabelMap[widget.history.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 { + + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List histories = router.historyManager.histories; + 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: (){ + int fixIndex = histories.length - 1 - index; + router.closeHistory(fixIndex); + }, + onPressed: (){ + router.changeRoute(histories[index].copyWith(recordHistory: false)); + Navigator.of(context).pop(); + }, + history: histories[index]), + ); + } + + void _onChange() { + setState(() {}); + } +} 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 new file mode 100644 index 0000000..3ee6513 --- /dev/null +++ b/lib/v11/app/navigation/views/app_top_bar/route_history_button.dart @@ -0,0 +1,58 @@ +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'; + +class RouteHistoryButton extends StatefulWidget { + const RouteHistoryButton({super.key}); + + @override + State createState() => _RouteHistoryButtonState(); +} + +class _RouteHistoryButtonState extends State { + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool hasHistory = router.historyManager.hasHistory; + bool hasBackHistory = router.historyManager.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?router.back:null, + ), + const SizedBox(width: 8,), + HoverIconButton( + size: 20, + hoverColor: backHistoryColor, + defaultColor: backHistoryColor, + icon: CupertinoIcons.arrow_right_circle, + onPressed: hasBackHistory?router.revocation:null, + ), + ], + ); + } + + void _onChange() { + setState(() {}); + } +} diff --git a/lib/v11/app/unit_app.dart b/lib/v11/app/unit_app.dart new file mode 100644 index 0000000..7e4acb0 --- /dev/null +++ b/lib/v11/app/unit_app.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'navigation/router/app_router_parser.dart'; +import 'navigation/router/app_router_delegate.dart'; +import '../pages/sort/provider/state.dart'; +import 'navigation/transition/fade_page_transitions_builder.dart'; + +class UnitApp extends StatelessWidget { + const UnitApp({super.key}); + + @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')), + ), + 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_add_page.dart b/lib/v11/pages/color/color_add_page.dart new file mode 100644 index 0000000..48e6dc6 --- /dev/null +++ b/lib/v11/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/v11/pages/color/color_detail_page.dart b/lib/v11/pages/color/color_detail_page.dart new file mode 100644 index 0000000..7dfed86 --- /dev/null +++ b/lib/v11/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/v11/pages/color/color_page.dart b/lib/v11/pages/color/color_page.dart new file mode 100644 index 0000000..644005e --- /dev/null +++ b/lib/v11/pages/color/color_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.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}); + + @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); + // router.path = '/color/detail?color=$value'; + router.changePath('/app/color/detail',extra: color); + + } + + void _toAddPage() async { + Color? color = await router.changePath('/app/color/add',forResult: true,recordHistory: false); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v11/pages/counter/counter_page.dart b/lib/v11/pages/counter/counter_page.dart new file mode 100644 index 0000000..b74a199 --- /dev/null +++ b/lib/v11/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/v11/pages/empty/empty_page.dart b/lib/v11/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v11/pages/empty/empty_page.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class EmptyPage extends StatelessWidget { + const EmptyPage({super.key}); + + @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, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.grey), + Text( + '404 界面丢失', + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/v11/pages/login/login.dart b/lib/v11/pages/login/login.dart new file mode 100644 index 0000000..af1568a --- /dev/null +++ b/lib/v11/pages/login/login.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import '../../app/navigation/router/app_router_delegate.dart'; + +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Login Page',style: TextStyle(fontSize: 24),), + const SizedBox(height: 20,), + ElevatedButton(onPressed: (){ + router.changePath('/app/color'); + }, child: Text('点击进入')) + ], + ), + ), + ); + } +} diff --git a/lib/v11/pages/settings/settings_page.dart b/lib/v11/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions.dart b/lib/v11/pages/sort/functions.dart new file mode 100644 index 0000000..6ae1176 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/bubble.dart b/lib/v11/pages/sort/functions/bubble.dart new file mode 100644 index 0000000..3cf3c99 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/cocktail.dart b/lib/v11/pages/sort/functions/cocktail.dart new file mode 100644 index 0000000..8c2d18c --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/comb.dart b/lib/v11/pages/sort/functions/comb.dart new file mode 100644 index 0000000..821f4a9 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/cycle.dart b/lib/v11/pages/sort/functions/cycle.dart new file mode 100644 index 0000000..4bef6eb --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/gnome.dart b/lib/v11/pages/sort/functions/gnome.dart new file mode 100644 index 0000000..5e08fc3 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/heap.dart b/lib/v11/pages/sort/functions/heap.dart new file mode 100644 index 0000000..9f5410b --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/insertion.dart b/lib/v11/pages/sort/functions/insertion.dart new file mode 100644 index 0000000..b1c7814 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/merage.dart b/lib/v11/pages/sort/functions/merage.dart new file mode 100644 index 0000000..12135b4 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/oddEven.dart b/lib/v11/pages/sort/functions/oddEven.dart new file mode 100644 index 0000000..bd6f548 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/pigeonHole.dart b/lib/v11/pages/sort/functions/pigeonHole.dart new file mode 100644 index 0000000..83bbce1 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/quick.dart b/lib/v11/pages/sort/functions/quick.dart new file mode 100644 index 0000000..25b9685 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/selection.dart b/lib/v11/pages/sort/functions/selection.dart new file mode 100644 index 0000000..185dae2 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/functions/shell.dart b/lib/v11/pages/sort/functions/shell.dart new file mode 100644 index 0000000..232f872 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/provider/sort_config.dart b/lib/v11/pages/sort/provider/sort_config.dart new file mode 100644 index 0000000..1fc3790 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/provider/state.dart b/lib/v11/pages/sort/provider/state.dart new file mode 100644 index 0000000..1e79b05 --- /dev/null +++ b/lib/v11/pages/sort/provider/state.dart @@ -0,0 +1,101 @@ +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!; +} \ No newline at end of file diff --git a/lib/v11/pages/sort/views/code_page/code_page.dart b/lib/v11/pages/sort/views/code_page/code_page.dart new file mode 100644 index 0000000..7d8d4c9 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/views/player/data_painter.dart b/lib/v11/pages/sort/views/player/data_painter.dart new file mode 100644 index 0000000..392a9e8 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/views/settings/color_picker.dart b/lib/v11/pages/sort/views/settings/color_picker.dart new file mode 100644 index 0000000..b0bb469 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/views/settings/sort_setting.dart b/lib/v11/pages/sort/views/settings/sort_setting.dart new file mode 100644 index 0000000..84f5f52 --- /dev/null +++ b/lib/v11/pages/sort/views/settings/sort_setting.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.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.of(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 + ); + 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/v11/pages/sort/views/sort_page/sort_button.dart b/lib/v11/pages/sort/views/sort_page/sort_button.dart new file mode 100644 index 0000000..93df9d9 --- /dev/null +++ b/lib/v11/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/v11/pages/sort/views/sort_page/sort_page.dart b/lib/v11/pages/sort/views/sort_page/sort_page.dart new file mode 100644 index 0000000..f865e77 --- /dev/null +++ b/lib/v11/pages/sort/views/sort_page/sort_page.dart @@ -0,0 +1,166 @@ + +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 '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: () { + 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); + router.changePath('/app/sort/player'); + }, + ), + ), + ], + ); + } +} + +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/v11/pages/user/user_page.dart b/lib/v11/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v11/pages/user/user_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +class UserPage extends StatelessWidget { + const UserPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body:Center(child: Text('UserPage'))); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9cee4ac..5efc459 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -116,10 +116,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: a206cc4621a644531a2e05e7774616ab4d9d85eab1f3b0e255f3102937fccab1 + sha256: c247a4f76071c3b97bb5ae8912968870d5565644801c5e09f3bc961b4d874895 url: "https://pub.flutter-io.cn" source: hosted - version: "12.0.0" + version: "12.1.1" lints: dependency: transitive description: diff --git a/test/tree/meter_topology_result_1.json b/test/tree/meter_topology_result_1.json new file mode 100644 index 0000000..4d8f5b8 --- /dev/null +++ b/test/tree/meter_topology_result_1.json @@ -0,0 +1,16 @@ +{ + "set_error_type": [], + "phase_type": [], + "hierarchy": { + "5": [ + { + "meter_id": "180001507008", + "error_type": null, + "is_ltu": true, + "addr": "江西省宜春市袁州区温汤镇社埠村社布台区", + "phase": "0" + } + ] + }, + "meter_set": [] +} \ No newline at end of file diff --git a/test/tree/node.dart b/test/tree/node.dart index a4312ea..7c7a175 100644 --- a/test/tree/node.dart +++ b/test/tree/node.dart @@ -1,3 +1,26 @@ +class A{ + final T data; + + + A(this.data); + + void printName(){ + print(data.name); + } +} + +mixin NameAble{ + String get name; +} + +class B with NameAble{ + @override + String get name => 'B'; + +} + + + class Node { final String value; final List children;