v8
This commit is contained in:
175
lib/go_router/examples/input/decoration/code_decoration.dart
Normal file
175
lib/go_router/examples/input/decoration/code_decoration.dart
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
215
lib/go_router/examples/input/input.dart
Normal file
215
lib/go_router/examples/input/input.dart
Normal file
@@ -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<bool> 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<TolyCodeInput>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
///输入监听器
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
late AnimationController _animaCtrl;
|
||||||
|
late Animation<int> _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,
|
||||||
|
);
|
||||||
|
}
|
||||||
141
lib/go_router/examples/input/input_painter.dart
Normal file
141
lib/go_router/examples/input/input_painter.dart
Normal file
@@ -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<int> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/go_router/examples/input/main.dart
Normal file
25
lib/go_router/examples/input/main.dart
Normal file
@@ -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;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
|
|
||||||
final List<String> keepAlivePath = [] ;
|
final List<String> keepAlivePath = [] ;
|
||||||
|
|
||||||
|
|
||||||
void setPathKeepLive(String value){
|
void setPathKeepLive(String value){
|
||||||
if(keepAlivePath.contains(value)){
|
if(keepAlivePath.contains(value)){
|
||||||
keepAlivePath.remove(value);
|
keepAlivePath.remove(value);
|
||||||
@@ -52,6 +53,8 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
path = value;
|
path = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void setPathForData(String value,dynamic data){
|
void setPathForData(String value,dynamic data){
|
||||||
_pathExtraMap[value] = data;
|
_pathExtraMap[value] = data;
|
||||||
path = value;
|
path = value;
|
||||||
@@ -82,6 +85,17 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
List<Page> pages = [];
|
List<Page> pages = [];
|
||||||
List<Page> topPages = _buildPageByPath(path);
|
List<Page> 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){
|
if(keepAlivePath.isNotEmpty){
|
||||||
for (String alivePath in keepAlivePath) {
|
for (String alivePath in keepAlivePath) {
|
||||||
if(alivePath!=path){
|
if(alivePath!=path){
|
||||||
@@ -171,7 +185,6 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
_completerMap[path]?.complete(result);
|
_completerMap[path]?.complete(result);
|
||||||
_completerMap.remove(path);
|
_completerMap.remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
path = backPath(path);
|
path = backPath(path);
|
||||||
return route.didPop(result);
|
return route.didPop(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class AppNavigationRail extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||||
|
|
||||||
final List<MenuMeta> deskNavBarMenus = const [
|
final List<MenuMeta> deskNavBarMenus = const [
|
||||||
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
|
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
|
||||||
MenuMeta(label: '计数器', icon: Icons.add_chart),
|
MenuMeta(label: '计数器', icon: Icons.add_chart),
|
||||||
@@ -41,22 +40,24 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
|
|||||||
),
|
),
|
||||||
tail: Padding(
|
tail: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 6.0),
|
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),
|
backgroundColor: const Color(0xff3975c6),
|
||||||
onDestinationSelected: _onDestinationSelected,
|
onDestinationSelected: _onDestinationSelected,
|
||||||
selectedIndex: router.activeIndex,
|
selectedIndex: router.activeIndex,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDestinationSelected(int index) {
|
void _onDestinationSelected(int index) {
|
||||||
if(index==1){
|
if (index == 1) {
|
||||||
router.setPathKeepLive(kDestinationsPaths[index]);
|
router.setPathKeepLive(kDestinationsPaths[index]);
|
||||||
}else{
|
return;
|
||||||
router.path = kDestinationsPaths[index];
|
|
||||||
}
|
}
|
||||||
|
router.path = kDestinationsPaths[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onRouterChange() {
|
void _onRouterChange() {
|
||||||
|
|||||||
@@ -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))
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,15 +23,15 @@ class SortPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
// color: Color(0xffF4F4F4),
|
// color: Color(0xffF4F4F4),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12,vertical: 8),
|
||||||
child: Row(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
SortButton(),
|
SortButton(),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(height: 1,),
|
const Divider(height: 1,),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SortSelectorPanel(
|
child: SortSelectorPanel(
|
||||||
active: state.config.name,
|
active: state.config.name,
|
||||||
@@ -43,57 +43,22 @@ class SortPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VerticalDivider(width: 1,),
|
const VerticalDivider(width: 1,),
|
||||||
Expanded(
|
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<NavigatorScope> createState() => _NavigatorScopeState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavigatorScopeState extends State<NavigatorScope> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
SortState state = SortStateScope.of(context);
|
|
||||||
List<int> 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<dynamic> route, result) {
|
|
||||||
return route.didPop(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SortSelectorPanel extends StatelessWidget {
|
class SortSelectorPanel extends StatelessWidget {
|
||||||
final String active;
|
final String active;
|
||||||
final ValueChanged<String> onSelected;
|
final ValueChanged<String> onSelected;
|
||||||
@@ -105,7 +70,7 @@ class SortSelectorPanel extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
itemExtent: 46,
|
itemExtent: 46,
|
||||||
itemCount: sortNameMap.length,
|
itemCount: sortNameMap.length,
|
||||||
itemBuilder: _buildByIndex,
|
itemBuilder: _buildByIndex,
|
||||||
@@ -145,9 +110,9 @@ class _SortItemTileState extends State<SortItemTile> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(6),
|
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,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.title,
|
widget.title,
|
||||||
|
|||||||
@@ -6,17 +6,27 @@ import 'route_history_manager.dart';
|
|||||||
import 'routes.dart';
|
import 'routes.dart';
|
||||||
import 'views/not_find_view.dart';
|
import 'views/not_find_view.dart';
|
||||||
|
|
||||||
AppRouterDelegate router = AppRouterDelegate();
|
AppRouterDelegate router = AppRouterDelegate(
|
||||||
|
initial: IRouteConfig(uri: Uri.parse('/color')),
|
||||||
|
);
|
||||||
|
|
||||||
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||||
String _path = '/color';
|
|
||||||
|
|
||||||
String get path => _path;
|
/// 核心数据,路由配置数据列表
|
||||||
|
final List<IRouteConfig> _configs = [];
|
||||||
|
|
||||||
|
String get path => current.uri.path;
|
||||||
|
|
||||||
|
IRouteConfig get current => _configs.last;
|
||||||
|
|
||||||
final IRoutePageBuilder? notFindPageBuilder;
|
final IRoutePageBuilder? notFindPageBuilder;
|
||||||
|
|
||||||
AppRouterDelegate({this.notFindPageBuilder}) {
|
AppRouterDelegate({
|
||||||
_historyManager.recode(IRouteConfig(uri: Uri.parse(path)));
|
this.notFindPageBuilder,
|
||||||
|
required IRouteConfig initial,
|
||||||
|
}) {
|
||||||
|
_configs.add(initial);
|
||||||
|
_historyManager.recode(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
|
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
|
||||||
@@ -45,48 +55,60 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// final List<IRouteConfig> _pathStack = [];
|
||||||
|
|
||||||
|
bool get canPop => _configs.where((e) => e.routeStyle==RouteStyle.push).isNotEmpty;
|
||||||
|
|
||||||
final Map<String, Completer<dynamic>> _completerMap = {};
|
final Map<String, Completer<dynamic>> _completerMap = {};
|
||||||
final Map<String, dynamic> _pathExtraMap = {};
|
|
||||||
final List<String> keepAlivePath = [];
|
|
||||||
|
|
||||||
FutureOr<dynamic> changeRoute(IRouteConfig config) {
|
FutureOr<dynamic> changeRoute(IRouteConfig config) {
|
||||||
String value = config.uri.path;
|
String value = config.uri.path;
|
||||||
if (_path == value) null;
|
if (current == config) null;
|
||||||
|
_handleChangeStyle(config);
|
||||||
|
|
||||||
if (config.forResult) {
|
if (config.forResult) {
|
||||||
_completerMap[value] = Completer();
|
_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) {
|
if (config.recordHistory) {
|
||||||
_historyManager.recode(config);
|
_historyManager.recode(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
_path = value;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
if (config.forResult) {
|
if (config.forResult) {
|
||||||
return _completerMap[value]!.future;
|
return _completerMap[value]!.future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleChangeStyle(IRouteConfig config){
|
||||||
|
switch (config.routeStyle) {
|
||||||
|
case RouteStyle.push:
|
||||||
|
if (_configs.contains(config)) {
|
||||||
|
_configs.remove(config);
|
||||||
|
}
|
||||||
|
_configs.add(config);
|
||||||
|
break;
|
||||||
|
case RouteStyle.replace:
|
||||||
|
List<IRouteConfig> liveRoutes = _configs.where((e) => e.keepAlive&&e!=config).toList();
|
||||||
|
_configs.clear();
|
||||||
|
_configs.addAll([...liveRoutes,config]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FutureOr<dynamic> changePath(
|
FutureOr<dynamic> changePath(
|
||||||
String value, {
|
String value, {
|
||||||
bool forResult = false,
|
bool forResult = false,
|
||||||
Object? extra,
|
Object? extra,
|
||||||
bool keepAlive = false,
|
bool keepAlive = false,
|
||||||
bool recordHistory = true,
|
bool recordHistory = true,
|
||||||
|
RouteStyle style = RouteStyle.replace,
|
||||||
}) {
|
}) {
|
||||||
return changeRoute(IRouteConfig(
|
return changeRoute(IRouteConfig(
|
||||||
uri: Uri.parse(value),
|
uri: Uri.parse(value),
|
||||||
forResult: forResult,
|
forResult: forResult,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
|
routeStyle: style,
|
||||||
keepAlive: keepAlive,
|
keepAlive: keepAlive,
|
||||||
recordHistory: recordHistory,
|
recordHistory: recordHistory,
|
||||||
));
|
));
|
||||||
@@ -96,46 +118,47 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Navigator(
|
return Navigator(
|
||||||
onPopPage: _onPopPage,
|
onPopPage: _onPopPage,
|
||||||
pages: _buildPages(context, path),
|
pages: _buildPages(context, _configs),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Page> _buildPages(BuildContext context, String path) {
|
List<Page> _buildPages(BuildContext context, List<IRouteConfig> configs) {
|
||||||
|
IRouteConfig top = configs.last;
|
||||||
|
List<IRouteConfig> bottoms = _configs.sublist(0,_configs.length-1).toList();
|
||||||
List<Page> pages = [];
|
List<Page> pages = [];
|
||||||
List<Page> topPages = _buildPageByPathFromTree(context, path);
|
List<Page> topPages = _buildPageByPathFromTree(context, top);
|
||||||
|
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
pages.addAll(topPages);
|
pages.addAll(topPages);
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Page> _buildPageByPathFromTree(BuildContext context, String path) {
|
List<Page> _buildLivePageByPathList(
|
||||||
|
BuildContext context,
|
||||||
|
List<IRouteConfig> paths,
|
||||||
|
IRouteConfig curConfig,
|
||||||
|
List<Page> curPages,
|
||||||
|
) {
|
||||||
|
List<Page> pages = [];
|
||||||
|
if (paths.isNotEmpty) {
|
||||||
|
for (IRouteConfig path in paths) {
|
||||||
|
if (path != curConfig) {
|
||||||
|
pages.addAll(_buildPageByPathFromTree(context, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 去除和 curPages 中重复的界面
|
||||||
|
pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key));
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Page> _buildPageByPathFromTree(
|
||||||
|
BuildContext context, IRouteConfig config) {
|
||||||
List<Page> result = [];
|
List<Page> result = [];
|
||||||
List<IRouteNode> iRoutes = rootRoute.find(path);
|
List<IRouteNode> iRoutes = rootRoute.find(config.path);
|
||||||
if (iRoutes.isNotEmpty) {
|
if (iRoutes.isNotEmpty) {
|
||||||
for (int i = 0; i < iRoutes.length; i++) {
|
for (int i = 0; i < iRoutes.length; i++) {
|
||||||
IRouteNode iroute = iRoutes[i];
|
IRouteNode iroute = iRoutes[i];
|
||||||
String path = iroute.path;
|
config = config.copyWith(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,
|
|
||||||
);
|
|
||||||
Page? page;
|
Page? page;
|
||||||
if (iroute is NotFindNode) {
|
if (iroute is NotFindNode) {
|
||||||
page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config);
|
page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config);
|
||||||
@@ -145,6 +168,9 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
if (page != null) {
|
if (page != null) {
|
||||||
result.add(page);
|
result.add(page);
|
||||||
}
|
}
|
||||||
|
if(iroute is CellIRoute){
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -156,12 +182,29 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void backStack() {
|
||||||
|
if (_configs.isNotEmpty) {
|
||||||
|
_configs.removeLast();
|
||||||
|
if (_configs.isNotEmpty) {
|
||||||
|
changeRoute(_configs.last);
|
||||||
|
} else {
|
||||||
|
changeRoute(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool _onPopPage(Route route, result) {
|
bool _onPopPage(Route route, result) {
|
||||||
if (_completerMap.containsKey(path)) {
|
if (_completerMap.containsKey(path)) {
|
||||||
_completerMap[path]?.complete(result);
|
_completerMap[path]?.complete(result);
|
||||||
_completerMap.remove(path);
|
_completerMap.remove(path);
|
||||||
}
|
}
|
||||||
changePath(backPath(path), recordHistory: false);
|
|
||||||
|
if (_configs.isNotEmpty) {
|
||||||
|
_configs.removeLast();
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
changePath(backPath(path), recordHistory: false);
|
||||||
|
}
|
||||||
return route.didPop(result);
|
return route.didPop(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
import 'iroute_config.dart';
|
import 'iroute_config.dart';
|
||||||
|
|
||||||
typedef IRoutePageBuilder = Page? Function(
|
typedef IRoutePageBuilder = Page? Function(
|
||||||
@@ -24,7 +23,9 @@ abstract class IRouteNode {
|
|||||||
|
|
||||||
Page? createPage(BuildContext context, IRouteConfig config);
|
Page? createPage(BuildContext context, IRouteConfig config);
|
||||||
|
|
||||||
List<IRouteNode> find(String input,) {
|
List<IRouteNode> find(
|
||||||
|
String input,
|
||||||
|
) {
|
||||||
return findNodes(this, Uri.parse(input), 0, '/', []);
|
return findNodes(this, Uri.parse(input), 0, '/', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,15 +42,16 @@ abstract class IRouteNode {
|
|||||||
}
|
}
|
||||||
String target = parts[deep];
|
String target = parts[deep];
|
||||||
if (node.children.isNotEmpty) {
|
if (node.children.isNotEmpty) {
|
||||||
target = prefix + target;
|
target = prefix + target;
|
||||||
List<IRouteNode> nodes = node.children.where((e) => e.path == target).toList();
|
List<IRouteNode> nodes =
|
||||||
|
node.children.where((e) => e.path == target).toList();
|
||||||
bool match = nodes.isNotEmpty;
|
bool match = nodes.isNotEmpty;
|
||||||
if (match) {
|
if (match) {
|
||||||
IRouteNode matched = nodes.first;
|
IRouteNode matched = nodes.first;
|
||||||
result.add(matched);
|
result.add(matched);
|
||||||
String nextPrefix = '${matched.path}/';
|
String nextPrefix = '${matched.path}/';
|
||||||
findNodes(matched, uri, ++deep, nextPrefix, result);
|
findNodes(matched, uri, ++deep, nextPrefix, result);
|
||||||
}else{
|
} else {
|
||||||
result.add(NotFindNode(path: target));
|
result.add(NotFindNode(path: target));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -92,8 +94,8 @@ class IRoute extends IRouteNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 未知路由
|
/// 未知路由
|
||||||
class NotFindNode extends IRouteNode{
|
class NotFindNode extends IRouteNode {
|
||||||
NotFindNode({required super.path, super.children= const[]});
|
NotFindNode({required super.path, super.children = const []});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||||
@@ -101,15 +103,14 @@ class NotFindNode extends IRouteNode{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellIRouter extends IRoute {
|
|
||||||
final CellBuilder cellBuilder;
|
|
||||||
|
|
||||||
CellIRouter(
|
class CellIRoute extends IRoute {
|
||||||
{required super.path,
|
const CellIRoute({
|
||||||
super.pageBuilder,
|
required super.path,
|
||||||
super.children,
|
super.pageBuilder,
|
||||||
required this.cellBuilder});
|
super.children,
|
||||||
|
super.widget,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef CellBuilder = Widget Function(BuildContext context, Widget child);
|
typedef CellBuilder = Widget Function(BuildContext context,IRouteConfig config, CellIRoute cell);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum RouteStyle{
|
||||||
|
push,
|
||||||
|
replace,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IRouteConfig {
|
class IRouteConfig {
|
||||||
final Object? extra;
|
final Object? extra;
|
||||||
final bool forResult;
|
final bool forResult;
|
||||||
final Uri uri;
|
final Uri uri;
|
||||||
final bool keepAlive;
|
final bool keepAlive;
|
||||||
|
final RouteStyle routeStyle;
|
||||||
final bool recordHistory;
|
final bool recordHistory;
|
||||||
|
|
||||||
const IRouteConfig({
|
const IRouteConfig({
|
||||||
@@ -12,6 +19,7 @@ class IRouteConfig {
|
|||||||
required this.uri,
|
required this.uri,
|
||||||
this.forResult = false,
|
this.forResult = false,
|
||||||
this.keepAlive = false,
|
this.keepAlive = false,
|
||||||
|
this.routeStyle = RouteStyle.replace,
|
||||||
this.recordHistory = false,
|
this.recordHistory = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -22,14 +30,42 @@ class IRouteConfig {
|
|||||||
bool? forResult,
|
bool? forResult,
|
||||||
bool? keepAlive,
|
bool? keepAlive,
|
||||||
bool? recordHistory,
|
bool? recordHistory,
|
||||||
|
String? path,
|
||||||
}) =>
|
}) =>
|
||||||
IRouteConfig(
|
IRouteConfig(
|
||||||
extra: extra ?? this.extra,
|
extra: extra ?? this.extra,
|
||||||
forResult: forResult ?? this.forResult,
|
forResult: forResult ?? this.forResult,
|
||||||
keepAlive: keepAlive ?? this.keepAlive,
|
keepAlive: keepAlive ?? this.keepAlive,
|
||||||
recordHistory: recordHistory ?? this.recordHistory,
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../pages/sort/views/player/sort_player.dart';
|
||||||
import 'iroute_config.dart';
|
import 'iroute_config.dart';
|
||||||
import 'iroute.dart';
|
import 'iroute.dart';
|
||||||
import '../../../pages/color/color_add_page.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/counter/counter_page.dart';
|
||||||
import '../../../pages/user/user_page.dart';
|
import '../../../pages/user/user_page.dart';
|
||||||
import '../../../pages/settings/settings_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(
|
IRoute rootRoute = const IRoute(
|
||||||
path: 'root',
|
path: 'root',
|
||||||
@@ -22,7 +23,20 @@ IRoute rootRoute = const IRoute(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
IRoute(path: '/counter', widget: CounterPage()),
|
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: '/user', widget: UserPage()),
|
||||||
IRoute(path: '/settings', widget: SettingPage()),
|
IRoute(path: '/settings', widget: SettingPage()),
|
||||||
],
|
],
|
||||||
|
|||||||
52
lib/v8/app/navigation/router/views/route_back_indicator.dart
Normal file
52
lib/v8/app/navigation/router/views/route_back_indicator.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../app_router_delegate.dart';
|
||||||
|
class RouteBackIndicator extends StatefulWidget {
|
||||||
|
const RouteBackIndicator({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RouteBackIndicator> createState() => _RouteBackIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
|
||||||
|
|
||||||
|
@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(() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:iroute/components/components.dart';
|
import 'package:iroute/components/components.dart';
|
||||||
import '../router/app_router_delegate.dart';
|
import '../router/app_router_delegate.dart';
|
||||||
|
import '../router/iroute_config.dart';
|
||||||
|
|
||||||
class AppNavigationRail extends StatefulWidget {
|
class AppNavigationRail extends StatefulWidget {
|
||||||
const AppNavigationRail({super.key});
|
const AppNavigationRail({super.key});
|
||||||
@@ -10,13 +11,12 @@ class AppNavigationRail extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||||
|
|
||||||
final List<MenuMeta> deskNavBarMenus = const [
|
final List<MenuMeta> deskNavBarMenus = const [
|
||||||
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined,path: '/color'),
|
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'),
|
||||||
MenuMeta(label: '计数器', icon: Icons.add_chart,path: '/counter'),
|
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'),
|
||||||
MenuMeta(label: '排序', icon: Icons.sort,path: '/sort'),
|
MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'),
|
||||||
MenuMeta(label: '我的', icon: Icons.person,path: '/user'),
|
MenuMeta(label: '我的', icon: Icons.person, path: '/user'),
|
||||||
MenuMeta(label: '设置', icon: Icons.settings,path: '/settings'),
|
MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -42,7 +42,10 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
|
|||||||
),
|
),
|
||||||
tail: Padding(
|
tail: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 6.0),
|
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),
|
backgroundColor: const Color(0xff3975c6),
|
||||||
onDestinationSelected: _onDestinationSelected,
|
onDestinationSelected: _onDestinationSelected,
|
||||||
@@ -53,21 +56,26 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
|
|||||||
|
|
||||||
RegExp _segReg = RegExp(r'/\w+');
|
RegExp _segReg = RegExp(r'/\w+');
|
||||||
|
|
||||||
int? get activeIndex{
|
int? get activeIndex {
|
||||||
String path = router.path;
|
String path = router.path;
|
||||||
RegExpMatch? match = _segReg.firstMatch(path);
|
RegExpMatch? match = _segReg.firstMatch(path);
|
||||||
if(match==null) return null;
|
if (match == null) return null;
|
||||||
String? target = match.group(0);
|
String? target = match.group(0);
|
||||||
int index = deskNavBarMenus.indexWhere((menu) => menu.path==target);
|
int index = deskNavBarMenus.indexWhere((menu) => menu.path == target);
|
||||||
if(index==-1) return null;
|
if (index == -1) return null;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDestinationSelected(int index) {
|
void _onDestinationSelected(int index) {
|
||||||
String path = deskNavBarMenus[index].path!;
|
String path = deskNavBarMenus[index].path!;
|
||||||
if(index==1){
|
if (index == 1) {
|
||||||
router.changePath(path,keepAlive: true);
|
router.changePath(path, keepAlive: true);
|
||||||
}else{
|
return;
|
||||||
|
}
|
||||||
|
if (index == 4) {
|
||||||
|
router.changePath(path, style: RouteStyle.push);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
router.changePath(path);
|
router.changePath(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:iroute/components/components.dart';
|
import 'package:iroute/components/components.dart';
|
||||||
import '../../router/app_router_delegate.dart';
|
import '../../router/app_router_delegate.dart';
|
||||||
|
import '../../router/views/route_back_indicator.dart';
|
||||||
import 'app_router_editor.dart';
|
import 'app_router_editor.dart';
|
||||||
import 'history_view_icon.dart';
|
import 'history_view_icon.dart';
|
||||||
import 'route_history_button.dart';
|
import 'route_history_button.dart';
|
||||||
@@ -17,6 +18,7 @@ class AppTopBar extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
const RouteBackIndicator(),
|
||||||
const RouterIndicator(),
|
const RouterIndicator(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
@@ -58,6 +60,7 @@ Map<String, String> kRouteLabelMap = {
|
|||||||
'/color/detail': '颜色详情',
|
'/color/detail': '颜色详情',
|
||||||
'/counter': '计数器',
|
'/counter': '计数器',
|
||||||
'/sort': '可视化排序算法',
|
'/sort': '可视化排序算法',
|
||||||
|
'/sort/settings': '排序配置',
|
||||||
'/user': '我的',
|
'/user': '我的',
|
||||||
'/settings': '系统设置',
|
'/settings': '系统设置',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ class SortConfig {
|
|||||||
final int seed;
|
final int seed;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
final String name;
|
final String name;
|
||||||
|
final int colorIndex;
|
||||||
|
|
||||||
SortConfig({
|
SortConfig({
|
||||||
this.count = 100,
|
this.count = 100,
|
||||||
this.duration = const Duration(microseconds: 1500),
|
this.duration = const Duration(microseconds: 1500),
|
||||||
this.seed = -1,
|
this.seed = -1,
|
||||||
|
this.colorIndex = 0,
|
||||||
this.name = 'insertion',
|
this.name = 'insertion',
|
||||||
});
|
});
|
||||||
|
|
||||||
SortConfig copyWith({
|
SortConfig copyWith({
|
||||||
int? count,
|
int? count,
|
||||||
int? seed,
|
int? seed,
|
||||||
|
int? colorIndex,
|
||||||
Duration? duration,
|
Duration? duration,
|
||||||
String? name,
|
String? name,
|
||||||
}) =>
|
}) =>
|
||||||
@@ -26,6 +29,7 @@ class SortConfig {
|
|||||||
seed:seed??this.seed,
|
seed:seed??this.seed,
|
||||||
duration:duration??this.duration,
|
duration:duration??this.duration,
|
||||||
name:name??this.name,
|
name:name??this.name,
|
||||||
|
colorIndex:colorIndex??this.colorIndex,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../functions.dart';
|
import '../functions.dart';
|
||||||
import 'sort_config.dart';
|
import 'sort_config.dart';
|
||||||
@@ -11,6 +11,20 @@ enum SortStatus{
|
|||||||
sorted, // 排序完成
|
sorted, // 排序完成
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<MaterialColor> 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{
|
class SortState with ChangeNotifier{
|
||||||
|
|
||||||
SortState(){
|
SortState(){
|
||||||
@@ -37,6 +51,11 @@ class SortState with ChangeNotifier{
|
|||||||
config = config.copyWith(name: name);
|
config = config.copyWith(name: name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectColor(int colorIndex){
|
||||||
|
if(colorIndex==config.colorIndex) return;
|
||||||
|
config = config.copyWith(colorIndex: colorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void reset(){
|
void reset(){
|
||||||
data.clear();
|
data.clear();
|
||||||
status = SortStatus.none;
|
status = SortStatus.none;
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import 'package:flutter/material.dart';
|
|||||||
class DataPainter extends CustomPainter{
|
class DataPainter extends CustomPainter{
|
||||||
|
|
||||||
final List<int> data;
|
final List<int> data;
|
||||||
|
final MaterialColor color;
|
||||||
|
|
||||||
DataPainter({required this.data});
|
DataPainter( {required this.data,required this.color,});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
@@ -17,28 +18,29 @@ class DataPainter extends CustomPainter{
|
|||||||
paint.strokeWidth = itemWidth;
|
paint.strokeWidth = itemWidth;
|
||||||
paint.strokeCap = StrokeCap.round;
|
paint.strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
|
|
||||||
for(int i=0;i<data.length;i++){
|
for(int i=0;i<data.length;i++){
|
||||||
int value = data[i];
|
int value = data[i];
|
||||||
if (value < 1000 * .10) {
|
if (value < 1000 * .10) {
|
||||||
paint.color = Colors.blue.shade100;
|
paint.color = color.shade50;
|
||||||
} else if (value < 1000 * .20) {
|
} else if (value < 1000 * .20) {
|
||||||
paint.color = Colors.blue.shade200;
|
paint.color = color.shade100;
|
||||||
} else if (value < 1000 * .30) {
|
} else if (value < 1000 * .30) {
|
||||||
paint.color = Colors.blue.shade300;
|
paint.color = color.shade200;
|
||||||
} else if (value < 1000 * .40) {
|
} else if (value < 1000 * .40) {
|
||||||
paint.color = Colors.blue.shade400;
|
paint.color = color.shade300;
|
||||||
} else if (value < 1000 * .50) {
|
} else if (value < 1000 * .50) {
|
||||||
paint.color = Colors.blue.shade500;
|
paint.color = color.shade400;
|
||||||
} else if (value < 1000 * .60) {
|
} else if (value < 1000 * .60) {
|
||||||
paint.color = Colors.blue.shade600;
|
paint.color = color.shade500;
|
||||||
} else if (value < 1000 * .70) {
|
} else if (value < 1000 * .70) {
|
||||||
paint.color = Colors.blue.shade700;
|
paint.color = color.shade600;
|
||||||
} else if (value < 1000 * .80) {
|
} else if (value < 1000 * .80) {
|
||||||
paint.color = Colors.blue.shade800;
|
paint.color = color.shade700;
|
||||||
} else if (value < 1000 * .90) {
|
} else if (value < 1000 * .90) {
|
||||||
paint.color = Colors.blue.shade900;
|
paint.color = color.shade800;
|
||||||
} else {
|
} else {
|
||||||
paint.color = const Color(0xFF011E51);
|
paint.color = color.shade900;
|
||||||
}
|
}
|
||||||
canvas.drawLine(
|
canvas.drawLine(
|
||||||
Offset(i * itemWidth+itemWidth/2, 0),
|
Offset(i * itemWidth+itemWidth/2, 0),
|
||||||
22
lib/v8/pages/sort/views/player/sort_player.dart
Normal file
22
lib/v8/pages/sort/views/player/sort_player.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../provider/state.dart';
|
||||||
|
import 'data_painter.dart';
|
||||||
|
|
||||||
|
class SortPlayer extends StatelessWidget {
|
||||||
|
const SortPlayer({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
SortState state = SortStateScope.of(context);
|
||||||
|
List<int> 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()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
lib/v8/pages/sort/views/settings/color_picker.dart
Normal file
41
lib/v8/pages/sort/views/settings/color_picker.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ColorPicker extends StatelessWidget {
|
||||||
|
final List<MaterialColor> colors;
|
||||||
|
final ValueChanged<int> 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
169
lib/v8/pages/sort/views/settings/sort_setting.dart
Normal file
169
lib/v8/pages/sort/views/settings/sort_setting.dart
Normal file
@@ -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<SortSettings> createState() => _SortSettingsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortSettingsState extends State<SortSettings> {
|
||||||
|
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('确定设置'))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<int> 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<NavigatorScope> createState() => _NavigatorScopeState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavigatorScopeState extends State<NavigatorScope> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
SortState state = SortStateScope.of(context);
|
|
||||||
List<int> 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<dynamic> route, result) {
|
|
||||||
return route.didPop(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SortSelectorPanel extends StatelessWidget {
|
|
||||||
final String active;
|
|
||||||
final ValueChanged<String> onSelected;
|
|
||||||
final List<String> 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<SortItemTile> createState() => _SortItemTileState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SortItemTileState extends State<SortItemTile> {
|
|
||||||
@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
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../provider/state.dart';
|
import '../../provider/state.dart';
|
||||||
|
|
||||||
class SortButton extends StatelessWidget {
|
class SortButton extends StatelessWidget {
|
||||||
const SortButton({super.key});
|
const SortButton({super.key});
|
||||||
@@ -49,7 +49,6 @@ class SortButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4,),
|
const SizedBox(width: 4,),
|
||||||
Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),),
|
Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
230
lib/v8/pages/sort/views/sort_page/sort_page.dart
Normal file
230
lib/v8/pages/sort/views/sort_page/sort_page.dart
Normal file
@@ -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<SortPage> createState() => _SortPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortPageState extends State<SortPage> {
|
||||||
|
@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<SortNavigatorScope> createState() => _SortNavigatorScopeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortNavigatorScopeState extends State<SortNavigatorScope> {
|
||||||
|
@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<Page> pages = buildPagesByPath(context, path);
|
||||||
|
return Navigator(
|
||||||
|
onPopPage: _onPopPage,
|
||||||
|
pages: pages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _onPopPage(Route<dynamic> route, result) {
|
||||||
|
return route.didPop(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Page> 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<String> onSelected;
|
||||||
|
final List<String> 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<SortItemTile> createState() => _SortItemTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortItemTileState extends State<SortItemTile> {
|
||||||
|
@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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SortSettings> createState() => _SortSettingsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SortSettingsState extends State<SortSettings> {
|
|
||||||
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('确定设置'))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
lib/v9/app.dart
Normal file
1
lib/v9/app.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export 'app/unit_app.dart';
|
||||||
220
lib/v9/app/navigation/router/app_router_delegate.dart
Normal file
220
lib/v9/app/navigation/router/app_router_delegate.dart
Normal file
@@ -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<Object> with ChangeNotifier {
|
||||||
|
|
||||||
|
/// 核心数据,路由配置数据列表
|
||||||
|
final List<IRouteConfig> _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<IRouteConfig> _pathStack = [];
|
||||||
|
|
||||||
|
bool get canPop => _configs.where((e) => e.routeStyle==RouteStyle.push).isNotEmpty;
|
||||||
|
|
||||||
|
final Map<String, Completer<dynamic>> _completerMap = {};
|
||||||
|
|
||||||
|
FutureOr<dynamic> changeRoute(IRouteConfig config) {
|
||||||
|
String value = config.uri.path;
|
||||||
|
if (current == config) null;
|
||||||
|
_handleChangeStyle(config);
|
||||||
|
|
||||||
|
if (config.forResult) {
|
||||||
|
_completerMap[value] = Completer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.recordHistory) {
|
||||||
|
_historyManager.recode(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
if (config.forResult) {
|
||||||
|
return _completerMap[value]!.future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleChangeStyle(IRouteConfig config){
|
||||||
|
switch (config.routeStyle) {
|
||||||
|
case RouteStyle.push:
|
||||||
|
if (_configs.contains(config)) {
|
||||||
|
_configs.remove(config);
|
||||||
|
}
|
||||||
|
_configs.add(config);
|
||||||
|
break;
|
||||||
|
case RouteStyle.replace:
|
||||||
|
List<IRouteConfig> liveRoutes = _configs.where((e) => e.keepAlive&&e!=config).toList();
|
||||||
|
_configs.clear();
|
||||||
|
_configs.addAll([...liveRoutes,config]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<dynamic> changePath(
|
||||||
|
String value, {
|
||||||
|
bool forResult = false,
|
||||||
|
Object? extra,
|
||||||
|
bool keepAlive = false,
|
||||||
|
bool recordHistory = 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<Page> _buildPages(BuildContext context, List<IRouteConfig> configs) {
|
||||||
|
IRouteConfig top = configs.last;
|
||||||
|
List<IRouteConfig> bottoms = _configs.sublist(0,_configs.length-1).toList();
|
||||||
|
List<Page> pages = [];
|
||||||
|
List<Page> topPages = _buildPageByPathFromTree(context, top);
|
||||||
|
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
|
||||||
|
pages.addAll(topPages);
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Page> _buildLivePageByPathList(
|
||||||
|
BuildContext context,
|
||||||
|
List<IRouteConfig> paths,
|
||||||
|
IRouteConfig curConfig,
|
||||||
|
List<Page> curPages,
|
||||||
|
) {
|
||||||
|
List<Page> pages = [];
|
||||||
|
if (paths.isNotEmpty) {
|
||||||
|
for (IRouteConfig path in paths) {
|
||||||
|
if (path != curConfig) {
|
||||||
|
pages.addAll(_buildPageByPathFromTree(context, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 去除和 curPages 中重复的界面
|
||||||
|
pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key));
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Page> _buildPageByPathFromTree(
|
||||||
|
BuildContext context, IRouteConfig config) {
|
||||||
|
List<Page> result = [];
|
||||||
|
List<IRouteNode> iRoutes = 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<bool> popRoute() async {
|
||||||
|
print('=======popRoute=========');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void backStack() {
|
||||||
|
if (_configs.isNotEmpty) {
|
||||||
|
_configs.removeLast();
|
||||||
|
if (_configs.isNotEmpty) {
|
||||||
|
changeRoute(_configs.last);
|
||||||
|
} else {
|
||||||
|
changeRoute(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _onPopPage(Route route, result) {
|
||||||
|
if (_completerMap.containsKey(path)) {
|
||||||
|
_completerMap[path]?.complete(result);
|
||||||
|
_completerMap.remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_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<String> parts = List.of(uri.pathSegments)..removeLast();
|
||||||
|
return '/${parts.join('/')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setNewRoutePath(configuration) async {}
|
||||||
|
}
|
||||||
116
lib/v9/app/navigation/router/iroute.dart
Normal file
116
lib/v9/app/navigation/router/iroute.dart
Normal file
@@ -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<IRouteNode> children;
|
||||||
|
|
||||||
|
const IRouteNode({
|
||||||
|
required this.path,
|
||||||
|
required this.children,
|
||||||
|
});
|
||||||
|
|
||||||
|
Page? createPage(BuildContext context, IRouteConfig config);
|
||||||
|
|
||||||
|
List<IRouteNode> find(
|
||||||
|
String input,
|
||||||
|
) {
|
||||||
|
return findNodes(this, Uri.parse(input), 0, '/', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IRouteNode> findNodes(
|
||||||
|
IRouteNode node,
|
||||||
|
Uri uri,
|
||||||
|
int deep,
|
||||||
|
String prefix,
|
||||||
|
List<IRouteNode> result,
|
||||||
|
) {
|
||||||
|
List<String> parts = uri.pathSegments;
|
||||||
|
if (deep > parts.length - 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
String target = parts[deep];
|
||||||
|
if (node.children.isNotEmpty) {
|
||||||
|
target = prefix + target;
|
||||||
|
List<IRouteNode> nodes =
|
||||||
|
node.children.where((e) => 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);
|
||||||
71
lib/v9/app/navigation/router/iroute_config.dart
Normal file
71
lib/v9/app/navigation/router/iroute_config.dart
Normal file
@@ -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;
|
||||||
|
}
|
||||||
47
lib/v9/app/navigation/router/route_history_manager.dart
Normal file
47
lib/v9/app/navigation/router/route_history_manager.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'iroute_config.dart';
|
||||||
|
|
||||||
|
typedef OnRouteChange = void Function(IRouteConfig config);
|
||||||
|
|
||||||
|
class RouteHistoryManager{
|
||||||
|
final List<IRouteConfig> _histories = [];
|
||||||
|
final List<IRouteConfig> _backHistories = [];
|
||||||
|
|
||||||
|
List<IRouteConfig> get histories => _histories.reversed.toList();
|
||||||
|
|
||||||
|
bool get hasHistory => _histories.length > 1;
|
||||||
|
|
||||||
|
bool get hasBackHistory => _backHistories.isNotEmpty;
|
||||||
|
|
||||||
|
/// 将 [config] 加入历史记录
|
||||||
|
void recode(IRouteConfig config){
|
||||||
|
if (_histories.isNotEmpty && config.path == _histories.last.path) return;
|
||||||
|
_histories.add(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 历史回退操作
|
||||||
|
/// 将当前顶层移除,并加入 [_backHistories] 撤销列表
|
||||||
|
/// 并转到前一路径 [_histories.last]
|
||||||
|
void back(OnRouteChange callback) {
|
||||||
|
if (!hasHistory) return;
|
||||||
|
IRouteConfig top = _histories.removeLast();
|
||||||
|
_backHistories.add(top);
|
||||||
|
if (_histories.isNotEmpty) {
|
||||||
|
callback(_histories.last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 撤销回退操作
|
||||||
|
/// 取出回退列表的最后元素,跳转到该路径
|
||||||
|
void revocation(OnRouteChange callback) {
|
||||||
|
IRouteConfig target = _backHistories.removeLast();
|
||||||
|
callback(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close(int index) {
|
||||||
|
_histories.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
_histories.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lib/v9/app/navigation/router/routes.dart
Normal file
55
lib/v9/app/navigation/router/routes.dart
Normal file
@@ -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<String, String> queryParams = data.uri.queryParameters;
|
||||||
|
String? selectedColor = queryParams['color'];
|
||||||
|
Color color = Colors.black;
|
||||||
|
if (selectedColor != null) {
|
||||||
|
color = Color(int.parse(selectedColor, radix: 16));
|
||||||
|
} else if (data.extra is Color) {
|
||||||
|
color = data.extra as Color;
|
||||||
|
}
|
||||||
|
return ColorDetailPage(color: color);
|
||||||
|
}
|
||||||
25
lib/v9/app/navigation/router/views/not_find_view.dart
Normal file
25
lib/v9/app/navigation/router/views/not_find_view.dart
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
lib/v9/app/navigation/router/views/route_back_indicator.dart
Normal file
52
lib/v9/app/navigation/router/views/route_back_indicator.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../app_router_delegate.dart';
|
||||||
|
class RouteBackIndicator extends StatefulWidget {
|
||||||
|
const RouteBackIndicator({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RouteBackIndicator> createState() => _RouteBackIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
|
||||||
|
|
||||||
|
@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(() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FadePageTransitionsBuilder extends PageTransitionsBuilder {
|
||||||
|
|
||||||
|
const FadePageTransitionsBuilder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions<T>(
|
||||||
|
PageRoute<T>? route,
|
||||||
|
BuildContext? context,
|
||||||
|
Animation<double> animation,
|
||||||
|
Animation<double> 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<double> animation;
|
||||||
|
final Animation<double> secondaryAnimation;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation.drive(curveTween),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
lib/v9/app/navigation/transition/fade_transition_page.dart
Normal file
52
lib/v9/app/navigation/transition/fade_transition_page.dart
Normal file
@@ -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<T> extends Page<T> {
|
||||||
|
final Widget child;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const FadeTransitionPage({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.duration = const Duration(milliseconds: 300),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Route<T> createRoute(BuildContext context) => PageBasedFadeTransitionRoute<T>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
|
||||||
|
final FadeTransitionPage<T> _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<double> animation,
|
||||||
|
Animation<double> secondaryAnimation) {
|
||||||
|
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation.drive(curveTween),
|
||||||
|
child: (settings as FadeTransitionPage).child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation, Widget child) =>
|
||||||
|
child;
|
||||||
|
}
|
||||||
47
lib/v9/app/navigation/transition/no_transition_page.dart
Normal file
47
lib/v9/app/navigation/transition/no_transition_page.dart
Normal file
@@ -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<T> extends Page<T> {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const NoTransitionPage({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoTransitionRoute<T> extends PageRoute<T> {
|
||||||
|
|
||||||
|
final NoTransitionPage<T> _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<double> animation,
|
||||||
|
Animation<double> secondaryAnimation) {
|
||||||
|
return (settings as NoTransitionPage).child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||||
|
Animation<double> secondaryAnimation, Widget child) =>
|
||||||
|
child;
|
||||||
|
}
|
||||||
34
lib/v9/app/navigation/views/app_navigation.dart
Normal file
34
lib/v9/app/navigation/views/app_navigation.dart
Normal file
@@ -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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
lib/v9/app/navigation/views/app_navigation_rail.dart
Normal file
86
lib/v9/app/navigation/views/app_navigation_rail.dart
Normal file
@@ -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<AppNavigationRail> createState() => _AppNavigationRailState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||||
|
final List<MenuMeta> deskNavBarMenus = const [
|
||||||
|
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'),
|
||||||
|
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'),
|
||||||
|
MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'),
|
||||||
|
MenuMeta(label: '我的', icon: Icons.person, path: '/user'),
|
||||||
|
MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
router.addListener(_onRouterChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
router.removeListener(_onRouterChange);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
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(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String>? onSubmit;
|
||||||
|
const AppRouterEditor({super.key, this.onSubmit});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppRouterEditor> createState() => _AppRouterEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppRouterEditorState extends State<AppRouterEditor> {
|
||||||
|
|
||||||
|
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
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart
Normal file
114
lib/v9/app/navigation/views/app_top_bar/app_top_bar.dart
Normal file
@@ -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<RouterIndicator> createState() => _RouterIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> kRouteLabelMap = {
|
||||||
|
'/color': '颜色板',
|
||||||
|
'/color/add': '添加颜色',
|
||||||
|
'/color/detail': '颜色详情',
|
||||||
|
'/counter': '计数器',
|
||||||
|
'/sort': '可视化排序算法',
|
||||||
|
'/sort/settings': '排序配置',
|
||||||
|
'/user': '我的',
|
||||||
|
'/settings': '系统设置',
|
||||||
|
};
|
||||||
|
|
||||||
|
class _RouterIndicatorState extends State<RouterIndicator> {
|
||||||
|
@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<BreadcrumbItem> pathToBreadcrumbItems(String path) {
|
||||||
|
Uri uri = Uri.parse(path);
|
||||||
|
List<BreadcrumbItem> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
158
lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart
Normal file
158
lib/v9/app/navigation/views/app_top_bar/history_view_icon.dart
Normal file
@@ -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<HistoryItem> createState() => _HistoryItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryItemState extends State<HistoryItem> {
|
||||||
|
@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<HistoryPanel> createState() => _HistoryPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryPanelState extends State<HistoryPanel> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
router.addListener(_onChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
router.removeListener(_onChange);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<IRouteConfig> 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(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<RouteHistoryButton> createState() => _RouteHistoryButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RouteHistoryButtonState extends State<RouteHistoryButton> {
|
||||||
|
@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(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
43
lib/v9/app/unit_app.dart
Normal file
43
lib/v9/app/unit_app.dart
Normal file
@@ -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()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
99
lib/v9/pages/color/color_add_page.dart
Normal file
99
lib/v9/pages/color/color_add_page.dart
Normal file
@@ -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<ColorAddPage> createState() => _ColorAddPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColorAddPageState extends State<ColorAddPage> {
|
||||||
|
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(() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/v9/pages/color/color_detail_page.dart
Normal file
25
lib/v9/pages/color/color_detail_page.dart
Normal file
@@ -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,),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
lib/v9/pages/color/color_page.dart
Normal file
53
lib/v9/pages/color/color_page.dart
Normal file
@@ -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<ColorPage> createState() => _ColorPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColorPageState extends State<ColorPage> {
|
||||||
|
final List<Color> _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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
lib/v9/pages/counter/counter_page.dart
Normal file
43
lib/v9/pages/counter/counter_page.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CounterPage extends StatefulWidget {
|
||||||
|
const CounterPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CounterPage> createState() => _CounterPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CounterPageState extends State<CounterPage> {
|
||||||
|
int _counter = 0;
|
||||||
|
|
||||||
|
void _incrementCounter() {
|
||||||
|
setState(() {
|
||||||
|
_counter++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
lib/v9/pages/empty/empty_page.dart
Normal file
30
lib/v9/pages/empty/empty_page.dart
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
lib/v9/pages/settings/settings_page.dart
Normal file
11
lib/v9/pages/settings/settings_page.dart
Normal file
@@ -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')));
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user