diff --git a/lib/components/toly_ui/popable/drop_selectable_widget.dart b/lib/components/toly_ui/popable/drop_selectable_widget.dart index da8e4e8..daa115a 100644 --- a/lib/components/toly_ui/popable/drop_selectable_widget.dart +++ b/lib/components/toly_ui/popable/drop_selectable_widget.dart @@ -13,6 +13,7 @@ class DropSelectableWidget extends StatefulWidget { final double height; final double width; final double fontSize; + final String value; const DropSelectableWidget( {Key? key, @@ -20,6 +21,7 @@ class DropSelectableWidget extends StatefulWidget { this.onDropSelected, this.disableColor = Colors.black, this.iconSize = 24, + required this.value, this.height = 30, this.width = 200, this.fontSize = 14, @@ -40,7 +42,7 @@ class _DropSelectableWidgetState extends State late Animation animation; final LayerLink layerLink = LayerLink(); - int _selectedIndex = 0; + // int _selectedIndex = 0; @override void initState() { @@ -80,25 +82,31 @@ class _DropSelectableWidgetState extends State @override Widget build(BuildContext context) { _nodeAttachment.reparent(); - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - if (_focused) { - _node.unfocus(); - } else { - _node.requestFocus(); - } + return TapRegion( + groupId: 'selector', + onTapOutside: (_){ + _node.unfocus(); }, - child: CompositedTransformTarget( - link: layerLink, - child: buildTarget(), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (_focused) { + _node.unfocus(); + } else { + _node.requestFocus(); + } + }, + child: CompositedTransformTarget( + link: layerLink, + child: buildTarget(), + ), ), ); } void _showOverlay() { _overlayEntry = _createOverlayEntry(); - Overlay.of(context)?.insert(_overlayEntry!); + Overlay.of(context).insert(_overlayEntry!); } void _hideOverlay() { @@ -113,14 +121,12 @@ class _DropSelectableWidgetState extends State decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all( - width: _focused ?1:1/View.of(context).devicePixelRatio, color: _focused ? Colors.blue : widget.disableColor, )), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - widget.data.isNotEmpty ? widget.data[_selectedIndex] : "暂无数据",style: TextStyle( + Text( widget.value ,style: TextStyle( height: 1, fontSize: widget.fontSize ),), @@ -140,6 +146,7 @@ class _DropSelectableWidgetState extends State ); } + OverlayEntry _createOverlayEntry() => OverlayEntry( builder: (BuildContext context) => UnconstrainedBox( child: CompositedTransformFollower( @@ -179,20 +186,22 @@ class _DropSelectableWidgetState extends State ); Widget _buildItem(BuildContext context, int index) { - return Material( - child: InkWell( - onTap: () { - if (_selectedIndex != index) widget.onDropSelected?.call(index); - _selectedIndex = index; - _overlayEntry?.markNeedsBuild(); - _node.unfocus(); - }, - child: Container( - padding: const EdgeInsets.all(8), - color: index == _selectedIndex - ? Colors.blue.withOpacity(0.2) - : Colors.transparent, - child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)), + + bool active = widget.data[index] == widget.value; + return TapRegion( + groupId: 'selector', + child: Material( + child: InkWell( + onTap: () { + if (!active) widget.onDropSelected?.call(index); + _overlayEntry?.markNeedsBuild(); + _node.unfocus(); + }, + child: Container( + padding: const EdgeInsets.all(8), + color: active ? Colors.blue.withOpacity(0.2) : Colors.transparent, + child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)), + ), ), ); } diff --git a/lib/main.dart b/lib/main.dart index 95ccbe5..0061fed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; -import 'v5/app.dart'; +import 'v6/app.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/v5/app/navigation/router/app_router_delegate.dart b/lib/v5/app/navigation/router/app_router_delegate.dart index 6910126..7299895 100644 --- a/lib/v5/app/navigation/router/app_router_delegate.dart +++ b/lib/v5/app/navigation/router/app_router_delegate.dart @@ -7,16 +7,14 @@ import '../../../pages/color/color_detail_page.dart'; import '../../../pages/color/color_page.dart'; import '../../../pages/empty/empty_page.dart'; import '../../../pages/settings/settings_page.dart'; -import '../../../pages/user/user_page.dart'; import '../../../pages/counter/counter_page.dart'; -import '../../../pages/sort/views/sort_page.dart'; +import '../../../pages/user/user_page.dart'; import '../transition/fade_transition_page.dart'; import '../../../pages/color/color_add_page.dart'; const List kDestinationsPaths = [ '/color', '/counter', - '/sort', '/user', '/settings', ]; @@ -27,13 +25,14 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { String _path = '/color'; String get path => _path; - + AppRouterDelegate(){ + // keepAlivePath.add('/color'); + } int? get activeIndex { if(path.startsWith('/color')) return 0; if(path.startsWith('/counter')) return 1; - if(path.startsWith('/sort')) return 2; - if(path.startsWith('/user')) return 3; - if(path.startsWith('/settings')) return 4; + if(path.startsWith('/user')) return 2; + if(path.startsWith('/settings')) return 3; return null; } @@ -41,28 +40,29 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { Completer? completer; - final Map> _alivePageMap = {}; + final Map _pathExtraMap = {}; + + final List keepAlivePath = [] ; void setPathKeepLive(String value){ - _alivePageMap[value] = _buildPageByPath(value); + if(keepAlivePath.contains(value)){ + keepAlivePath.remove(value); + } + keepAlivePath.add(value); path = value; } - final Map _pathExtraMap = {}; - - FutureOr changePath(String value,{bool forResult=false,Object? extra}){ - if(forResult){ - _completerMap[value] = Completer(); - } - if(extra!=null){ - _pathExtraMap[value] = extra; - } - - if(forResult){ - return _completerMap[value]!.future; - } + void setPathForData(String value,dynamic data){ + _pathExtraMap[value] = data; + path = value; } + Future changePathForResult(String value) async{ + Completer completer = Completer(); + _completerMap[value] = completer; + path = value; + return completer.future; + } set path(String value) { if (_path == value) return; @@ -72,22 +72,30 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { @override Widget build(BuildContext context) { - List pages = []; - if(_alivePageMap.containsKey(path)){ - pages = _alivePageMap[path]!; - }else{ - for (var element in _alivePageMap.values) { - pages.addAll(element); - } - pages.addAll(_buildPageByPath(path)); - } - return Navigator( onPopPage: _onPopPage, - pages: pages.toSet().toList(), + pages: _buildPages(path), ); } + List _buildPages(path){ + List pages = []; + List topPages = _buildPageByPath(path); + + if(keepAlivePath.isNotEmpty){ + for (String alivePath in keepAlivePath) { + if(alivePath!=path){ + pages.addAll(_buildPageByPath(alivePath)) ; + } + } + /// 去除和 topPages 中重复的界面 + pages.removeWhere((element) => topPages.map((e) => e.key).contains(element.key)); + } + + pages.addAll(topPages); + return pages; + } + List _buildPageByPath(String path) { Widget? child; if(path.startsWith('/color')){ @@ -98,12 +106,9 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { child = const CounterPage(); } if (path == kDestinationsPaths[2]) { - child = SortPage(); - } - if (path == kDestinationsPaths[3]) { child = const UserPage(); } - if (path == kDestinationsPaths[4]) { + if (path == kDestinationsPaths[3]) { child = const SettingPage(); } return [ @@ -127,10 +132,9 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { if(segment =='detail'){ final Map queryParams = uri.queryParameters; String? selectedColor = queryParams['color']; - if (selectedColor != null) { Color color = Color(int.parse(selectedColor, radix: 16)); - result.add( FadeTransitionPage( + result.add(FadeTransitionPage( key: const ValueKey('/color/detail'), child:ColorDetailPage(color: color), )); diff --git a/lib/v5/app/navigation/router/iroute.dart b/lib/v5/app/navigation/router/iroute.dart index ef2160d..b703af9 100644 --- a/lib/v5/app/navigation/router/iroute.dart +++ b/lib/v5/app/navigation/router/iroute.dart @@ -1,105 +1,37 @@ -import 'package:flutter/cupertino.dart'; - -import '../../../pages/color/color_add_page.dart'; -import '../../../pages/color/color_detail_page.dart'; -import '../../../pages/color/color_page.dart'; -import '../transition/fade_transition_page.dart'; - class IRoute { final String path; - final IRoutePageBuilder builder; final List children; - const IRoute({ - required this.path, - this.children = const [], - required this.builder, - }); + const IRoute({required this.path, this.children = const []}); @override String toString() { return 'IRoute{path: $path, children: $children}'; } - List list() { + List list(){ + return []; } + } -typedef IRoutePageBuilder = Page? Function( - BuildContext context, IRouteData data); -class IRouteData { - final Object? extra; - final bool forResult; - final Uri uri; - final bool keepAlive; - - IRouteData({ - required this.extra, - required this.uri, - required this.forResult, - required this.keepAlive, - }); -} - -List kDestinationsIRoutes = [ +const List kDestinationsIRoutes = [ IRoute( path: '/color', - builder: (ctx, data) { - return const FadeTransitionPage( - key: ValueKey('/color'), - child: ColorPage(), - ); - }, children: [ - IRoute( - path: '/color/detail', - builder: (ctx, data) { - final Map queryParams = data.uri.queryParameters; - String? selectedColor = queryParams['color']; - if (selectedColor != null) { - Color color = Color(int.parse(selectedColor, radix: 16)); - return FadeTransitionPage( - key: const ValueKey('/color/detail'), - child: ColorDetailPage(color: color), - ); - } - return null; - }, - ), - IRoute( - path: '/color/add', - builder: (ctx, data) { - return const FadeTransitionPage( - key: ValueKey('/color/add'), - child: ColorAddPage(), - ); - }), + IRoute(path: '/color/add'), + IRoute(path: '/color/detail'), ], ), IRoute( - path: '/counter', - builder: (ctx, data) { - return const FadeTransitionPage( - key: ValueKey('/counter'), - child: ColorAddPage(), - ); - }), + path: '/counter', + ), IRoute( - path: '/user', - builder: (ctx, data) { - return const FadeTransitionPage( - key: ValueKey('/user'), - child: ColorAddPage(), - ); - }), + path: '/user', + ), IRoute( - path: '/settings', - builder: (ctx, data) { - return const FadeTransitionPage( - key: ValueKey('/settings'), - child: ColorAddPage(), - ); - }), + path: '/settings', + ), ]; diff --git a/lib/v5/app/navigation/views/app_navigation.dart b/lib/v5/app/navigation/views/app_navigation.dart index e1d002d..adcab6c 100644 --- a/lib/v5/app/navigation/views/app_navigation.dart +++ b/lib/v5/app/navigation/views/app_navigation.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import '../../../pages/sort/sort_setting.dart'; import '../router/app_router_delegate.dart'; import 'app_navigation_rail.dart'; import 'app_top_bar.dart'; @@ -11,9 +10,6 @@ class AppNavigation extends StatelessWidget { Widget build(BuildContext context) { double px1 = 1/View.of(context).devicePixelRatio; return Scaffold( - endDrawer: Drawer( - child: SortSettings(), - ), body: Row( children: [ const AppNavigationRail(), diff --git a/lib/v5/app/navigation/views/app_navigation_rail.dart b/lib/v5/app/navigation/views/app_navigation_rail.dart index 38aa912..f3f8ec8 100644 --- a/lib/v5/app/navigation/views/app_navigation_rail.dart +++ b/lib/v5/app/navigation/views/app_navigation_rail.dart @@ -14,7 +14,6 @@ class _AppNavigationRailState extends State { final List deskNavBarMenus = const [ MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), MenuMeta(label: '计数器', icon: Icons.add_chart), - MenuMeta(label: '排序', icon: Icons.sort), MenuMeta(label: '我的', icon: Icons.person), MenuMeta(label: '设置', icon: Icons.settings), ]; @@ -42,7 +41,7 @@ class _AppNavigationRailState extends State { ), tail: Padding( padding: const EdgeInsets.only(bottom: 6.0), - child: Text('V0.0.4',style: TextStyle(color: Colors.white,fontSize: 12),), + child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),), ), backgroundColor: const Color(0xff3975c6), onDestinationSelected: _onDestinationSelected, diff --git a/lib/v5/app/navigation/views/app_router_editor.dart b/lib/v5/app/navigation/views/app_router_editor.dart index 9173948..10d5701 100644 --- a/lib/v5/app/navigation/views/app_router_editor.dart +++ b/lib/v5/app/navigation/views/app_router_editor.dart @@ -1,10 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:iroute/components/toly_ui/button/hover_icon_button.dart'; -import 'package:iroute/components/toly_ui/popable/drop_selectable_widget.dart'; -import '../../../pages/sort/functions.dart'; -import '../../../pages/sort/views/sort_bar.dart'; -import '../../../pages/sort/views/sort_button.dart'; import '../router/app_router_delegate.dart'; class AppRouterEditor extends StatefulWidget { @@ -29,9 +25,6 @@ class _AppRouterEditorState extends State { void _onRouteChange() { _controller.text=router.path; - setState(() { - - }); } @override @@ -43,37 +36,29 @@ class _AppRouterEditorState extends State { @override Widget build(BuildContext context) { - print(router.path); - if(router.path=='/sort'){ - return SortBar(); - } - - return SizedBox( - width: 250, - child: 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)), - ), + 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 - ), - ) - ], - ), + ), + 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/v5/app/navigation/views/app_top_bar.dart b/lib/v5/app/navigation/views/app_top_bar.dart index 84890eb..1b95110 100644 --- a/lib/v5/app/navigation/views/app_top_bar.dart +++ b/lib/v5/app/navigation/views/app_top_bar.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:iroute/components/components.dart'; -import '../../../pages/sort/functions.dart'; import '../router/app_router_delegate.dart'; import 'app_router_editor.dart'; @@ -21,7 +20,7 @@ class AppTopBar extends StatelessWidget { child: Row(children: [ const Spacer(), SizedBox( - // width: 200, + width: 250, child: AppRouterEditor( onSubmit: (path) => router.path = path, )), @@ -53,7 +52,6 @@ Map kRouteLabelMap = { '/color/detail': '颜色详情', '/counter': '计数器', '/user': '我的', - '/sort': '可视化排序', '/settings': '系统设置', }; diff --git a/lib/v5/app/unit_app.dart b/lib/v5/app/unit_app.dart index 36e67ab..1a21114 100644 --- a/lib/v5/app/unit_app.dart +++ b/lib/v5/app/unit_app.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import '../pages/sort/provider/state.dart'; import 'navigation/router/app_router_delegate.dart'; import 'navigation/views/app_navigation.dart'; import 'navigation/views/app_navigation_rail.dart'; @@ -9,23 +8,21 @@ class UnitApp extends StatelessWidget { @override Widget build(BuildContext context) { - return SortStateScope( - notifier: SortState(), - child: MaterialApp( - theme: ThemeData( - fontFamily: "宋体", - scaffoldBackgroundColor: Colors.white, - appBarTheme: const AppBarTheme( - elevation: 0, - iconTheme: IconThemeData(color: Colors.black), - titleTextStyle: TextStyle( - color: Colors.black, - fontSize: 18, - fontWeight: FontWeight.bold, - ))), - debugShowCheckedModeBanner: false, - home: AppNavigation() - ), + + return MaterialApp( + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + 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/v5/pages/color/color_add_page.dart b/lib/v5/pages/color/color_add_page.dart index c6ca0cd..48e6dc6 100644 --- a/lib/v5/pages/color/color_add_page.dart +++ b/lib/v5/pages/color/color_add_page.dart @@ -89,9 +89,6 @@ class _ColorAddPageState extends State { ); } - void _selectColor() { - Navigator.of(context).pop(_color); - } void changeColor(Color value) { _color = value; diff --git a/lib/v5/pages/color/color_detail_page.dart b/lib/v5/pages/color/color_detail_page.dart index 17fcd17..7dfed86 100644 --- a/lib/v5/pages/color/color_detail_page.dart +++ b/lib/v5/pages/color/color_detail_page.dart @@ -15,15 +15,6 @@ class ColorDetailPage extends StatelessWidget { ); String text = '# ${color.value.toRadixString(16)}'; return Scaffold( - // appBar: AppBar( - // systemOverlayStyle: SystemUiOverlayStyle( - // statusBarColor: Colors.transparent, - // statusBarIconBrightness: Brightness.light - // ), - // iconTheme: IconThemeData(color: Colors.white), - // titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) , - // backgroundColor: color, - // title: Text('颜色界面',),), body: Container( alignment: Alignment.center, color: color, diff --git a/lib/v5/pages/color/color_page.dart b/lib/v5/pages/color/color_page.dart index 6764129..e7b88da 100644 --- a/lib/v5/pages/color/color_page.dart +++ b/lib/v5/pages/color/color_page.dart @@ -36,15 +36,14 @@ class _ColorPageState extends State { } void _selectColor(Color color){ - String value = color.value.toRadixString(16); - router.path = '/color/detail?color=$value'; - // router.setPathForData('/color/detail',color); - // router.setPathKeepLive('/color/detail?color=$value'); + // String value = color.value.toRadixString(16); + // router.path = '/color/detail?color=$value'; + router.setPathForData('/color/detail',color); } void _toAddPage() async { - Color? color = await router.changePath('/color/add',forResult: true); + Color? color = await router.changePathForResult('/color/add'); if (color != null) { setState(() { _colors.add(color); diff --git a/lib/v5/pages/sort/sort_page.dart b/lib/v5/pages/sort/sort_page.dart new file mode 100644 index 0000000..d440071 --- /dev/null +++ b/lib/v5/pages/sort/sort_page.dart @@ -0,0 +1,859 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class SortPage extends StatefulWidget { + const SortPage({Key? key}) : super(key: key); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + //存放随机数组 + List numbers = []; + + //订阅流 + StreamController> streamController = StreamController(); + String currentSort = 'bubble'; + + //柱子的数量 -> 生成排序数组的长度 + double sampleSize = 0; + + //是否排序 + bool isSorted = false; + + //是否在排序中 + bool isSorting = false; + + //排序动画更新的速度 + int speed = 0; + + static int duration = 1500; + + String getTitle() { + switch (currentSort) { + case "bubble": + return "Bubble Sort"; + case "coctail": + return "Coctail Sort"; + case "comb": + return "Comb Sort"; + case "pigeonhole": + return "Pigeonhole Sort"; + case "shell": + return "Shell Sort"; + case "selection": + return "Selection Sort"; + case "cycle": + return "Cycle Sort"; + case "heap": + return "Heap Sort"; + case "insertion": + return "Insertion Sort"; + case "gnome": + return "Gnome Sort"; + case "oddeven": + return "OddEven Sort"; + case "quick": + return "Quick Sort"; + case "merge": + return "Merge Sort"; + } + return ""; + } + + reset() { + isSorted = false; + numbers = []; + for (int i = 0; i < sampleSize; ++i) { + numbers.add(Random().nextInt(500)); + } + streamController.add(numbers); + } + + Duration getDuration() { + return Duration(microseconds: duration); + } + + ///动画时间 + changeSpeed() { + if (speed >= 3) { + speed = 0; + duration = 1500; + } else { + speed++; + duration = duration ~/ 2; + } + setState(() {}); + } + + ///冒泡排序 + bubbleSort() async { + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < numbers.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < numbers.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (numbers[j] > numbers[j + 1]) { + int temp = numbers[j]; + numbers[j] = numbers[j + 1]; + numbers[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); + } + } + } + + ///鸡尾酒排序(双向冒泡排序) + cocktailSort() async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = numbers.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } + } + + ///梳排序(Comb Sort) + combSort() async { + int gap = numbers.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < numbers.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (numbers[i] > numbers[i + gap]) { + int temp = numbers[i]; + numbers[i] = numbers[i + gap]; + numbers[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; + } + + ///鸽巢排序 + pigeonHole() async { + int min = numbers[0]; + int max = numbers[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < numbers.length; a++) { + if (numbers[a] > max) max = numbers[a]; + if (numbers[a] < min) min = numbers[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < numbers.length; i++) { + p[numbers[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + numbers[index++] = j + min; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///希尔排序 + shellSort() async { + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < numbers.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = numbers[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) { + numbers[j] = numbers[j - gap]; + } + numbers[j] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///选择排序 + selectionSort() async { + for (int i = 0; i < numbers.length; i++) { + for (int j = i + 1; j < numbers.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (numbers[i] > numbers[j]) { + // 判断当前元素是否比后续元素小 + int temp = numbers[j]; + // 交换当前元素和后续较小的元素 + numbers[j] = numbers[i]; + numbers[i] = temp; + } + + await Future.delayed(getDuration(), () {}); + + streamController.add(numbers); + } + } + } + + ///循环排序 + cycleSort() async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) { + int item = numbers[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (item != numbers[pos]) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///堆排序 + heapSort() async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = numbers.length ~/ 2; i >= 0; i--) { + await heapify(numbers, numbers.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = numbers.length - 1; i >= 0; i--) { + int temp = numbers[0]; + numbers[0] = numbers[i]; + numbers[i] = temp; + await heapify(numbers, i, 0); + streamController.add(numbers); + } + } + + heapify(List arr, int n, int i) async { + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && arr[l] > arr[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && arr[r] > arr[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = numbers[i]; + numbers[i] = numbers[largest]; + numbers[largest] = temp; + heapify(arr, n, largest); + } + + await Future.delayed(getDuration()); // 延迟操作,用于可视化排序过程 + streamController.add(numbers); + } + + ///插入排序 + insertionSort() async { + for (int i = 1; i < numbers.length; i++) { + int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < numbers[j]) { + numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + await Future.delayed(getDuration()); + streamController.add(numbers); // 更新排序结果 + } + + numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); // 更新排序结果 + } + } + + ///地精排序 (侏儒排序) + gnomeSort() async { + int index = 0; + + while (index < numbers.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (numbers[index] >= numbers[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = numbers[index]; + numbers[index] = numbers[index - 1]; + numbers[index - 1] = temp; + index--; + } + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + return; + } + + ///奇偶排序(Odd-Even Sort) + oddEvenSort() async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= numbers.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + for (int i = 0; i <= numbers.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + return; + } + + ///快速排序 + quickSort(int leftIndex, int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async{ + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = numbers[p]; + numbers[p] = numbers[right]; + numbers[right] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(numbers[i], numbers[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = numbers[i]; + numbers[i] = numbers[cursor]; + numbers[cursor] = temp; + cursor++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 将基准元素放置在游标位置 + temp = numbers[right]; + numbers[right] = numbers[cursor]; + numbers[cursor] = temp; + + await Future.delayed(getDuration()); + streamController.add(numbers); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await quickSort(leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await quickSort(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 + } + } + + ///归并排序 + mergeSort(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] = numbers[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = numbers[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + numbers[k] = leftList[i]; + i++; + } else { + numbers[k] = rightList[j]; + j++; + } + + await Future.delayed(getDuration()); + streamController.add(numbers); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + numbers[k] = leftList[i]; + i++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + while (j < rightSize) { + numbers[k] = rightList[j]; + j++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await mergeSort(leftIndex, middleIndex); + await mergeSort(middleIndex + 1, rightIndex); + + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } + } + + checkAndResetIfSorted() async { + if (isSorted) { + reset(); + await Future.delayed(const Duration(milliseconds: 200)); + } + } + + sort() async { + setState(() { + isSorting = true; + }); + + await checkAndResetIfSorted(); + + Stopwatch stopwatch = Stopwatch()..start(); + + switch (currentSort) { + case "bubble": + await bubbleSort(); + break; + case "coctail": + await cocktailSort(); + break; + case "comb": + await combSort(); + break; + case "pigeonhole": + await pigeonHole(); + break; + case "shell": + await shellSort(); + break; + case "selection": + await selectionSort(); + break; + case "cycle": + await cycleSort(); + break; + case "heap": + await heapSort(); + break; + case "insertion": + await insertionSort(); + break; + case "gnome": + await gnomeSort(); + break; + case "oddeven": + await oddEvenSort(); + break; + case "quick": + await quickSort(0, sampleSize.toInt() - 1); + break; + case "merge": + await mergeSort(0, sampleSize.toInt() - 1); + break; + } + + stopwatch.stop(); + + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + setState(() { + isSorting = false; + isSorted = true; + }); + } + + setSort(String type) { + setState(() { + currentSort = type; + }); + } + + @override + void initState() { + super.initState(); + // reset(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + sampleSize = MediaQuery.of(context).size.width / 2; + for (int i = 0; i < sampleSize; ++i) { + //随机往数组中填值 + numbers.add(Random().nextInt(500)); + } + setState(() {}); + } + + @override + void dispose() { + streamController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "当前选择的是:${getTitle()}", + style: const TextStyle(fontSize: 14), + ), + actions: [ + PopupMenuButton( + initialValue: currentSort, + itemBuilder: (ctx) { + return const [ + PopupMenuItem( + value: 'bubble', + child: Text("Bubble Sort — 冒泡排序"), + ), + PopupMenuItem( + value: 'coctail', + child: Text("Coctail Sort — 鸡尾酒排序(双向冒泡排序)"), + ), + PopupMenuItem( + value: 'comb', + child: Text("Comb Sort — 梳排序"), + ), + PopupMenuItem( + value: 'pigeonhole', + child: Text("pigeonhole Sort — 鸽巢排序"), + ), + PopupMenuItem( + value: 'shell', + child: Text("shell Sort — 希尔排序"), + ), + PopupMenuItem( + value: 'selection', + child: Text("Selection Sort — 选择排序"), + ), + PopupMenuItem( + value: 'cycle', + child: Text("CycleSort — 循环排序"), + ), + PopupMenuItem( + value: 'heap', + child: Text("HeapSort — 堆排序"), + ), + PopupMenuItem( + value: 'insertion', + child: Text("InsertionSort — 插入排序"), + ), + PopupMenuItem( + value: 'gnome', + child: Text("GnomeSort — 地精排序 (侏儒排序)"), + ), + PopupMenuItem( + value: 'oddeven', + child: Text("OddEvenSort — 奇偶排序"), + ), + PopupMenuItem( + value: 'quick', + child: Text("QuickSort — 快速排序"), + ), + PopupMenuItem( + value: 'merge', + child: Text("MergeSort — 归并排序"), + ), + ]; + }, + onSelected: (String value) { + reset(); + setSort(value); + }, + ) + ], + ), + body: StreamBuilder( + initialData: numbers, + stream: streamController.stream, + builder: (context, snapshot) { + List numbers = snapshot.data as List; + int counter = 0; + return Row( + children: numbers.map((int num) { + counter++; + return CustomPaint( + painter: BarPainter( + width: MediaQuery.of(context).size.width / sampleSize, + value: num, + index: counter, + ), + ); + }).toList(), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: isSorting + ? null + : () { + reset(); + setSort(currentSort); + }, + child: const Text("重置")), + ElevatedButton( + onPressed: isSorting ? null : sort, child: const Text("开始排序")), + ElevatedButton( + onPressed: isSorting ? null : changeSpeed, + child: Text( + "${speed + 1}x", + style: const TextStyle(fontSize: 20), + ), + ), + ], + ), + ), + ); + } +} + +class BarPainter extends CustomPainter { + //宽度 + final double width; + + //高度(数组中对应的值) + final int value; + + //位置索引 + final int index; + + BarPainter({required this.width, required this.value, required this.index}); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint(); + if (value < 500 * .10) { + paint.color = Colors.blue.shade100; + } else if (value < 500 * .20) { + paint.color = Colors.blue.shade200; + } else if (value < 500 * .30) { + paint.color = Colors.blue.shade300; + } else if (value < 500 * .40) { + paint.color = Colors.blue.shade400; + } else if (value < 500 * .50) { + paint.color = Colors.blue.shade500; + } else if (value < 500 * .60) { + paint.color = Colors.blue.shade600; + } else if (value < 500 * .70) { + paint.color = Colors.blue.shade700; + } else if (value < 500 * .80) { + paint.color = Colors.blue.shade800; + } else if (value < 500 * .90) { + paint.color = Colors.blue.shade900; + } else { + paint.color = const Color(0xFF011E51); + } + + paint.strokeWidth = width; + paint.strokeCap = StrokeCap.round; + + canvas.drawLine( + Offset(index * width, 0), + Offset( + index * width, + value.ceilToDouble(), + ), + paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/v5_/app.dart b/lib/v5_/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v5_/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v5_/app/navigation/router/app_router_delegate.dart b/lib/v5_/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..222f5fe --- /dev/null +++ b/lib/v5_/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,237 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../../../pages/empty/empty_page.dart'; +import '../../../pages/settings/settings_page.dart'; +import '../../../pages/user/user_page.dart'; +import '../../../pages/counter/counter_page.dart'; +import '../transition/fade_transition_page.dart'; +import '../../../pages/color/color_add_page.dart'; + +const List kDestinationsPaths = [ + '/color', + '/counter', + '/user', + '/settings', +]; + +AppRouterDelegate router = AppRouterDelegate(); + +class AppRouterDelegate extends RouterDelegate with ChangeNotifier { + + AppRouterDelegate({String initial = '/'}){ + // changePath('/color',keepAlive: true); + } + + String _path = '/color'; + + String get path => _path; + + int? get activeIndex { + if(path.startsWith('/color')) return 0; + if(path.startsWith('/counter')) return 1; + if(path.startsWith('/user')) return 2; + if(path.startsWith('/settings')) return 3; + return null; + } + + final Map> _completerMap = {}; + + Completer? completer; + + final Map> _alivePageMap = {}; + + void setPathKeepLive(String value){ + _alivePageMap[value] = _buildPageByPath(value); + path = value; + } + + final Map _pathExtraMap = {}; + + FutureOr changePath(String value,{bool forResult=false,Object? extra,bool keepAlive = false}){ + if(forResult){ + _completerMap[value] = Completer(); + } + if(keepAlive){ + _alivePageMap[value] = _buildPageByPath(value); + } + if(extra!=null){ + _pathExtraMap[value] = extra; + } + path = value; + if(forResult){ + return _completerMap[value]!.future; + } + } + + + set path(String value) { + if (_path == value) return; + _path = value; + notifyListeners(); + } + + @override + Widget build(BuildContext context) { + List pages = []; + if(_alivePageMap.containsKey(path)){ + for (String key in _alivePageMap.keys) { + if(path!=key){ + pages.addAll(_alivePageMap[key]!); + } + } + pages.addAll(_alivePageMap[path]!) ; + }else{ + for (var element in _alivePageMap.values) { + pages.addAll(element); + } + List currentPages = _buildPageByPath(path); + pages.removeWhere((element) { + return currentPages.map((e) => e.key).contains(element.key); + }); + // 移除 pages 中和 currentPages 具有相同key 的元素 + pages.addAll(_buildPageByPath(path)); + } + return Navigator( + onPopPage: _onPopPage, + pages: pages, + ); + } + + List _buildPageByPath(String path) { + Widget? child; + if(path.startsWith('/color')){ + return buildColorPages(path); + } + if (path == kDestinationsPaths[1]) { + child = const CounterPage(); + } + if (path == kDestinationsPaths[2]) { + child = const UserPage(); + } + if (path == kDestinationsPaths[3]) { + child = const SettingPage(); + } + return [ + FadeTransitionPage( + key: ValueKey(path), + child: child ?? const EmptyPage(), + ) + ]; + } + + List buildColorPages(String path){ + List result = []; + Uri uri = Uri.parse(path); + for (String segment in uri.pathSegments) { + if(segment == 'color'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color'), + child:ColorPage(), + )); + } + if(segment =='detail'){ + final Map queryParams = uri.queryParameters; + String? selectedColor = queryParams['color']; + + if (selectedColor != null) { + Color color = Color(int.parse(selectedColor, radix: 16)); + result.add( FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: color), + )); + }else{ + Color? selectedColor = _pathExtraMap[path]; + if (selectedColor != null) { + result.add( FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: selectedColor), + )); + _pathExtraMap.remove(path); + } + } + } + if(segment == 'add'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color/add'), + child:ColorAddPage(), + )); + } + + } + return result; + } + + @override + Future popRoute() async { + print('=======popRoute========='); + return true; + } + + bool _onPopPage(Route route, result) { + if(_completerMap.containsKey(path)){ + _completerMap[path]?.complete(result); + _completerMap.remove(path); + } + + path = backPath(path); + return route.didPop(result); + } + + String backPath(String path){ + Uri uri = Uri.parse(path); + if(uri.pathSegments.length==1) return path; + List parts = List.of(uri.pathSegments)..removeLast(); + return '/${parts.join('/')}'; + } + + @override + Future setNewRoutePath(configuration) async {} +} + + + +// class AppRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { +// +// List _value = ['/']; +// +// +// List get value => _value; +// +// set value(List value){ +// _value = value; +// notifyListeners(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Navigator( +// onPopPage: _onPopPage, +// pages: _value.map((e) => _pageMap[e]!).toList(), +// ); +// } +// +// final Map _pageMap = const { +// '/': MaterialPage(child: HomePage()), +// 'a': MaterialPage(child: PageA()), +// 'b': MaterialPage(child: PageB()), +// 'c': MaterialPage(child: PageC()), +// }; +// +// bool _onPopPage(Route route, result) { +// _value = List.of(_value)..removeLast(); +// notifyListeners(); +// return route.didPop(result); +// } +// +// @override +// GlobalKey? navigatorKey = GlobalKey(); +// +// @override +// Future setNewRoutePath(String configuration) async{ +// } +// } diff --git a/lib/v5_/app/navigation/router/iroute.dart b/lib/v5_/app/navigation/router/iroute.dart new file mode 100644 index 0000000..ef2160d --- /dev/null +++ b/lib/v5_/app/navigation/router/iroute.dart @@ -0,0 +1,105 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../pages/color/color_add_page.dart'; +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../transition/fade_transition_page.dart'; + +class IRoute { + final String path; + final IRoutePageBuilder builder; + final List children; + + const IRoute({ + required this.path, + this.children = const [], + required this.builder, + }); + + @override + String toString() { + return 'IRoute{path: $path, children: $children}'; + } + + List list() { + return []; + } +} + +typedef IRoutePageBuilder = Page? Function( + BuildContext context, IRouteData data); + +class IRouteData { + final Object? extra; + final bool forResult; + final Uri uri; + final bool keepAlive; + + IRouteData({ + required this.extra, + required this.uri, + required this.forResult, + required this.keepAlive, + }); +} + +List kDestinationsIRoutes = [ + IRoute( + path: '/color', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color'), + child: ColorPage(), + ); + }, + children: [ + IRoute( + path: '/color/detail', + builder: (ctx, data) { + final Map queryParams = data.uri.queryParameters; + String? selectedColor = queryParams['color']; + if (selectedColor != null) { + Color color = Color(int.parse(selectedColor, radix: 16)); + return FadeTransitionPage( + key: const ValueKey('/color/detail'), + child: ColorDetailPage(color: color), + ); + } + return null; + }, + ), + IRoute( + path: '/color/add', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color/add'), + child: ColorAddPage(), + ); + }), + ], + ), + IRoute( + path: '/counter', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/counter'), + child: ColorAddPage(), + ); + }), + IRoute( + path: '/user', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/user'), + child: ColorAddPage(), + ); + }), + IRoute( + path: '/settings', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/settings'), + child: ColorAddPage(), + ); + }), +]; diff --git a/lib/v5_/app/navigation/transition/fade_transition_page.dart b/lib/v5_/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..552171b --- /dev/null +++ b/lib/v5_/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,53 @@ +// 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/v5_/app/navigation/transition/no_transition_page.dart b/lib/v5_/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v5_/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/v5_/app/navigation/views/app_navigation.dart b/lib/v5_/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..adcab6c --- /dev/null +++ b/lib/v5_/app/navigation/views/app_navigation.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import '../router/app_router_delegate.dart'; +import 'app_navigation_rail.dart'; +import 'app_top_bar.dart'; + +class AppNavigation extends StatelessWidget { + const AppNavigation({super.key}); + + @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: Router( + routerDelegate: router, + backButtonDispatcher: RootBackButtonDispatcher(), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/v5_/app/navigation/views/app_navigation_rail.dart b/lib/v5_/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..cdf945f --- /dev/null +++ b/lib/v5_/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + + final List deskNavBarMenus = const [ + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), + MenuMeta(label: '计数器', icon: Icons.add_chart), + MenuMeta(label: '我的', icon: Icons.person), + MenuMeta(label: '设置', icon: Icons.settings), + ]; + + @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.4',style: TextStyle(color: Colors.white,fontSize: 12),), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: router.activeIndex, + ), + ); + + } + + void _onDestinationSelected(int index) { + if(index==1){ + router.changePath(kDestinationsPaths[index],keepAlive: true); + }else{ + router.path = kDestinationsPaths[index]; + } + } + + void _onRouterChange() { + setState(() {}); + } +} diff --git a/lib/v5_/app/navigation/views/app_router_editor.dart b/lib/v5_/app/navigation/views/app_router_editor.dart new file mode 100644 index 0000000..b7a0670 --- /dev/null +++ b/lib/v5_/app/navigation/views/app_router_editor.dart @@ -0,0 +1,70 @@ +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; + setState(() { + + }); + } + + @override + void dispose() { + _controller.dispose(); + router.removeListener(_onRouteChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 250, + child: 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/v5_/app/navigation/views/app_top_bar.dart b/lib/v5_/app/navigation/views/app_top_bar.dart new file mode 100644 index 0000000..e9f8acd --- /dev/null +++ b/lib/v5_/app/navigation/views/app_top_bar.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; +import 'app_router_editor.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 RouterIndicator(), + Expanded( + child: Row(children: [ + const Spacer(), + SizedBox( + // width: 200, + child: AppRouterEditor( + onSubmit: (path) => router.path = path, + )), + 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(); +} + +Map kRouteLabelMap = { + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/user': '我的', + '/settings': '系统设置', +}; + +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.path = 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] ?? '未知路由'; + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + return result; + } +} diff --git a/lib/v5_/app/unit_app.dart b/lib/v5_/app/unit_app.dart new file mode 100644 index 0000000..886f427 --- /dev/null +++ b/lib/v5_/app/unit_app.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'navigation/views/app_navigation.dart'; + + +class UnitApp extends StatelessWidget { + const UnitApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + 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/v5_/pages/color/color_add_page.dart b/lib/v5_/pages/color/color_add_page.dart new file mode 100644 index 0000000..c6ca0cd --- /dev/null +++ b/lib/v5_/pages/color/color_add_page.dart @@ -0,0 +1,102 @@ +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 _selectColor() { + Navigator.of(context).pop(_color); + } + + void changeColor(Color value) { + _color = value; + setState(() { + + }); + } +} diff --git a/lib/v5_/pages/color/color_detail_page.dart b/lib/v5_/pages/color/color_detail_page.dart new file mode 100644 index 0000000..17fcd17 --- /dev/null +++ b/lib/v5_/pages/color/color_detail_page.dart @@ -0,0 +1,34 @@ +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( + // appBar: AppBar( + // systemOverlayStyle: SystemUiOverlayStyle( + // statusBarColor: Colors.transparent, + // statusBarIconBrightness: Brightness.light + // ), + // iconTheme: IconThemeData(color: Colors.white), + // titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) , + // backgroundColor: color, + // title: Text('颜色界面',),), + body: Container( + alignment: Alignment.center, + color: color, + child: Text(text ,style: style,), + ), + ); + } +} diff --git a/lib/v5_/pages/color/color_page.dart b/lib/v5_/pages/color/color_page.dart new file mode 100644 index 0000000..6764129 --- /dev/null +++ b/lib/v5_/pages/color/color_page.dart @@ -0,0 +1,54 @@ +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 + 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.setPathForData('/color/detail',color); + // router.setPathKeepLive('/color/detail?color=$value'); + + } + + void _toAddPage() async { + Color? color = await router.changePath('/color/add',forResult: true); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v5_/pages/counter/counter_page.dart b/lib/v5_/pages/counter/counter_page.dart new file mode 100644 index 0000000..b5b2e17 --- /dev/null +++ b/lib/v5_/pages/counter/counter_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CounterPage extends StatefulWidget { + const CounterPage({super.key}); + + @override + State createState() => _CounterPageState(); +} + +class _CounterPageState extends State { + int _counter = 0; + + 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/v5_/pages/empty/empty_page.dart b/lib/v5_/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v5_/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/v5_/pages/settings/settings_page.dart b/lib/v5_/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v5_/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/v5_/pages/user/user_page.dart b/lib/v5_/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v5_/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/lib/v5_02/app.dart b/lib/v5_02/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v5_02/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v5_02/app/navigation/router/app_router_delegate.dart b/lib/v5_02/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..56c2777 --- /dev/null +++ b/lib/v5_02/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,241 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../../../pages/empty/empty_page.dart'; +import '../../../pages/settings/settings_page.dart'; +import '../../../pages/counter/counter_page.dart'; +import '../../../pages/user/user_page.dart'; +import '../transition/fade_transition_page.dart'; +import '../../../pages/color/color_add_page.dart'; + +const List kDestinationsPaths = [ + '/color', + '/counter', + '/user', + '/settings', +]; + +AppRouterDelegate router = AppRouterDelegate(); +ValueNotifier counter = ValueNotifier(0); + +class AppRouterDelegate extends RouterDelegate with ChangeNotifier { + String _path = '/color'; + + String get path => _path; + + AppRouterDelegate(){ + // keepAlivePath.add('/color'); + } + int? get activeIndex { + if(path.startsWith('/color')) return 0; + if(path.startsWith('/counter')) return 1; + if(path.startsWith('/user')) return 2; + if(path.startsWith('/settings')) return 3; + return null; + } + + final Map> _completerMap = {}; + + Completer? completer; + + final Map _pathExtraMap = {}; + + final List keepAlivePath = [] ; + + void setPathKeepLive(String value){ + if(keepAlivePath.contains(value)){ + keepAlivePath.remove(value); + } + keepAlivePath.add(value); + path = value; + } + + void setPathForData(String value,dynamic data){ + _pathExtraMap[value] = data; + path = value; + } + + Future changePathForResult(String value) async{ + Completer completer = Completer(); + _completerMap[value] = completer; + path = value; + return completer.future; + } + + set path(String value) { + if (_path == value) return; + _path = value; + notifyListeners(); + } + + @override + Widget build(BuildContext context) { + return Navigator( + onPopPage: _onPopPage, + pages: _buildPages(path), + ); + } + + List _buildPages(path){ + List pages = []; + List topPages = _buildPageByPath(path); + + if(keepAlivePath.isNotEmpty){ + for (String alivePath in keepAlivePath) { + if(alivePath!=path){ + pages.addAll(_buildPageByPath(alivePath)) ; + } + } + /// 去除和 topPages 中重复的界面 + pages.removeWhere((element) => topPages.map((e) => e.key).contains(element.key)); + } + + pages.addAll(topPages); + return pages; + } + + int _counter = 0; + + List _buildPageByPath(String path) { + Widget? child; + if(path.startsWith('/color')){ + return buildColorPages(path); + } + + if (path == kDestinationsPaths[1]) { + child = CounterPage( + counter: _counter, + onAddTap: (){ + _counter++; + notifyListeners(); + }, + ); + } + if (path == kDestinationsPaths[2]) { + child = const UserPage(); + } + if (path == kDestinationsPaths[3]) { + child = const SettingPage(); + } + return [ + FadeTransitionPage( + key: ValueKey(path), + child: child ?? const EmptyPage(), + ) + ]; + } + + List buildColorPages(String path){ + List result = []; + Uri uri = Uri.parse(path); + for (String segment in uri.pathSegments) { + if(segment == 'color'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color'), + child:ColorPage(), + )); + } + if(segment =='detail'){ + final Map queryParams = uri.queryParameters; + String? selectedColor = queryParams['color']; + if (selectedColor != null) { + Color color = Color(int.parse(selectedColor, radix: 16)); + result.add(FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: color), + )); + }else{ + Color? selectedColor = _pathExtraMap[path]; + if (selectedColor != null) { + result.add( FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: selectedColor), + )); + _pathExtraMap.remove(path); + } + } + } + if(segment == 'add'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color/add'), + child:ColorAddPage(), + )); + } + + } + return result; + } + + @override + Future popRoute() async { + print('=======popRoute========='); + return true; + } + + bool _onPopPage(Route route, result) { + if(_completerMap.containsKey(path)){ + _completerMap[path]?.complete(result); + _completerMap.remove(path); + } + + path = backPath(path); + return route.didPop(result); + } + + String backPath(String path){ + Uri uri = Uri.parse(path); + if(uri.pathSegments.length==1) return path; + List parts = List.of(uri.pathSegments)..removeLast(); + return '/${parts.join('/')}'; + } + + @override + Future setNewRoutePath(configuration) async {} +} + + + +// class AppRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { +// +// List _value = ['/']; +// +// +// List get value => _value; +// +// set value(List value){ +// _value = value; +// notifyListeners(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Navigator( +// onPopPage: _onPopPage, +// pages: _value.map((e) => _pageMap[e]!).toList(), +// ); +// } +// +// final Map _pageMap = const { +// '/': MaterialPage(child: HomePage()), +// 'a': MaterialPage(child: PageA()), +// 'b': MaterialPage(child: PageB()), +// 'c': MaterialPage(child: PageC()), +// }; +// +// bool _onPopPage(Route route, result) { +// _value = List.of(_value)..removeLast(); +// notifyListeners(); +// return route.didPop(result); +// } +// +// @override +// GlobalKey? navigatorKey = GlobalKey(); +// +// @override +// Future setNewRoutePath(String configuration) async{ +// } +// } diff --git a/lib/v5_02/app/navigation/router/iroute.dart b/lib/v5_02/app/navigation/router/iroute.dart new file mode 100644 index 0000000..b703af9 --- /dev/null +++ b/lib/v5_02/app/navigation/router/iroute.dart @@ -0,0 +1,37 @@ +class IRoute { + final String path; + final List children; + + const IRoute({required this.path, this.children = const []}); + + @override + String toString() { + return 'IRoute{path: $path, children: $children}'; + } + + List list(){ + + return []; + } + +} + + +const List kDestinationsIRoutes = [ + IRoute( + path: '/color', + children: [ + IRoute(path: '/color/add'), + IRoute(path: '/color/detail'), + ], + ), + IRoute( + path: '/counter', + ), + IRoute( + path: '/user', + ), + IRoute( + path: '/settings', + ), +]; diff --git a/lib/v5_02/app/navigation/transition/fade_transition_page.dart b/lib/v5_02/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..552171b --- /dev/null +++ b/lib/v5_02/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,53 @@ +// 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/v5_02/app/navigation/transition/no_transition_page.dart b/lib/v5_02/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v5_02/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/v5_02/app/navigation/views/app_navigation.dart b/lib/v5_02/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..adcab6c --- /dev/null +++ b/lib/v5_02/app/navigation/views/app_navigation.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import '../router/app_router_delegate.dart'; +import 'app_navigation_rail.dart'; +import 'app_top_bar.dart'; + +class AppNavigation extends StatelessWidget { + const AppNavigation({super.key}); + + @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: Router( + routerDelegate: router, + backButtonDispatcher: RootBackButtonDispatcher(), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/v5_02/app/navigation/views/app_navigation_rail.dart b/lib/v5_02/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..f4e8256 --- /dev/null +++ b/lib/v5_02/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + + final List deskNavBarMenus = const [ + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), + MenuMeta(label: '计数器', icon: Icons.add_chart), + MenuMeta(label: '我的', icon: Icons.person), + MenuMeta(label: '设置', icon: Icons.settings), + ]; + + @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.5',style: TextStyle(color: Colors.white,fontSize: 12),), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: router.activeIndex, + ), + ); + + } + + void _onDestinationSelected(int index) { + // if(index==1){ + // router.setPathKeepLive(kDestinationsPaths[index]); + // }else{ + // router.path = kDestinationsPaths[index]; + // } + router.path = kDestinationsPaths[index]; + + } + + void _onRouterChange() { + setState(() {}); + } +} diff --git a/lib/v5_02/app/navigation/views/app_router_editor.dart b/lib/v5_02/app/navigation/views/app_router_editor.dart new file mode 100644 index 0000000..10d5701 --- /dev/null +++ b/lib/v5_02/app/navigation/views/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/v5_02/app/navigation/views/app_top_bar.dart b/lib/v5_02/app/navigation/views/app_top_bar.dart new file mode 100644 index 0000000..1b95110 --- /dev/null +++ b/lib/v5_02/app/navigation/views/app_top_bar.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; +import 'app_router_editor.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 RouterIndicator(), + Expanded( + child: Row(children: [ + const Spacer(), + SizedBox( + width: 250, + child: AppRouterEditor( + onSubmit: (path) => router.path = path, + )), + 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(); +} + +Map kRouteLabelMap = { + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/user': '我的', + '/settings': '系统设置', +}; + +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.path = 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] ?? '未知路由'; + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + return result; + } +} diff --git a/lib/v5_02/app/unit_app.dart b/lib/v5_02/app/unit_app.dart new file mode 100644 index 0000000..1a21114 --- /dev/null +++ b/lib/v5_02/app/unit_app.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'navigation/router/app_router_delegate.dart'; +import 'navigation/views/app_navigation.dart'; +import 'navigation/views/app_navigation_rail.dart'; + +class UnitApp extends StatelessWidget { + const UnitApp({super.key}); + + @override + Widget build(BuildContext context) { + + return MaterialApp( + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + 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/v5_02/pages/color/color_add_page.dart b/lib/v5_02/pages/color/color_add_page.dart new file mode 100644 index 0000000..48e6dc6 --- /dev/null +++ b/lib/v5_02/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/v5_02/pages/color/color_detail_page.dart b/lib/v5_02/pages/color/color_detail_page.dart new file mode 100644 index 0000000..7dfed86 --- /dev/null +++ b/lib/v5_02/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/v5_02/pages/color/color_page.dart b/lib/v5_02/pages/color/color_page.dart new file mode 100644 index 0000000..e7b88da --- /dev/null +++ b/lib/v5_02/pages/color/color_page.dart @@ -0,0 +1,53 @@ +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 + 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.setPathForData('/color/detail',color); + + } + + void _toAddPage() async { + Color? color = await router.changePathForResult('/color/add'); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v5_02/pages/counter/counter_page.dart b/lib/v5_02/pages/counter/counter_page.dart new file mode 100644 index 0000000..5591eba --- /dev/null +++ b/lib/v5_02/pages/counter/counter_page.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class CounterPage extends StatelessWidget { + final int counter; + final VoidCallback onAddTap; + + const CounterPage({super.key, required this.counter, required this.onAddTap}); + + @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: onAddTap, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} \ No newline at end of file diff --git a/lib/v5_02/pages/empty/empty_page.dart b/lib/v5_02/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v5_02/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/v5_02/pages/settings/settings_page.dart b/lib/v5_02/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v5_02/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/v5_02/pages/sort/sort_page.dart b/lib/v5_02/pages/sort/sort_page.dart new file mode 100644 index 0000000..d440071 --- /dev/null +++ b/lib/v5_02/pages/sort/sort_page.dart @@ -0,0 +1,859 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class SortPage extends StatefulWidget { + const SortPage({Key? key}) : super(key: key); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + //存放随机数组 + List numbers = []; + + //订阅流 + StreamController> streamController = StreamController(); + String currentSort = 'bubble'; + + //柱子的数量 -> 生成排序数组的长度 + double sampleSize = 0; + + //是否排序 + bool isSorted = false; + + //是否在排序中 + bool isSorting = false; + + //排序动画更新的速度 + int speed = 0; + + static int duration = 1500; + + String getTitle() { + switch (currentSort) { + case "bubble": + return "Bubble Sort"; + case "coctail": + return "Coctail Sort"; + case "comb": + return "Comb Sort"; + case "pigeonhole": + return "Pigeonhole Sort"; + case "shell": + return "Shell Sort"; + case "selection": + return "Selection Sort"; + case "cycle": + return "Cycle Sort"; + case "heap": + return "Heap Sort"; + case "insertion": + return "Insertion Sort"; + case "gnome": + return "Gnome Sort"; + case "oddeven": + return "OddEven Sort"; + case "quick": + return "Quick Sort"; + case "merge": + return "Merge Sort"; + } + return ""; + } + + reset() { + isSorted = false; + numbers = []; + for (int i = 0; i < sampleSize; ++i) { + numbers.add(Random().nextInt(500)); + } + streamController.add(numbers); + } + + Duration getDuration() { + return Duration(microseconds: duration); + } + + ///动画时间 + changeSpeed() { + if (speed >= 3) { + speed = 0; + duration = 1500; + } else { + speed++; + duration = duration ~/ 2; + } + setState(() {}); + } + + ///冒泡排序 + bubbleSort() async { + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < numbers.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < numbers.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (numbers[j] > numbers[j + 1]) { + int temp = numbers[j]; + numbers[j] = numbers[j + 1]; + numbers[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); + } + } + } + + ///鸡尾酒排序(双向冒泡排序) + cocktailSort() async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = numbers.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } + } + + ///梳排序(Comb Sort) + combSort() async { + int gap = numbers.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < numbers.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (numbers[i] > numbers[i + gap]) { + int temp = numbers[i]; + numbers[i] = numbers[i + gap]; + numbers[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; + } + + ///鸽巢排序 + pigeonHole() async { + int min = numbers[0]; + int max = numbers[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < numbers.length; a++) { + if (numbers[a] > max) max = numbers[a]; + if (numbers[a] < min) min = numbers[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < numbers.length; i++) { + p[numbers[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + numbers[index++] = j + min; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///希尔排序 + shellSort() async { + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < numbers.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = numbers[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) { + numbers[j] = numbers[j - gap]; + } + numbers[j] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///选择排序 + selectionSort() async { + for (int i = 0; i < numbers.length; i++) { + for (int j = i + 1; j < numbers.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (numbers[i] > numbers[j]) { + // 判断当前元素是否比后续元素小 + int temp = numbers[j]; + // 交换当前元素和后续较小的元素 + numbers[j] = numbers[i]; + numbers[i] = temp; + } + + await Future.delayed(getDuration(), () {}); + + streamController.add(numbers); + } + } + } + + ///循环排序 + cycleSort() async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) { + int item = numbers[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (item != numbers[pos]) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///堆排序 + heapSort() async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = numbers.length ~/ 2; i >= 0; i--) { + await heapify(numbers, numbers.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = numbers.length - 1; i >= 0; i--) { + int temp = numbers[0]; + numbers[0] = numbers[i]; + numbers[i] = temp; + await heapify(numbers, i, 0); + streamController.add(numbers); + } + } + + heapify(List arr, int n, int i) async { + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && arr[l] > arr[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && arr[r] > arr[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = numbers[i]; + numbers[i] = numbers[largest]; + numbers[largest] = temp; + heapify(arr, n, largest); + } + + await Future.delayed(getDuration()); // 延迟操作,用于可视化排序过程 + streamController.add(numbers); + } + + ///插入排序 + insertionSort() async { + for (int i = 1; i < numbers.length; i++) { + int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < numbers[j]) { + numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + await Future.delayed(getDuration()); + streamController.add(numbers); // 更新排序结果 + } + + numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); // 更新排序结果 + } + } + + ///地精排序 (侏儒排序) + gnomeSort() async { + int index = 0; + + while (index < numbers.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (numbers[index] >= numbers[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = numbers[index]; + numbers[index] = numbers[index - 1]; + numbers[index - 1] = temp; + index--; + } + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + return; + } + + ///奇偶排序(Odd-Even Sort) + oddEvenSort() async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= numbers.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + for (int i = 0; i <= numbers.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + return; + } + + ///快速排序 + quickSort(int leftIndex, int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async{ + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = numbers[p]; + numbers[p] = numbers[right]; + numbers[right] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(numbers[i], numbers[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = numbers[i]; + numbers[i] = numbers[cursor]; + numbers[cursor] = temp; + cursor++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 将基准元素放置在游标位置 + temp = numbers[right]; + numbers[right] = numbers[cursor]; + numbers[cursor] = temp; + + await Future.delayed(getDuration()); + streamController.add(numbers); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await quickSort(leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await quickSort(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 + } + } + + ///归并排序 + mergeSort(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] = numbers[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = numbers[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + numbers[k] = leftList[i]; + i++; + } else { + numbers[k] = rightList[j]; + j++; + } + + await Future.delayed(getDuration()); + streamController.add(numbers); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + numbers[k] = leftList[i]; + i++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + while (j < rightSize) { + numbers[k] = rightList[j]; + j++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await mergeSort(leftIndex, middleIndex); + await mergeSort(middleIndex + 1, rightIndex); + + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } + } + + checkAndResetIfSorted() async { + if (isSorted) { + reset(); + await Future.delayed(const Duration(milliseconds: 200)); + } + } + + sort() async { + setState(() { + isSorting = true; + }); + + await checkAndResetIfSorted(); + + Stopwatch stopwatch = Stopwatch()..start(); + + switch (currentSort) { + case "bubble": + await bubbleSort(); + break; + case "coctail": + await cocktailSort(); + break; + case "comb": + await combSort(); + break; + case "pigeonhole": + await pigeonHole(); + break; + case "shell": + await shellSort(); + break; + case "selection": + await selectionSort(); + break; + case "cycle": + await cycleSort(); + break; + case "heap": + await heapSort(); + break; + case "insertion": + await insertionSort(); + break; + case "gnome": + await gnomeSort(); + break; + case "oddeven": + await oddEvenSort(); + break; + case "quick": + await quickSort(0, sampleSize.toInt() - 1); + break; + case "merge": + await mergeSort(0, sampleSize.toInt() - 1); + break; + } + + stopwatch.stop(); + + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + setState(() { + isSorting = false; + isSorted = true; + }); + } + + setSort(String type) { + setState(() { + currentSort = type; + }); + } + + @override + void initState() { + super.initState(); + // reset(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + sampleSize = MediaQuery.of(context).size.width / 2; + for (int i = 0; i < sampleSize; ++i) { + //随机往数组中填值 + numbers.add(Random().nextInt(500)); + } + setState(() {}); + } + + @override + void dispose() { + streamController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "当前选择的是:${getTitle()}", + style: const TextStyle(fontSize: 14), + ), + actions: [ + PopupMenuButton( + initialValue: currentSort, + itemBuilder: (ctx) { + return const [ + PopupMenuItem( + value: 'bubble', + child: Text("Bubble Sort — 冒泡排序"), + ), + PopupMenuItem( + value: 'coctail', + child: Text("Coctail Sort — 鸡尾酒排序(双向冒泡排序)"), + ), + PopupMenuItem( + value: 'comb', + child: Text("Comb Sort — 梳排序"), + ), + PopupMenuItem( + value: 'pigeonhole', + child: Text("pigeonhole Sort — 鸽巢排序"), + ), + PopupMenuItem( + value: 'shell', + child: Text("shell Sort — 希尔排序"), + ), + PopupMenuItem( + value: 'selection', + child: Text("Selection Sort — 选择排序"), + ), + PopupMenuItem( + value: 'cycle', + child: Text("CycleSort — 循环排序"), + ), + PopupMenuItem( + value: 'heap', + child: Text("HeapSort — 堆排序"), + ), + PopupMenuItem( + value: 'insertion', + child: Text("InsertionSort — 插入排序"), + ), + PopupMenuItem( + value: 'gnome', + child: Text("GnomeSort — 地精排序 (侏儒排序)"), + ), + PopupMenuItem( + value: 'oddeven', + child: Text("OddEvenSort — 奇偶排序"), + ), + PopupMenuItem( + value: 'quick', + child: Text("QuickSort — 快速排序"), + ), + PopupMenuItem( + value: 'merge', + child: Text("MergeSort — 归并排序"), + ), + ]; + }, + onSelected: (String value) { + reset(); + setSort(value); + }, + ) + ], + ), + body: StreamBuilder( + initialData: numbers, + stream: streamController.stream, + builder: (context, snapshot) { + List numbers = snapshot.data as List; + int counter = 0; + return Row( + children: numbers.map((int num) { + counter++; + return CustomPaint( + painter: BarPainter( + width: MediaQuery.of(context).size.width / sampleSize, + value: num, + index: counter, + ), + ); + }).toList(), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: isSorting + ? null + : () { + reset(); + setSort(currentSort); + }, + child: const Text("重置")), + ElevatedButton( + onPressed: isSorting ? null : sort, child: const Text("开始排序")), + ElevatedButton( + onPressed: isSorting ? null : changeSpeed, + child: Text( + "${speed + 1}x", + style: const TextStyle(fontSize: 20), + ), + ), + ], + ), + ), + ); + } +} + +class BarPainter extends CustomPainter { + //宽度 + final double width; + + //高度(数组中对应的值) + final int value; + + //位置索引 + final int index; + + BarPainter({required this.width, required this.value, required this.index}); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint(); + if (value < 500 * .10) { + paint.color = Colors.blue.shade100; + } else if (value < 500 * .20) { + paint.color = Colors.blue.shade200; + } else if (value < 500 * .30) { + paint.color = Colors.blue.shade300; + } else if (value < 500 * .40) { + paint.color = Colors.blue.shade400; + } else if (value < 500 * .50) { + paint.color = Colors.blue.shade500; + } else if (value < 500 * .60) { + paint.color = Colors.blue.shade600; + } else if (value < 500 * .70) { + paint.color = Colors.blue.shade700; + } else if (value < 500 * .80) { + paint.color = Colors.blue.shade800; + } else if (value < 500 * .90) { + paint.color = Colors.blue.shade900; + } else { + paint.color = const Color(0xFF011E51); + } + + paint.strokeWidth = width; + paint.strokeCap = StrokeCap.round; + + canvas.drawLine( + Offset(index * width, 0), + Offset( + index * width, + value.ceilToDouble(), + ), + paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/v5_02/pages/user/user_page.dart b/lib/v5_02/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v5_02/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/lib/v6/app.dart b/lib/v6/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v6/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v6/app/navigation/router/app_router_delegate.dart b/lib/v6/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..4965d43 --- /dev/null +++ b/lib/v6/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,154 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'iroute.dart'; + +const List kDestinationsPaths = [ + '/color', + '/counter', + '/user', + '/settings', +]; + +AppRouterDelegate router = AppRouterDelegate(); + +class AppRouterDelegate extends RouterDelegate with ChangeNotifier { + String _path = '/color'; + + String get path => _path; + AppRouterDelegate() { + // keepAlivePath.add('/color'); + } + + int? get activeIndex { + if (path.startsWith('/color')) return 0; + if (path.startsWith('/counter')) return 1; + if (path.startsWith('/user')) return 2; + if (path.startsWith('/settings')) return 3; + return null; + } + + final Map> _completerMap = {}; + + Completer? completer; + + final Map _pathExtraMap = {}; + + final List keepAlivePath = []; + + FutureOr changePath(String value, + {bool forResult = false, Object? extra, bool keepAlive = false}) { + if (forResult) { + _completerMap[value] = Completer(); + } + if (keepAlive) { + if (keepAlivePath.contains(value)) { + keepAlivePath.remove(value); + } + keepAlivePath.add(value); + } + if (extra != null) { + _pathExtraMap[value] = extra; + } + path = value; + if (forResult) { + return _completerMap[value]!.future; + } + } + + set path(String value) { + if (_path == value) return; + _path = value; + notifyListeners(); + } + + @override + Widget build(BuildContext context) { + return Navigator( + onPopPage: _onPopPage, + pages: _buildPages(context, path), + ); + } + + List _buildPages(BuildContext context,String path) { + List pages = []; + List topPages = _buildPageByPath(context,path); + + if (keepAlivePath.isNotEmpty) { + for (String alivePath in keepAlivePath) { + if (alivePath != path) { + pages.addAll(_buildPageByPath(context,alivePath)); + } + } + + /// 去除和 topPages 中重复的界面 + pages.removeWhere( + (element) => topPages.map((e) => e.key).contains(element.key)); + } + + pages.addAll(topPages); + return pages; + } + + Page? _buildPageByPathFromTree(BuildContext context, String path) { + // 1. 根据 path 在 iroute 树中查询节点 + IRoute? iroute = root.match(path); + Object? extra = _pathExtraMap[path]; + bool keepAlive = keepAlivePath.contains(path); + bool forResult = _completerMap.containsKey(path); + Page? page; + if (iroute != null) { + page = iroute.builder?.call( + context, + IRouteData( + uri: Uri.parse(path), + extra: extra, + keepAlive: keepAlive, + forResult: forResult, + ), + ); + } + return page; + } + + List _buildPageByPath(BuildContext context,String path) { + List result = []; + Uri uri = Uri.parse(path); + String dist = ''; + for (String segment in uri.pathSegments) { + dist += '/$segment'; + Page? page = _buildPageByPathFromTree(context,dist); + if(page!=null){ + result.add(page); + } + } + return result; + } + + @override + Future popRoute() async { + print('=======popRoute========='); + return true; + } + + bool _onPopPage(Route route, result) { + if (_completerMap.containsKey(path)) { + _completerMap[path]?.complete(result); + _completerMap.remove(path); + } + + path = backPath(path); + return route.didPop(result); + } + + String backPath(String path) { + Uri uri = Uri.parse(path); + if (uri.pathSegments.length == 1) return path; + List parts = List.of(uri.pathSegments)..removeLast(); + return '/${parts.join('/')}'; + } + + @override + Future setNewRoutePath(configuration) async {} +} diff --git a/lib/v6/app/navigation/router/iroute.dart b/lib/v6/app/navigation/router/iroute.dart new file mode 100644 index 0000000..6ef5328 --- /dev/null +++ b/lib/v6/app/navigation/router/iroute.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.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 '../transition/fade_transition_page.dart'; + +class IRoute { + final String path; + final IRoutePageBuilder? builder; + final List children; + + const IRoute({ + required this.path, + this.children = const [], + this.builder, + }); + + @override + String toString() { + return 'IRoute{path: $path, children: $children}'; + } + + IRoute? match(String path) { + return matchRoute(this, path); + } + + IRoute? matchRoute(IRoute route, String path) { + if (route.path == path) { + return route; + } else { + if (route.children.isNotEmpty) { + for (int i = 0; i < route.children.length; i++) { + IRoute current = route.children[i]; + IRoute? target = matchRoute(current, path); + if (target != null) { + return target; + } + } + } else { + return null; + } + } + return null; + } +} + +typedef IRoutePageBuilder = Page? Function( + BuildContext context, IRouteData data); + +class IRouteData { + final Object? extra; + final bool forResult; + final Uri uri; + final bool keepAlive; + + IRouteData({ + this.extra, + required this.uri, + this.forResult = false, + this.keepAlive = false, + }); +} + +IRoute root = IRoute(path: '/', children: kDestinationsIRoutes); + +List kDestinationsIRoutes = [ + IRoute( + path: '/color', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color'), + child: ColorPage(), + ); + }, + children: [ + IRoute( + path: '/color/detail', + builder: (ctx, 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 FadeTransitionPage( + key: const ValueKey('/color/detail'), + child: ColorDetailPage(color: color), + ); + }, + ), + IRoute( + path: '/color/add', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color/add'), + child: ColorAddPage(), + ); + }), + ], + ), + IRoute( + path: '/counter', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/counter'), + child: CounterPage(), + ); + }), + IRoute( + path: '/user', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/user'), + child: UserPage(), + ); + }), + IRoute( + path: '/settings', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/settings'), + child: SettingPage(), + ); + }), +]; diff --git a/lib/v6/app/navigation/transition/fade_transition_page.dart b/lib/v6/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..552171b --- /dev/null +++ b/lib/v6/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,53 @@ +// 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/v6/app/navigation/transition/no_transition_page.dart b/lib/v6/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v6/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/v6/app/navigation/views/app_navigation.dart b/lib/v6/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..adcab6c --- /dev/null +++ b/lib/v6/app/navigation/views/app_navigation.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import '../router/app_router_delegate.dart'; +import 'app_navigation_rail.dart'; +import 'app_top_bar.dart'; + +class AppNavigation extends StatelessWidget { + const AppNavigation({super.key}); + + @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: Router( + routerDelegate: router, + backButtonDispatcher: RootBackButtonDispatcher(), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/v6/app/navigation/views/app_navigation_rail.dart b/lib/v6/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..8ea8ace --- /dev/null +++ b/lib/v6/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + + final List deskNavBarMenus = const [ + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), + MenuMeta(label: '计数器', icon: Icons.add_chart), + MenuMeta(label: '我的', icon: Icons.person), + MenuMeta(label: '设置', icon: Icons.settings), + ]; + + @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.5',style: TextStyle(color: Colors.white,fontSize: 12),), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: router.activeIndex, + ), + ); + + } + + void _onDestinationSelected(int index) { + if(index==1){ + router.changePath(kDestinationsPaths[index],keepAlive: true); + }else{ + router.path = kDestinationsPaths[index]; + } + } + + void _onRouterChange() { + setState(() {}); + } +} diff --git a/lib/v6/app/navigation/views/app_router_editor.dart b/lib/v6/app/navigation/views/app_router_editor.dart new file mode 100644 index 0000000..10d5701 --- /dev/null +++ b/lib/v6/app/navigation/views/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/v6/app/navigation/views/app_top_bar.dart b/lib/v6/app/navigation/views/app_top_bar.dart new file mode 100644 index 0000000..1b95110 --- /dev/null +++ b/lib/v6/app/navigation/views/app_top_bar.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; +import 'app_router_editor.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 RouterIndicator(), + Expanded( + child: Row(children: [ + const Spacer(), + SizedBox( + width: 250, + child: AppRouterEditor( + onSubmit: (path) => router.path = path, + )), + 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(); +} + +Map kRouteLabelMap = { + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/user': '我的', + '/settings': '系统设置', +}; + +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.path = 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] ?? '未知路由'; + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + return result; + } +} diff --git a/lib/v6/app/unit_app.dart b/lib/v6/app/unit_app.dart new file mode 100644 index 0000000..1a21114 --- /dev/null +++ b/lib/v6/app/unit_app.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'navigation/router/app_router_delegate.dart'; +import 'navigation/views/app_navigation.dart'; +import 'navigation/views/app_navigation_rail.dart'; + +class UnitApp extends StatelessWidget { + const UnitApp({super.key}); + + @override + Widget build(BuildContext context) { + + return MaterialApp( + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + 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/v6/pages/color/color_add_page.dart b/lib/v6/pages/color/color_add_page.dart new file mode 100644 index 0000000..48e6dc6 --- /dev/null +++ b/lib/v6/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/v6/pages/color/color_detail_page.dart b/lib/v6/pages/color/color_detail_page.dart new file mode 100644 index 0000000..7dfed86 --- /dev/null +++ b/lib/v6/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/v6/pages/color/color_page.dart b/lib/v6/pages/color/color_page.dart new file mode 100644 index 0000000..85239d3 --- /dev/null +++ b/lib/v6/pages/color/color_page.dart @@ -0,0 +1,52 @@ +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 + 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('/color/detail',extra: color); + } + + void _toAddPage() async { + Color? color = await router.changePath('/color/add',forResult: true); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v6/pages/counter/counter_page.dart b/lib/v6/pages/counter/counter_page.dart new file mode 100644 index 0000000..b5b2e17 --- /dev/null +++ b/lib/v6/pages/counter/counter_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CounterPage extends StatefulWidget { + const CounterPage({super.key}); + + @override + State createState() => _CounterPageState(); +} + +class _CounterPageState extends State { + int _counter = 0; + + 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/v6/pages/empty/empty_page.dart b/lib/v6/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v6/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/v6/pages/settings/settings_page.dart b/lib/v6/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v6/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/v6/pages/sort/sort_page.dart b/lib/v6/pages/sort/sort_page.dart new file mode 100644 index 0000000..d440071 --- /dev/null +++ b/lib/v6/pages/sort/sort_page.dart @@ -0,0 +1,859 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class SortPage extends StatefulWidget { + const SortPage({Key? key}) : super(key: key); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + //存放随机数组 + List numbers = []; + + //订阅流 + StreamController> streamController = StreamController(); + String currentSort = 'bubble'; + + //柱子的数量 -> 生成排序数组的长度 + double sampleSize = 0; + + //是否排序 + bool isSorted = false; + + //是否在排序中 + bool isSorting = false; + + //排序动画更新的速度 + int speed = 0; + + static int duration = 1500; + + String getTitle() { + switch (currentSort) { + case "bubble": + return "Bubble Sort"; + case "coctail": + return "Coctail Sort"; + case "comb": + return "Comb Sort"; + case "pigeonhole": + return "Pigeonhole Sort"; + case "shell": + return "Shell Sort"; + case "selection": + return "Selection Sort"; + case "cycle": + return "Cycle Sort"; + case "heap": + return "Heap Sort"; + case "insertion": + return "Insertion Sort"; + case "gnome": + return "Gnome Sort"; + case "oddeven": + return "OddEven Sort"; + case "quick": + return "Quick Sort"; + case "merge": + return "Merge Sort"; + } + return ""; + } + + reset() { + isSorted = false; + numbers = []; + for (int i = 0; i < sampleSize; ++i) { + numbers.add(Random().nextInt(500)); + } + streamController.add(numbers); + } + + Duration getDuration() { + return Duration(microseconds: duration); + } + + ///动画时间 + changeSpeed() { + if (speed >= 3) { + speed = 0; + duration = 1500; + } else { + speed++; + duration = duration ~/ 2; + } + setState(() {}); + } + + ///冒泡排序 + bubbleSort() async { + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < numbers.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < numbers.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (numbers[j] > numbers[j + 1]) { + int temp = numbers[j]; + numbers[j] = numbers[j + 1]; + numbers[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); + } + } + } + + ///鸡尾酒排序(双向冒泡排序) + cocktailSort() async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = numbers.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } + } + + ///梳排序(Comb Sort) + combSort() async { + int gap = numbers.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < numbers.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (numbers[i] > numbers[i + gap]) { + int temp = numbers[i]; + numbers[i] = numbers[i + gap]; + numbers[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; + } + + ///鸽巢排序 + pigeonHole() async { + int min = numbers[0]; + int max = numbers[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < numbers.length; a++) { + if (numbers[a] > max) max = numbers[a]; + if (numbers[a] < min) min = numbers[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < numbers.length; i++) { + p[numbers[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + numbers[index++] = j + min; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///希尔排序 + shellSort() async { + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < numbers.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = numbers[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) { + numbers[j] = numbers[j - gap]; + } + numbers[j] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///选择排序 + selectionSort() async { + for (int i = 0; i < numbers.length; i++) { + for (int j = i + 1; j < numbers.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (numbers[i] > numbers[j]) { + // 判断当前元素是否比后续元素小 + int temp = numbers[j]; + // 交换当前元素和后续较小的元素 + numbers[j] = numbers[i]; + numbers[i] = temp; + } + + await Future.delayed(getDuration(), () {}); + + streamController.add(numbers); + } + } + } + + ///循环排序 + cycleSort() async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) { + int item = numbers[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (item != numbers[pos]) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///堆排序 + heapSort() async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = numbers.length ~/ 2; i >= 0; i--) { + await heapify(numbers, numbers.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = numbers.length - 1; i >= 0; i--) { + int temp = numbers[0]; + numbers[0] = numbers[i]; + numbers[i] = temp; + await heapify(numbers, i, 0); + streamController.add(numbers); + } + } + + heapify(List arr, int n, int i) async { + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && arr[l] > arr[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && arr[r] > arr[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = numbers[i]; + numbers[i] = numbers[largest]; + numbers[largest] = temp; + heapify(arr, n, largest); + } + + await Future.delayed(getDuration()); // 延迟操作,用于可视化排序过程 + streamController.add(numbers); + } + + ///插入排序 + insertionSort() async { + for (int i = 1; i < numbers.length; i++) { + int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < numbers[j]) { + numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + await Future.delayed(getDuration()); + streamController.add(numbers); // 更新排序结果 + } + + numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); // 更新排序结果 + } + } + + ///地精排序 (侏儒排序) + gnomeSort() async { + int index = 0; + + while (index < numbers.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (numbers[index] >= numbers[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = numbers[index]; + numbers[index] = numbers[index - 1]; + numbers[index - 1] = temp; + index--; + } + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + return; + } + + ///奇偶排序(Odd-Even Sort) + oddEvenSort() async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= numbers.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + for (int i = 0; i <= numbers.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + return; + } + + ///快速排序 + quickSort(int leftIndex, int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async{ + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = numbers[p]; + numbers[p] = numbers[right]; + numbers[right] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(numbers[i], numbers[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = numbers[i]; + numbers[i] = numbers[cursor]; + numbers[cursor] = temp; + cursor++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 将基准元素放置在游标位置 + temp = numbers[right]; + numbers[right] = numbers[cursor]; + numbers[cursor] = temp; + + await Future.delayed(getDuration()); + streamController.add(numbers); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await quickSort(leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await quickSort(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 + } + } + + ///归并排序 + mergeSort(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] = numbers[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = numbers[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + numbers[k] = leftList[i]; + i++; + } else { + numbers[k] = rightList[j]; + j++; + } + + await Future.delayed(getDuration()); + streamController.add(numbers); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + numbers[k] = leftList[i]; + i++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + while (j < rightSize) { + numbers[k] = rightList[j]; + j++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await mergeSort(leftIndex, middleIndex); + await mergeSort(middleIndex + 1, rightIndex); + + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } + } + + checkAndResetIfSorted() async { + if (isSorted) { + reset(); + await Future.delayed(const Duration(milliseconds: 200)); + } + } + + sort() async { + setState(() { + isSorting = true; + }); + + await checkAndResetIfSorted(); + + Stopwatch stopwatch = Stopwatch()..start(); + + switch (currentSort) { + case "bubble": + await bubbleSort(); + break; + case "coctail": + await cocktailSort(); + break; + case "comb": + await combSort(); + break; + case "pigeonhole": + await pigeonHole(); + break; + case "shell": + await shellSort(); + break; + case "selection": + await selectionSort(); + break; + case "cycle": + await cycleSort(); + break; + case "heap": + await heapSort(); + break; + case "insertion": + await insertionSort(); + break; + case "gnome": + await gnomeSort(); + break; + case "oddeven": + await oddEvenSort(); + break; + case "quick": + await quickSort(0, sampleSize.toInt() - 1); + break; + case "merge": + await mergeSort(0, sampleSize.toInt() - 1); + break; + } + + stopwatch.stop(); + + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + setState(() { + isSorting = false; + isSorted = true; + }); + } + + setSort(String type) { + setState(() { + currentSort = type; + }); + } + + @override + void initState() { + super.initState(); + // reset(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + sampleSize = MediaQuery.of(context).size.width / 2; + for (int i = 0; i < sampleSize; ++i) { + //随机往数组中填值 + numbers.add(Random().nextInt(500)); + } + setState(() {}); + } + + @override + void dispose() { + streamController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "当前选择的是:${getTitle()}", + style: const TextStyle(fontSize: 14), + ), + actions: [ + PopupMenuButton( + initialValue: currentSort, + itemBuilder: (ctx) { + return const [ + PopupMenuItem( + value: 'bubble', + child: Text("Bubble Sort — 冒泡排序"), + ), + PopupMenuItem( + value: 'coctail', + child: Text("Coctail Sort — 鸡尾酒排序(双向冒泡排序)"), + ), + PopupMenuItem( + value: 'comb', + child: Text("Comb Sort — 梳排序"), + ), + PopupMenuItem( + value: 'pigeonhole', + child: Text("pigeonhole Sort — 鸽巢排序"), + ), + PopupMenuItem( + value: 'shell', + child: Text("shell Sort — 希尔排序"), + ), + PopupMenuItem( + value: 'selection', + child: Text("Selection Sort — 选择排序"), + ), + PopupMenuItem( + value: 'cycle', + child: Text("CycleSort — 循环排序"), + ), + PopupMenuItem( + value: 'heap', + child: Text("HeapSort — 堆排序"), + ), + PopupMenuItem( + value: 'insertion', + child: Text("InsertionSort — 插入排序"), + ), + PopupMenuItem( + value: 'gnome', + child: Text("GnomeSort — 地精排序 (侏儒排序)"), + ), + PopupMenuItem( + value: 'oddeven', + child: Text("OddEvenSort — 奇偶排序"), + ), + PopupMenuItem( + value: 'quick', + child: Text("QuickSort — 快速排序"), + ), + PopupMenuItem( + value: 'merge', + child: Text("MergeSort — 归并排序"), + ), + ]; + }, + onSelected: (String value) { + reset(); + setSort(value); + }, + ) + ], + ), + body: StreamBuilder( + initialData: numbers, + stream: streamController.stream, + builder: (context, snapshot) { + List numbers = snapshot.data as List; + int counter = 0; + return Row( + children: numbers.map((int num) { + counter++; + return CustomPaint( + painter: BarPainter( + width: MediaQuery.of(context).size.width / sampleSize, + value: num, + index: counter, + ), + ); + }).toList(), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: isSorting + ? null + : () { + reset(); + setSort(currentSort); + }, + child: const Text("重置")), + ElevatedButton( + onPressed: isSorting ? null : sort, child: const Text("开始排序")), + ElevatedButton( + onPressed: isSorting ? null : changeSpeed, + child: Text( + "${speed + 1}x", + style: const TextStyle(fontSize: 20), + ), + ), + ], + ), + ), + ); + } +} + +class BarPainter extends CustomPainter { + //宽度 + final double width; + + //高度(数组中对应的值) + final int value; + + //位置索引 + final int index; + + BarPainter({required this.width, required this.value, required this.index}); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint(); + if (value < 500 * .10) { + paint.color = Colors.blue.shade100; + } else if (value < 500 * .20) { + paint.color = Colors.blue.shade200; + } else if (value < 500 * .30) { + paint.color = Colors.blue.shade300; + } else if (value < 500 * .40) { + paint.color = Colors.blue.shade400; + } else if (value < 500 * .50) { + paint.color = Colors.blue.shade500; + } else if (value < 500 * .60) { + paint.color = Colors.blue.shade600; + } else if (value < 500 * .70) { + paint.color = Colors.blue.shade700; + } else if (value < 500 * .80) { + paint.color = Colors.blue.shade800; + } else if (value < 500 * .90) { + paint.color = Colors.blue.shade900; + } else { + paint.color = const Color(0xFF011E51); + } + + paint.strokeWidth = width; + paint.strokeCap = StrokeCap.round; + + canvas.drawLine( + Offset(index * width, 0), + Offset( + index * width, + value.ceilToDouble(), + ), + paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/v6/pages/user/user_page.dart b/lib/v6/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v6/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/lib/v6_/app.dart b/lib/v6_/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v6_/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v6_/app/navigation/router/app_router_delegate.dart b/lib/v6_/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..998a32e --- /dev/null +++ b/lib/v6_/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,245 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../../../pages/empty/empty_page.dart'; +import '../../../pages/settings/settings_page.dart'; +import '../../../pages/user/user_page.dart'; +import '../../../pages/counter/counter_page.dart'; +import '../../../pages/sort/views/sort_page.dart'; +import '../transition/fade_transition_page.dart'; +import '../../../pages/color/color_add_page.dart'; + +const List kDestinationsPaths = [ + '/color', + '/counter', + '/sort', + '/user', + '/settings', +]; + +AppRouterDelegate router = AppRouterDelegate(); + +class AppRouterDelegate extends RouterDelegate with ChangeNotifier { + + AppRouterDelegate({String initial = '/'}){ + changePath('/color',keepAlive: true); + } + + String _path = '/color'; + + String get path => _path; + + int? get activeIndex { + if(path.startsWith('/color')) return 0; + if(path.startsWith('/counter')) return 1; + if(path.startsWith('/sort')) return 2; + if(path.startsWith('/user')) return 3; + if(path.startsWith('/settings')) return 4; + return null; + } + + final Map> _completerMap = {}; + + Completer? completer; + + final Map> _alivePageMap = {}; + + void setPathKeepLive(String value){ + _alivePageMap[value] = _buildPageByPath(value); + path = value; + } + + final Map _pathExtraMap = {}; + + FutureOr changePath(String value,{bool forResult=false,Object? extra,bool keepAlive = false}){ + if(forResult){ + _completerMap[value] = Completer(); + } + if(keepAlive){ + _alivePageMap[value] = _buildPageByPath(value); + } + if(extra!=null){ + _pathExtraMap[value] = extra; + } + path = value; + if(forResult){ + return _completerMap[value]!.future; + } + } + + + set path(String value) { + if (_path == value) return; + _path = value; + notifyListeners(); + } + + @override + Widget build(BuildContext context) { + List pages = []; + if(_alivePageMap.containsKey(path)){ + for (String key in _alivePageMap.keys) { + if(path!=key){ + pages.addAll(_alivePageMap[key]!); + } + } + pages.addAll(_alivePageMap[path]!) ; + }else{ + for (var element in _alivePageMap.values) { + pages.addAll(element); + } + List currentPages = _buildPageByPath(path); + pages.removeWhere((element) { + return currentPages.map((e) => e.key).contains(element.key); + }); + // 移除 pages 中和 currentPages 具有相同key 的元素 + pages.addAll(_buildPageByPath(path)); + } + + return Navigator( + onPopPage: _onPopPage, + pages: pages.toSet().toList(), + ); + } + + List _buildPageByPath(String path) { + Widget? child; + if(path.startsWith('/color')){ + return buildColorPages(path); + } + + if (path == kDestinationsPaths[1]) { + child = const CounterPage(); + } + if (path == kDestinationsPaths[2]) { + child = SortPage(); + } + if (path == kDestinationsPaths[3]) { + child = const UserPage(); + } + if (path == kDestinationsPaths[4]) { + child = const SettingPage(); + } + return [ + FadeTransitionPage( + key: ValueKey(path), + child: child ?? const EmptyPage(), + ) + ]; + } + + List buildColorPages(String path){ + List result = []; + Uri uri = Uri.parse(path); + for (String segment in uri.pathSegments) { + if(segment == 'color'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color'), + child:ColorPage(), + )); + } + if(segment =='detail'){ + final Map queryParams = uri.queryParameters; + String? selectedColor = queryParams['color']; + + if (selectedColor != null) { + Color color = Color(int.parse(selectedColor, radix: 16)); + result.add( FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: color), + )); + }else{ + Color? selectedColor = _pathExtraMap[path]; + if (selectedColor != null) { + result.add( FadeTransitionPage( + key: const ValueKey('/color/detail'), + child:ColorDetailPage(color: selectedColor), + )); + _pathExtraMap.remove(path); + } + } + } + if(segment == 'add'){ + result.add( const FadeTransitionPage( + key: ValueKey('/color/add'), + child:ColorAddPage(), + )); + } + + } + return result; + } + + @override + Future popRoute() async { + print('=======popRoute========='); + return true; + } + + bool _onPopPage(Route route, result) { + if(_completerMap.containsKey(path)){ + _completerMap[path]?.complete(result); + _completerMap.remove(path); + } + + path = backPath(path); + return route.didPop(result); + } + + String backPath(String path){ + Uri uri = Uri.parse(path); + if(uri.pathSegments.length==1) return path; + List parts = List.of(uri.pathSegments)..removeLast(); + return '/${parts.join('/')}'; + } + + @override + Future setNewRoutePath(configuration) async {} +} + + + +// class AppRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { +// +// List _value = ['/']; +// +// +// List get value => _value; +// +// set value(List value){ +// _value = value; +// notifyListeners(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Navigator( +// onPopPage: _onPopPage, +// pages: _value.map((e) => _pageMap[e]!).toList(), +// ); +// } +// +// final Map _pageMap = const { +// '/': MaterialPage(child: HomePage()), +// 'a': MaterialPage(child: PageA()), +// 'b': MaterialPage(child: PageB()), +// 'c': MaterialPage(child: PageC()), +// }; +// +// bool _onPopPage(Route route, result) { +// _value = List.of(_value)..removeLast(); +// notifyListeners(); +// return route.didPop(result); +// } +// +// @override +// GlobalKey? navigatorKey = GlobalKey(); +// +// @override +// Future setNewRoutePath(String configuration) async{ +// } +// } diff --git a/lib/v6_/app/navigation/router/iroute.dart b/lib/v6_/app/navigation/router/iroute.dart new file mode 100644 index 0000000..ef2160d --- /dev/null +++ b/lib/v6_/app/navigation/router/iroute.dart @@ -0,0 +1,105 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../pages/color/color_add_page.dart'; +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../transition/fade_transition_page.dart'; + +class IRoute { + final String path; + final IRoutePageBuilder builder; + final List children; + + const IRoute({ + required this.path, + this.children = const [], + required this.builder, + }); + + @override + String toString() { + return 'IRoute{path: $path, children: $children}'; + } + + List list() { + return []; + } +} + +typedef IRoutePageBuilder = Page? Function( + BuildContext context, IRouteData data); + +class IRouteData { + final Object? extra; + final bool forResult; + final Uri uri; + final bool keepAlive; + + IRouteData({ + required this.extra, + required this.uri, + required this.forResult, + required this.keepAlive, + }); +} + +List kDestinationsIRoutes = [ + IRoute( + path: '/color', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color'), + child: ColorPage(), + ); + }, + children: [ + IRoute( + path: '/color/detail', + builder: (ctx, data) { + final Map queryParams = data.uri.queryParameters; + String? selectedColor = queryParams['color']; + if (selectedColor != null) { + Color color = Color(int.parse(selectedColor, radix: 16)); + return FadeTransitionPage( + key: const ValueKey('/color/detail'), + child: ColorDetailPage(color: color), + ); + } + return null; + }, + ), + IRoute( + path: '/color/add', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/color/add'), + child: ColorAddPage(), + ); + }), + ], + ), + IRoute( + path: '/counter', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/counter'), + child: ColorAddPage(), + ); + }), + IRoute( + path: '/user', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/user'), + child: ColorAddPage(), + ); + }), + IRoute( + path: '/settings', + builder: (ctx, data) { + return const FadeTransitionPage( + key: ValueKey('/settings'), + child: ColorAddPage(), + ); + }), +]; diff --git a/lib/v6_/app/navigation/transition/fade_transition_page.dart b/lib/v6_/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..552171b --- /dev/null +++ b/lib/v6_/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,53 @@ +// 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/v6_/app/navigation/transition/no_transition_page.dart b/lib/v6_/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v6_/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/v6_/app/navigation/views/app_navigation.dart b/lib/v6_/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..e1d002d --- /dev/null +++ b/lib/v6_/app/navigation/views/app_navigation.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import '../../../pages/sort/sort_setting.dart'; +import '../router/app_router_delegate.dart'; +import 'app_navigation_rail.dart'; +import 'app_top_bar.dart'; + +class AppNavigation extends StatelessWidget { + const AppNavigation({super.key}); + + @override + Widget build(BuildContext context) { + double px1 = 1/View.of(context).devicePixelRatio; + return Scaffold( + endDrawer: Drawer( + child: SortSettings(), + ), + body: Row( + children: [ + const AppNavigationRail(), + Expanded( + child: Column( + children: [ + const AppTopBar(), + Divider(height: px1,), + Expanded( + child: Router( + routerDelegate: router, + backButtonDispatcher: RootBackButtonDispatcher(), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/v6_/app/navigation/views/app_navigation_rail.dart b/lib/v6_/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..29ea0b3 --- /dev/null +++ b/lib/v6_/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + + final List deskNavBarMenus = const [ + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), + MenuMeta(label: '计数器', icon: Icons.add_chart), + MenuMeta(label: '排序', icon: Icons.sort), + MenuMeta(label: '我的', icon: Icons.person), + MenuMeta(label: '设置', icon: Icons.settings), + ]; + + @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.4',style: TextStyle(color: Colors.white,fontSize: 12),), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: router.activeIndex, + ), + ); + + } + + void _onDestinationSelected(int index) { + if(index==1){ + router.changePath(kDestinationsPaths[index],keepAlive: true); + }else{ + router.path = kDestinationsPaths[index]; + } + } + + void _onRouterChange() { + setState(() {}); + } +} diff --git a/lib/v6_/app/navigation/views/app_router_editor.dart b/lib/v6_/app/navigation/views/app_router_editor.dart new file mode 100644 index 0000000..9173948 --- /dev/null +++ b/lib/v6_/app/navigation/views/app_router_editor.dart @@ -0,0 +1,79 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:iroute/components/toly_ui/button/hover_icon_button.dart'; +import 'package:iroute/components/toly_ui/popable/drop_selectable_widget.dart'; +import '../../../pages/sort/functions.dart'; +import '../../../pages/sort/views/sort_bar.dart'; +import '../../../pages/sort/views/sort_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; + setState(() { + + }); + } + + @override + void dispose() { + _controller.dispose(); + router.removeListener(_onRouteChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + print(router.path); + if(router.path=='/sort'){ + return SortBar(); + } + + return SizedBox( + width: 250, + child: 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/v6_/app/navigation/views/app_top_bar.dart b/lib/v6_/app/navigation/views/app_top_bar.dart new file mode 100644 index 0000000..84890eb --- /dev/null +++ b/lib/v6_/app/navigation/views/app_top_bar.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../../../pages/sort/functions.dart'; +import '../router/app_router_delegate.dart'; +import 'app_router_editor.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 RouterIndicator(), + Expanded( + child: Row(children: [ + const Spacer(), + SizedBox( + // width: 200, + child: AppRouterEditor( + onSubmit: (path) => router.path = path, + )), + 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(); +} + +Map kRouteLabelMap = { + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/user': '我的', + '/sort': '可视化排序', + '/settings': '系统设置', +}; + +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.path = 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] ?? '未知路由'; + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + return result; + } +} diff --git a/lib/v6_/app/unit_app.dart b/lib/v6_/app/unit_app.dart new file mode 100644 index 0000000..36e67ab --- /dev/null +++ b/lib/v6_/app/unit_app.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import '../pages/sort/provider/state.dart'; +import 'navigation/router/app_router_delegate.dart'; +import 'navigation/views/app_navigation.dart'; +import 'navigation/views/app_navigation_rail.dart'; + +class UnitApp extends StatelessWidget { + const UnitApp({super.key}); + + @override + Widget build(BuildContext context) { + return SortStateScope( + notifier: SortState(), + child: MaterialApp( + theme: ThemeData( + fontFamily: "宋体", + scaffoldBackgroundColor: Colors.white, + 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/v6_/pages/color/color_add_page.dart b/lib/v6_/pages/color/color_add_page.dart new file mode 100644 index 0000000..c6ca0cd --- /dev/null +++ b/lib/v6_/pages/color/color_add_page.dart @@ -0,0 +1,102 @@ +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 _selectColor() { + Navigator.of(context).pop(_color); + } + + void changeColor(Color value) { + _color = value; + setState(() { + + }); + } +} diff --git a/lib/v6_/pages/color/color_detail_page.dart b/lib/v6_/pages/color/color_detail_page.dart new file mode 100644 index 0000000..17fcd17 --- /dev/null +++ b/lib/v6_/pages/color/color_detail_page.dart @@ -0,0 +1,34 @@ +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( + // appBar: AppBar( + // systemOverlayStyle: SystemUiOverlayStyle( + // statusBarColor: Colors.transparent, + // statusBarIconBrightness: Brightness.light + // ), + // iconTheme: IconThemeData(color: Colors.white), + // titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) , + // backgroundColor: color, + // title: Text('颜色界面',),), + body: Container( + alignment: Alignment.center, + color: color, + child: Text(text ,style: style,), + ), + ); + } +} diff --git a/lib/v6_/pages/color/color_page.dart b/lib/v6_/pages/color/color_page.dart new file mode 100644 index 0000000..6764129 --- /dev/null +++ b/lib/v6_/pages/color/color_page.dart @@ -0,0 +1,54 @@ +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 + 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.setPathForData('/color/detail',color); + // router.setPathKeepLive('/color/detail?color=$value'); + + } + + void _toAddPage() async { + Color? color = await router.changePath('/color/add',forResult: true); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v6_/pages/counter/counter_page.dart b/lib/v6_/pages/counter/counter_page.dart new file mode 100644 index 0000000..b5b2e17 --- /dev/null +++ b/lib/v6_/pages/counter/counter_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CounterPage extends StatefulWidget { + const CounterPage({super.key}); + + @override + State createState() => _CounterPageState(); +} + +class _CounterPageState extends State { + int _counter = 0; + + 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/v6_/pages/empty/empty_page.dart b/lib/v6_/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v6_/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/v6_/pages/settings/settings_page.dart b/lib/v6_/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v6_/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/v5/pages/sort/functions.dart b/lib/v6_/pages/sort/functions.dart similarity index 100% rename from lib/v5/pages/sort/functions.dart rename to lib/v6_/pages/sort/functions.dart diff --git a/lib/v5/pages/sort/functions/bubble.dart b/lib/v6_/pages/sort/functions/bubble.dart similarity index 100% rename from lib/v5/pages/sort/functions/bubble.dart rename to lib/v6_/pages/sort/functions/bubble.dart diff --git a/lib/v5/pages/sort/functions/cocktail.dart b/lib/v6_/pages/sort/functions/cocktail.dart similarity index 100% rename from lib/v5/pages/sort/functions/cocktail.dart rename to lib/v6_/pages/sort/functions/cocktail.dart diff --git a/lib/v5/pages/sort/functions/comb.dart b/lib/v6_/pages/sort/functions/comb.dart similarity index 100% rename from lib/v5/pages/sort/functions/comb.dart rename to lib/v6_/pages/sort/functions/comb.dart diff --git a/lib/v5/pages/sort/functions/cycle.dart b/lib/v6_/pages/sort/functions/cycle.dart similarity index 100% rename from lib/v5/pages/sort/functions/cycle.dart rename to lib/v6_/pages/sort/functions/cycle.dart diff --git a/lib/v5/pages/sort/functions/gnome.dart b/lib/v6_/pages/sort/functions/gnome.dart similarity index 100% rename from lib/v5/pages/sort/functions/gnome.dart rename to lib/v6_/pages/sort/functions/gnome.dart diff --git a/lib/v5/pages/sort/functions/heap.dart b/lib/v6_/pages/sort/functions/heap.dart similarity index 100% rename from lib/v5/pages/sort/functions/heap.dart rename to lib/v6_/pages/sort/functions/heap.dart diff --git a/lib/v5/pages/sort/functions/insertion.dart b/lib/v6_/pages/sort/functions/insertion.dart similarity index 100% rename from lib/v5/pages/sort/functions/insertion.dart rename to lib/v6_/pages/sort/functions/insertion.dart diff --git a/lib/v5/pages/sort/functions/merage.dart b/lib/v6_/pages/sort/functions/merage.dart similarity index 100% rename from lib/v5/pages/sort/functions/merage.dart rename to lib/v6_/pages/sort/functions/merage.dart diff --git a/lib/v5/pages/sort/functions/oddEven.dart b/lib/v6_/pages/sort/functions/oddEven.dart similarity index 100% rename from lib/v5/pages/sort/functions/oddEven.dart rename to lib/v6_/pages/sort/functions/oddEven.dart diff --git a/lib/v5/pages/sort/functions/pigeonHole.dart b/lib/v6_/pages/sort/functions/pigeonHole.dart similarity index 100% rename from lib/v5/pages/sort/functions/pigeonHole.dart rename to lib/v6_/pages/sort/functions/pigeonHole.dart diff --git a/lib/v5/pages/sort/functions/quick.dart b/lib/v6_/pages/sort/functions/quick.dart similarity index 100% rename from lib/v5/pages/sort/functions/quick.dart rename to lib/v6_/pages/sort/functions/quick.dart diff --git a/lib/v5/pages/sort/functions/selection.dart b/lib/v6_/pages/sort/functions/selection.dart similarity index 100% rename from lib/v5/pages/sort/functions/selection.dart rename to lib/v6_/pages/sort/functions/selection.dart diff --git a/lib/v5/pages/sort/functions/shell.dart b/lib/v6_/pages/sort/functions/shell.dart similarity index 100% rename from lib/v5/pages/sort/functions/shell.dart rename to lib/v6_/pages/sort/functions/shell.dart diff --git a/lib/v5/pages/sort/provider/sort_config.dart b/lib/v6_/pages/sort/provider/sort_config.dart similarity index 100% rename from lib/v5/pages/sort/provider/sort_config.dart rename to lib/v6_/pages/sort/provider/sort_config.dart diff --git a/lib/v5/pages/sort/provider/state.dart b/lib/v6_/pages/sort/provider/state.dart similarity index 100% rename from lib/v5/pages/sort/provider/state.dart rename to lib/v6_/pages/sort/provider/state.dart diff --git a/lib/v6_/pages/sort/sort_page.dart b/lib/v6_/pages/sort/sort_page.dart new file mode 100644 index 0000000..d440071 --- /dev/null +++ b/lib/v6_/pages/sort/sort_page.dart @@ -0,0 +1,859 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class SortPage extends StatefulWidget { + const SortPage({Key? key}) : super(key: key); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + //存放随机数组 + List numbers = []; + + //订阅流 + StreamController> streamController = StreamController(); + String currentSort = 'bubble'; + + //柱子的数量 -> 生成排序数组的长度 + double sampleSize = 0; + + //是否排序 + bool isSorted = false; + + //是否在排序中 + bool isSorting = false; + + //排序动画更新的速度 + int speed = 0; + + static int duration = 1500; + + String getTitle() { + switch (currentSort) { + case "bubble": + return "Bubble Sort"; + case "coctail": + return "Coctail Sort"; + case "comb": + return "Comb Sort"; + case "pigeonhole": + return "Pigeonhole Sort"; + case "shell": + return "Shell Sort"; + case "selection": + return "Selection Sort"; + case "cycle": + return "Cycle Sort"; + case "heap": + return "Heap Sort"; + case "insertion": + return "Insertion Sort"; + case "gnome": + return "Gnome Sort"; + case "oddeven": + return "OddEven Sort"; + case "quick": + return "Quick Sort"; + case "merge": + return "Merge Sort"; + } + return ""; + } + + reset() { + isSorted = false; + numbers = []; + for (int i = 0; i < sampleSize; ++i) { + numbers.add(Random().nextInt(500)); + } + streamController.add(numbers); + } + + Duration getDuration() { + return Duration(microseconds: duration); + } + + ///动画时间 + changeSpeed() { + if (speed >= 3) { + speed = 0; + duration = 1500; + } else { + speed++; + duration = duration ~/ 2; + } + setState(() {}); + } + + ///冒泡排序 + bubbleSort() async { + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < numbers.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < numbers.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (numbers[j] > numbers[j + 1]) { + int temp = numbers[j]; + numbers[j] = numbers[j + 1]; + numbers[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); + } + } + } + + ///鸡尾酒排序(双向冒泡排序) + cocktailSort() async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = numbers.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } + } + + ///梳排序(Comb Sort) + combSort() async { + int gap = numbers.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < numbers.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (numbers[i] > numbers[i + gap]) { + int temp = numbers[i]; + numbers[i] = numbers[i + gap]; + numbers[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; + } + + ///鸽巢排序 + pigeonHole() async { + int min = numbers[0]; + int max = numbers[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < numbers.length; a++) { + if (numbers[a] > max) max = numbers[a]; + if (numbers[a] < min) min = numbers[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < numbers.length; i++) { + p[numbers[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + numbers[index++] = j + min; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///希尔排序 + shellSort() async { + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < numbers.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = numbers[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) { + numbers[j] = numbers[j - gap]; + } + numbers[j] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///选择排序 + selectionSort() async { + for (int i = 0; i < numbers.length; i++) { + for (int j = i + 1; j < numbers.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (numbers[i] > numbers[j]) { + // 判断当前元素是否比后续元素小 + int temp = numbers[j]; + // 交换当前元素和后续较小的元素 + numbers[j] = numbers[i]; + numbers[i] = temp; + } + + await Future.delayed(getDuration(), () {}); + + streamController.add(numbers); + } + } + } + + ///循环排序 + cycleSort() async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) { + int item = numbers[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < numbers.length; i++) { + if (numbers[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == numbers[pos]) { + pos += 1; + } + if (item != numbers[pos]) { + int temp = item; + item = numbers[pos]; + numbers[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + ///堆排序 + heapSort() async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = numbers.length ~/ 2; i >= 0; i--) { + await heapify(numbers, numbers.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = numbers.length - 1; i >= 0; i--) { + int temp = numbers[0]; + numbers[0] = numbers[i]; + numbers[i] = temp; + await heapify(numbers, i, 0); + streamController.add(numbers); + } + } + + heapify(List arr, int n, int i) async { + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && arr[l] > arr[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && arr[r] > arr[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = numbers[i]; + numbers[i] = numbers[largest]; + numbers[largest] = temp; + heapify(arr, n, largest); + } + + await Future.delayed(getDuration()); // 延迟操作,用于可视化排序过程 + streamController.add(numbers); + } + + ///插入排序 + insertionSort() async { + for (int i = 1; i < numbers.length; i++) { + int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < numbers[j]) { + numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + await Future.delayed(getDuration()); + streamController.add(numbers); // 更新排序结果 + } + + numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await Future.delayed(getDuration(), () {}); + streamController.add(numbers); // 更新排序结果 + } + } + + ///地精排序 (侏儒排序) + gnomeSort() async { + int index = 0; + + while (index < numbers.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (numbers[index] >= numbers[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = numbers[index]; + numbers[index] = numbers[index - 1]; + numbers[index - 1] = temp; + index--; + } + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + return; + } + + ///奇偶排序(Odd-Even Sort) + oddEvenSort() async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= numbers.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + for (int i = 0; i <= numbers.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (numbers[i] > numbers[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = numbers[i]; + numbers[i] = numbers[i + 1]; + numbers[i + 1] = temp; + isSorted = false; + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + } + + return; + } + + ///快速排序 + quickSort(int leftIndex, int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async{ + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = numbers[p]; + numbers[p] = numbers[right]; + numbers[right] = temp; + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(numbers[i], numbers[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = numbers[i]; + numbers[i] = numbers[cursor]; + numbers[cursor] = temp; + cursor++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 将基准元素放置在游标位置 + temp = numbers[right]; + numbers[right] = numbers[cursor]; + numbers[cursor] = temp; + + await Future.delayed(getDuration()); + streamController.add(numbers); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await quickSort(leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await quickSort(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 + } + } + + ///归并排序 + mergeSort(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] = numbers[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = numbers[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + numbers[k] = leftList[i]; + i++; + } else { + numbers[k] = rightList[j]; + j++; + } + + await Future.delayed(getDuration()); + streamController.add(numbers); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + numbers[k] = leftList[i]; + i++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + + while (j < rightSize) { + numbers[k] = rightList[j]; + j++; + k++; + + await Future.delayed(getDuration()); + streamController.add(numbers); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await mergeSort(leftIndex, middleIndex); + await mergeSort(middleIndex + 1, rightIndex); + + await Future.delayed(getDuration()); + streamController.add(numbers); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } + } + + checkAndResetIfSorted() async { + if (isSorted) { + reset(); + await Future.delayed(const Duration(milliseconds: 200)); + } + } + + sort() async { + setState(() { + isSorting = true; + }); + + await checkAndResetIfSorted(); + + Stopwatch stopwatch = Stopwatch()..start(); + + switch (currentSort) { + case "bubble": + await bubbleSort(); + break; + case "coctail": + await cocktailSort(); + break; + case "comb": + await combSort(); + break; + case "pigeonhole": + await pigeonHole(); + break; + case "shell": + await shellSort(); + break; + case "selection": + await selectionSort(); + break; + case "cycle": + await cycleSort(); + break; + case "heap": + await heapSort(); + break; + case "insertion": + await insertionSort(); + break; + case "gnome": + await gnomeSort(); + break; + case "oddeven": + await oddEvenSort(); + break; + case "quick": + await quickSort(0, sampleSize.toInt() - 1); + break; + case "merge": + await mergeSort(0, sampleSize.toInt() - 1); + break; + } + + stopwatch.stop(); + + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + setState(() { + isSorting = false; + isSorted = true; + }); + } + + setSort(String type) { + setState(() { + currentSort = type; + }); + } + + @override + void initState() { + super.initState(); + // reset(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + sampleSize = MediaQuery.of(context).size.width / 2; + for (int i = 0; i < sampleSize; ++i) { + //随机往数组中填值 + numbers.add(Random().nextInt(500)); + } + setState(() {}); + } + + @override + void dispose() { + streamController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "当前选择的是:${getTitle()}", + style: const TextStyle(fontSize: 14), + ), + actions: [ + PopupMenuButton( + initialValue: currentSort, + itemBuilder: (ctx) { + return const [ + PopupMenuItem( + value: 'bubble', + child: Text("Bubble Sort — 冒泡排序"), + ), + PopupMenuItem( + value: 'coctail', + child: Text("Coctail Sort — 鸡尾酒排序(双向冒泡排序)"), + ), + PopupMenuItem( + value: 'comb', + child: Text("Comb Sort — 梳排序"), + ), + PopupMenuItem( + value: 'pigeonhole', + child: Text("pigeonhole Sort — 鸽巢排序"), + ), + PopupMenuItem( + value: 'shell', + child: Text("shell Sort — 希尔排序"), + ), + PopupMenuItem( + value: 'selection', + child: Text("Selection Sort — 选择排序"), + ), + PopupMenuItem( + value: 'cycle', + child: Text("CycleSort — 循环排序"), + ), + PopupMenuItem( + value: 'heap', + child: Text("HeapSort — 堆排序"), + ), + PopupMenuItem( + value: 'insertion', + child: Text("InsertionSort — 插入排序"), + ), + PopupMenuItem( + value: 'gnome', + child: Text("GnomeSort — 地精排序 (侏儒排序)"), + ), + PopupMenuItem( + value: 'oddeven', + child: Text("OddEvenSort — 奇偶排序"), + ), + PopupMenuItem( + value: 'quick', + child: Text("QuickSort — 快速排序"), + ), + PopupMenuItem( + value: 'merge', + child: Text("MergeSort — 归并排序"), + ), + ]; + }, + onSelected: (String value) { + reset(); + setSort(value); + }, + ) + ], + ), + body: StreamBuilder( + initialData: numbers, + stream: streamController.stream, + builder: (context, snapshot) { + List numbers = snapshot.data as List; + int counter = 0; + return Row( + children: numbers.map((int num) { + counter++; + return CustomPaint( + painter: BarPainter( + width: MediaQuery.of(context).size.width / sampleSize, + value: num, + index: counter, + ), + ); + }).toList(), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: isSorting + ? null + : () { + reset(); + setSort(currentSort); + }, + child: const Text("重置")), + ElevatedButton( + onPressed: isSorting ? null : sort, child: const Text("开始排序")), + ElevatedButton( + onPressed: isSorting ? null : changeSpeed, + child: Text( + "${speed + 1}x", + style: const TextStyle(fontSize: 20), + ), + ), + ], + ), + ), + ); + } +} + +class BarPainter extends CustomPainter { + //宽度 + final double width; + + //高度(数组中对应的值) + final int value; + + //位置索引 + final int index; + + BarPainter({required this.width, required this.value, required this.index}); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint(); + if (value < 500 * .10) { + paint.color = Colors.blue.shade100; + } else if (value < 500 * .20) { + paint.color = Colors.blue.shade200; + } else if (value < 500 * .30) { + paint.color = Colors.blue.shade300; + } else if (value < 500 * .40) { + paint.color = Colors.blue.shade400; + } else if (value < 500 * .50) { + paint.color = Colors.blue.shade500; + } else if (value < 500 * .60) { + paint.color = Colors.blue.shade600; + } else if (value < 500 * .70) { + paint.color = Colors.blue.shade700; + } else if (value < 500 * .80) { + paint.color = Colors.blue.shade800; + } else if (value < 500 * .90) { + paint.color = Colors.blue.shade900; + } else { + paint.color = const Color(0xFF011E51); + } + + paint.strokeWidth = width; + paint.strokeCap = StrokeCap.round; + + canvas.drawLine( + Offset(index * width, 0), + Offset( + index * width, + value.ceilToDouble(), + ), + paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/v5/pages/sort/sort_setting.dart b/lib/v6_/pages/sort/sort_setting.dart similarity index 100% rename from lib/v5/pages/sort/sort_setting.dart rename to lib/v6_/pages/sort/sort_setting.dart diff --git a/lib/v5/pages/sort/views/data_painter.dart b/lib/v6_/pages/sort/views/data_painter.dart similarity index 100% rename from lib/v5/pages/sort/views/data_painter.dart rename to lib/v6_/pages/sort/views/data_painter.dart diff --git a/lib/v5/pages/sort/views/sort_bar.dart b/lib/v6_/pages/sort/views/sort_bar.dart similarity index 93% rename from lib/v5/pages/sort/views/sort_bar.dart rename to lib/v6_/pages/sort/views/sort_bar.dart index ab401eb..20c0705 100644 --- a/lib/v5/pages/sort/views/sort_bar.dart +++ b/lib/v6_/pages/sort/views/sort_bar.dart @@ -10,11 +10,13 @@ class SortBar extends StatelessWidget { @override Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); return Row( children: [ const SortButton(), const SizedBox(width: 10,), DropSelectableWidget( + value: sortNameMap[state.config.name]!, fontSize: 12, data: sortNameMap.values.toList(), iconSize: 20, diff --git a/lib/v5/pages/sort/views/sort_button.dart b/lib/v6_/pages/sort/views/sort_button.dart similarity index 100% rename from lib/v5/pages/sort/views/sort_button.dart rename to lib/v6_/pages/sort/views/sort_button.dart diff --git a/lib/v5/pages/sort/views/sort_page.dart b/lib/v6_/pages/sort/views/sort_page.dart similarity index 100% rename from lib/v5/pages/sort/views/sort_page.dart rename to lib/v6_/pages/sort/views/sort_page.dart diff --git a/lib/v6_/pages/user/user_page.dart b/lib/v6_/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v6_/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/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e180b20..ea24344 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,13 +6,11 @@ import FlutterMacOS import Foundation import screen_retriever -import shared_preferences_foundation import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 8cbe93b..9cee4ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -73,22 +81,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.0" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -137,7 +129,7 @@ packages: source: hosted version: "2.1.1" logging: - dependency: "direct main" + dependency: transitive description: name: logging sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" @@ -184,38 +176,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.8.3" - path_provider_linux: - dependency: transitive + path_to_regexp: + dependency: "direct main" description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + name: path_to_regexp + sha256: "169d78fbd55e61ea8873bcca545979f559d22238f66facdd7ef30870c7f53327" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.1" - platform: - dependency: transitive - description: - name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.3" + version: "0.4.0" plugin_platform_interface: dependency: transitive description: @@ -232,6 +200,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.0.5" + quiver: + dependency: "direct main" + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" screen_retriever: dependency: transitive description: @@ -240,62 +216,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.9" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.2" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.1" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.2" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.1" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.2" sky_engine: dependency: transitive description: flutter @@ -413,6 +333,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.8" + url_strategy: + dependency: "direct main" + description: + name: url_strategy + sha256: "42b68b42a9864c4d710401add17ad06e28f1c1d5500c93b98c431f6b0ea4ab87" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.0" vector_math: dependency: transitive description: @@ -429,14 +357,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.4-beta" - win32: - dependency: transitive - description: - name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.0.9" window_manager: dependency: "direct main" description: @@ -445,14 +365,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.3.7" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.3" sdks: dart: ">=3.1.0 <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index d4f622c..947280d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: flutter_colorpicker: ^1.0.3 window_manager: ^0.3.7 adaptive_navigation: ^0.0.4 - go_router: ^10.1.0 + go_router: ^12.0.0 provider: 6.0.5 url_launcher: ^6.0.7 equatable: ^2.0.5 @@ -43,8 +43,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - toly_menu: - path: E:\Projects\Flutter\packages\toly_menu dev_dependencies: flutter_test: