This commit is contained in:
toly
2023-11-08 09:35:29 +08:00
parent 88cd6fb3b4
commit 8fb4bf57d6
78 changed files with 4344 additions and 544 deletions

View 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;
}
}
}

View 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,
);
}

View 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;
}
}

View 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;
},
)),
),
);
}
}

View File

@@ -44,6 +44,7 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
final List<String> keepAlivePath = [] ;
void setPathKeepLive(String value){
if(keepAlivePath.contains(value)){
keepAlivePath.remove(value);
@@ -52,6 +53,8 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
path = value;
}
void setPathForData(String value,dynamic data){
_pathExtraMap[value] = data;
path = value;
@@ -82,6 +85,17 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
List<Page> pages = [];
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){
for (String alivePath in keepAlivePath) {
if(alivePath!=path){
@@ -171,7 +185,6 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}

View File

@@ -10,7 +10,6 @@ class AppNavigationRail extends StatefulWidget {
}
class _AppNavigationRailState extends State<AppNavigationRail> {
final List<MenuMeta> deskNavBarMenus = const [
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
MenuMeta(label: '计数器', icon: Icons.add_chart),
@@ -41,22 +40,24 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),),
child: Text(
'V0.0.5',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
if (index == 1) {
router.setPathKeepLive(kDestinationsPaths[index]);
}else{
router.path = kDestinationsPaths[index];
return;
}
router.path = kDestinationsPaths[index];
}
void _onRouterChange() {

View File

@@ -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))
],
);
}
}

View File

