From 8fb4bf57d6d7de0a59dc3a1afaf4432907dad5bf Mon Sep 17 00:00:00 2001 From: toly <1981462002@qq.com> Date: Wed, 8 Nov 2023 09:35:29 +0800 Subject: [PATCH] v8 --- .../input/decoration/code_decoration.dart | 175 +++++++++++++ lib/go_router/examples/input/input.dart | 215 ++++++++++++++++ .../examples/input/input_painter.dart | 141 +++++++++++ lib/go_router/examples/input/main.dart | 25 ++ .../router/app_router_delegate.dart | 15 +- .../navigation/views/app_navigation_rail.dart | 13 +- lib/v7/pages/sort/views/sort_bar.dart | 48 ---- lib/v7/pages/sort/views/sort_page.dart | 63 ++--- .../router/app_router_delegate.dart | 137 +++++++---- lib/v8/app/navigation/router/iroute.dart | 33 +-- .../app/navigation/router/iroute_config.dart | 40 ++- lib/v8/app/navigation/router/routes.dart | 20 +- .../router/views/route_back_indicator.dart | 52 ++++ .../navigation/views/app_navigation_rail.dart | 38 +-- .../views/app_top_bar/app_top_bar.dart | 3 + lib/v8/pages/sort/provider/sort_config.dart | 4 + lib/v8/pages/sort/provider/state.dart | 21 +- .../sort/views/{ => player}/data_painter.dart | 24 +- .../pages/sort/views/player/sort_player.dart | 22 ++ .../sort/views/settings/color_picker.dart | 41 ++++ .../sort/views/settings/sort_setting.dart | 169 +++++++++++++ lib/v8/pages/sort/views/sort_bar.dart | 48 ---- lib/v8/pages/sort/views/sort_page.dart | 185 -------------- .../views/{ => sort_page}/sort_button.dart | 3 +- .../pages/sort/views/sort_page/sort_page.dart | 230 ++++++++++++++++++ lib/v8/pages/sort/views/sort_setting.dart | 109 --------- lib/v9/app.dart | 1 + .../router/app_router_delegate.dart | 220 +++++++++++++++++ lib/v9/app/navigation/router/iroute.dart | 116 +++++++++ .../app/navigation/router/iroute_config.dart | 71 ++++++ .../router/route_history_manager.dart | 47 ++++ lib/v9/app/navigation/router/routes.dart | 55 +++++ .../router/views/not_find_view.dart | 25 ++ .../router/views/route_back_indicator.dart | 52 ++++ .../fade_page_transitions_builder.dart | 43 ++++ .../transition/fade_transition_page.dart | 52 ++++ .../transition/no_transition_page.dart | 47 ++++ .../app/navigation/views/app_navigation.dart | 34 +++ .../navigation/views/app_navigation_rail.dart | 86 +++++++ .../views/app_top_bar/app_router_editor.dart | 64 +++++ .../views/app_top_bar/app_top_bar.dart | 114 +++++++++ .../views/app_top_bar/history_view_icon.dart | 158 ++++++++++++ .../app_top_bar/route_history_button.dart | 58 +++++ lib/v9/app/unit_app.dart | 43 ++++ lib/v9/pages/color/color_add_page.dart | 99 ++++++++ lib/v9/pages/color/color_detail_page.dart | 25 ++ lib/v9/pages/color/color_page.dart | 53 ++++ lib/v9/pages/counter/counter_page.dart | 43 ++++ lib/v9/pages/empty/empty_page.dart | 30 +++ lib/v9/pages/settings/settings_page.dart | 11 + lib/v9/pages/sort/functions.dart | 46 ++++ lib/v9/pages/sort/functions/bubble.dart | 19 ++ lib/v9/pages/sort/functions/cocktail.dart | 52 ++++ lib/v9/pages/sort/functions/comb.dart | 34 +++ lib/v9/pages/sort/functions/cycle.dart | 54 ++++ lib/v9/pages/sort/functions/gnome.dart | 22 ++ lib/v9/pages/sort/functions/heap.dart | 38 +++ lib/v9/pages/sort/functions/insertion.dart | 19 ++ lib/v9/pages/sort/functions/merage.dart | 79 ++++++ lib/v9/pages/sort/functions/oddEven.dart | 37 +++ lib/v9/pages/sort/functions/pigeonHole.dart | 33 +++ lib/v9/pages/sort/functions/quick.dart | 65 +++++ lib/v9/pages/sort/functions/selection.dart | 18 ++ lib/v9/pages/sort/functions/shell.dart | 21 ++ lib/v9/pages/sort/provider/sort_config.dart | 35 +++ lib/v9/pages/sort/provider/state.dart | 101 ++++++++ .../pages/sort/views/code_page/code_page.dart | 0 .../pages/sort/views/player/data_painter.dart | 62 +++++ .../pages/sort/views/player/sort_player.dart | 22 ++ .../sort/views/settings/color_picker.dart | 41 ++++ .../sort/views/settings/sort_setting.dart | 169 +++++++++++++ .../sort/views/sort_page/sort_button.dart | 57 +++++ .../pages/sort/views/sort_page/sort_page.dart | 230 ++++++++++++++++++ lib/v9/pages/user/user_page.dart | 11 + .../{add.dart => 02_link_list_add.dart} | 2 +- test/algorithm/1052.dart | 104 ++++++++ test/algorithm/643_max_sub_list_of_k.dart | 85 +++++++ test/set/main.dart | 11 + 78 files changed, 4344 insertions(+), 544 deletions(-) create mode 100644 lib/go_router/examples/input/decoration/code_decoration.dart create mode 100644 lib/go_router/examples/input/input.dart create mode 100644 lib/go_router/examples/input/input_painter.dart create mode 100644 lib/go_router/examples/input/main.dart delete mode 100644 lib/v7/pages/sort/views/sort_bar.dart create mode 100644 lib/v8/app/navigation/router/views/route_back_indicator.dart rename lib/v8/pages/sort/views/{ => player}/data_painter.dart (69%) create mode 100644 lib/v8/pages/sort/views/player/sort_player.dart create mode 100644 lib/v8/pages/sort/views/settings/color_picker.dart create mode 100644 lib/v8/pages/sort/views/settings/sort_setting.dart delete mode 100644 lib/v8/pages/sort/views/sort_bar.dart delete mode 100644 lib/v8/pages/sort/views/sort_page.dart rename lib/v8/pages/sort/views/{ => sort_page}/sort_button.dart (97%) create mode 100644 lib/v8/pages/sort/views/sort_page/sort_page.dart delete mode 100644 lib/v8/pages/sort/views/sort_setting.dart create mode 100644 lib/v9/app.dart create mode 100644 lib/v9/app/navigation/router/app_router_delegate.dart create mode 100644 lib/v9/app/navigation/router/iroute.dart create mode 100644 lib/v9/app/navigation/router/iroute_config.dart create mode 100644 lib/v9/app/navigation/router/route_history_manager.dart create mode 100644 lib/v9/app/navigation/router/routes.dart create mode 100644 lib/v9/app/navigation/router/views/not_find_view.dart create mode 100644 lib/v9/app/navigation/router/views/route_back_indicator.dart create mode 100644 lib/v9/app/navigation/transition/fade_page_transitions_builder.dart create mode 100644 lib/v9/app/navigation/transition/fade_transition_page.dart create mode 100644 lib/v9/app/navigation/transition/no_transition_page.dart create mode 100644 lib/v9/app/navigation/views/app_navigation.dart create mode 100644 lib/v9/app/navigation/views/app_navigation_rail.dart create mode 100644 lib/v9/app/navigation/views/app_top_bar/app_router_editor.dart create mode 100644 lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart create mode 100644 lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart create mode 100644 lib/v9/app/navigation/views/app_top_bar/route_history_button.dart create mode 100644 lib/v9/app/unit_app.dart create mode 100644 lib/v9/pages/color/color_add_page.dart create mode 100644 lib/v9/pages/color/color_detail_page.dart create mode 100644 lib/v9/pages/color/color_page.dart create mode 100644 lib/v9/pages/counter/counter_page.dart create mode 100644 lib/v9/pages/empty/empty_page.dart create mode 100644 lib/v9/pages/settings/settings_page.dart create mode 100644 lib/v9/pages/sort/functions.dart create mode 100644 lib/v9/pages/sort/functions/bubble.dart create mode 100644 lib/v9/pages/sort/functions/cocktail.dart create mode 100644 lib/v9/pages/sort/functions/comb.dart create mode 100644 lib/v9/pages/sort/functions/cycle.dart create mode 100644 lib/v9/pages/sort/functions/gnome.dart create mode 100644 lib/v9/pages/sort/functions/heap.dart create mode 100644 lib/v9/pages/sort/functions/insertion.dart create mode 100644 lib/v9/pages/sort/functions/merage.dart create mode 100644 lib/v9/pages/sort/functions/oddEven.dart create mode 100644 lib/v9/pages/sort/functions/pigeonHole.dart create mode 100644 lib/v9/pages/sort/functions/quick.dart create mode 100644 lib/v9/pages/sort/functions/selection.dart create mode 100644 lib/v9/pages/sort/functions/shell.dart create mode 100644 lib/v9/pages/sort/provider/sort_config.dart create mode 100644 lib/v9/pages/sort/provider/state.dart rename lib/{v7 => v9}/pages/sort/views/code_page/code_page.dart (100%) create mode 100644 lib/v9/pages/sort/views/player/data_painter.dart create mode 100644 lib/v9/pages/sort/views/player/sort_player.dart create mode 100644 lib/v9/pages/sort/views/settings/color_picker.dart create mode 100644 lib/v9/pages/sort/views/settings/sort_setting.dart create mode 100644 lib/v9/pages/sort/views/sort_page/sort_button.dart create mode 100644 lib/v9/pages/sort/views/sort_page/sort_page.dart create mode 100644 lib/v9/pages/user/user_page.dart rename test/algorithm/{add.dart => 02_link_list_add.dart} (99%) create mode 100644 test/algorithm/1052.dart create mode 100644 test/algorithm/643_max_sub_list_of_k.dart create mode 100644 test/set/main.dart diff --git a/lib/go_router/examples/input/decoration/code_decoration.dart b/lib/go_router/examples/input/decoration/code_decoration.dart new file mode 100644 index 0000000..e1f35c4 --- /dev/null +++ b/lib/go_router/examples/input/decoration/code_decoration.dart @@ -0,0 +1,175 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +abstract class CodeDecoration { + final Color activeColor; + final Color inactiveColor; + final Size cursorSize; + final Color cursorColor; + + final TextStyle textStyle; + final String? obscureText; + + CodeDecoration({ + required this.activeColor, + required this.inactiveColor, + required this.textStyle, + required this.cursorSize, + required this.cursorColor, + this.obscureText, + }); + + void paint( + Canvas canvas, Size size, int alpha, String text, int count, double gap) { + double boxWidth = (size.width - (count - 1) * gap) / count; + + /// 绘制装饰 + paintDecoration(canvas, size, text, count, gap); + + /// 绘制游标 + paintCursor(canvas, alpha,size, text.length, boxWidth, gap); + + /// 绘制文字 + paintText(canvas,size, text, boxWidth, gap); + } + + void paintCursor( + Canvas canvas, int alpha,Size size, int count, double boxWidth, double gap) { + Paint cursorPaint = Paint() + ..color = cursorColor.withAlpha(alpha) + ..strokeWidth = cursorSize.width + ..style = PaintingStyle.fill + ..isAntiAlias = true; + + double startX = count * (boxWidth + gap) + boxWidth / 2; + double startY = size.height/2-cursorSize.height/2; + var endX = startX + cursorSize.width; + var endY = size.height/2 + cursorSize.height/2; +// var endY = size.height - 28.0 - 12; +// canvas.drawLine(Offset(startX, startY), Offset(startX, endY), cursorPaint); + //绘制圆角光标 + Rect rect = Rect.fromLTRB(startX, startY, endX, endY); + RRect rrect = + RRect.fromRectAndRadius(rect, Radius.circular(cursorSize.width)); + canvas.drawRRect(rrect, cursorPaint); + } + + void paintText(Canvas canvas, Size size,String text, double boxWidth, double gap) { + /// 画文本 + double startX = 0.0; + /// Determine whether display obscureText. + bool obscure = obscureText != null; + + for (int i = 0; i < text.runes.length; i++) { + int rune = text.runes.elementAt(i); + String code = obscure ? obscureText! : String.fromCharCode(rune); + TextPainter textPainter = TextPainter( + text: TextSpan( + style: textStyle, + text: code, + ), + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ); + + /// Layout the text. + textPainter.layout(); + var startY = size.height/2-textPainter.height/2; + startX = boxWidth * i + boxWidth / 2 - textPainter.width / 2 + gap * i; + textPainter.paint(canvas, Offset(startX, startY)); + } + } + + void paintDecoration( + Canvas canvas, Size size, String text, int count, double gap); +} + +class UnderlineCodeDecoration extends CodeDecoration { + final double strokeWidth; + + UnderlineCodeDecoration( + {required this.strokeWidth, + required super.activeColor, + required super.inactiveColor, + required super.cursorColor, + required super.textStyle, + required super.cursorSize, + super.obscureText}); + + @override + void paintDecoration( + Canvas canvas, Size size, String text, int count, double gap) { + Paint underlinePaint = Paint() + ..color = activeColor + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..isAntiAlias = true; + + /// 画下划线 + double singleWidth = (size.width - (count - 1) * gap) / count; + var startX = 0.0; + var startY = size.height; + + for (int i = 0; i < count; i++) { + if (i == text.length) { + underlinePaint.color = activeColor; + // underlinePaint.strokeWidth = strokeWidth; + } else { + underlinePaint.color = inactiveColor; + // underlinePaint.strokeWidth = strokeWidth; + } + canvas.drawLine(Offset(startX, startY), + Offset(startX + singleWidth, startY), underlinePaint); + startX += singleWidth + gap; + } + } +} + +class RRectCodeDecoration extends CodeDecoration { + final double strokeWidth; + final double height; + + RRectCodeDecoration( + {required this.height, + required this.strokeWidth, + required super.activeColor, + required super.inactiveColor, + required super.cursorColor, + required super.textStyle, + required super.cursorSize, + super.obscureText}); + + @override + void paintDecoration( + Canvas canvas, Size size, String text, int count, double gap) { + Paint paint = Paint() + ..color = activeColor + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..isAntiAlias = true; + + /// 画下划线 + double singleWidth = (size.width - (count - 1) * gap) / count; + var startX = 0.0; + var startY = size.height; + + for (int i = 0; i < count; i++) { + if (i == text.length) { + paint.color = activeColor; + } else { + paint.color = inactiveColor; + } + RRect rRect = RRect.fromRectAndRadius( + Rect.fromPoints( + Offset(startX, 0), + Offset(startX + singleWidth, startY), + ), + Radius.circular(6)); + canvas.drawRRect(rRect, paint); + // canvas.drawLine(Offset(startX, startY), + // Offset(startX + singleWidth, startY), underlinePaint); + startX += singleWidth + gap; + } + } +} diff --git a/lib/go_router/examples/input/input.dart b/lib/go_router/examples/input/input.dart new file mode 100644 index 0000000..d0cadf6 --- /dev/null +++ b/lib/go_router/examples/input/input.dart @@ -0,0 +1,215 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'decoration/code_decoration.dart'; +import 'input_painter.dart'; + +typedef AsyncSubmit = Future Function(String value); + +/// @desc 短信验证码输入框 +/// @time 2019-05-14 16:16 +/// @author Cheney +class TolyCodeInput extends StatefulWidget { + final int count; + + final AsyncSubmit onSubmit; + + final CodeDecoration decoration; + + final TextInputType keyboardType; + + final bool autoFocus; + + final FocusNode? focusNode; + + final TextInputAction textInputAction; + + const TolyCodeInput( + {this.count = 6, + required this.onSubmit, + this.decoration = const UnderlineDecoration(), + this.keyboardType = TextInputType.number, + this.focusNode, + this.autoFocus = false, + this.textInputAction = TextInputAction.done, + super.key}); + + @override + State createState() { + return TolyCodeInputState(); + } +} + +class TolyCodeInputState extends State + with SingleTickerProviderStateMixin { + ///输入监听器 + final TextEditingController _controller = TextEditingController(); + late AnimationController _animaCtrl; + late Animation _animation; + + late FocusNode _focusNode; + + @override + void initState() { + _focusNode = widget.focusNode ?? FocusNode(); + _controller.addListener(_listenValueChange); + initAnimation(); + super.initState(); + } + + void initAnimation() { + _animaCtrl = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + _animation = IntTween(begin: 0, end: 255).animate(_animaCtrl) + ..addStatusListener(_stateChange); + _animaCtrl.forward(); + } + + void _listenValueChange() { + submit(_controller.text); + } + + void submit(String text) async{ + if (text.length >= widget.count) { + bool success = await widget.onSubmit.call(text.substring(0, widget.count)); + if(success){ + _focusNode.unfocus(); + }else{ + _controller.text = ""; + } + } + } + + @override + void dispose() { + /// Only execute when the controller is autoDispose. + _controller.dispose(); + _animaCtrl.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return CustomPaint( + foregroundPainter: InputPainter( + RRectCodeDecoration( + height: 56, + strokeWidth: 1, + activeColor: const Color(0xff010101), + inactiveColor: const Color(0xffB6B6B6), + cursorColor: Color(0xff3776E9), + textStyle: TextStyle(color: Colors.black), + cursorSize: Size(1, 24), + ), + controller: _controller, + count: widget.count, + decoration: widget.decoration, + alpha: _animation, + ), + child: RepaintBoundary( + child: TextField( + controller: _controller, + /// Fake the text style. + style: const TextStyle( + color: Colors.transparent, + ), + cursorColor: Colors.transparent, + cursorWidth: 0.0, + autocorrect: false, + textAlign: TextAlign.center, + enableInteractiveSelection: false, + maxLength: widget.count, + onSubmitted: submit, + keyboardType: widget.keyboardType, + focusNode: _focusNode, + autofocus: widget.autoFocus, + textInputAction: widget.textInputAction, + obscureText: true, + decoration: const InputDecoration( + counterText: '', + contentPadding: EdgeInsets.symmetric(vertical: 24), + border: OutlineInputBorder( + borderSide: BorderSide.none, + ), + ), + ), + ), + ); + } + + void _stateChange(AnimationStatus status) { + if (status == AnimationStatus.completed) { + _animaCtrl.reverse(); + } else if (status == AnimationStatus.dismissed) { + _animaCtrl.forward(); + } + } + + +} + +/// 默认的样式 +const TextStyle defaultStyle = TextStyle( + /// Default text color. + color: Color(0x80000000), + + /// Default text size. + fontSize: 24.0, +); + +abstract class CodeDecoration { + /// The style of painting text. + final TextStyle? textStyle; + + final ObscureStyle? obscureStyle; + + const CodeDecoration({ + this.textStyle, + this.obscureStyle, + }); +} + +/// The object determine the obscure display +class ObscureStyle { + /// Determine whether replace [obscureText] with number. + final bool isTextObscure; + + /// The display text when [isTextObscure] is true + final String obscureText; + + const ObscureStyle({ + this.isTextObscure = false, + this.obscureText = '*', + }) : assert(obscureText.length == 1); +} + +/// The object determine the underline color etc. +class UnderlineDecoration extends CodeDecoration { + /// The space between text and underline. + final double gapSpace; + + /// The color of the underline. + final Color color; + + /// The height of the underline. + final double lineHeight; + + /// The underline changed color when user enter pin. + final Color? enteredColor; + + const UnderlineDecoration({ + TextStyle? textStyle, + ObscureStyle? obscureStyle, + this.enteredColor = const Color(0xff3776E9), + this.gapSpace = 15.0, + this.color = const Color(0x24000000), + this.lineHeight = 0.5, + }) : super( + textStyle: textStyle, + obscureStyle: obscureStyle, + ); +} diff --git a/lib/go_router/examples/input/input_painter.dart b/lib/go_router/examples/input/input_painter.dart new file mode 100644 index 0000000..03233c1 --- /dev/null +++ b/lib/go_router/examples/input/input_painter.dart @@ -0,0 +1,141 @@ +import 'package:flutter/cupertino.dart'; + +import 'decoration/code_decoration.dart' as c; +import 'input.dart'; + +class InputPainter extends CustomPainter { + final TextEditingController controller; + final int count; + final double space; + final CodeDecoration decoration; + final c.CodeDecoration underlineCodeDecoration; + final Animation alpha; + + InputPainter( + this.underlineCodeDecoration, { + required this.controller, + required this.count, + required this.decoration, + this.space = 4.0, + required this.alpha, + }) : super(repaint: Listenable.merge([controller, alpha])); + + void _drawUnderLine(Canvas canvas, Size size) { + /// Force convert to [UnderlineDecoration]. + var dr = decoration as UnderlineDecoration; + Paint underlinePaint = Paint() + ..color = dr.color + ..strokeWidth = dr.lineHeight + ..style = PaintingStyle.stroke + ..isAntiAlias = true; + + var startX = 0.0; + var startY = size.height; + + /// 画下划线 + double singleWidth = (size.width - (count - 1) * dr.gapSpace) / count; + + for (int i = 0; i < count; i++) { + if (i == controller.text.length && dr.enteredColor != null) { + underlinePaint.color = dr.enteredColor!; + underlinePaint.strokeWidth = 1; + } else { + underlinePaint.color = dr.color; + underlinePaint.strokeWidth = 0.5; + } + canvas.drawLine(Offset(startX, startY), + Offset(startX + singleWidth, startY), underlinePaint); + startX += singleWidth + dr.gapSpace; + } + + /// 画文本 + var index = 0; + startX = 0.0; + startY = 28; + + /// Determine whether display obscureText. + bool obscureOn; + obscureOn = decoration.obscureStyle != null && + decoration.obscureStyle!.isTextObscure; + + /// The text style of pin. + TextStyle textStyle; + if (decoration.textStyle == null) { + textStyle = defaultStyle; + } else { + textStyle = decoration.textStyle!; + } + + controller.text.runes.forEach((rune) { + String code; + if (obscureOn) { + code = decoration.obscureStyle!.obscureText; + } else { + code = String.fromCharCode(rune); + } + TextPainter textPainter = TextPainter( + text: TextSpan( + style: textStyle, + text: code, + ), + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ); + + /// Layout the text. + textPainter.layout(); + + startX = singleWidth * index + + singleWidth / 2 - + textPainter.width / 2 + + dr.gapSpace * index; + textPainter.paint(canvas, Offset(startX, startY)); + index++; + }); + + ///画光标 如果外部有传,则直接使用外部 + Color cursorColor = dr.enteredColor ?? const Color(0xff3776E9); + cursorColor = cursorColor.withAlpha(alpha.value); + + double cursorWidth = 1; + double cursorHeight = 24; + + //LogUtil.v("animation.value=$alpha"); + + Paint cursorPaint = Paint() + ..color = cursorColor + ..strokeWidth = cursorWidth + ..style = PaintingStyle.stroke + ..isAntiAlias = true; + + startX = + controller.text.length * (singleWidth + dr.gapSpace) + singleWidth / 2; + + var endX = startX + cursorWidth; + var endY = startY + cursorHeight; +// var endY = size.height - 28.0 - 12; +// canvas.drawLine(Offset(startX, startY), Offset(startX, endY), cursorPaint); + //绘制圆角光标 + Rect rect = Rect.fromLTRB(startX, startY, endX, endY); + RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(cursorWidth)); + canvas.drawRRect(rrect, cursorPaint); + } + + @override + void paint(Canvas canvas, Size size) { + // _drawUnderLine(canvas, size); + underlineCodeDecoration.paint( + canvas, + size, + alpha.value, + controller.text, + count, + space, + ); + } + + @override + bool shouldRepaint(covariant InputPainter oldDelegate) { + return oldDelegate.controller.text != controller.text; + } +} diff --git a/lib/go_router/examples/input/main.dart b/lib/go_router/examples/input/main.dart new file mode 100644 index 0000000..ba1a330 --- /dev/null +++ b/lib/go_router/examples/input/main.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import 'input.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: TolyCodeInput( + autoFocus: true, + onSubmit: (value) async { + print(value); + return true; + }, + )), + ), + ); + } +} diff --git a/lib/v5/app/navigation/router/app_router_delegate.dart b/lib/v5/app/navigation/router/app_router_delegate.dart index 7299895..8d5af8b 100644 --- a/lib/v5/app/navigation/router/app_router_delegate.dart +++ b/lib/v5/app/navigation/router/app_router_delegate.dart @@ -44,6 +44,7 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { final List keepAlivePath = [] ; + void setPathKeepLive(String value){ if(keepAlivePath.contains(value)){ keepAlivePath.remove(value); @@ -52,6 +53,8 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { path = value; } + + void setPathForData(String value,dynamic data){ _pathExtraMap[value] = data; path = value; @@ -82,6 +85,17 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { 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)); + } + + if(keepAlivePath.isNotEmpty){ for (String alivePath in keepAlivePath) { if(alivePath!=path){ @@ -171,7 +185,6 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { _completerMap[path]?.complete(result); _completerMap.remove(path); } - path = backPath(path); return route.didPop(result); } diff --git a/lib/v5/app/navigation/views/app_navigation_rail.dart b/lib/v5/app/navigation/views/app_navigation_rail.dart index f3f8ec8..2580ed2 100644 --- a/lib/v5/app/navigation/views/app_navigation_rail.dart +++ b/lib/v5/app/navigation/views/app_navigation_rail.dart @@ -10,7 +10,6 @@ class AppNavigationRail extends StatefulWidget { } class _AppNavigationRailState extends State { - final List deskNavBarMenus = const [ MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined), MenuMeta(label: '计数器', icon: Icons.add_chart), @@ -41,22 +40,24 @@ class _AppNavigationRailState extends State { ), tail: Padding( padding: const EdgeInsets.only(bottom: 6.0), - child: Text('V0.0.5',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, selectedIndex: router.activeIndex, ), ); - } void _onDestinationSelected(int index) { - if(index==1){ + if (index == 1) { router.setPathKeepLive(kDestinationsPaths[index]); - }else{ - router.path = kDestinationsPaths[index]; + return; } + router.path = kDestinationsPaths[index]; } void _onRouterChange() { diff --git a/lib/v7/pages/sort/views/sort_bar.dart b/lib/v7/pages/sort/views/sort_bar.dart deleted file mode 100644 index 20c0705..0000000 --- a/lib/v7/pages/sort/views/sort_bar.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:iroute/components/components.dart'; - -import '../provider/state.dart'; -import '../functions.dart'; -import 'sort_button.dart'; - -class SortBar extends StatelessWidget { - const SortBar({super.key}); - - @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, - height: 28, - width: 200, - disableColor: const Color(0xff1F425F), - onDropSelected: (int index) async { - SortState state = SortStateScope.of(context); - state.config =state.config.copyWith( - name: sortNameMap.keys.toList()[index] - ); - // curveAnim = CurvedAnimation( - // parent: _ctrl, curve: maps.values.toList()[index]); - // _startAnim(); - }, - ), - const SizedBox(width: 10,), - GestureDetector( - onTap: (){ - Scaffold.of(context).openEndDrawer(); - // showDialog( - // useRootNavigator: false, - // context: context, builder: (ctx)=>AlertDialog()); - }, - child: const Icon(Icons.settings)) - ], - ); - } -} diff --git a/lib/v7/pages/sort/views/sort_page.dart b/lib/v7/pages/sort/views/sort_page.dart index fe1cba7..5c6e2c0 100644 --- a/lib/v7/pages/sort/views/sort_page.dart +++ b/lib/v7/pages/sort/views/sort_page.dart @@ -23,15 +23,15 @@ class SortPage extends StatelessWidget { children: [ Container( // color: Color(0xffF4F4F4), - padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8), - child: Row( + padding: const EdgeInsets.symmetric(horizontal: 12,vertical: 8), + child: const Row( children: [ SortButton(), Spacer(), ], ), ), - Divider(height: 1,), + const Divider(height: 1,), Expanded( child: SortSelectorPanel( active: state.config.name, @@ -43,57 +43,22 @@ class SortPage extends StatelessWidget { ], ), ), - VerticalDivider(width: 1,), + const VerticalDivider(width: 1,), Expanded( - child: NavigatorScope(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomPaint( + painter: DataPainter(data: numbers), + child: ConstrainedBox(constraints: const BoxConstraints.expand()), + ), + ), ) ], ), ); } - - void _onSelected(String value) { - - } } -final GlobalKey key = GlobalKey(); - -class NavigatorScope extends StatefulWidget { - const NavigatorScope({super.key}); - - @override - State createState() => _NavigatorScopeState(); -} - -class _NavigatorScopeState extends State { - - @override - Widget build(BuildContext context) { - SortState state = SortStateScope.of(context); - List numbers = state.data; - return Navigator( - onPopPage: _onPopPage, - key: key, - pages: [ - MaterialPage(child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: CustomPaint( - painter: DataPainter(data: numbers), - child: ConstrainedBox(constraints: BoxConstraints.expand()), - ), - )) - ], - ); - } - - bool _onPopPage(Route route, result) { - return route.didPop(result); - } -} - - - class SortSelectorPanel extends StatelessWidget { final String active; final ValueChanged onSelected; @@ -105,7 +70,7 @@ class SortSelectorPanel extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( - padding: EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 8), itemExtent: 46, itemCount: sortNameMap.length, itemBuilder: _buildByIndex, @@ -145,9 +110,9 @@ class _SortItemTileState extends State { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), - color: widget.selected?Color(0xffE6F0FF):null + color: widget.selected?const Color(0xffE6F0FF):null ), - padding: EdgeInsets.only(left: 12), + padding: const EdgeInsets.only(left: 12), alignment: Alignment.centerLeft, child: Text( widget.title, diff --git a/lib/v8/app/navigation/router/app_router_delegate.dart b/lib/v8/app/navigation/router/app_router_delegate.dart index be3d580..b439c4e 100644 --- a/lib/v8/app/navigation/router/app_router_delegate.dart +++ b/lib/v8/app/navigation/router/app_router_delegate.dart @@ -6,17 +6,27 @@ import 'route_history_manager.dart'; import 'routes.dart'; import 'views/not_find_view.dart'; -AppRouterDelegate router = AppRouterDelegate(); +AppRouterDelegate router = AppRouterDelegate( + initial: IRouteConfig(uri: Uri.parse('/color')), +); class AppRouterDelegate extends RouterDelegate with ChangeNotifier { - String _path = '/color'; - String get path => _path; + /// 核心数据,路由配置数据列表 + final List _configs = []; + + String get path => current.uri.path; + + IRouteConfig get current => _configs.last; final IRoutePageBuilder? notFindPageBuilder; - AppRouterDelegate({this.notFindPageBuilder}) { - _historyManager.recode(IRouteConfig(uri: Uri.parse(path))); + AppRouterDelegate({ + this.notFindPageBuilder, + required IRouteConfig initial, + }) { + _configs.add(initial); + _historyManager.recode(initial); } Page _defaultNotFindPageBuilder(_, __) => const MaterialPage( @@ -45,48 +55,60 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { notifyListeners(); } + // final List _pathStack = []; + + bool get canPop => _configs.where((e) => e.routeStyle==RouteStyle.push).isNotEmpty; + final Map> _completerMap = {}; - final Map _pathExtraMap = {}; - final List keepAlivePath = []; FutureOr changeRoute(IRouteConfig config) { String value = config.uri.path; - if (_path == value) null; + if (current == config) null; + _handleChangeStyle(config); + if (config.forResult) { _completerMap[value] = Completer(); } - if (config.keepAlive) { - if (keepAlivePath.contains(value)) { - keepAlivePath.remove(value); - } - keepAlivePath.add(value); - } - if (config.extra != null) { - _pathExtraMap[value] = config.extra; - } if (config.recordHistory) { _historyManager.recode(config); } - _path = value; notifyListeners(); if (config.forResult) { return _completerMap[value]!.future; } } + void _handleChangeStyle(IRouteConfig config){ + switch (config.routeStyle) { + case RouteStyle.push: + if (_configs.contains(config)) { + _configs.remove(config); + } + _configs.add(config); + break; + case RouteStyle.replace: + List liveRoutes = _configs.where((e) => e.keepAlive&&e!=config).toList(); + _configs.clear(); + _configs.addAll([...liveRoutes,config]); + break; + } + } + FutureOr changePath( String value, { bool forResult = false, Object? extra, bool keepAlive = false, bool recordHistory = true, + RouteStyle style = RouteStyle.replace, }) { return changeRoute(IRouteConfig( uri: Uri.parse(value), forResult: forResult, extra: extra, + routeStyle: style, keepAlive: keepAlive, recordHistory: recordHistory, )); @@ -96,46 +118,47 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { Widget build(BuildContext context) { return Navigator( onPopPage: _onPopPage, - pages: _buildPages(context, path), + pages: _buildPages(context, _configs), ); } - List _buildPages(BuildContext context, String path) { + List _buildPages(BuildContext context, List configs) { + IRouteConfig top = configs.last; + List bottoms = _configs.sublist(0,_configs.length-1).toList(); List pages = []; - List topPages = _buildPageByPathFromTree(context, path); - - if (keepAlivePath.isNotEmpty) { - for (String alivePath in keepAlivePath) { - if (alivePath != path) { - pages.addAll(_buildPageByPathFromTree(context, alivePath)); - } - } - - /// 去除和 topPages 中重复的界面 - pages.removeWhere( - (element) => topPages.map((e) => e.key).contains(element.key)); - } - + List topPages = _buildPageByPathFromTree(context, top); + pages = _buildLivePageByPathList(context, bottoms, top, topPages); pages.addAll(topPages); return pages; } - List _buildPageByPathFromTree(BuildContext context, String path) { + List _buildLivePageByPathList( + BuildContext context, + List paths, + IRouteConfig curConfig, + List curPages, + ) { + List pages = []; + if (paths.isNotEmpty) { + for (IRouteConfig path in paths) { + if (path != curConfig) { + pages.addAll(_buildPageByPathFromTree(context, path)); + } + } + /// 去除和 curPages 中重复的界面 + pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key)); + } + return pages; + } + + List _buildPageByPathFromTree( + BuildContext context, IRouteConfig config) { List result = []; - List iRoutes = rootRoute.find(path); + List iRoutes = rootRoute.find(config.path); if (iRoutes.isNotEmpty) { for (int i = 0; i < iRoutes.length; i++) { IRouteNode iroute = iRoutes[i]; - String path = iroute.path; - Object? extra = _pathExtraMap[path]; - bool keepAlive = keepAlivePath.contains(path); - bool forResult = _completerMap.containsKey(path); - IRouteConfig config = IRouteConfig( - uri: Uri.parse(path), - extra: extra, - keepAlive: keepAlive, - forResult: forResult, - ); + config = config.copyWith(path: iroute.path); Page? page; if (iroute is NotFindNode) { page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config); @@ -145,6 +168,9 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { if (page != null) { result.add(page); } + if(iroute is CellIRoute){ + break; + } } } return result; @@ -156,12 +182,29 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { return true; } + void backStack() { + if (_configs.isNotEmpty) { + _configs.removeLast(); + if (_configs.isNotEmpty) { + changeRoute(_configs.last); + } else { + changeRoute(current); + } + } + } + bool _onPopPage(Route route, result) { if (_completerMap.containsKey(path)) { _completerMap[path]?.complete(result); _completerMap.remove(path); } - changePath(backPath(path), recordHistory: false); + + if (_configs.isNotEmpty) { + _configs.removeLast(); + notifyListeners(); + } else { + changePath(backPath(path), recordHistory: false); + } return route.didPop(result); } diff --git a/lib/v8/app/navigation/router/iroute.dart b/lib/v8/app/navigation/router/iroute.dart index 4026e1c..8e1de0f 100644 --- a/lib/v8/app/navigation/router/iroute.dart +++ b/lib/v8/app/navigation/router/iroute.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; - import 'iroute_config.dart'; typedef IRoutePageBuilder = Page? Function( @@ -24,7 +23,9 @@ abstract class IRouteNode { Page? createPage(BuildContext context, IRouteConfig config); - List find(String input,) { + List find( + String input, + ) { return findNodes(this, Uri.parse(input), 0, '/', []); } @@ -41,15 +42,16 @@ abstract class IRouteNode { } String target = parts[deep]; if (node.children.isNotEmpty) { - target = prefix + target; - List nodes = node.children.where((e) => e.path == target).toList(); + target = prefix + target; + List nodes = + node.children.where((e) => e.path == target).toList(); bool match = nodes.isNotEmpty; if (match) { IRouteNode matched = nodes.first; result.add(matched); String nextPrefix = '${matched.path}/'; findNodes(matched, uri, ++deep, nextPrefix, result); - }else{ + } else { result.add(NotFindNode(path: target)); return result; } @@ -92,8 +94,8 @@ class IRoute extends IRouteNode { } /// 未知路由 -class NotFindNode extends IRouteNode{ - NotFindNode({required super.path, super.children= const[]}); +class NotFindNode extends IRouteNode { + NotFindNode({required super.path, super.children = const []}); @override Page? createPage(BuildContext context, IRouteConfig config) { @@ -101,15 +103,14 @@ class NotFindNode extends IRouteNode{ } } -class CellIRouter extends IRoute { - final CellBuilder cellBuilder; - CellIRouter( - {required super.path, - super.pageBuilder, - super.children, - required this.cellBuilder}); +class CellIRoute extends IRoute { + const CellIRoute({ + required super.path, + super.pageBuilder, + super.children, + super.widget, + }); } -typedef CellBuilder = Widget Function(BuildContext context, Widget child); - +typedef CellBuilder = Widget Function(BuildContext context,IRouteConfig config, CellIRoute cell); diff --git a/lib/v8/app/navigation/router/iroute_config.dart b/lib/v8/app/navigation/router/iroute_config.dart index 36a58ed..f526e71 100644 --- a/lib/v8/app/navigation/router/iroute_config.dart +++ b/lib/v8/app/navigation/router/iroute_config.dart @@ -1,10 +1,17 @@ import 'package:flutter/material.dart'; +enum RouteStyle{ + push, + replace, +} + + class IRouteConfig { final Object? extra; final bool forResult; final Uri uri; final bool keepAlive; + final RouteStyle routeStyle; final bool recordHistory; const IRouteConfig({ @@ -12,6 +19,7 @@ class IRouteConfig { required this.uri, this.forResult = false, this.keepAlive = false, + this.routeStyle = RouteStyle.replace, this.recordHistory = false, }); @@ -22,14 +30,42 @@ class IRouteConfig { bool? forResult, bool? keepAlive, bool? recordHistory, + String? path, }) => IRouteConfig( extra: extra ?? this.extra, forResult: forResult ?? this.forResult, keepAlive: keepAlive ?? this.keepAlive, recordHistory: recordHistory ?? this.recordHistory, - uri: uri, + uri: path!=null?Uri.parse(path):uri, ); - ValueKey get pageKey => ValueKey(path); + ValueKey get pageKey => ValueKey(hashCode); + + + @override + String toString() { + return 'IRouteConfig{extra: $extra, forResult: $forResult, uri: $uri, keepAlive: $keepAlive, routeStyle: $routeStyle, recordHistory: $recordHistory}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IRouteConfig && + runtimeType == other.runtimeType && + extra == other.extra && + forResult == other.forResult && + uri == other.uri && + keepAlive == other.keepAlive && + routeStyle == other.routeStyle && + recordHistory == other.recordHistory; + + @override + int get hashCode => + extra.hashCode ^ + forResult.hashCode ^ + uri.hashCode ^ + keepAlive.hashCode ^ + routeStyle.hashCode ^ + recordHistory.hashCode; } diff --git a/lib/v8/app/navigation/router/routes.dart b/lib/v8/app/navigation/router/routes.dart index 0400bde..da6fb72 100644 --- a/lib/v8/app/navigation/router/routes.dart +++ b/lib/v8/app/navigation/router/routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../../pages/sort/views/player/sort_player.dart'; import 'iroute_config.dart'; import 'iroute.dart'; import '../../../pages/color/color_add_page.dart'; @@ -7,8 +8,8 @@ import '../../../pages/color/color_page.dart'; import '../../../pages/counter/counter_page.dart'; import '../../../pages/user/user_page.dart'; import '../../../pages/settings/settings_page.dart'; -import '../../../pages/sort/views/sort_page.dart'; - +import '../../../pages/sort/views/sort_page/sort_page.dart'; +import '../../../pages/sort/views/settings/sort_setting.dart'; IRoute rootRoute = const IRoute( path: 'root', @@ -22,7 +23,20 @@ IRoute rootRoute = const IRoute( ], ), IRoute(path: '/counter', widget: CounterPage()), - IRoute(path: '/sort', widget: SortPage()), + CellIRoute( + path: '/sort', + widget: SortPage(), + children: [ + IRoute( + path: '/sort/settings', + widget: SortSettings(), + ), + IRoute( + path: '/sort/player', + widget: SortPlayer(), + ), + ], + ), IRoute(path: '/user', widget: UserPage()), IRoute(path: '/settings', widget: SettingPage()), ], diff --git a/lib/v8/app/navigation/router/views/route_back_indicator.dart b/lib/v8/app/navigation/router/views/route_back_indicator.dart new file mode 100644 index 0000000..5be781a --- /dev/null +++ b/lib/v8/app/navigation/router/views/route_back_indicator.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import '../app_router_delegate.dart'; +class RouteBackIndicator extends StatefulWidget { + const RouteBackIndicator({super.key}); + + @override + State createState() => _RouteBackIndicatorState(); +} + +class _RouteBackIndicatorState extends State { + + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if(router.canPop){ + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: router.backStack, + child: Container( + width: 26, + height: 26, + margin: EdgeInsets.only(right: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffE3E5E7), + borderRadius: BorderRadius.circular(6) + ), + child: Icon(Icons.arrow_back_ios_new,size: 14,)), + ), + ); + } + return SizedBox(); + } + + void _onChange() { + setState(() { + + }); + } +} diff --git a/lib/v8/app/navigation/views/app_navigation_rail.dart b/lib/v8/app/navigation/views/app_navigation_rail.dart index 67a986c..8ea2fb0 100644 --- a/lib/v8/app/navigation/views/app_navigation_rail.dart +++ b/lib/v8/app/navigation/views/app_navigation_rail.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:iroute/components/components.dart'; import '../router/app_router_delegate.dart'; +import '../router/iroute_config.dart'; class AppNavigationRail extends StatefulWidget { const AppNavigationRail({super.key}); @@ -10,13 +11,12 @@ class AppNavigationRail extends StatefulWidget { } class _AppNavigationRailState extends State { - final List deskNavBarMenus = const [ - MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined,path: '/color'), - MenuMeta(label: '计数器', icon: Icons.add_chart,path: '/counter'), - MenuMeta(label: '排序', icon: Icons.sort,path: '/sort'), - MenuMeta(label: '我的', icon: Icons.person,path: '/user'), - MenuMeta(label: '设置', icon: Icons.settings,path: '/settings'), + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'), + MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'), + MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'), + MenuMeta(label: '我的', icon: Icons.person, path: '/user'), + MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'), ]; @override @@ -42,7 +42,10 @@ class _AppNavigationRailState extends State { ), tail: Padding( padding: const EdgeInsets.only(bottom: 6.0), - child: Text('V0.0.7',style: TextStyle(color: Colors.white,fontSize: 12),), + child: Text( + 'V0.0.8', + style: TextStyle(color: Colors.white, fontSize: 12), + ), ), backgroundColor: const Color(0xff3975c6), onDestinationSelected: _onDestinationSelected, @@ -53,21 +56,26 @@ class _AppNavigationRailState extends State { RegExp _segReg = RegExp(r'/\w+'); - int? get activeIndex{ + int? get activeIndex { String path = router.path; RegExpMatch? match = _segReg.firstMatch(path); - if(match==null) return null; + if (match == null) return null; String? target = match.group(0); - int index = deskNavBarMenus.indexWhere((menu) => menu.path==target); - if(index==-1) return null; + int index = deskNavBarMenus.indexWhere((menu) => menu.path == target); + if (index == -1) return null; return index; -} + } void _onDestinationSelected(int index) { String path = deskNavBarMenus[index].path!; - if(index==1){ - router.changePath(path,keepAlive: true); - }else{ + if (index == 1) { + router.changePath(path, keepAlive: true); + return; + } + if (index == 4) { + router.changePath(path, style: RouteStyle.push); + return; + } else { router.changePath(path); } } diff --git a/lib/v8/app/navigation/views/app_top_bar/app_top_bar.dart b/lib/v8/app/navigation/views/app_top_bar/app_top_bar.dart index ad7d875..072a838 100644 --- a/lib/v8/app/navigation/views/app_top_bar/app_top_bar.dart +++ b/lib/v8/app/navigation/views/app_top_bar/app_top_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:iroute/components/components.dart'; import '../../router/app_router_delegate.dart'; +import '../../router/views/route_back_indicator.dart'; import 'app_router_editor.dart'; import 'history_view_icon.dart'; import 'route_history_button.dart'; @@ -17,6 +18,7 @@ class AppTopBar extends StatelessWidget { child: Row( children: [ const SizedBox(width: 16), + const RouteBackIndicator(), const RouterIndicator(), Expanded( child: Row(children: [ @@ -58,6 +60,7 @@ Map kRouteLabelMap = { '/color/detail': '颜色详情', '/counter': '计数器', '/sort': '可视化排序算法', + '/sort/settings': '排序配置', '/user': '我的', '/settings': '系统设置', }; diff --git a/lib/v8/pages/sort/provider/sort_config.dart b/lib/v8/pages/sort/provider/sort_config.dart index 2ad50ed..1fc3790 100644 --- a/lib/v8/pages/sort/provider/sort_config.dart +++ b/lib/v8/pages/sort/provider/sort_config.dart @@ -7,17 +7,20 @@ class SortConfig { final int seed; final Duration duration; final String name; + final int colorIndex; SortConfig({ this.count = 100, this.duration = const Duration(microseconds: 1500), this.seed = -1, + this.colorIndex = 0, this.name = 'insertion', }); SortConfig copyWith({ int? count, int? seed, + int? colorIndex, Duration? duration, String? name, }) => @@ -26,6 +29,7 @@ class SortConfig { seed:seed??this.seed, duration:duration??this.duration, name:name??this.name, + colorIndex:colorIndex??this.colorIndex, ); } diff --git a/lib/v8/pages/sort/provider/state.dart b/lib/v8/pages/sort/provider/state.dart index 90803cf..1e79b05 100644 --- a/lib/v8/pages/sort/provider/state.dart +++ b/lib/v8/pages/sort/provider/state.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import '../functions.dart'; import 'sort_config.dart'; @@ -11,6 +11,20 @@ enum SortStatus{ sorted, // 排序完成 } +List kColorSupport = [ + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.red, + Colors.pink, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.indigo, + Colors.purple, + Colors.deepPurple, +]; + class SortState with ChangeNotifier{ SortState(){ @@ -37,6 +51,11 @@ class SortState with ChangeNotifier{ config = config.copyWith(name: name); } + void selectColor(int colorIndex){ + if(colorIndex==config.colorIndex) return; + config = config.copyWith(colorIndex: colorIndex); + } + void reset(){ data.clear(); status = SortStatus.none; diff --git a/lib/v8/pages/sort/views/data_painter.dart b/lib/v8/pages/sort/views/player/data_painter.dart similarity index 69% rename from lib/v8/pages/sort/views/data_painter.dart rename to lib/v8/pages/sort/views/player/data_painter.dart index d23e2f5..392a9e8 100644 --- a/lib/v8/pages/sort/views/data_painter.dart +++ b/lib/v8/pages/sort/views/player/data_painter.dart @@ -5,8 +5,9 @@ import 'package:flutter/material.dart'; class DataPainter extends CustomPainter{ final List data; + final MaterialColor color; - DataPainter({required this.data}); + DataPainter( {required this.data,required this.color,}); @override void paint(Canvas canvas, Size size) { @@ -17,28 +18,29 @@ class DataPainter extends CustomPainter{ paint.strokeWidth = itemWidth; paint.strokeCap = StrokeCap.round; + for(int i=0;i numbers = state.data; + MaterialColor color = kColorSupport[state.config.colorIndex]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomPaint( + painter: DataPainter(data: numbers,color: color), + child: ConstrainedBox(constraints: const BoxConstraints.expand()), + ), + ); + } +} diff --git a/lib/v8/pages/sort/views/settings/color_picker.dart b/lib/v8/pages/sort/views/settings/color_picker.dart new file mode 100644 index 0000000..b0bb469 --- /dev/null +++ b/lib/v8/pages/sort/views/settings/color_picker.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class ColorPicker extends StatelessWidget { + final List colors; + final ValueChanged onSelected; + final int activeIndex; + + const ColorPicker({ + super.key, + required this.colors, + required this.activeIndex, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Wrap( + children: colors + .asMap() + .keys + .map((int index) => MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: ()=>onSelected(index), + child: Container( + width: 32, + height: 32, + color: colors[index], + child: activeIndex == index + ? const Icon( + Icons.check, + color: Colors.white, + ) + : null, + ), + ), + )) + .toList(), + ); + } +} diff --git a/lib/v8/pages/sort/views/settings/sort_setting.dart b/lib/v8/pages/sort/views/settings/sort_setting.dart new file mode 100644 index 0000000..654d549 --- /dev/null +++ b/lib/v8/pages/sort/views/settings/sort_setting.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import '../../provider/state.dart'; +import 'color_picker.dart'; +class SortSettings extends StatefulWidget { + + const SortSettings({super.key,}); + + @override + State createState() => _SortSettingsState(); +} + +class _SortSettingsState extends State { + final TextEditingController _count = TextEditingController(); + final TextEditingController _duration = TextEditingController(); + final TextEditingController _seed = TextEditingController(); + int _colorIndex = 0; + + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + SortState state = SortStateScope.of(context); + _count.text = state.config.count.toString(); + _duration.text = state.config.duration.inMicroseconds.toString(); + _seed.text = state.config.seed.toString(); + _colorIndex = state.config.colorIndex; + } + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + leading: Align( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: (){ + Navigator.of(context).pop(); + }, + child: Container( + width: 28, + height: 28, + margin: EdgeInsets.only(right: 8,left: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffE3E5E7), + borderRadius: BorderRadius.circular(6) + ), + child: Icon(Icons.arrow_back_ios_new,size: 18,)), + ), + ), + ), + // leading: BackButton(), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + splashRadius: 20, + onPressed: (){ + SortState state = SortStateScope.of(context); + state.config =state.config.copyWith( + count: int.parse(_count.text), + duration: Duration( + microseconds: int.parse(_duration.text), + ), + seed: int.parse(_seed.text), + colorIndex: _colorIndex + ); + Navigator.of(context).pop(); + }, icon: Icon(Icons.check)), + )], + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + centerTitle: true, + title: Text('排序算法配置'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + Row( + children: [ + Text('数据数量(个数):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _count, + )), + ], + ), + Row( + children: [ + Text('时间间隔(微秒):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _duration, + )), + ], + ), + Row( + children: [ + Text('随机种子:'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _seed, + )), + ], + ), + const SizedBox(height: 20,), + + Row( + children: [ + Text('选择颜色:'), + const SizedBox( + width: 20, + ), + Expanded( + child: ColorPicker( + colors: kColorSupport, + onSelected: (index){ + setState(() { + _colorIndex = index; + }); + }, + activeIndex: _colorIndex, + ),), + ], + ), + + Spacer(), + // ElevatedButton( + // onPressed: () { + // SortState state = SortStateScope.of(context); + // state.config =state.config.copyWith( + // count: int.parse(_count.text), + // duration: Duration( + // microseconds: int.parse(_duration.text), + // ), + // seed: int.parse(_seed.text) + // ); + // Navigator.of(context).pop(); + // }, + // child: Text('确定设置')) + ], + ), + ), + ); + } +} diff --git a/lib/v8/pages/sort/views/sort_bar.dart b/lib/v8/pages/sort/views/sort_bar.dart deleted file mode 100644 index 20c0705..0000000 --- a/lib/v8/pages/sort/views/sort_bar.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:iroute/components/components.dart'; - -import '../provider/state.dart'; -import '../functions.dart'; -import 'sort_button.dart'; - -class SortBar extends StatelessWidget { - const SortBar({super.key}); - - @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, - height: 28, - width: 200, - disableColor: const Color(0xff1F425F), - onDropSelected: (int index) async { - SortState state = SortStateScope.of(context); - state.config =state.config.copyWith( - name: sortNameMap.keys.toList()[index] - ); - // curveAnim = CurvedAnimation( - // parent: _ctrl, curve: maps.values.toList()[index]); - // _startAnim(); - }, - ), - const SizedBox(width: 10,), - GestureDetector( - onTap: (){ - Scaffold.of(context).openEndDrawer(); - // showDialog( - // useRootNavigator: false, - // context: context, builder: (ctx)=>AlertDialog()); - }, - child: const Icon(Icons.settings)) - ], - ); - } -} diff --git a/lib/v8/pages/sort/views/sort_page.dart b/lib/v8/pages/sort/views/sort_page.dart deleted file mode 100644 index 980c385..0000000 --- a/lib/v8/pages/sort/views/sort_page.dart +++ /dev/null @@ -1,185 +0,0 @@ -import 'dart:ffi'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'sort_setting.dart'; -import 'code_page/code_page.dart'; -import 'sort_button.dart'; - -import '../functions.dart'; -import '../provider/state.dart'; -import 'data_painter.dart'; - -class SortPage extends StatelessWidget { - const SortPage({super.key}); - - @override - Widget build(BuildContext context) { - SortState state = SortStateScope.of(context); - List numbers = state.data; - return Scaffold( - body: Row( - children: [ - SizedBox( - width: 220, - child: Column( - children: [ - Container( - // color: Color(0xffF4F4F4), - padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8), - child: Row( - children: [ - SortButton(), - Spacer(), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: (){ - (key.currentState as NavigatorState).push(MaterialPageRoute(builder: (_)=>CodePage())); - }, - child: const Icon(CupertinoIcons.chevron_left_slash_chevron_right,size: 18,)), - ), - SizedBox(width: 8,), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: (){ - - (key.currentState as NavigatorState).push(MaterialPageRoute(builder: (_)=>SortSettings())); - }, - child: Icon(CupertinoIcons.settings,size: 18,)), - ), - ], - ), - ), - Divider(height: 1,), - Expanded( - child: SortSelectorPanel( - active: state.config.name, - options: sortNameMap.values.toList(), - onSelected: state.selectName, - ), - ), - - ], - ), - ), - VerticalDivider(width: 1,), - Expanded( - child: NavigatorScope(), - ) - ], - ), - ); - } - - void _onSelected(String value) { - - } -} - -final GlobalKey key = GlobalKey(); - -class NavigatorScope extends StatefulWidget { - const NavigatorScope({super.key}); - - @override - State createState() => _NavigatorScopeState(); -} - -class _NavigatorScopeState extends State { - - @override - Widget build(BuildContext context) { - SortState state = SortStateScope.of(context); - List numbers = state.data; - return Navigator( - onPopPage: _onPopPage, - key: key, - pages: [ - MaterialPage(child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: CustomPaint( - painter: DataPainter(data: numbers), - child: ConstrainedBox(constraints: BoxConstraints.expand()), - ), - )) - ], - ); - } - - bool _onPopPage(Route route, result) { - return route.didPop(result); - } -} - - - -class SortSelectorPanel extends StatelessWidget { - final String active; - final ValueChanged onSelected; - final List options; - - const SortSelectorPanel( - {super.key, required this.active, required this.options, required this.onSelected}); - - @override - Widget build(BuildContext context) { - return ListView.builder( - padding: EdgeInsets.symmetric(vertical: 8), - itemExtent: 46, - itemCount: sortNameMap.length, - itemBuilder: _buildByIndex, - ); - } - - Widget? _buildByIndex(BuildContext context, int index) { - String key = sortNameMap.keys.toList()[index]; - bool selected = sortNameMap.keys.toList()[index] == active; - return SortItemTile( - selected: selected, - onTap: ()=>onSelected(key), - title: options[index], - ); - } -} - -class SortItemTile extends StatefulWidget { - final String title; - final VoidCallback onTap; - final bool selected; - const SortItemTile({super.key, required this.title, required this.selected, required this.onTap}); - - @override - State createState() => _SortItemTileState(); -} - -class _SortItemTileState extends State { - @override - Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: widget.onTap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 2), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: widget.selected?Color(0xffE6F0FF):null - ), - padding: EdgeInsets.only(left: 12), - alignment: Alignment.centerLeft, - child: Text( - widget.title, - style: TextStyle(fontSize: 14, - fontWeight: widget.selected?FontWeight.bold:null - ), - ), - ), - ), - ), - ); - } -} - diff --git a/lib/v8/pages/sort/views/sort_button.dart b/lib/v8/pages/sort/views/sort_page/sort_button.dart similarity index 97% rename from lib/v8/pages/sort/views/sort_button.dart rename to lib/v8/pages/sort/views/sort_page/sort_button.dart index cab02aa..93df9d9 100644 --- a/lib/v8/pages/sort/views/sort_button.dart +++ b/lib/v8/pages/sort/views/sort_page/sort_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../provider/state.dart'; +import '../../provider/state.dart'; class SortButton extends StatelessWidget { const SortButton({super.key}); @@ -49,7 +49,6 @@ class SortButton extends StatelessWidget { ), const SizedBox(width: 4,), Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),), - ], ), ), diff --git a/lib/v8/pages/sort/views/sort_page/sort_page.dart b/lib/v8/pages/sort/views/sort_page/sort_page.dart new file mode 100644 index 0000000..7c11b9a --- /dev/null +++ b/lib/v8/pages/sort/views/sort_page/sort_page.dart @@ -0,0 +1,230 @@ +import 'dart:ffi'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../player/sort_player.dart'; +import '../../../../app/navigation/router/app_router_delegate.dart'; +import '../settings/sort_setting.dart'; +import 'sort_button.dart'; + +import '../../functions.dart'; +import '../../provider/state.dart'; + +class SortPage extends StatefulWidget { + const SortPage({super.key}); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + @override + Widget build(BuildContext context) { + return Material( + child: Row( + children: [ + SizedBox( + width: 220, + child: SortRailPanel(), + ), + VerticalDivider( + width: 1, + ), + Expanded( + child: SortNavigatorScope(), + ) + ], + ), + ); + } +} + +class SortRailPanel extends StatelessWidget { + const SortRailPanel({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + const SortButton(), + const Spacer(), + const MouseRegion( + cursor: SystemMouseCursors.click, + child: Icon( + CupertinoIcons.chevron_left_slash_chevron_right, + size: 18, + ), + ), + const SizedBox( + width: 8, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + router.changePath('/sort/settings'); + }, + child: const Icon( + CupertinoIcons.settings, + size: 18, + )), + ), + ], + ), + ), + const Divider( + height: 1, + ), + Expanded( + child: SortSelectorPanel( + active: state.config.name, + options: sortNameMap.values.toList(), + onSelected: (name) { + state.selectName(name); + router.changePath('/sort'); + }, + ), + ), + ], + ); + } +} + +class SortNavigatorScope extends StatefulWidget { + const SortNavigatorScope({super.key}); + + @override + State createState() => _SortNavigatorScopeState(); +} + +class _SortNavigatorScopeState extends State { + @override + void initState() { + router.addListener(_update); + super.initState(); + } + + @override + void dispose() { + router.removeListener(_update); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + String path = router.path; + List pages = buildPagesByPath(context, path); + return Navigator( + onPopPage: _onPopPage, + pages: pages, + ); + } + + bool _onPopPage(Route route, result) { + return route.didPop(result); + } + + List buildPagesByPath(BuildContext context, String path) { + if (path == '/sort/settings') { + return [ + const MaterialPage(key: ValueKey('/sort/player'), child: SortPlayer()), + const MaterialPage( + key: ValueKey('/sort/settings'), + child: SortSettings(), + ), + ]; + } + + return [ + const MaterialPage( + key: ValueKey('/sort/player'), + child: SortPlayer(), + ) + ]; + } + + void _update() { + setState(() {}); + } +} + +class SortSelectorPanel extends StatelessWidget { + final String active; + final ValueChanged onSelected; + final List options; + + const SortSelectorPanel( + {super.key, + required this.active, + required this.options, + required this.onSelected}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemExtent: 46, + itemCount: sortNameMap.length, + itemBuilder: _buildByIndex, + ); + } + + Widget? _buildByIndex(BuildContext context, int index) { + String key = sortNameMap.keys.toList()[index]; + bool selected = sortNameMap.keys.toList()[index] == active; + return SortItemTile( + selected: selected, + onTap: () => onSelected(key), + title: options[index], + ); + } +} + +class SortItemTile extends StatefulWidget { + final String title; + final VoidCallback onTap; + final bool selected; + + const SortItemTile( + {super.key, + required this.title, + required this.selected, + required this.onTap}); + + @override + State createState() => _SortItemTileState(); +} + +class _SortItemTileState extends State { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: widget.selected ? const Color(0xffE6F0FF) : null), + padding: const EdgeInsets.only(left: 12), + alignment: Alignment.centerLeft, + child: Text( + widget.title, + style: TextStyle( + fontSize: 14, + fontWeight: widget.selected ? FontWeight.bold : null), + ), + ), + ), + ), + ); + } +} diff --git a/lib/v8/pages/sort/views/sort_setting.dart b/lib/v8/pages/sort/views/sort_setting.dart deleted file mode 100644 index 9a96a88..0000000 --- a/lib/v8/pages/sort/views/sort_setting.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../provider/sort_config.dart'; -import '../provider/state.dart'; - -class SortSettings extends StatefulWidget { - - const SortSettings({super.key,}); - - @override - State createState() => _SortSettingsState(); -} - -class _SortSettingsState extends State { - late TextEditingController _count = - TextEditingController(); - late TextEditingController _duration = TextEditingController(); - late TextEditingController _seed = - TextEditingController(); - - @override - void initState() { - super.initState(); - } - - @override - void didChangeDependencies() { - print('========_SortSettingsState#didChangeDependencies============='); - super.didChangeDependencies(); - SortState state = SortStateScope.of(context); - _count.text = state.config.count.toString(); - _duration.text = state.config.duration.inMicroseconds.toString(); - _seed.text = state.config.seed.toString(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.white, - leading: BackButton(), - titleTextStyle: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - centerTitle: true, - title: Text('排序算法配置'), - ), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - children: [ - Text('数据数量(个数):'), - const SizedBox( - width: 20, - ), - Expanded( - child: TextField( - controller: _count, - )), - ], - ), - Row( - children: [ - Text('时间间隔(微秒):'), - const SizedBox( - width: 20, - ), - Expanded( - child: TextField( - controller: _duration, - )), - ], - ), - Row( - children: [ - Text('随机种子:'), - const SizedBox( - width: 20, - ), - Expanded( - child: TextField( - controller: _seed, - )), - ], - ), - Spacer(), - ElevatedButton( - onPressed: () { - SortState state = SortStateScope.of(context); - state.config =state.config.copyWith( - count: int.parse(_count.text), - duration: Duration( - microseconds: int.parse(_duration.text), - ), - seed: int.parse(_seed.text) - ); - Navigator.of(context).pop(); - }, - child: Text('确定设置')) - ], - ), - ), - ); - } -} diff --git a/lib/v9/app.dart b/lib/v9/app.dart new file mode 100644 index 0000000..c9460c2 --- /dev/null +++ b/lib/v9/app.dart @@ -0,0 +1 @@ +export 'app/unit_app.dart'; \ No newline at end of file diff --git a/lib/v9/app/navigation/router/app_router_delegate.dart b/lib/v9/app/navigation/router/app_router_delegate.dart new file mode 100644 index 0000000..b439c4e --- /dev/null +++ b/lib/v9/app/navigation/router/app_router_delegate.dart @@ -0,0 +1,220 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'iroute.dart'; +import 'iroute_config.dart'; +import 'route_history_manager.dart'; +import 'routes.dart'; +import 'views/not_find_view.dart'; + +AppRouterDelegate router = AppRouterDelegate( + initial: IRouteConfig(uri: Uri.parse('/color')), +); + +class AppRouterDelegate extends RouterDelegate with ChangeNotifier { + + /// 核心数据,路由配置数据列表 + final List _configs = []; + + String get path => current.uri.path; + + IRouteConfig get current => _configs.last; + + final IRoutePageBuilder? notFindPageBuilder; + + AppRouterDelegate({ + this.notFindPageBuilder, + required IRouteConfig initial, + }) { + _configs.add(initial); + _historyManager.recode(initial); + } + + Page _defaultNotFindPageBuilder(_, __) => const MaterialPage( + child: Material(child: NotFindPage()), + ); + + final RouteHistoryManager _historyManager = RouteHistoryManager(); + + RouteHistoryManager get historyManager => _historyManager; + + /// 历史回退操作 + /// 详见: [RouteHistoryManager.back] + void back() => _historyManager.back(changeRoute); + + /// 撤销回退操作 + /// 详见: [RouteHistoryManager.revocation] + void revocation() => _historyManager.revocation(changeRoute); + + void closeHistory(int index) { + _historyManager.close(index); + notifyListeners(); + } + + void clearHistory() { + _historyManager.clear(); + notifyListeners(); + } + + // final List _pathStack = []; + + bool get canPop => _configs.where((e) => e.routeStyle==RouteStyle.push).isNotEmpty; + + final Map> _completerMap = {}; + + FutureOr changeRoute(IRouteConfig config) { + String value = config.uri.path; + if (current == config) null; + _handleChangeStyle(config); + + if (config.forResult) { + _completerMap[value] = Completer(); + } + + if (config.recordHistory) { + _historyManager.recode(config); + } + + notifyListeners(); + if (config.forResult) { + return _completerMap[value]!.future; + } + } + + void _handleChangeStyle(IRouteConfig config){ + switch (config.routeStyle) { + case RouteStyle.push: + if (_configs.contains(config)) { + _configs.remove(config); + } + _configs.add(config); + break; + case RouteStyle.replace: + List liveRoutes = _configs.where((e) => e.keepAlive&&e!=config).toList(); + _configs.clear(); + _configs.addAll([...liveRoutes,config]); + break; + } + } + + FutureOr changePath( + String value, { + bool forResult = false, + Object? extra, + bool keepAlive = false, + bool recordHistory = true, + RouteStyle style = RouteStyle.replace, + }) { + return changeRoute(IRouteConfig( + uri: Uri.parse(value), + forResult: forResult, + extra: extra, + routeStyle: style, + keepAlive: keepAlive, + recordHistory: recordHistory, + )); + } + + @override + Widget build(BuildContext context) { + return Navigator( + onPopPage: _onPopPage, + pages: _buildPages(context, _configs), + ); + } + + List _buildPages(BuildContext context, List configs) { + IRouteConfig top = configs.last; + List bottoms = _configs.sublist(0,_configs.length-1).toList(); + List pages = []; + List topPages = _buildPageByPathFromTree(context, top); + pages = _buildLivePageByPathList(context, bottoms, top, topPages); + pages.addAll(topPages); + return pages; + } + + List _buildLivePageByPathList( + BuildContext context, + List paths, + IRouteConfig curConfig, + List curPages, + ) { + List pages = []; + if (paths.isNotEmpty) { + for (IRouteConfig path in paths) { + if (path != curConfig) { + pages.addAll(_buildPageByPathFromTree(context, path)); + } + } + /// 去除和 curPages 中重复的界面 + pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key)); + } + return pages; + } + + List _buildPageByPathFromTree( + BuildContext context, IRouteConfig config) { + List result = []; + List iRoutes = rootRoute.find(config.path); + if (iRoutes.isNotEmpty) { + for (int i = 0; i < iRoutes.length; i++) { + IRouteNode iroute = iRoutes[i]; + config = config.copyWith(path: iroute.path); + Page? page; + if (iroute is NotFindNode) { + page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config); + } else { + page = iroute.createPage(context, config); + } + if (page != null) { + result.add(page); + } + if(iroute is CellIRoute){ + break; + } + } + } + return result; + } + + @override + Future popRoute() async { + print('=======popRoute========='); + return true; + } + + void backStack() { + if (_configs.isNotEmpty) { + _configs.removeLast(); + if (_configs.isNotEmpty) { + changeRoute(_configs.last); + } else { + changeRoute(current); + } + } + } + + bool _onPopPage(Route route, result) { + if (_completerMap.containsKey(path)) { + _completerMap[path]?.complete(result); + _completerMap.remove(path); + } + + if (_configs.isNotEmpty) { + _configs.removeLast(); + notifyListeners(); + } else { + changePath(backPath(path), recordHistory: false); + } + return route.didPop(result); + } + + String backPath(String path) { + Uri uri = Uri.parse(path); + if (uri.pathSegments.length == 1) return path; + List parts = List.of(uri.pathSegments)..removeLast(); + return '/${parts.join('/')}'; + } + + @override + Future setNewRoutePath(configuration) async {} +} diff --git a/lib/v9/app/navigation/router/iroute.dart b/lib/v9/app/navigation/router/iroute.dart new file mode 100644 index 0000000..8e1de0f --- /dev/null +++ b/lib/v9/app/navigation/router/iroute.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; + +import 'iroute_config.dart'; + +typedef IRoutePageBuilder = Page? Function( + BuildContext context, + IRouteConfig data, +); + +typedef IRouteWidgetBuilder = Widget? Function( + BuildContext context, + IRouteConfig data, +); + +abstract class IRouteNode { + final String path; + final List children; + + const IRouteNode({ + required this.path, + required this.children, + }); + + Page? createPage(BuildContext context, IRouteConfig config); + + List find( + String input, + ) { + return findNodes(this, Uri.parse(input), 0, '/', []); + } + + List findNodes( + IRouteNode node, + Uri uri, + int deep, + String prefix, + List result, + ) { + List parts = uri.pathSegments; + if (deep > parts.length - 1) { + return result; + } + String target = parts[deep]; + if (node.children.isNotEmpty) { + target = prefix + target; + List nodes = + node.children.where((e) => e.path == target).toList(); + bool match = nodes.isNotEmpty; + if (match) { + IRouteNode matched = nodes.first; + result.add(matched); + String nextPrefix = '${matched.path}/'; + findNodes(matched, uri, ++deep, nextPrefix, result); + } else { + result.add(NotFindNode(path: target)); + return result; + } + } + return result; + } +} + +/// 优先调用 [pageBuilder] 构建 Page +/// 没有 [pageBuilder] 时, 使用 [widgetBuilder] 构建组件 +/// 没有 [pageBuilder] 和 [widgetBuilder] 时, 使用 [widget] 构建组件 +class IRoute extends IRouteNode { + final IRoutePageBuilder? pageBuilder; + final IRouteWidgetBuilder? widgetBuilder; + final Widget? widget; + + const IRoute({ + required super.path, + super.children = const [], + this.widget, + this.pageBuilder, + this.widgetBuilder, + }); + + @override + Page? createPage(BuildContext context, IRouteConfig config) { + if (pageBuilder != null) { + return pageBuilder!(context, config); + } + Widget? child; + if (widgetBuilder != null) { + child = widgetBuilder!(context, config); + } + child ??= widget; + if (child != null) { + return MaterialPage(child: child, key: config.pageKey); + } + return null; + } +} + +/// 未知路由 +class NotFindNode extends IRouteNode { + NotFindNode({required super.path, super.children = const []}); + + @override + Page? createPage(BuildContext context, IRouteConfig config) { + return null; + } +} + + +class CellIRoute extends IRoute { + const CellIRoute({ + required super.path, + super.pageBuilder, + super.children, + super.widget, + }); +} + +typedef CellBuilder = Widget Function(BuildContext context,IRouteConfig config, CellIRoute cell); diff --git a/lib/v9/app/navigation/router/iroute_config.dart b/lib/v9/app/navigation/router/iroute_config.dart new file mode 100644 index 0000000..f526e71 --- /dev/null +++ b/lib/v9/app/navigation/router/iroute_config.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +enum RouteStyle{ + push, + replace, +} + + +class IRouteConfig { + final Object? extra; + final bool forResult; + final Uri uri; + final bool keepAlive; + final RouteStyle routeStyle; + final bool recordHistory; + + const IRouteConfig({ + this.extra, + required this.uri, + this.forResult = false, + this.keepAlive = false, + this.routeStyle = RouteStyle.replace, + this.recordHistory = false, + }); + + String get path => uri.path; + + IRouteConfig copyWith({ + Object? extra, + bool? forResult, + bool? keepAlive, + bool? recordHistory, + String? path, + }) => + IRouteConfig( + extra: extra ?? this.extra, + forResult: forResult ?? this.forResult, + keepAlive: keepAlive ?? this.keepAlive, + recordHistory: recordHistory ?? this.recordHistory, + uri: path!=null?Uri.parse(path):uri, + ); + + ValueKey get pageKey => ValueKey(hashCode); + + + @override + String toString() { + return 'IRouteConfig{extra: $extra, forResult: $forResult, uri: $uri, keepAlive: $keepAlive, routeStyle: $routeStyle, recordHistory: $recordHistory}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IRouteConfig && + runtimeType == other.runtimeType && + extra == other.extra && + forResult == other.forResult && + uri == other.uri && + keepAlive == other.keepAlive && + routeStyle == other.routeStyle && + recordHistory == other.recordHistory; + + @override + int get hashCode => + extra.hashCode ^ + forResult.hashCode ^ + uri.hashCode ^ + keepAlive.hashCode ^ + routeStyle.hashCode ^ + recordHistory.hashCode; +} diff --git a/lib/v9/app/navigation/router/route_history_manager.dart b/lib/v9/app/navigation/router/route_history_manager.dart new file mode 100644 index 0000000..8844898 --- /dev/null +++ b/lib/v9/app/navigation/router/route_history_manager.dart @@ -0,0 +1,47 @@ +import 'iroute_config.dart'; + +typedef OnRouteChange = void Function(IRouteConfig config); + +class RouteHistoryManager{ + final List _histories = []; + final List _backHistories = []; + + List get histories => _histories.reversed.toList(); + + bool get hasHistory => _histories.length > 1; + + bool get hasBackHistory => _backHistories.isNotEmpty; + + /// 将 [config] 加入历史记录 + void recode(IRouteConfig config){ + if (_histories.isNotEmpty && config.path == _histories.last.path) return; + _histories.add(config); + } + + /// 历史回退操作 + /// 将当前顶层移除,并加入 [_backHistories] 撤销列表 + /// 并转到前一路径 [_histories.last] + void back(OnRouteChange callback) { + if (!hasHistory) return; + IRouteConfig top = _histories.removeLast(); + _backHistories.add(top); + if (_histories.isNotEmpty) { + callback(_histories.last); + } + } + + /// 撤销回退操作 + /// 取出回退列表的最后元素,跳转到该路径 + void revocation(OnRouteChange callback) { + IRouteConfig target = _backHistories.removeLast(); + callback(target); + } + + void close(int index) { + _histories.removeAt(index); + } + + void clear() { + _histories.clear(); + } +} \ No newline at end of file diff --git a/lib/v9/app/navigation/router/routes.dart b/lib/v9/app/navigation/router/routes.dart new file mode 100644 index 0000000..da6fb72 --- /dev/null +++ b/lib/v9/app/navigation/router/routes.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import '../../../pages/sort/views/player/sort_player.dart'; +import 'iroute_config.dart'; +import 'iroute.dart'; +import '../../../pages/color/color_add_page.dart'; +import '../../../pages/color/color_detail_page.dart'; +import '../../../pages/color/color_page.dart'; +import '../../../pages/counter/counter_page.dart'; +import '../../../pages/user/user_page.dart'; +import '../../../pages/settings/settings_page.dart'; +import '../../../pages/sort/views/sort_page/sort_page.dart'; +import '../../../pages/sort/views/settings/sort_setting.dart'; + +IRoute rootRoute = const IRoute( + path: 'root', + children: [ + IRoute( + path: '/color', + widget: ColorPage(), + children: [ + IRoute(path: '/color/detail', widgetBuilder: _buildColorDetail), + IRoute(path: '/color/add', widget: ColorAddPage()), + ], + ), + IRoute(path: '/counter', widget: CounterPage()), + CellIRoute( + path: '/sort', + widget: SortPage(), + children: [ + IRoute( + path: '/sort/settings', + widget: SortSettings(), + ), + IRoute( + path: '/sort/player', + widget: SortPlayer(), + ), + ], + ), + IRoute(path: '/user', widget: UserPage()), + IRoute(path: '/settings', widget: SettingPage()), + ], +); + +Widget? _buildColorDetail(BuildContext context, IRouteConfig data) { + final Map queryParams = data.uri.queryParameters; + String? selectedColor = queryParams['color']; + Color color = Colors.black; + if (selectedColor != null) { + color = Color(int.parse(selectedColor, radix: 16)); + } else if (data.extra is Color) { + color = data.extra as Color; + } + return ColorDetailPage(color: color); +} diff --git a/lib/v9/app/navigation/router/views/not_find_view.dart b/lib/v9/app/navigation/router/views/not_find_view.dart new file mode 100644 index 0000000..ccefa16 --- /dev/null +++ b/lib/v9/app/navigation/router/views/not_find_view.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class NotFindPage extends StatelessWidget { + const NotFindPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Material( + child: Center( + child: Wrap( + spacing: 16, + crossAxisAlignment: WrapCrossAlignment.center, + direction: Axis.vertical, + children: [ + Icon(Icons.nearby_error,size: 64, color: Colors.redAccent), + Text( + '404 Page Not Find', + style: TextStyle(fontSize: 24, color: Colors.grey), + ), + ], + ), + ), + ); + } +} diff --git a/lib/v9/app/navigation/router/views/route_back_indicator.dart b/lib/v9/app/navigation/router/views/route_back_indicator.dart new file mode 100644 index 0000000..5be781a --- /dev/null +++ b/lib/v9/app/navigation/router/views/route_back_indicator.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import '../app_router_delegate.dart'; +class RouteBackIndicator extends StatefulWidget { + const RouteBackIndicator({super.key}); + + @override + State createState() => _RouteBackIndicatorState(); +} + +class _RouteBackIndicatorState extends State { + + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if(router.canPop){ + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: router.backStack, + child: Container( + width: 26, + height: 26, + margin: EdgeInsets.only(right: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffE3E5E7), + borderRadius: BorderRadius.circular(6) + ), + child: Icon(Icons.arrow_back_ios_new,size: 14,)), + ), + ); + } + return SizedBox(); + } + + void _onChange() { + setState(() { + + }); + } +} diff --git a/lib/v9/app/navigation/transition/fade_page_transitions_builder.dart b/lib/v9/app/navigation/transition/fade_page_transitions_builder.dart new file mode 100644 index 0000000..0587ecc --- /dev/null +++ b/lib/v9/app/navigation/transition/fade_page_transitions_builder.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class FadePageTransitionsBuilder extends PageTransitionsBuilder { + + const FadePageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute? route, + BuildContext? context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return _FadePagePageTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + } +} + +class _FadePagePageTransition extends StatelessWidget { + + const _FadePagePageTransition({ + required this.animation, + required this.secondaryAnimation, + required this.child, + }); + + final Animation animation; + final Animation secondaryAnimation; + final Widget child; + + @override + Widget build(BuildContext context) { + var curveTween = CurveTween(curve: Curves.easeIn); + return FadeTransition( + opacity: animation.drive(curveTween), + child: child, + ); + } +} \ No newline at end of file diff --git a/lib/v9/app/navigation/transition/fade_transition_page.dart b/lib/v9/app/navigation/transition/fade_transition_page.dart new file mode 100644 index 0000000..fc4e6e6 --- /dev/null +++ b/lib/v9/app/navigation/transition/fade_transition_page.dart @@ -0,0 +1,52 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class FadeTransitionPage extends Page { + final Widget child; + final Duration duration; + + const FadeTransitionPage({ + super.key, + required this.child, + this.duration = const Duration(milliseconds: 300), + }); + + @override + Route createRoute(BuildContext context) => PageBasedFadeTransitionRoute(this); +} + +class PageBasedFadeTransitionRoute extends PageRoute { + final FadeTransitionPage _page; + + PageBasedFadeTransitionRoute(this._page) : super(settings: _page); + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + Duration get transitionDuration => _page.duration; + + @override + bool get maintainState => true; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + var curveTween = CurveTween(curve: Curves.easeIn); + return FadeTransition( + opacity: animation.drive(curveTween), + child: (settings as FadeTransitionPage).child, + ); + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) => + child; +} diff --git a/lib/v9/app/navigation/transition/no_transition_page.dart b/lib/v9/app/navigation/transition/no_transition_page.dart new file mode 100644 index 0000000..291910b --- /dev/null +++ b/lib/v9/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/v9/app/navigation/views/app_navigation.dart b/lib/v9/app/navigation/views/app_navigation.dart new file mode 100644 index 0000000..5abca82 --- /dev/null +++ b/lib/v9/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/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/v9/app/navigation/views/app_navigation_rail.dart b/lib/v9/app/navigation/views/app_navigation_rail.dart new file mode 100644 index 0000000..50287ff --- /dev/null +++ b/lib/v9/app/navigation/views/app_navigation_rail.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../router/app_router_delegate.dart'; +import '../router/iroute_config.dart'; + +class AppNavigationRail extends StatefulWidget { + const AppNavigationRail({super.key}); + + @override + State createState() => _AppNavigationRailState(); +} + +class _AppNavigationRailState extends State { + final List deskNavBarMenus = const [ + MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'), + MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'), + MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'), + MenuMeta(label: '我的', icon: Icons.person, path: '/user'), + MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'), + ]; + + @override + 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.9', + style: TextStyle(color: Colors.white, fontSize: 12), + ), + ), + backgroundColor: const Color(0xff3975c6), + onDestinationSelected: _onDestinationSelected, + selectedIndex: activeIndex, + ), + ); + } + + RegExp _segReg = RegExp(r'/\w+'); + + int? get activeIndex { + String path = router.path; + RegExpMatch? match = _segReg.firstMatch(path); + if (match == null) return null; + String? target = match.group(0); + int index = deskNavBarMenus.indexWhere((menu) => menu.path == target); + if (index == -1) return null; + return index; + } + + void _onDestinationSelected(int index) { + String path = deskNavBarMenus[index].path!; + if (index == 1) { + router.changePath(path, keepAlive: true); + return; + } + if (index == 4) { + router.changePath(path, style: RouteStyle.push); + return; + } else { + router.changePath(path); + } + } + + void _onRouterChange() { + setState(() {}); + } +} diff --git a/lib/v9/app/navigation/views/app_top_bar/app_router_editor.dart b/lib/v9/app/navigation/views/app_top_bar/app_router_editor.dart new file mode 100644 index 0000000..40516a7 --- /dev/null +++ b/lib/v9/app/navigation/views/app_top_bar/app_router_editor.dart @@ -0,0 +1,64 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:iroute/components/toly_ui/button/hover_icon_button.dart'; +import '../../router/app_router_delegate.dart'; + +class AppRouterEditor extends StatefulWidget { + final ValueChanged? onSubmit; + const AppRouterEditor({super.key, this.onSubmit}); + + @override + State createState() => _AppRouterEditorState(); +} + +class _AppRouterEditorState extends State { + + final TextEditingController _controller = TextEditingController(); + + + @override + void initState() { + super.initState(); + _onRouteChange(); + router.addListener(_onRouteChange); + } + + void _onRouteChange() { + _controller.text=router.path; + } + + @override + void dispose() { + _controller.dispose(); + router.removeListener(_onRouteChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.centerRight, + children: [ + SizedBox( + child: CupertinoTextField( + controller: _controller, + style: TextStyle(fontSize: 14), + padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32), + placeholder: '输入路由地址导航', + onSubmitted: widget.onSubmit, + decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: HoverIconButton( + icon: Icons.directions_outlined, + defaultColor: Color(0xff68696B), + onPressed:()=>widget.onSubmit?.call(_controller.text), + size: 20 + ), + ) + ], + ); + } +} diff --git a/lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart b/lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart new file mode 100644 index 0000000..072a838 --- /dev/null +++ b/lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../../router/app_router_delegate.dart'; +import '../../router/views/route_back_indicator.dart'; +import 'app_router_editor.dart'; +import 'history_view_icon.dart'; +import 'route_history_button.dart'; + +class AppTopBar extends StatelessWidget { + const AppTopBar({super.key}); + + @override + Widget build(BuildContext context) { + return DragToMoveWrap( + child: Container( + alignment: Alignment.center, + height: 46, + child: Row( + children: [ + const SizedBox(width: 16), + const RouteBackIndicator(), + const RouterIndicator(), + Expanded( + child: Row(children: [ + const Spacer(), + RouteHistoryButton(), + const SizedBox(width: 12,), + SizedBox( + width: 250, + child: AppRouterEditor( + onSubmit: (path) => router.changePath(path), + )), + const SizedBox(width: 12,), + HistoryViewIcon(), + const Padding( + padding: EdgeInsets.symmetric(vertical: 12.0), + child: VerticalDivider( + width: 32, + ), + ) + ])), + const WindowButtons() + ], + ), + ), + ); + } +} + +class RouterIndicator extends StatefulWidget { + const RouterIndicator({super.key}); + + @override + State createState() => _RouterIndicatorState(); +} + +Map kRouteLabelMap = { + '/color': '颜色板', + '/color/add': '添加颜色', + '/color/detail': '颜色详情', + '/counter': '计数器', + '/sort': '可视化排序算法', + '/sort/settings': '排序配置', + '/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.changePath(item.to!); + } + }, + ); + } + + void _onRouterChange() { + setState(() {}); + } + + List pathToBreadcrumbItems(String path) { + Uri uri = Uri.parse(path); + List result = []; + String to = ''; + + String distPath = ''; + for (String segment in uri.pathSegments) { + distPath += '/$segment'; + } + + for (String segment in uri.pathSegments) { + to += '/$segment'; + String label = kRouteLabelMap[to] ?? '未知路由'; + result.add(BreadcrumbItem(to: to, label: label, active: to == distPath)); + } + return result; + } +} diff --git a/lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart b/lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart new file mode 100644 index 0000000..3390a49 --- /dev/null +++ b/lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:iroute/components/components.dart'; +import '../../router/app_router_delegate.dart'; +import '../../router/iroute.dart'; +import '../../router/iroute_config.dart'; +import 'app_top_bar.dart'; + +class HistoryViewIcon extends StatelessWidget{ + const HistoryViewIcon({super.key}); + + @override + Widget build(BuildContext context) { + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: PopPanel( + offset: const Offset(0, 10), + panel: SizedBox( + height: 350, + child: Column( + children: [ + _buildTopBar(), + const Expanded( + child:HistoryPanel(), + ), + ], + ), + ), + child: const Icon( + Icons.history, + size: 20, + ), + ), + ); + } + + Widget _buildTopBar() { + return Container( + decoration: BoxDecoration( + color: const Color(0xffFAFAFC), + borderRadius: BorderRadius.circular(6), + ), + padding: + const EdgeInsets.only(top: 10, left: 12, right: 12, bottom: 8), + child: Row( + children: [ + const Text( + '浏览历史', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const Spacer(), + TextButton(onPressed: router.clearHistory, child: const Text('清空历史')) + ], + )); + } +} + +class HistoryItem extends StatefulWidget { + final IRouteConfig history; + final VoidCallback onPressed; + final VoidCallback onDelete; + + const HistoryItem({super.key, required this.history, required this.onPressed, required this.onDelete}); + + @override + State createState() => _HistoryItemState(); +} + +class _HistoryItemState extends State { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onPressed, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.history.path), + const SizedBox( + height: 2, + ), + Text(kRouteLabelMap[widget.history.path]??'未知路由'), + ], + )), + GestureDetector( + onTap: widget.onDelete, + child: const Icon( + Icons.close, + size: 18, + color: Color(0xff8E92A9), + ), + ), + ], + ), + ), + ); + } +} + +class HistoryPanel extends StatefulWidget { + const HistoryPanel({super.key}); + + @override + State createState() => _HistoryPanelState(); +} + +class _HistoryPanelState extends State { + + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List histories = router.historyManager.histories; + if(histories.isEmpty){ + return const Center( + child: Text( + '暂无浏览历史记录', + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ); + } + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + itemExtent: 46, + itemCount: histories.length, + itemBuilder: (_, index) => + HistoryItem( + onDelete: (){ + int fixIndex = histories.length - 1 - index; + router.closeHistory(fixIndex); + }, + onPressed: (){ + router.changeRoute(histories[index].copyWith(recordHistory: false)); + Navigator.of(context).pop(); + }, + history: histories[index]), + ); + } + + void _onChange() { + setState(() {}); + } +} diff --git a/lib/v9/app/navigation/views/app_top_bar/route_history_button.dart b/lib/v9/app/navigation/views/app_top_bar/route_history_button.dart new file mode 100644 index 0000000..3ee6513 --- /dev/null +++ b/lib/v9/app/navigation/views/app_top_bar/route_history_button.dart @@ -0,0 +1,58 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:iroute/components/toly_ui/button/hover_icon_button.dart'; +import '../../router/app_router_delegate.dart'; + +class RouteHistoryButton extends StatefulWidget { + const RouteHistoryButton({super.key}); + + @override + State createState() => _RouteHistoryButtonState(); +} + +class _RouteHistoryButtonState extends State { + @override + void initState() { + super.initState(); + router.addListener(_onChange); + } + + @override + void dispose() { + router.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool hasHistory = router.historyManager.hasHistory; + bool hasBackHistory = router.historyManager.hasBackHistory; + Color activeColor = const Color(0xff9195AC); + Color inActiveColor = const Color(0xffC7CAD5); + Color historyColor = hasHistory?activeColor:inActiveColor; + Color backHistoryColor = hasBackHistory?activeColor:inActiveColor; + return Wrap( + children: [ + HoverIconButton( + size: 20, + hoverColor: historyColor, + defaultColor: historyColor, + icon: CupertinoIcons.arrow_left_circle, + onPressed: hasHistory?router.back:null, + ), + const SizedBox(width: 8,), + HoverIconButton( + size: 20, + hoverColor: backHistoryColor, + defaultColor: backHistoryColor, + icon: CupertinoIcons.arrow_right_circle, + onPressed: hasBackHistory?router.revocation:null, + ), + ], + ); + } + + void _onChange() { + setState(() {}); + } +} diff --git a/lib/v9/app/unit_app.dart b/lib/v9/app/unit_app.dart new file mode 100644 index 0000000..33f59bf --- /dev/null +++ b/lib/v9/app/unit_app.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import '../pages/sort/provider/state.dart'; +import 'navigation/transition/fade_page_transitions_builder.dart'; +import 'navigation/views/app_navigation.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, + pageTransitionsTheme: const PageTransitionsTheme( + builders: { + TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.macOS: FadePageTransitionsBuilder(), + TargetPlatform.windows: FadePageTransitionsBuilder(), + TargetPlatform.linux: FadePageTransitionsBuilder(), + } + ), + appBarTheme: const AppBarTheme( + elevation: 0, + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ))), + debugShowCheckedModeBanner: false, + home: AppNavigation() + ), + ); + } +} + + diff --git a/lib/v9/pages/color/color_add_page.dart b/lib/v9/pages/color/color_add_page.dart new file mode 100644 index 0000000..48e6dc6 --- /dev/null +++ b/lib/v9/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/v9/pages/color/color_detail_page.dart b/lib/v9/pages/color/color_detail_page.dart new file mode 100644 index 0000000..7dfed86 --- /dev/null +++ b/lib/v9/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/v9/pages/color/color_page.dart b/lib/v9/pages/color/color_page.dart new file mode 100644 index 0000000..39bcc1b --- /dev/null +++ b/lib/v9/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.changePath('/color/detail_error',extra: color); + + } + + void _toAddPage() async { + Color? color = await router.changePath('/color/add',forResult: true,recordHistory: false); + if (color != null) { + setState(() { + _colors.add(color); + }); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/counter/counter_page.dart b/lib/v9/pages/counter/counter_page.dart new file mode 100644 index 0000000..b5b2e17 --- /dev/null +++ b/lib/v9/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/v9/pages/empty/empty_page.dart b/lib/v9/pages/empty/empty_page.dart new file mode 100644 index 0000000..b05f56f --- /dev/null +++ b/lib/v9/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/v9/pages/settings/settings_page.dart b/lib/v9/pages/settings/settings_page.dart new file mode 100644 index 0000000..0b53503 --- /dev/null +++ b/lib/v9/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/v9/pages/sort/functions.dart b/lib/v9/pages/sort/functions.dart new file mode 100644 index 0000000..6ae1176 --- /dev/null +++ b/lib/v9/pages/sort/functions.dart @@ -0,0 +1,46 @@ +import 'functions/bubble.dart'; +import 'functions/cocktail.dart'; +import 'functions/comb.dart'; +import 'functions/cycle.dart'; +import 'functions/gnome.dart'; +import 'functions/heap.dart'; +import 'functions/insertion.dart'; +import 'functions/merage.dart'; +import 'functions/pigeonHole.dart'; +import 'functions/quick.dart'; +import 'functions/selection.dart'; +import 'functions/shell.dart'; + +typedef SortFunction = Future Function(List src, SortCallback callback); +typedef SortCallback = Future Function(List dist); + +Map sortFunctionMap = { + 'insertion': insertionSort, + 'bubble': bubbleSort, + 'cocktail': cocktailSort, + 'comb': combSort, + 'pigeonHole': pigeonHoleSort, + 'shell': shellSort, + 'selection': selectionSort, + 'gnome': gnomeSort, + 'cycle': cycleSort, + 'heap': heapSort, + 'quick': quickSort, + 'mergeSort': mergeSort, +}; + +Map sortNameMap = { + 'insertion': '插入排序', + 'bubble': '冒泡排序', + 'cocktail': '鸡尾酒排序(双向冒泡排序)', + 'comb': '梳排序', + 'pigeonHole': '鸽巢排序', + 'shell': '希尔排序', + 'selection': '选择排序', + 'gnome': '侏儒排序', + 'cycle': '循环排序', + 'heap': '堆排序', + 'quick': '快速排序', + 'mergeSort': '归并排序', +}; + diff --git a/lib/v9/pages/sort/functions/bubble.dart b/lib/v9/pages/sort/functions/bubble.dart new file mode 100644 index 0000000..3cf3c99 --- /dev/null +++ b/lib/v9/pages/sort/functions/bubble.dart @@ -0,0 +1,19 @@ +import '../functions.dart'; + +///冒泡排序 +Future bubbleSort(List src, SortCallback callback ) async{ + //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 + for (int i = 0; i < src.length; ++i) { + //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 + for (int j = 0; j < src.length - i - 1; ++j) { + //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 + if (src[j] > src[j + 1]) { + int temp = src[j]; + src[j] = src[j + 1]; + src[j + 1] = temp; + } + //实现一个延迟,以便在ui上展示排序的动画效果 + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/cocktail.dart b/lib/v9/pages/sort/functions/cocktail.dart new file mode 100644 index 0000000..8c2d18c --- /dev/null +++ b/lib/v9/pages/sort/functions/cocktail.dart @@ -0,0 +1,52 @@ +import '../functions.dart'; + +///鸡尾酒排序(双向冒泡排序) +Future cocktailSort(List src, SortCallback callback ) async { + bool swapped = true; // 表示是否进行了交换 + int start = 0; // 当前未排序部分的起始位置 + int end = src.length; // 当前未排序部分的结束位置 + + // 开始排序循环,只有当没有进行交换时才会退出循环 + while (swapped == true) { + swapped = false; + + // 从左往右遍历需要排序的部分 + for (int i = start; i < end - 1; ++i) { + // 对每两个相邻元素进行比较 + if (src[i] > src[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await callback(src); + } + + // 如果没有进行交换,则说明已经排好序,退出循环 + if (swapped == false) break; + // 重设为false,准备进行下一轮排序 + swapped = false; + // 将end设置为上一轮排序的最后一个元素的位置 + end = end - 1; + + // 从右往左遍历需要排序的部分 + for (int i = end - 1; i >= start; i--) { + // 对每两个相邻元素进行比较 + if (src[i] > src[i + 1]) { + // 如果前面的元素大于后面的元素,则交换它们的位置 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + swapped = true; // 进行了交换 + } + + // 实现动画效果,延迟一段时间后更新数组状态 + await callback(src); + } + // 将start向右移一位,准备下一轮排序 + start = start + 1; + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/comb.dart b/lib/v9/pages/sort/functions/comb.dart new file mode 100644 index 0000000..821f4a9 --- /dev/null +++ b/lib/v9/pages/sort/functions/comb.dart @@ -0,0 +1,34 @@ +import '../functions.dart'; + +///梳排序(Comb Sort) +Future combSort(List src, SortCallback callback) async{ + int gap = src.length; + + bool swapped = true; + + // 当间隔不为1或存在交换时执行循环 + while (gap != 1 || swapped == true) { + // 通过缩小间隔来逐步将元素归位 + gap = getNextGap(gap); + swapped = false; + for (int i = 0; i < src.length - gap; i++) { + // 如果当前元素大于间隔位置上的元素,则交换它们的位置 + if (src[i] > src[i + gap]) { + int temp = src[i]; + src[i] = src[i + gap]; + src[i + gap] = temp; + swapped = true; + } + + // 实现一个延迟,以便在 UI 上展示排序的动画效果。 + await callback(src); + } + } +} + +int getNextGap(int gap) { + // 根据当前间隔值计算下一个间隔值 + gap = (gap * 10) ~/ 13; + if (gap < 1) return 1; + return gap; +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/cycle.dart b/lib/v9/pages/sort/functions/cycle.dart new file mode 100644 index 0000000..4bef6eb --- /dev/null +++ b/lib/v9/pages/sort/functions/cycle.dart @@ -0,0 +1,54 @@ +import '../functions.dart'; + +///循环排序 +Future cycleSort(List src, SortCallback callback) async { + int writes = 0; + for (int cycleStart = 0; cycleStart <= src.length - 2; cycleStart++) { + int item = src[cycleStart]; + int pos = cycleStart; + + // 在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < src.length; i++) { + if (src[i] < item) pos++; + } + + // 如果当前元素已经在正确位置上,则跳过此次迭代 + if (pos == cycleStart) { + continue; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == src[pos]) { + pos += 1; + } + if (pos != cycleStart) { + int temp = item; + item = src[pos]; + src[pos] = temp; + writes++; + } + + // 循环将位于当前位置的元素放置到正确的位置上 + while (pos != cycleStart) { + pos = cycleStart; + // 继续在未排序部分中寻找比当前元素小的元素个数 + for (int i = cycleStart + 1; i < src.length; i++) { + if (src[i] < item) pos += 1; + } + + // 将当前元素放置到正确的位置上,并记录写操作次数 + while (item == src[pos]) { + pos += 1; + } + if (item != src[pos]) { + int temp = item; + item = src[pos]; + src[pos] = temp; + writes++; + } + + // 添加延迟操作以展示排序过程 + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/gnome.dart b/lib/v9/pages/sort/functions/gnome.dart new file mode 100644 index 0000000..5e08fc3 --- /dev/null +++ b/lib/v9/pages/sort/functions/gnome.dart @@ -0,0 +1,22 @@ +import '../functions.dart'; + +///地精排序 (侏儒排序) +Future gnomeSort(List src, SortCallback callback) async { + int index = 0; + while (index < src.length) { + // 当 index 小于数组长度时执行循环 + if (index == 0) index++; + if (src[index] >= src[index - 1]) { + // 如果当前元素大于等于前面的元素,则将 index 加1 + index++; + } else { + // 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置) + int temp = src[index]; + src[index] = src[index - 1]; + src[index - 1] = temp; + index--; + } + await callback(src); + } + return; +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/heap.dart b/lib/v9/pages/sort/functions/heap.dart new file mode 100644 index 0000000..9f5410b --- /dev/null +++ b/lib/v9/pages/sort/functions/heap.dart @@ -0,0 +1,38 @@ +import '../functions.dart'; + +///堆排序 +Future heapSort(List src, SortCallback callback) async { + // 从最后一个非叶子节点开始,构建最大堆 + for (int i = src.length ~/ 2; i >= 0; i--) { + await heapify(src,callback, src.length, i); + } + + // 依次取出最大堆的根节点(最大值),并进行堆化 + for (int i = src.length - 1; i >= 0; i--) { + int temp = src[0]; + src[0] = src[i]; + src[i] = temp; + await heapify(src, callback,i, 0); + } +} + +Future heapify(List src, SortCallback callback, int n, int i) async{ + int largest = i; + int l = 2 * i + 1; // 左子节点索引 + int r = 2 * i + 2; // 右子节点索引 + + // 如果左子节点存在并且大于父节点,则更新最大值索引 + if (l < n && src[l] > src[largest]) largest = l; + + // 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引 + if (r < n && src[r] > src[largest]) largest = r; + + // 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化 + if (largest != i) { + int temp = src[i]; + src[i] = src[largest]; + src[largest] = temp; + heapify(src,callback, n, largest); + } + await callback(src); +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/insertion.dart b/lib/v9/pages/sort/functions/insertion.dart new file mode 100644 index 0000000..b1c7814 --- /dev/null +++ b/lib/v9/pages/sort/functions/insertion.dart @@ -0,0 +1,19 @@ +import '../functions.dart'; + +///插入排序 +Future insertionSort(List src, SortCallback callback) async { + for (int i = 1; i < src.length; i++) { + int temp = src[i]; // 将当前元素存储到临时变量 temp 中 + int j = i - 1; // j 表示已排序部分的最后一个元素的索引 + + // 在已排序部分从后往前查找,找到合适位置插入当前元素 + while (j >= 0 && temp < src[j]) { + src[j + 1] = src[j]; // 当前元素比已排序部分的元素小,将元素后移一位 + --j; // 向前遍历 + // 更新排序结果回调 + await callback(src); + } + src[j + 1] = temp; // 插入当前元素到已排序部分的正确位置 + await callback(src); + } +} diff --git a/lib/v9/pages/sort/functions/merage.dart b/lib/v9/pages/sort/functions/merage.dart new file mode 100644 index 0000000..12135b4 --- /dev/null +++ b/lib/v9/pages/sort/functions/merage.dart @@ -0,0 +1,79 @@ +import '../functions.dart'; + +//快速排序 +Future mergeSort(List src, SortCallback callback) async { + await _mergeSort(src,callback,0,src.length-1); +} + +///归并排序 +Future _mergeSort(List src, SortCallback callback,int leftIndex, int rightIndex) async { + // 定义一个名为 merge 的异步函数,用于合并两个有序子数组 + Future merge(int leftIndex, int middleIndex, int rightIndex) async { + // 计算左侧子数组和右侧子数组的大小 + int leftSize = middleIndex - leftIndex + 1; + int rightSize = rightIndex - middleIndex; + + // 创建左侧子数组和右侧子数组 + List leftList = List.generate(leftSize, (index) => 0); + List rightList = List.generate(rightSize, (index) => 0); + + // 将原始数组中的元素分别复制到左侧子数组和右侧子数组中 + for (int i = 0; i < leftSize; i++) { + leftList[i] = src[leftIndex + i]; + } + for (int j = 0; j < rightSize; j++) { + rightList[j] = src[middleIndex + j + 1]; + } + + // 初始化游标和索引 + int i = 0, j = 0; + int k = leftIndex; + + // 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中 + while (i < leftSize && j < rightSize) { + if (leftList[i] <= rightList[j]) { + src[k] = leftList[i]; + i++; + } else { + src[k] = rightList[j]; + j++; + } + + await callback(src); + + k++; + } + + // 将左侧子数组或右侧子数组中剩余的元素放入原始数组中 + while (i < leftSize) { + src[k] = leftList[i]; + i++; + k++; + + await callback(src); + } + + while (j < rightSize) { + src[k] = rightList[j]; + j++; + k++; + + await callback(src); + } + } + + // 如果左索引小于右索引,则递归地对数组进行归并排序 + if (leftIndex < rightIndex) { + // 计算中间索引位置 + int middleIndex = (rightIndex + leftIndex) ~/ 2; + + // 分别对左侧子数组和右侧子数组进行归并排序 + await _mergeSort(src,callback,leftIndex, middleIndex); + await _mergeSort(src,callback,middleIndex + 1, rightIndex); + + await callback(src); + + // 合并两个有序子数组 + await merge(leftIndex, middleIndex, rightIndex); + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/oddEven.dart b/lib/v9/pages/sort/functions/oddEven.dart new file mode 100644 index 0000000..bd6f548 --- /dev/null +++ b/lib/v9/pages/sort/functions/oddEven.dart @@ -0,0 +1,37 @@ +import '../functions.dart'; + +///奇偶排序(Odd-Even Sort) +Future oddEvenSort(List src, SortCallback callback) async { + bool isSorted = false; + + while (!isSorted) { + // 当 isSorted 为 false 时执行循环 + isSorted = true; // 先假设数组已经排好序 + + for (int i = 1; i <= src.length - 2; i = i + 2) { + // 对奇数索引位置进行比较 + if (src[i] > src[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false + await callback(src); + } + } + + for (int i = 0; i <= src.length - 2; i = i + 2) { + // 对偶数索引位置进行比较 + if (src[i] > src[i + 1]) { + // 如果当前元素大于后面的元素,则交换它们的值 + int temp = src[i]; + src[i] = src[i + 1]; + src[i + 1] = temp; + isSorted = false; + await callback(src); + } + } + } + + return; +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/pigeonHole.dart b/lib/v9/pages/sort/functions/pigeonHole.dart new file mode 100644 index 0000000..83bbce1 --- /dev/null +++ b/lib/v9/pages/sort/functions/pigeonHole.dart @@ -0,0 +1,33 @@ +import '../functions.dart'; + +///鸽巢排序 +Future pigeonHoleSort(List src, SortCallback callback ) async{ + int min = src[0]; + int max = src[0]; + int range, i, j, index; + + // 找到数组中的最大值和最小值 + for (int a = 0; a < src.length; a++) { + if (src[a] > max) max = src[a]; + if (src[a] < min) min = src[a]; + } + + // 计算鸽巢的个数 + range = max - min + 1; + List p = List.generate(range, (i) => 0); + + // 将数字分配到各个鸽巢中 + for (i = 0; i < src.length; i++) { + p[src[i] - min]++; + } + + index = 0; + + // 将鸽巢中的数字取出,重新放回到数组中 + for (j = 0; j < range; j++) { + while (p[j]-- > 0) { + src[index++] = j + min; + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/quick.dart b/lib/v9/pages/sort/functions/quick.dart new file mode 100644 index 0000000..25b9685 --- /dev/null +++ b/lib/v9/pages/sort/functions/quick.dart @@ -0,0 +1,65 @@ +import '../functions.dart'; + +//快速排序 +Future quickSort(List src, SortCallback callback) async { + await _quickSort(src,callback,0,src.length-1); +} + +///快速排序 +Future _quickSort(List src, SortCallback callback,int leftIndex,int rightIndex) async { + // 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置 + Future _partition(int left, int right) async { + // 选择中间位置的元素作为基准元素 + int p = (left + (right - left) / 2).toInt(); + + // 交换基准元素和最右边的元素 + var temp = src[p]; + src[p] = src[right]; + src[right] = temp; + await callback(src); + + // 初始化游标 cursor + int cursor = left; + + // 遍历数组并根据基准元素将元素交换到左侧或右侧 + for (int i = left; i < right; i++) { + if (cf(src[i], src[right]) <= 0) { + // 如果当前元素小于等于基准元素,则交换它和游标位置的元素 + var temp = src[i]; + src[i] = src[cursor]; + src[cursor] = temp; + cursor++; + await callback(src); + } + } + + // 将基准元素放置在游标位置 + temp = src[right]; + src[right] = src[cursor]; + src[cursor] = temp; + + await callback(src); + + return cursor; // 返回基准元素的索引位置 + } + + // 如果左索引小于右索引,则递归地对数组进行快速排序 + if (leftIndex < rightIndex) { + int p = await _partition(leftIndex, rightIndex); + + await _quickSort(src,callback,leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序 + + await _quickSort(src,callback, p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序 + } +} + +// 比较函数,用于判断两个元素的大小关系 +cf(int a, int b) { + if (a < b) { + return -1; // 若 a 小于 b,则返回 -1 + } else if (a > b) { + return 1; // 若 a 大于 b,则返回 1 + } else { + return 0; // 若 a 等于 b,则返回 0 + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/selection.dart b/lib/v9/pages/sort/functions/selection.dart new file mode 100644 index 0000000..185dae2 --- /dev/null +++ b/lib/v9/pages/sort/functions/selection.dart @@ -0,0 +1,18 @@ +import '../functions.dart'; + +///选择排序 +Future selectionSort(List src, SortCallback callback ) async { + for (int i = 0; i < src.length; i++) { + for (int j = i + 1; j < src.length; j++) { + // 遍历未排序部分,内层循环控制变量 j + if (src[i] > src[j]) { + // 判断当前元素是否比后续元素小 + int temp = src[j]; + // 交换当前元素和后续较小的元素 + src[j] = src[i]; + src[i] = temp; + } + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/functions/shell.dart b/lib/v9/pages/sort/functions/shell.dart new file mode 100644 index 0000000..232f872 --- /dev/null +++ b/lib/v9/pages/sort/functions/shell.dart @@ -0,0 +1,21 @@ +import '../functions.dart'; + +///希尔排序 +Future shellSort(List src, SortCallback callback) async{ + //定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。 + for (int gap = src.length ~/ 2; gap > 0; gap ~/= 2) { + //遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。 + for (int i = gap; i < src.length; i += 1) { + //将当前遍历到的元素赋值给它 + int temp = src[i]; + //内部使用一个 for 循环来实现插入排序。 + //循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。 + int j; + for (j = i; j >= gap && src[j - gap] > temp; j -= gap) { + src[j] = src[j - gap]; + } + src[j] = temp; + await callback(src); + } + } +} \ No newline at end of file diff --git a/lib/v9/pages/sort/provider/sort_config.dart b/lib/v9/pages/sort/provider/sort_config.dart new file mode 100644 index 0000000..1fc3790 --- /dev/null +++ b/lib/v9/pages/sort/provider/sort_config.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class SortConfig { + final int count; + final int seed; + final Duration duration; + final String name; + final int colorIndex; + + SortConfig({ + this.count = 100, + this.duration = const Duration(microseconds: 1500), + this.seed = -1, + this.colorIndex = 0, + this.name = 'insertion', + }); + + SortConfig copyWith({ + int? count, + int? seed, + int? colorIndex, + Duration? duration, + String? name, + }) => + SortConfig( + count:count??this.count, + seed:seed??this.seed, + duration:duration??this.duration, + name:name??this.name, + colorIndex:colorIndex??this.colorIndex, + ); +} + diff --git a/lib/v9/pages/sort/provider/state.dart b/lib/v9/pages/sort/provider/state.dart new file mode 100644 index 0000000..1e79b05 --- /dev/null +++ b/lib/v9/pages/sort/provider/state.dart @@ -0,0 +1,101 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../functions.dart'; +import 'sort_config.dart'; + +enum SortStatus{ + none, // 未操作 + sorting, // 排序中 + sorted, // 排序完成 +} + +List kColorSupport = [ + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.red, + Colors.pink, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.indigo, + Colors.purple, + Colors.deepPurple, +]; + +class SortState with ChangeNotifier{ + + SortState(){ + reset(); + } + + SortStatus status = SortStatus.none; + + List data = []; + List stepData = []; + + SortConfig _config = SortConfig(); + SortConfig get config => _config; + Random random = Random(); + + set config(SortConfig config){ + _config = config; + reset(); + notifyListeners(); + } + + void selectName(String name){ + if(name==config.name) return; + config = config.copyWith(name: name); + } + + void selectColor(int colorIndex){ + if(colorIndex==config.colorIndex) return; + config = config.copyWith(colorIndex: colorIndex); + } + + void reset(){ + data.clear(); + status = SortStatus.none; + notifyListeners(); + int count = config.count; + if(config.seed!=-1){ + random = Random(config.seed); + } + for (int i = 0; i < count; i++) { + //随机往数组中填值 + data.add(random.nextInt(1000)); + } + } + + void sort() async{ + status = SortStatus.sorting; + notifyListeners(); + Stopwatch stopwatch = Stopwatch()..start(); + SortFunction? sortFunction = sortFunctionMap[config.name]; + if(sortFunction!=null){ + await sortFunction(data,(arr) async { + await Future.delayed(config.duration); + notifyListeners(); + }); + } + status = SortStatus.sorted; + notifyListeners(); + stopwatch.stop(); + print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms."); + } +} + +/// Provides the current [SortState] to descendant widgets in the tree. +class SortStateScope extends InheritedNotifier { + const SortStateScope({ + required super.notifier, + required super.child, + super.key, + }); + + static SortState of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!.notifier!; +} \ No newline at end of file diff --git a/lib/v7/pages/sort/views/code_page/code_page.dart b/lib/v9/pages/sort/views/code_page/code_page.dart similarity index 100% rename from lib/v7/pages/sort/views/code_page/code_page.dart rename to lib/v9/pages/sort/views/code_page/code_page.dart diff --git a/lib/v9/pages/sort/views/player/data_painter.dart b/lib/v9/pages/sort/views/player/data_painter.dart new file mode 100644 index 0000000..392a9e8 --- /dev/null +++ b/lib/v9/pages/sort/views/player/data_painter.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + + + +class DataPainter extends CustomPainter{ + + final List data; + final MaterialColor color; + + DataPainter( {required this.data,required this.color,}); + + @override + void paint(Canvas canvas, Size size) { + double itemWidth = size.width/data.length; + double height = size.height; + + Paint paint = Paint(); + paint.strokeWidth = itemWidth; + paint.strokeCap = StrokeCap.round; + + + for(int i=0;i numbers = state.data; + MaterialColor color = kColorSupport[state.config.colorIndex]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomPaint( + painter: DataPainter(data: numbers,color: color), + child: ConstrainedBox(constraints: const BoxConstraints.expand()), + ), + ); + } +} diff --git a/lib/v9/pages/sort/views/settings/color_picker.dart b/lib/v9/pages/sort/views/settings/color_picker.dart new file mode 100644 index 0000000..b0bb469 --- /dev/null +++ b/lib/v9/pages/sort/views/settings/color_picker.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class ColorPicker extends StatelessWidget { + final List colors; + final ValueChanged onSelected; + final int activeIndex; + + const ColorPicker({ + super.key, + required this.colors, + required this.activeIndex, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Wrap( + children: colors + .asMap() + .keys + .map((int index) => MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: ()=>onSelected(index), + child: Container( + width: 32, + height: 32, + color: colors[index], + child: activeIndex == index + ? const Icon( + Icons.check, + color: Colors.white, + ) + : null, + ), + ), + )) + .toList(), + ); + } +} diff --git a/lib/v9/pages/sort/views/settings/sort_setting.dart b/lib/v9/pages/sort/views/settings/sort_setting.dart new file mode 100644 index 0000000..654d549 --- /dev/null +++ b/lib/v9/pages/sort/views/settings/sort_setting.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import '../../provider/state.dart'; +import 'color_picker.dart'; +class SortSettings extends StatefulWidget { + + const SortSettings({super.key,}); + + @override + State createState() => _SortSettingsState(); +} + +class _SortSettingsState extends State { + final TextEditingController _count = TextEditingController(); + final TextEditingController _duration = TextEditingController(); + final TextEditingController _seed = TextEditingController(); + int _colorIndex = 0; + + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + SortState state = SortStateScope.of(context); + _count.text = state.config.count.toString(); + _duration.text = state.config.duration.inMicroseconds.toString(); + _seed.text = state.config.seed.toString(); + _colorIndex = state.config.colorIndex; + } + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + leading: Align( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: (){ + Navigator.of(context).pop(); + }, + child: Container( + width: 28, + height: 28, + margin: EdgeInsets.only(right: 8,left: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffE3E5E7), + borderRadius: BorderRadius.circular(6) + ), + child: Icon(Icons.arrow_back_ios_new,size: 18,)), + ), + ), + ), + // leading: BackButton(), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + splashRadius: 20, + onPressed: (){ + SortState state = SortStateScope.of(context); + state.config =state.config.copyWith( + count: int.parse(_count.text), + duration: Duration( + microseconds: int.parse(_duration.text), + ), + seed: int.parse(_seed.text), + colorIndex: _colorIndex + ); + Navigator.of(context).pop(); + }, icon: Icon(Icons.check)), + )], + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + centerTitle: true, + title: Text('排序算法配置'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + children: [ + Row( + children: [ + Text('数据数量(个数):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _count, + )), + ], + ), + Row( + children: [ + Text('时间间隔(微秒):'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _duration, + )), + ], + ), + Row( + children: [ + Text('随机种子:'), + const SizedBox( + width: 20, + ), + Expanded( + child: TextField( + controller: _seed, + )), + ], + ), + const SizedBox(height: 20,), + + Row( + children: [ + Text('选择颜色:'), + const SizedBox( + width: 20, + ), + Expanded( + child: ColorPicker( + colors: kColorSupport, + onSelected: (index){ + setState(() { + _colorIndex = index; + }); + }, + activeIndex: _colorIndex, + ),), + ], + ), + + Spacer(), + // ElevatedButton( + // onPressed: () { + // SortState state = SortStateScope.of(context); + // state.config =state.config.copyWith( + // count: int.parse(_count.text), + // duration: Duration( + // microseconds: int.parse(_duration.text), + // ), + // seed: int.parse(_seed.text) + // ); + // Navigator.of(context).pop(); + // }, + // child: Text('确定设置')) + ], + ), + ), + ); + } +} diff --git a/lib/v9/pages/sort/views/sort_page/sort_button.dart b/lib/v9/pages/sort/views/sort_page/sort_button.dart new file mode 100644 index 0000000..93df9d9 --- /dev/null +++ b/lib/v9/pages/sort/views/sort_page/sort_button.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../provider/state.dart'; + +class SortButton extends StatelessWidget { + const SortButton({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + VoidCallback? action; + IconData icon; + String text = ''; + Color color; + switch (state.status) { + case SortStatus.none: + icon = Icons.not_started_outlined; + color = Colors.green; + action = state.sort; + text = '点击启动'; + break; + case SortStatus.sorting: + icon = Icons.stop_circle_outlined; + color = Colors.grey; + action = null; + text = '排序中...'; + break; + case SortStatus.sorted: + icon = CupertinoIcons.repeat; + color = Colors.black; + action = state.reset; + text = '点击重置'; + break; + } + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: action, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon( + icon, + color: color, + size: 18, + ), + const SizedBox(width: 4,), + Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),), + ], + ), + ), + ); + } +} diff --git a/lib/v9/pages/sort/views/sort_page/sort_page.dart b/lib/v9/pages/sort/views/sort_page/sort_page.dart new file mode 100644 index 0000000..7c11b9a --- /dev/null +++ b/lib/v9/pages/sort/views/sort_page/sort_page.dart @@ -0,0 +1,230 @@ +import 'dart:ffi'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../player/sort_player.dart'; +import '../../../../app/navigation/router/app_router_delegate.dart'; +import '../settings/sort_setting.dart'; +import 'sort_button.dart'; + +import '../../functions.dart'; +import '../../provider/state.dart'; + +class SortPage extends StatefulWidget { + const SortPage({super.key}); + + @override + State createState() => _SortPageState(); +} + +class _SortPageState extends State { + @override + Widget build(BuildContext context) { + return Material( + child: Row( + children: [ + SizedBox( + width: 220, + child: SortRailPanel(), + ), + VerticalDivider( + width: 1, + ), + Expanded( + child: SortNavigatorScope(), + ) + ], + ), + ); + } +} + +class SortRailPanel extends StatelessWidget { + const SortRailPanel({super.key}); + + @override + Widget build(BuildContext context) { + SortState state = SortStateScope.of(context); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + const SortButton(), + const Spacer(), + const MouseRegion( + cursor: SystemMouseCursors.click, + child: Icon( + CupertinoIcons.chevron_left_slash_chevron_right, + size: 18, + ), + ), + const SizedBox( + width: 8, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + router.changePath('/sort/settings'); + }, + child: const Icon( + CupertinoIcons.settings, + size: 18, + )), + ), + ], + ), + ), + const Divider( + height: 1, + ), + Expanded( + child: SortSelectorPanel( + active: state.config.name, + options: sortNameMap.values.toList(), + onSelected: (name) { + state.selectName(name); + router.changePath('/sort'); + }, + ), + ), + ], + ); + } +} + +class SortNavigatorScope extends StatefulWidget { + const SortNavigatorScope({super.key}); + + @override + State createState() => _SortNavigatorScopeState(); +} + +class _SortNavigatorScopeState extends State { + @override + void initState() { + router.addListener(_update); + super.initState(); + } + + @override + void dispose() { + router.removeListener(_update); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + String path = router.path; + List pages = buildPagesByPath(context, path); + return Navigator( + onPopPage: _onPopPage, + pages: pages, + ); + } + + bool _onPopPage(Route route, result) { + return route.didPop(result); + } + + List buildPagesByPath(BuildContext context, String path) { + if (path == '/sort/settings') { + return [ + const MaterialPage(key: ValueKey('/sort/player'), child: SortPlayer()), + const MaterialPage( + key: ValueKey('/sort/settings'), + child: SortSettings(), + ), + ]; + } + + return [ + const MaterialPage( + key: ValueKey('/sort/player'), + child: SortPlayer(), + ) + ]; + } + + void _update() { + setState(() {}); + } +} + +class SortSelectorPanel extends StatelessWidget { + final String active; + final ValueChanged onSelected; + final List options; + + const SortSelectorPanel( + {super.key, + required this.active, + required this.options, + required this.onSelected}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemExtent: 46, + itemCount: sortNameMap.length, + itemBuilder: _buildByIndex, + ); + } + + Widget? _buildByIndex(BuildContext context, int index) { + String key = sortNameMap.keys.toList()[index]; + bool selected = sortNameMap.keys.toList()[index] == active; + return SortItemTile( + selected: selected, + onTap: () => onSelected(key), + title: options[index], + ); + } +} + +class SortItemTile extends StatefulWidget { + final String title; + final VoidCallback onTap; + final bool selected; + + const SortItemTile( + {super.key, + required this.title, + required this.selected, + required this.onTap}); + + @override + State createState() => _SortItemTileState(); +} + +class _SortItemTileState extends State { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: widget.selected ? const Color(0xffE6F0FF) : null), + padding: const EdgeInsets.only(left: 12), + alignment: Alignment.centerLeft, + child: Text( + widget.title, + style: TextStyle( + fontSize: 14, + fontWeight: widget.selected ? FontWeight.bold : null), + ), + ), + ), + ), + ); + } +} diff --git a/lib/v9/pages/user/user_page.dart b/lib/v9/pages/user/user_page.dart new file mode 100644 index 0000000..aba9710 --- /dev/null +++ b/lib/v9/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/test/algorithm/add.dart b/test/algorithm/02_link_list_add.dart similarity index 99% rename from test/algorithm/add.dart rename to test/algorithm/02_link_list_add.dart index 265f296..903d57e 100644 --- a/test/algorithm/add.dart +++ b/test/algorithm/02_link_list_add.dart @@ -1,4 +1,4 @@ -///两数相加 +/// 两数相加 // 给你两个非空的链表表示两个非负的整数。 // 它们每位数字都是按照逆序 的方式存储的,并且每个节点只能存储一位数字 diff --git a/test/algorithm/1052.dart b/test/algorithm/1052.dart new file mode 100644 index 0000000..414cf4a --- /dev/null +++ b/test/algorithm/1052.dart @@ -0,0 +1,104 @@ +/// 1052. 爱生气的书店老板 +//有一个书店老板,他的书店开了 n 分钟。每分钟都有一些顾客进入这家商店。 +// 给定一个长度为 n 的整数数组 customers , +// 其中 customers[i] 是在第 i 分钟开始时进入商店的顾客数量,所有这些顾客在第 i 分钟结束后离开。 +// 在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1, +// 否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,若老板不生气则顾客是满意的。 +// 书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 minutes 分钟不生气,但却只能使用一次。 +// 请你返回 这一天营业下来,最多有多少客户能够感到满意 。 +// +//输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3 +// 输出:16 +// 解释:书店老板在最后 3 分钟保持冷静。 +// 感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16. + +// +// 算法思想: +// 因为有X分钟可以控制情绪,所以这X分钟要用在关键的时间段,也就是需要知道哪个长度为X的时间段不满意的客人最多。 +// 最多满意客户数量 = 原本就满意的客户 + X时间段内因为控制了情绪而态度反转的最多客户数量 +// +// 算法步骤: +// +// 统计老板不生气时的客人数量; +// 利用长度为X的滑动窗口统计,长度为X的时间段内不满意的客户最多数量是多少; + +import 'dart:math'; + +void main() { + List customers = [4, 6, 3, 8, 7, 2, 6]; + //1生气 0不气 + List grumpy = [1, 0, 1, 1, 0, 1, 0]; + //最大不满数 3+8 + // 没有魔法时满意数 6+7+6 = 19; + //拥有魔法 6+3+8+7+6 =30 + int magic = 2; + maxSatisfied(customers, grumpy, magic); +} + +// int maxSatisfied(List customers, List grumpy, int minutes) { +// int m = customers.length; +// int total = 0; +// // 统计不生气时间内的客人总数 +// for (int i = 0; i < m; i++) { +// if (grumpy[i] == 0) { +// total += customers[i]; +// } +// } +// int curAngry = 0; // 长度为X的时间段内,不满意的客人数 +// // 统计第一个大小为X的窗口内,不满意的客人数 +// for (int i = 0; i < minutes; i++) { +// curAngry += customers[i] * grumpy[i]; +// } +// int maxAngry = curAngry; // 所有窗口时间段内,遇到不满意的客人的最多数量 +// // 滑动窗口,统计max_angry +// for (int i = minutes; i < m; i++) { +// curAngry += customers[i] * grumpy[i]; // 只把不满意的客人数量加进来 +// curAngry -= customers[i - minutes] * grumpy[i - minutes]; // 只减去不满意客人数量 +// maxAngry = max(maxAngry, curAngry); // 更新窗口内的不满意客人数量 +// } +// // 总满意人数 = 本来就满意的人数 + 老板抑制情绪的时间段内不满意变为满意的人数 +// return total + maxAngry; +// } + +///最大满意人数 +int maxSatisfied(List customers, List grumpy, int minutes) { + int maxSatisNumber = 0; + int maxUnSatisNumber = 0; + int unSatisNumber = 0; + //先算第一个窗口不满意人数 + for (int i = 0; i < minutes; i++) { + if (grumpy[i] == 1) { + maxUnSatisNumber += customers[i]; + unSatisNumber = maxUnSatisNumber; + } + } +//格口向后移动 + for (int i = minutes; i < customers.length; i++) { + //减去前一个格口不满数 加上后一个格口不满数 + if (grumpy[i - minutes] == 1) { + if (grumpy[i] == 1) { + unSatisNumber = unSatisNumber - customers[i - minutes] + customers[i]; + } else { + unSatisNumber = unSatisNumber - customers[i - minutes] + 0; + } + } else { + if (grumpy[i] == 1) { + unSatisNumber = unSatisNumber - 0 + customers[i]; + } else { + unSatisNumber = unSatisNumber - 0 + 0; + } + } + + maxUnSatisNumber = max(maxUnSatisNumber, unSatisNumber); + } + print("最大不满意数$maxUnSatisNumber"); + for (int i = 0; i < customers.length; i++) { + if (grumpy[i] == 0) { + maxSatisNumber += customers[i]; + } + } + maxSatisNumber = maxSatisNumber + maxUnSatisNumber; + print("最大满意数$maxSatisNumber"); + + return maxSatisNumber; +} \ No newline at end of file diff --git a/test/algorithm/643_max_sub_list_of_k.dart b/test/algorithm/643_max_sub_list_of_k.dart new file mode 100644 index 0000000..a734015 --- /dev/null +++ b/test/algorithm/643_max_sub_list_of_k.dart @@ -0,0 +1,85 @@ +import 'dart:math'; + +/// 子数组最大平均数 +/// +///给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。 +/// 请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。 +///输入:nums = [1,12,-5,-6,50,3], k = 4 +///输出:12.75 +///解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75 + +///滑动窗口解法 +//class Solution: +// def problemName(self, s: str) -> int: +// # Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表) +// x, y = ..., ... +// +// # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口 +// start = 0 +// for end in range(len(s)): +// # Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度) +// x = new_x +// if condition: +// y = new_y +// +// ''' +// ------------- 下面是两种情况,读者请根据题意二选1 ------------- +// ''' +// # Step 4 - 情况1 +// # 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否达到了限定长度 +// # 如果达到了,窗口左指针前移一个单位,从而保证下一次右指针右移时,窗口长度保持不变, +// # 左指针移动之前, 先更新Step 1定义的(部分或所有)维护变量 +// if 窗口长度达到了限定长度: +// # 更新 (部分或所有) 维护变量 +// # 窗口左指针前移一个单位保证下一次右指针右移时窗口长度保持不变 +// +// # Step 4 - 情况2 +// # 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题 +// # 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法 +// # 在左指针移动之前更新Step 1定义的(部分或所有)维护变量 +// while 不合法: +// # 更新 (部分或所有) 维护变量 +// # 不断移动窗口左指针直到窗口再次合法 +// +// # Step 5: 返回答案 +// return ... +// + +void main() { + List num = [4, 6, 3, 8, 7, 2, 6]; + print("${findMaxAverage(num, 3)}"); +} +// +double findMaxAverage(List num, int k) { + int sum = 0; + int n = num.length; + //窗口大小 + for (int i = 0; i < k; i++) { + sum += num[i]; + } + int maxSum = sum; + + for (int i = k; i < n; i++) { + sum = sum - num[i - k] + num[i];//减前一个 + 后一个 == 相当于视口向前滑动一格 + //第一次 sum = num[0]+num[1] - num[0] + num[2] = num[1] + num[2] + //第二次 sum = num[1]+num[2] - num[1] +num[3] = num[2] + num[3] + maxSum = max(maxSum, sum); + } + return 1.0 * maxSum / k; +} + +// double findMaxAverage(List num, int k) { +// int maxSum = 0; +// //窗口开始的和 +// for (int i = 0; i < k; i++) { +// maxSum += num[i]; +// } +// for( int i = k;i l1 = ['/a','/a/1']; + List l2 = ['/b','/b/1']; + List l3 = ['/a','/a/2']; + List l4 = ['/a','/a/3']; + + Set result = {...l4,...l1,...l2,...l3,}; + print(result); + + +} \ No newline at end of file