@@ -23,15 +23,15 @@ class SortPage extends StatelessWidget {
children: [
Container(
// color: Color(0xffF4F4F4),
padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8),
child: Row(
padding: const EdgeInsets.symmetric(horizontal: 12,vertical: 8),
child: const Row(
children: [
SortButton(),
Spacer(),
],
),
),
Divider(height: 1,),
const Divider(height: 1,),
Expanded(
child: SortSelectorPanel(
active: state.config.name,
@@ -43,57 +43,22 @@ class SortPage extends StatelessWidget {
],
),
),
VerticalDivider(width: 1,),
const VerticalDivider(width: 1,),
Expanded(
child: NavigatorScope(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: CustomPaint(
painter: DataPainter(data: numbers),
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
),
),
)
],
),
);
}
void _onSelected(String value) {
}
}
final GlobalKey key = GlobalKey();
class NavigatorScope extends StatefulWidget {
const NavigatorScope({super.key});
@override
State<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;
@@ -105,7 +70,7 @@ class SortSelectorPanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 8),
itemExtent: 46,
itemCount: sortNameMap.length,
itemBuilder: _buildByIndex,
@@ -145,9 +110,9 @@ class _SortItemTileState extends State<SortItemTile> {
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: widget.selected?Color(0xffE6F0FF):null
color: widget.selected?const Color(0xffE6F0FF):null
),
padding: EdgeInsets.only(left: 12),
padding: const EdgeInsets.only(left: 12),
alignment: Alignment.centerLeft,
child: Text(
widget.title,

View File

@@ -6,17 +6,27 @@ import 'route_history_manager.dart';
import 'routes.dart';
import 'views/not_find_view.dart';
AppRouterDelegate router = AppRouterDelegate();
AppRouterDelegate router = AppRouterDelegate(
initial: IRouteConfig(uri: Uri.parse('/color')),
);
class AppRouterDelegate extends RouterDelegate<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;
AppRouterDelegate({this.notFindPageBuilder}) {
_historyManager.recode(IRouteConfig(uri: Uri.parse(path)));
AppRouterDelegate({
this.notFindPageBuilder,
required IRouteConfig initial,
}) {
_configs.add(initial);
_historyManager.recode(initial);
}
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
@@ -45,48 +55,60 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
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, dynamic> _pathExtraMap = {};
final List<String> keepAlivePath = [];
FutureOr<dynamic> changeRoute(IRouteConfig config) {
String value = config.uri.path;
if (_path == value) null;
if (current == config) null;
_handleChangeStyle(config);
if (config.forResult) {
_completerMap[value] = Completer();
}
if (config.keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (config.extra != null) {
_pathExtraMap[value] = config.extra;
}
if (config.recordHistory) {
_historyManager.recode(config);
}
_path = value;
notifyListeners();
if (config.forResult) {
return _completerMap[value]!.future;
}
}
void _handleChangeStyle(IRouteConfig config){
switch (config.routeStyle) {
case RouteStyle.push:
if (_configs.contains(config)) {
_configs.remove(config);
}
_configs.add(config);
break;
case RouteStyle.replace:
List<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,
));
@@ -96,46 +118,47 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
Widget build(BuildContext context) {
return Navigator(
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> topPages = _buildPageByPathFromTree(context, path);
if (keepAlivePath.isNotEmpty) {
for (String alivePath in keepAlivePath) {
if (alivePath != path) {
pages.addAll(_buildPageByPathFromTree(context, alivePath));
}
}
/// 去除和 topPages 中重复的界面
pages.removeWhere(
(element) => topPages.map((e) => e.key).contains(element.key));
}
List<Page> topPages = _buildPageByPathFromTree(context, top);
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
pages.addAll(topPages);
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<IRouteNode> iRoutes = rootRoute.find(path);
List<IRouteNode> iRoutes = rootRoute.find(config.path);
if (iRoutes.isNotEmpty) {
for (int i = 0; i < iRoutes.length; i++) {
IRouteNode iroute = iRoutes[i];
String path = iroute.path;
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
IRouteConfig config = IRouteConfig(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
);
config = config.copyWith(path: iroute.path);
Page? page;
if (iroute is NotFindNode) {
page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config);
@@ -145,6 +168,9 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
if (page != null) {
result.add(page);
}
if(iroute is CellIRoute){
break;
}
}
}
return result;
@@ -156,12 +182,29 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
return true;
}
void backStack() {
if (_configs.isNotEmpty) {
_configs.removeLast();
if (_configs.isNotEmpty) {
changeRoute(_configs.last);
} else {
changeRoute(current);
}
}
}
bool _onPopPage(Route route, result) {
if (_completerMap.containsKey(path)) {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
changePath(backPath(path), recordHistory: false);
if (_configs.isNotEmpty) {
_configs.removeLast();
notifyListeners();
} else {
changePath(backPath(path), recordHistory: false);
}
return route.didPop(result);
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'iroute_config.dart';
typedef IRoutePageBuilder = Page? Function(
@@ -24,7 +23,9 @@ abstract class IRouteNode {
Page? createPage(BuildContext context, IRouteConfig config);
List<IRouteNode> find(String input,) {
List<IRouteNode> find(
String input,
) {
return findNodes(this, Uri.parse(input), 0, '/', []);
}
@@ -41,15 +42,16 @@ abstract class IRouteNode {
}
String target = parts[deep];
if (node.children.isNotEmpty) {
target = prefix + target;
List<IRouteNode> nodes = node.children.where((e) => e.path == target).toList();
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{
} else {
result.add(NotFindNode(path: target));
return result;
}
@@ -92,8 +94,8 @@ class IRoute extends IRouteNode {
}
/// 未知路由
class NotFindNode extends IRouteNode{
NotFindNode({required super.path, super.children= const[]});
class NotFindNode extends IRouteNode {
NotFindNode({required super.path, super.children = const []});
@override
Page? createPage(BuildContext context, IRouteConfig config) {
@@ -101,15 +103,14 @@ class NotFindNode extends IRouteNode{
}
}
class CellIRouter extends IRoute {
final CellBuilder cellBuilder;
CellIRouter(
{required super.path,
super.pageBuilder,
super.children,
required this.cellBuilder});
class CellIRoute extends IRoute {
const CellIRoute({
required super.path,
super.pageBuilder,
super.children,
super.widget,
});
}
typedef CellBuilder = Widget Function(BuildContext context, Widget child);
typedef CellBuilder = Widget Function(BuildContext context,IRouteConfig config, CellIRoute cell);

View File

@@ -1,10 +1,17 @@
import 'package:flutter/material.dart';
enum RouteStyle{
push,
replace,
}
class IRouteConfig {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
final RouteStyle routeStyle;
final bool recordHistory;
const IRouteConfig({
@@ -12,6 +19,7 @@ class IRouteConfig {
required this.uri,
this.forResult = false,
this.keepAlive = false,
this.routeStyle = RouteStyle.replace,
this.recordHistory = false,
});
@@ -22,14 +30,42 @@ class IRouteConfig {
bool? forResult,
bool? keepAlive,
bool? recordHistory,
String? path,
}) =>
IRouteConfig(
extra: extra ?? this.extra,
forResult: forResult ?? this.forResult,
keepAlive: keepAlive ?? this.keepAlive,
recordHistory: recordHistory ?? this.recordHistory,
uri: uri,
uri: path!=null?Uri.parse(path):uri,
);
ValueKey get pageKey => ValueKey(path);
ValueKey get pageKey => ValueKey(hashCode);
@override
String toString() {
return 'IRouteConfig{extra: $extra, forResult: $forResult, uri: $uri, keepAlive: $keepAlive, routeStyle: $routeStyle, recordHistory: $recordHistory}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is IRouteConfig &&
runtimeType == other.runtimeType &&
extra == other.extra &&
forResult == other.forResult &&
uri == other.uri &&
keepAlive == other.keepAlive &&
routeStyle == other.routeStyle &&
recordHistory == other.recordHistory;
@override
int get hashCode =>
extra.hashCode ^
forResult.hashCode ^
uri.hashCode ^
keepAlive.hashCode ^
routeStyle.hashCode ^
recordHistory.hashCode;
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../pages/sort/views/player/sort_player.dart';
import 'iroute_config.dart';
import 'iroute.dart';
import '../../../pages/color/color_add_page.dart';
@@ -7,8 +8,8 @@ import '../../../pages/color/color_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/sort/views/sort_page.dart';
import '../../../pages/sort/views/sort_page/sort_page.dart';
import '../../../pages/sort/views/settings/sort_setting.dart';
IRoute rootRoute = const IRoute(
path: 'root',
@@ -22,7 +23,20 @@ IRoute rootRoute = const IRoute(
],
),
IRoute(path: '/counter', widget: CounterPage()),
IRoute(path: '/sort', widget: SortPage()),
CellIRoute(
path: '/sort',
widget: SortPage(),
children: [
IRoute(
path: '/sort/settings',
widget: SortSettings(),
),
IRoute(
path: '/sort/player',
widget: SortPlayer(),
),
],
),
IRoute(path: '/user', widget: UserPage()),
IRoute(path: '/settings', widget: SettingPage()),
],

View 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(() {
});
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
import '../router/iroute_config.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@@ -10,13 +11,12 @@ class AppNavigationRail extends StatefulWidget {
}
class _AppNavigationRailState extends State<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'),
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/color'),
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/counter'),
MenuMeta(label: '排序', icon: Icons.sort, path: '/sort'),
MenuMeta(label: '我的', icon: Icons.person, path: '/user'),
MenuMeta(label: '设置', icon: Icons.settings, path: '/settings'),
];
@override
@@ -42,7 +42,10 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.7',style: TextStyle(color: Colors.white,fontSize: 12),),
child: Text(
'V0.0.8',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
@@ -53,21 +56,26 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
RegExp _segReg = RegExp(r'/\w+');
int? get activeIndex{
int? get activeIndex {
String path = router.path;
RegExpMatch? match = _segReg.firstMatch(path);
if(match==null) return null;
if (match == null) return null;
String? target = match.group(0);
int index = deskNavBarMenus.indexWhere((menu) => menu.path==target);
if(index==-1) return null;
int index = deskNavBarMenus.indexWhere((menu) => menu.path == target);
if (index == -1) return null;
return index;
}
}
void _onDestinationSelected(int index) {
String path = deskNavBarMenus[index].path!;
if(index==1){
router.changePath(path,keepAlive: true);
}else{
if (index == 1) {
router.changePath(path, keepAlive: true);
return;
}
if (index == 4) {
router.changePath(path, style: RouteStyle.push);
return;
} else {
router.changePath(path);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../router/app_router_delegate.dart';
import '../../router/views/route_back_indicator.dart';
import 'app_router_editor.dart';
import 'history_view_icon.dart';
import 'route_history_button.dart';
@@ -17,6 +18,7 @@ class AppTopBar extends StatelessWidget {
child: Row(
children: [
const SizedBox(width: 16),
const RouteBackIndicator(),
const RouterIndicator(),
Expanded(
child: Row(children: [
@@ -58,6 +60,7 @@ Map<String, String> kRouteLabelMap = {
'/color/detail': '颜色详情',
'/counter': '计数器',
'/sort': '可视化排序算法',
'/sort/settings': '排序配置',
'/user': '我的',
'/settings': '系统设置',
};

View File

@@ -7,17 +7,20 @@ class SortConfig {
final int seed;
final Duration duration;
final String name;
final int colorIndex;
SortConfig({
this.count = 100,
this.duration = const Duration(microseconds: 1500),
this.seed = -1,
this.colorIndex = 0,
this.name = 'insertion',
});
SortConfig copyWith({
int? count,
int? seed,
int? colorIndex,
Duration? duration,
String? name,
}) =>
@@ -26,6 +29,7 @@ class SortConfig {
seed:seed??this.seed,
duration:duration??this.duration,
name:name??this.name,
colorIndex:colorIndex??this.colorIndex,
);
}

View File

@@ -1,6 +1,6 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../functions.dart';
import 'sort_config.dart';
@@ -11,6 +11,20 @@ enum SortStatus{
sorted, // 排序完成
}
List<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{
SortState(){
@@ -37,6 +51,11 @@ class SortState with ChangeNotifier{
config = config.copyWith(name: name);
}
void selectColor(int colorIndex){
if(colorIndex==config.colorIndex) return;
config = config.copyWith(colorIndex: colorIndex);
}
void reset(){
data.clear();
status = SortStatus.none;

View File

@@ -5,8 +5,9 @@ import 'package:flutter/material.dart';
class DataPainter extends CustomPainter{
final List<int> data;
final MaterialColor color;
DataPainter({required this.data});
DataPainter( {required this.data,required this.color,});
@override
void paint(Canvas canvas, Size size) {
@@ -17,28 +18,29 @@ class DataPainter extends CustomPainter{
paint.strokeWidth = itemWidth;
paint.strokeCap = StrokeCap.round;
for(int i=0;i<data.length;i++){
int value = data[i];
if (value < 1000 * .10) {
paint.color = Colors.blue.shade100;
paint.color = color.shade50;
} else if (value < 1000 * .20) {
paint.color = Colors.blue.shade200;
paint.color = color.shade100;
} else if (value < 1000 * .30) {
paint.color = Colors.blue.shade300;
paint.color = color.shade200;
} else if (value < 1000 * .40) {
paint.color = Colors.blue.shade400;
paint.color = color.shade300;
} else if (value < 1000 * .50) {
paint.color = Colors.blue.shade500;
paint.color = color.shade400;
} else if (value < 1000 * .60) {
paint.color = Colors.blue.shade600;
paint.color = color.shade500;
} else if (value < 1000 * .70) {
paint.color = Colors.blue.shade700;
paint.color = color.shade600;
} else if (value < 1000 * .80) {
paint.color = Colors.blue.shade800;
paint.color = color.shade700;
} else if (value < 1000 * .90) {
paint.color = Colors.blue.shade900;
paint.color = color.shade800;
} else {
paint.color = const Color(0xFF011E51);
paint.color = color.shade900;
}
canvas.drawLine(
Offset(i * itemWidth+itemWidth/2, 0),

View 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()),
),
);
}
}

View 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(),
);
}
}

View 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('确定设置'))
],
),
),
);
}
}

View File

@@ -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))
],
);
}
}

View File

@@ -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
),
),
),
),
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../provider/state.dart';
import '../../provider/state.dart';
class SortButton extends StatelessWidget {
const SortButton({super.key});
@@ -49,7 +49,6 @@ class SortButton extends StatelessWidget {
),
const SizedBox(width: 4,),
Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),),
],
),
),

View 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),
),
),
),
),
);
}
}

View File

@@ -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
View File

@@ -0,0 +1 @@
export 'app/unit_app.dart';

View 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 {}
}

View 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);

View 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;
}

View 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();
}
}

View 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);
}

View 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),
),
],
),
),
);
}
}

View 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(() {
});
}
}

View File

@@ -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,
);
}
}

View 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;
}

View 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;
}

View 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(),
),
),
],
),
),
],
),
);
}
}

View 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(() {});
}
}

View File

@@ -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
),
)
],
);
}
}

View 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;
}
}

View 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(() {});
}
}

View File

@@ -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
View 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()
),
);
}
}

View 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(() {
});
}
}

View 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,),
),
);
}
}

View 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);
});
}
}
}

View 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),
),
);
}
}

View 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),
),
],
),
),
),
);
}
}

View 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')));
}
}

View File

@@ -0,0 +1,46 @@
import 'functions/bubble.dart';
import 'functions/cocktail.dart';
import 'functions/comb.dart';
import 'functions/cycle.dart';
import 'functions/gnome.dart';
import 'functions/heap.dart';
import 'functions/insertion.dart';
import 'functions/merage.dart';
import 'functions/pigeonHole.dart';
import 'functions/quick.dart';
import 'functions/selection.dart';
import 'functions/shell.dart';
typedef SortFunction = Future<void> Function(List<int> src, SortCallback callback);
typedef SortCallback = Future<void> Function(List<int> dist);
Map<String, SortFunction> sortFunctionMap = {
'insertion': insertionSort,
'bubble': bubbleSort,
'cocktail': cocktailSort,
'comb': combSort,
'pigeonHole': pigeonHoleSort,
'shell': shellSort,
'selection': selectionSort,
'gnome': gnomeSort,
'cycle': cycleSort,
'heap': heapSort,
'quick': quickSort,
'mergeSort': mergeSort,
};
Map<String, String> sortNameMap = {
'insertion': '插入排序',
'bubble': '冒泡排序',
'cocktail': '鸡尾酒排序(双向冒泡排序)',
'comb': '梳排序',
'pigeonHole': '鸽巢排序',
'shell': '希尔排序',
'selection': '选择排序',
'gnome': '侏儒排序',
'cycle': '循环排序',
'heap': '堆排序',
'quick': '快速排序',
'mergeSort': '归并排序',
};

View File

@@ -0,0 +1,19 @@
import '../functions.dart';
///冒泡排序
Future<void> bubbleSort(List<int> src, SortCallback callback ) async{
//控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。
for (int i = 0; i < src.length; ++i) {
//遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。
for (int j = 0; j < src.length - i - 1; ++j) {
//如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。
if (src[j] > src[j + 1]) {
int temp = src[j];
src[j] = src[j + 1];
src[j + 1] = temp;
}
//实现一个延迟以便在ui上展示排序的动画效果
await callback(src);
}
}
}

View File

@@ -0,0 +1,52 @@
import '../functions.dart';
///鸡尾酒排序(双向冒泡排序)
Future<void> cocktailSort(List<int> src, SortCallback callback ) async {
bool swapped = true; // 表示是否进行了交换
int start = 0; // 当前未排序部分的起始位置
int end = src.length; // 当前未排序部分的结束位置
// 开始排序循环,只有当没有进行交换时才会退出循环
while (swapped == true) {
swapped = false;
// 从左往右遍历需要排序的部分
for (int i = start; i < end - 1; ++i) {
// 对每两个相邻元素进行比较
if (src[i] > src[i + 1]) {
// 如果前面的元素大于后面的元素,则交换它们的位置
int temp = src[i];
src[i] = src[i + 1];
src[i + 1] = temp;
swapped = true; // 进行了交换
}
// 实现动画效果,延迟一段时间后更新数组状态
await callback(src);
}
// 如果没有进行交换,则说明已经排好序,退出循环
if (swapped == false) break;
// 重设为false准备进行下一轮排序
swapped = false;
// 将end设置为上一轮排序的最后一个元素的位置
end = end - 1;
// 从右往左遍历需要排序的部分
for (int i = end - 1; i >= start; i--) {
// 对每两个相邻元素进行比较
if (src[i] > src[i + 1]) {
// 如果前面的元素大于后面的元素,则交换它们的位置
int temp = src[i];
src[i] = src[i + 1];
src[i + 1] = temp;
swapped = true; // 进行了交换
}
// 实现动画效果,延迟一段时间后更新数组状态
await callback(src);
}
// 将start向右移一位准备下一轮排序
start = start + 1;
}
}

View File

@@ -0,0 +1,34 @@
import '../functions.dart';
///梳排序Comb Sort
Future<void> combSort(List<int> src, SortCallback callback) async{
int gap = src.length;
bool swapped = true;
// 当间隔不为1或存在交换时执行循环
while (gap != 1 || swapped == true) {
// 通过缩小间隔来逐步将元素归位
gap = getNextGap(gap);
swapped = false;
for (int i = 0; i < src.length - gap; i++) {
// 如果当前元素大于间隔位置上的元素,则交换它们的位置
if (src[i] > src[i + gap]) {
int temp = src[i];
src[i] = src[i + gap];
src[i + gap] = temp;
swapped = true;
}
// 实现一个延迟,以便在 UI 上展示排序的动画效果。
await callback(src);
}
}
}
int getNextGap(int gap) {
// 根据当前间隔值计算下一个间隔值
gap = (gap * 10) ~/ 13;
if (gap < 1) return 1;
return gap;
}

View File

@@ -0,0 +1,54 @@
import '../functions.dart';
///循环排序
Future<void> cycleSort(List<int> src, SortCallback callback) async {
int writes = 0;
for (int cycleStart = 0; cycleStart <= src.length - 2; cycleStart++) {
int item = src[cycleStart];
int pos = cycleStart;
// 在未排序部分中寻找比当前元素小的元素个数
for (int i = cycleStart + 1; i < src.length; i++) {
if (src[i] < item) pos++;
}
// 如果当前元素已经在正确位置上,则跳过此次迭代
if (pos == cycleStart) {
continue;
}
// 将当前元素放置到正确的位置上,并记录写操作次数
while (item == src[pos]) {
pos += 1;
}
if (pos != cycleStart) {
int temp = item;
item = src[pos];
src[pos] = temp;
writes++;
}
// 循环将位于当前位置的元素放置到正确的位置上
while (pos != cycleStart) {
pos = cycleStart;
// 继续在未排序部分中寻找比当前元素小的元素个数
for (int i = cycleStart + 1; i < src.length; i++) {
if (src[i] < item) pos += 1;
}
// 将当前元素放置到正确的位置上,并记录写操作次数
while (item == src[pos]) {
pos += 1;
}
if (item != src[pos]) {
int temp = item;
item = src[pos];
src[pos] = temp;
writes++;
}
// 添加延迟操作以展示排序过程
await callback(src);
}
}
}

View File

@@ -0,0 +1,22 @@
import '../functions.dart';
///地精排序 (侏儒排序)
Future<void> gnomeSort(List<int> src, SortCallback callback) async {
int index = 0;
while (index < src.length) {
// 当 index 小于数组长度时执行循环
if (index == 0) index++;
if (src[index] >= src[index - 1]) {
// 如果当前元素大于等于前面的元素,则将 index 加1
index++;
} else {
// 否则,交换这两个元素,并将 index 减1使得元素可以沉到正确位置
int temp = src[index];
src[index] = src[index - 1];
src[index - 1] = temp;
index--;
}
await callback(src);
}
return;
}

View File

@@ -0,0 +1,38 @@
import '../functions.dart';
///堆排序
Future<void> heapSort(List<int> src, SortCallback callback) async {
// 从最后一个非叶子节点开始,构建最大堆
for (int i = src.length ~/ 2; i >= 0; i--) {
await heapify(src,callback, src.length, i);
}
// 依次取出最大堆的根节点(最大值),并进行堆化
for (int i = src.length - 1; i >= 0; i--) {
int temp = src[0];
src[0] = src[i];
src[i] = temp;
await heapify(src, callback,i, 0);
}
}
Future<void> heapify(List<int> src, SortCallback callback, int n, int i) async{
int largest = i;
int l = 2 * i + 1; // 左子节点索引
int r = 2 * i + 2; // 右子节点索引
// 如果左子节点存在并且大于父节点,则更新最大值索引
if (l < n && src[l] > src[largest]) largest = l;
// 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引
if (r < n && src[r] > src[largest]) largest = r;
// 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化
if (largest != i) {
int temp = src[i];
src[i] = src[largest];
src[largest] = temp;
heapify(src,callback, n, largest);
}
await callback(src);
}

View File

@@ -0,0 +1,19 @@
import '../functions.dart';
///插入排序
Future<void> insertionSort(List<int> src, SortCallback callback) async {
for (int i = 1; i < src.length; i++) {
int temp = src[i]; // 将当前元素存储到临时变量 temp 中
int j = i - 1; // j 表示已排序部分的最后一个元素的索引
// 在已排序部分从后往前查找,找到合适位置插入当前元素
while (j >= 0 && temp < src[j]) {
src[j + 1] = src[j]; // 当前元素比已排序部分的元素小,将元素后移一位
--j; // 向前遍历
// 更新排序结果回调
await callback(src);
}
src[j + 1] = temp; // 插入当前元素到已排序部分的正确位置
await callback(src);
}
}

View File

@@ -0,0 +1,79 @@
import '../functions.dart';
//快速排序
Future<void> mergeSort(List<int> src, SortCallback callback) async {
await _mergeSort(src,callback,0,src.length-1);
}
///归并排序
Future<void> _mergeSort(List<int> src, SortCallback callback,int leftIndex, int rightIndex) async {
// 定义一个名为 merge 的异步函数,用于合并两个有序子数组
Future<void> merge(int leftIndex, int middleIndex, int rightIndex) async {
// 计算左侧子数组和右侧子数组的大小
int leftSize = middleIndex - leftIndex + 1;
int rightSize = rightIndex - middleIndex;
// 创建左侧子数组和右侧子数组
List leftList = List.generate(leftSize, (index) => 0);
List rightList = List.generate(rightSize, (index) => 0);
// 将原始数组中的元素分别复制到左侧子数组和右侧子数组中
for (int i = 0; i < leftSize; i++) {
leftList[i] = src[leftIndex + i];
}
for (int j = 0; j < rightSize; j++) {
rightList[j] = src[middleIndex + j + 1];
}
// 初始化游标和索引
int i = 0, j = 0;
int k = leftIndex;
// 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中
while (i < leftSize && j < rightSize) {
if (leftList[i] <= rightList[j]) {
src[k] = leftList[i];
i++;
} else {
src[k] = rightList[j];
j++;
}
await callback(src);
k++;
}
// 将左侧子数组或右侧子数组中剩余的元素放入原始数组中
while (i < leftSize) {
src[k] = leftList[i];
i++;
k++;
await callback(src);
}
while (j < rightSize) {
src[k] = rightList[j];
j++;
k++;
await callback(src);
}
}
// 如果左索引小于右索引,则递归地对数组进行归并排序
if (leftIndex < rightIndex) {
// 计算中间索引位置
int middleIndex = (rightIndex + leftIndex) ~/ 2;
// 分别对左侧子数组和右侧子数组进行归并排序
await _mergeSort(src,callback,leftIndex, middleIndex);
await _mergeSort(src,callback,middleIndex + 1, rightIndex);
await callback(src);
// 合并两个有序子数组
await merge(leftIndex, middleIndex, rightIndex);
}
}

View File

@@ -0,0 +1,37 @@
import '../functions.dart';
///奇偶排序(Odd-Even Sort)
Future<void> oddEvenSort(List<int> src, SortCallback callback) async {
bool isSorted = false;
while (!isSorted) {
// 当 isSorted 为 false 时执行循环
isSorted = true; // 先假设数组已经排好序
for (int i = 1; i <= src.length - 2; i = i + 2) {
// 对奇数索引位置进行比较
if (src[i] > src[i + 1]) {
// 如果当前元素大于后面的元素,则交换它们的值
int temp = src[i];
src[i] = src[i + 1];
src[i + 1] = temp;
isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false
await callback(src);
}
}
for (int i = 0; i <= src.length - 2; i = i + 2) {
// 对偶数索引位置进行比较
if (src[i] > src[i + 1]) {
// 如果当前元素大于后面的元素,则交换它们的值
int temp = src[i];
src[i] = src[i + 1];
src[i + 1] = temp;
isSorted = false;
await callback(src);
}
}
}
return;
}

View File

@@ -0,0 +1,33 @@
import '../functions.dart';
///鸽巢排序
Future<void> pigeonHoleSort(List<int> src, SortCallback callback ) async{
int min = src[0];
int max = src[0];
int range, i, j, index;
// 找到数组中的最大值和最小值
for (int a = 0; a < src.length; a++) {
if (src[a] > max) max = src[a];
if (src[a] < min) min = src[a];
}
// 计算鸽巢的个数
range = max - min + 1;
List<int> p = List.generate(range, (i) => 0);
// 将数字分配到各个鸽巢中
for (i = 0; i < src.length; i++) {
p[src[i] - min]++;
}
index = 0;
// 将鸽巢中的数字取出,重新放回到数组中
for (j = 0; j < range; j++) {
while (p[j]-- > 0) {
src[index++] = j + min;
await callback(src);
}
}
}

View File

@@ -0,0 +1,65 @@
import '../functions.dart';
//快速排序
Future<void> quickSort(List<int> src, SortCallback callback) async {
await _quickSort(src,callback,0,src.length-1);
}
///快速排序
Future<void> _quickSort(List<int> src, SortCallback callback,int leftIndex,int rightIndex) async {
// 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置
Future<int> _partition(int left, int right) async {
// 选择中间位置的元素作为基准元素
int p = (left + (right - left) / 2).toInt();
// 交换基准元素和最右边的元素
var temp = src[p];
src[p] = src[right];
src[right] = temp;
await callback(src);
// 初始化游标 cursor
int cursor = left;
// 遍历数组并根据基准元素将元素交换到左侧或右侧
for (int i = left; i < right; i++) {
if (cf(src[i], src[right]) <= 0) {
// 如果当前元素小于等于基准元素,则交换它和游标位置的元素
var temp = src[i];
src[i] = src[cursor];
src[cursor] = temp;
cursor++;
await callback(src);
}
}
// 将基准元素放置在游标位置
temp = src[right];
src[right] = src[cursor];
src[cursor] = temp;
await callback(src);
return cursor; // 返回基准元素的索引位置
}
// 如果左索引小于右索引,则递归地对数组进行快速排序
if (leftIndex < rightIndex) {
int p = await _partition(leftIndex, rightIndex);
await _quickSort(src,callback,leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序
await _quickSort(src,callback, p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序
}
}
// 比较函数,用于判断两个元素的大小关系
cf(int a, int b) {
if (a < b) {
return -1; // 若 a 小于 b则返回 -1
} else if (a > b) {
return 1; // 若 a 大于 b则返回 1
} else {
return 0; // 若 a 等于 b则返回 0
}
}

View File

@@ -0,0 +1,18 @@
import '../functions.dart';
///选择排序
Future<void> selectionSort(List<int> src, SortCallback callback ) async {
for (int i = 0; i < src.length; i++) {
for (int j = i + 1; j < src.length; j++) {
// 遍历未排序部分,内层循环控制变量 j
if (src[i] > src[j]) {
// 判断当前元素是否比后续元素小
int temp = src[j];
// 交换当前元素和后续较小的元素
src[j] = src[i];
src[i] = temp;
}
await callback(src);
}
}
}

View File

@@ -0,0 +1,21 @@
import '../functions.dart';
///希尔排序
Future<void> shellSort(List<int> src, SortCallback callback) async{
//定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。
for (int gap = src.length ~/ 2; gap > 0; gap ~/= 2) {
//遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap以 gap 为步长逐个遍历每个子序列。
for (int i = gap; i < src.length; i += 1) {
//将当前遍历到的元素赋值给它
int temp = src[i];
//内部使用一个 for 循环来实现插入排序。
//循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。
int j;
for (j = i; j >= gap && src[j - gap] > temp; j -= gap) {
src[j] = src[j - gap];
}
src[j] = temp;
await callback(src);
}
}
}

View File

@@ -0,0 +1,35 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class SortConfig {
final int count;
final int seed;
final Duration duration;
final String name;
final int colorIndex;
SortConfig({
this.count = 100,
this.duration = const Duration(microseconds: 1500),
this.seed = -1,
this.colorIndex = 0,
this.name = 'insertion',
});
SortConfig copyWith({
int? count,
int? seed,
int? colorIndex,
Duration? duration,
String? name,
}) =>
SortConfig(
count:count??this.count,
seed:seed??this.seed,
duration:duration??this.duration,
name:name??this.name,
colorIndex:colorIndex??this.colorIndex,
);
}

View File

@@ -0,0 +1,101 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../functions.dart';
import 'sort_config.dart';
enum SortStatus{
none, // 未操作
sorting, // 排序中
sorted, // 排序完成
}
List<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{
SortState(){
reset();
}
SortStatus status = SortStatus.none;
List<int> data = [];
List<int> stepData = [];
SortConfig _config = SortConfig();
SortConfig get config => _config;
Random random = Random();
set config(SortConfig config){
_config = config;
reset();
notifyListeners();
}
void selectName(String name){
if(name==config.name) return;
config = config.copyWith(name: name);
}
void selectColor(int colorIndex){
if(colorIndex==config.colorIndex) return;
config = config.copyWith(colorIndex: colorIndex);
}
void reset(){
data.clear();
status = SortStatus.none;
notifyListeners();
int count = config.count;
if(config.seed!=-1){
random = Random(config.seed);
}
for (int i = 0; i < count; i++) {
//随机往数组中填值
data.add(random.nextInt(1000));
}
}
void sort() async{
status = SortStatus.sorting;
notifyListeners();
Stopwatch stopwatch = Stopwatch()..start();
SortFunction? sortFunction = sortFunctionMap[config.name];
if(sortFunction!=null){
await sortFunction(data,(arr) async {
await Future.delayed(config.duration);
notifyListeners();
});
}
status = SortStatus.sorted;
notifyListeners();
stopwatch.stop();
print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms.");
}
}
/// Provides the current [SortState] to descendant widgets in the tree.
class SortStateScope extends InheritedNotifier<SortState> {
const SortStateScope({
required super.notifier,
required super.child,
super.key,
});
static SortState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<SortStateScope>()!.notifier!;
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
class DataPainter extends CustomPainter{
final List<int> data;
final MaterialColor color;
DataPainter( {required this.data,required this.color,});
@override
void paint(Canvas canvas, Size size) {
double itemWidth = size.width/data.length;
double height = size.height;
Paint paint = Paint();
paint.strokeWidth = itemWidth;
paint.strokeCap = StrokeCap.round;
for(int i=0;i<data.length;i++){
int value = data[i];
if (value < 1000 * .10) {
paint.color = color.shade50;
} else if (value < 1000 * .20) {
paint.color = color.shade100;
} else if (value < 1000 * .30) {
paint.color = color.shade200;
} else if (value < 1000 * .40) {
paint.color = color.shade300;
} else if (value < 1000 * .50) {
paint.color = color.shade400;
} else if (value < 1000 * .60) {
paint.color = color.shade500;
} else if (value < 1000 * .70) {
paint.color = color.shade600;
} else if (value < 1000 * .80) {
paint.color = color.shade700;
} else if (value < 1000 * .90) {
paint.color = color.shade800;
} else {
paint.color = color.shade900;
}
canvas.drawLine(
Offset(i * itemWidth+itemWidth/2, 0),
Offset(
i * itemWidth+itemWidth/2,
size.height*(value/1000),
),
paint);
}
}
@override
bool shouldRepaint(covariant DataPainter oldDelegate) {
return true;
}
}

View 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()),
),
);
}
}

View 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(),
);
}
}

View 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('确定设置'))
],
),
),
);
}
}

View File

@@ -0,0 +1,57 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../provider/state.dart';
class SortButton extends StatelessWidget {
const SortButton({super.key});
@override
Widget build(BuildContext context) {
SortState state = SortStateScope.of(context);
VoidCallback? action;
IconData icon;
String text = '';
Color color;
switch (state.status) {
case SortStatus.none:
icon = Icons.not_started_outlined;
color = Colors.green;
action = state.sort;
text = '点击启动';
break;
case SortStatus.sorting:
icon = Icons.stop_circle_outlined;
color = Colors.grey;
action = null;
text = '排序中...';
break;
case SortStatus.sorted:
icon = CupertinoIcons.repeat;
color = Colors.black;
action = state.reset;
text = '点击重置';
break;
}
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: action,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Icon(
icon,
color: color,
size: 18,
),
const SizedBox(width: 4,),
Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),),
],
),
),
);
}
}

View 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),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class UserPage extends StatelessWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('UserPage')));
}
}

View File

@@ -1,4 +1,4 @@
///
///
//
//

104
test/algorithm/1052.dart Normal file
View File

@@ -0,0 +1,104 @@
/// 1052. 爱生气的书店老板
//有一个书店老板,他的书店开了 n 分钟。每分钟都有一些顾客进入这家商店。
// 给定一个长度为 n 的整数数组 customers
// 其中 customers[i] 是在第 i 分钟开始时进入商店的顾客数量,所有这些顾客在第 i 分钟结束后离开。
// 在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1
// 否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,若老板不生气则顾客是满意的。
// 书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 minutes 分钟不生气,但却只能使用一次。
// 请你返回 这一天营业下来,最多有多少客户能够感到满意 。
//
//输入customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3
// 输出16
// 解释:书店老板在最后 3 分钟保持冷静。
// 感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.
//
// 算法思想:
// 因为有X分钟可以控制情绪所以这X分钟要用在关键的时间段也就是需要知道哪个长度为X的时间段不满意的客人最多。
// 最多满意客户数量 = 原本就满意的客户 + X时间段内因为控制了情绪而态度反转的最多客户数量
//
// 算法步骤:
//
// 统计老板不生气时的客人数量;
// 利用长度为X的滑动窗口统计长度为X的时间段内不满意的客户最多数量是多少
import 'dart:math';
void main() {
List<int> customers = [4, 6, 3, 8, 7, 2, 6];
//1生气 0不气
List<int> grumpy = [1, 0, 1, 1, 0, 1, 0];
//最大不满数 3+8
// 没有魔法时满意数 6+7+6 = 19
//拥有魔法 6+3+8+7+6 =30
int magic = 2;
maxSatisfied(customers, grumpy, magic);
}
// int maxSatisfied(List<int> customers, List<int> grumpy, int minutes) {
// int m = customers.length;
// int total = 0;
// // 统计不生气时间内的客人总数
// for (int i = 0; i < m; i++) {
// if (grumpy[i] == 0) {
// total += customers[i];
// }
// }
// int curAngry = 0; // 长度为X的时间段内不满意的客人数
// // 统计第一个大小为X的窗口内不满意的客人数
// for (int i = 0; i < minutes; i++) {
// curAngry += customers[i] * grumpy[i];
// }
// int maxAngry = curAngry; // 所有窗口时间段内,遇到不满意的客人的最多数量
// // 滑动窗口统计max_angry
// for (int i = minutes; i < m; i++) {
// curAngry += customers[i] * grumpy[i]; // 只把不满意的客人数量加进来
// curAngry -= customers[i - minutes] * grumpy[i - minutes]; // 只减去不满意客人数量
// maxAngry = max(maxAngry, curAngry); // 更新窗口内的不满意客人数量
// }
// // 总满意人数 = 本来就满意的人数 + 老板抑制情绪的时间段内不满意变为满意的人数
// return total + maxAngry;
// }
///最大满意人数
int maxSatisfied(List<int> customers, List<int> grumpy, int minutes) {
int maxSatisNumber = 0;
int maxUnSatisNumber = 0;
int unSatisNumber = 0;
//先算第一个窗口不满意人数
for (int i = 0; i < minutes; i++) {
if (grumpy[i] == 1) {
maxUnSatisNumber += customers[i];
unSatisNumber = maxUnSatisNumber;
}
}
//格口向后移动
for (int i = minutes; i < customers.length; i++) {
//减去前一个格口不满数 加上后一个格口不满数
if (grumpy[i - minutes] == 1) {
if (grumpy[i] == 1) {
unSatisNumber = unSatisNumber - customers[i - minutes] + customers[i];
} else {
unSatisNumber = unSatisNumber - customers[i - minutes] + 0;
}
} else {
if (grumpy[i] == 1) {
unSatisNumber = unSatisNumber - 0 + customers[i];
} else {
unSatisNumber = unSatisNumber - 0 + 0;
}
}
maxUnSatisNumber = max(maxUnSatisNumber, unSatisNumber);
}
print("最大不满意数$maxUnSatisNumber");
for (int i = 0; i < customers.length; i++) {
if (grumpy[i] == 0) {
maxSatisNumber += customers[i];
}
}
maxSatisNumber = maxSatisNumber + maxUnSatisNumber;
print("最大满意数$maxSatisNumber");
return maxSatisNumber;
}

View File

@@ -0,0 +1,85 @@
import 'dart:math';
/// 子数组最大平均数
///
///给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。
/// 请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。
///输入nums = [1,12,-5,-6,50,3], k = 4
///输出12.75
///解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75
///滑动窗口解法
//class Solution:
// def problemName(self, s: str) -> int:
// # Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
// x, y = ..., ...
//
// # Step 2: 定义窗口的首尾端 (start, end) 然后滑动窗口
// start = 0
// for end in range(len(s)):
// # Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
// x = new_x
// if condition:
// y = new_y
//
// '''
// ------------- 下面是两种情况读者请根据题意二选1 -------------
// '''
// # Step 4 - 情况1
// # 如果题目的窗口长度固定用一个if语句判断一下当前窗口长度是否达到了限定长度
// # 如果达到了,窗口左指针前移一个单位,从而保证下一次右指针右移时,窗口长度保持不变,
// # 左指针移动之前, 先更新Step 1定义的(部分或所有)维护变量
// if 窗口长度达到了限定长度:
// # 更新 (部分或所有) 维护变量
// # 窗口左指针前移一个单位保证下一次右指针右移时窗口长度保持不变
//
// # Step 4 - 情况2
// # 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
// # 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
// # 在左指针移动之前更新Step 1定义的(部分或所有)维护变量
// while 不合法:
// # 更新 (部分或所有) 维护变量
// # 不断移动窗口左指针直到窗口再次合法
//
// # Step 5: 返回答案
// return ...
//
void main() {
List<int> num = [4, 6, 3, 8, 7, 2, 6];
print("${findMaxAverage(num, 3)}");
}
//
double findMaxAverage(List<int> num, int k) {
int sum = 0;
int n = num.length;
//窗口大小
for (int i = 0; i < k; i++) {
sum += num[i];
}
int maxSum = sum;
for (int i = k; i < n; i++) {
sum = sum - num[i - k] + num[i];//减前一个 + 后一个 == 相当于视口向前滑动一格
//第一次 sum = num[0]+num[1] - num[0] + num[2] = num[1] + num[2]
//第二次 sum = num[1]+num[2] - num[1] +num[3] = num[2] + num[3]
maxSum = max(maxSum, sum);
}
return 1.0 * maxSum / k;
}
// double findMaxAverage(List<int> num, int k) {
// int maxSum = 0;
// //窗口开始的和
// for (int i = 0; i < k; i++) {
// maxSum += num[i];
// }
// for( int i = k;i<num.length;i++){
//
//
// }
//
//
//
// return maxSum / k;
// }

11
test/set/main.dart Normal file
View File

@@ -0,0 +1,11 @@
void main(){
List<String> l1 = ['/a','/a/1'];
List<String> l2 = ['/b','/b/1'];
List<String> l3 = ['/a','/a/2'];
List<String> l4 = ['/a','/a/3'];
Set result = {...l4,...l1,...l2,...l3,};
print(result);
}