This commit is contained in:
toly
2023-10-30 16:12:23 +08:00
parent a1a70fae78
commit 8ef81ddb33
64 changed files with 6856 additions and 171 deletions

View File

@@ -2,32 +2,47 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class HoverIconButton extends StatefulWidget { class HoverIconButton extends StatefulWidget {
final VoidCallback onPressed; final VoidCallback? onPressed;
final IconData icon; final IconData icon;
final double size; final double size;
final Color? hoverColor; final Color? hoverColor;
final Color? defaultColor; final Color? defaultColor;
const HoverIconButton({super.key,required this.onPressed ,required this.icon,this.hoverColor,this.size=24,this.defaultColor}); final MouseCursor cursor;
const HoverIconButton({
super.key,
required this.onPressed,
required this.icon,
this.hoverColor,
this.size = 24,
this.defaultColor,
this.cursor = SystemMouseCursors.cell,
});
@override @override
State<HoverIconButton> createState() => _HoverIconButtonState(); State<HoverIconButton> createState() => _HoverIconButtonState();
} }
class _HoverIconButtonState extends State<HoverIconButton> { class _HoverIconButtonState extends State<HoverIconButton> {
bool _hover = false; bool _hover = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color? color = (_hover)?widget.hoverColor??Theme.of(context).primaryColor:(widget.defaultColor??null); Color? color = (_hover)
? widget.hoverColor ?? Theme.of(context).primaryColor
: (widget.defaultColor ?? null);
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, cursor: widget.cursor,
onEnter: _onEnter, onEnter: _onEnter,
onExit: _onExit, onExit: _onExit,
child: GestureDetector( child: GestureDetector(
onTap: widget.onPressed, onTap: widget.onPressed,
child: Icon(widget.icon,size: widget.size,color: color,)), child: Icon(
widget.icon,
size: widget.size,
color: color,
)),
); );
} }

View File

@@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'toly_pop_menu.dart';
class PopPanel<T> extends StatefulWidget {
final Widget child;
final Size size;
final Offset offset;
/// Builds the context menu.
final Widget panel;
const PopPanel({
super.key,
required this.child,
required this.panel,
this.offset = Offset.zero,
this.size = const Size(250, 0),
});
@override
State<PopPanel> createState() => _PopPanelState();
}
class _PopPanelState<T> extends State<PopPanel<T>> {
final ContextMenuController _contextMenuController = ContextMenuController();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onTap,
child: widget.child,
);
}
void _show() {
TolyPopupMenuEntry<T> item = CustomTolyMenuItem(
widget.panel,
size: widget.size,
);
final RenderBox button = context.findRenderObject()! as RenderBox;
final RenderBox overlay =
Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
final Offset offset = Offset(button.size.width / 2, button.size.height)+widget.offset;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(offset, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero) + offset,
ancestor: overlay),
),
Offset.zero & overlay.size,
);
showTolyMenu<T?>(
elevation: 0,
color: Colors.transparent,
context: context,
items: [item],
position: position,
).then<void>((T? newValue) {
if (!mounted) {
return null;
}
if (newValue == null) {
// widget.onCanceled?.call();
return null;
}
// widget.onSelected?.call(newValue);
});
}
void _onTap() {
_show();
}
}
class CustomTolyMenuItem<T> extends TolyPopupMenuEntry<T> {
final Size size;
final Widget content;
const CustomTolyMenuItem(this.content, {super.key, required this.size});
@override
State<StatefulWidget> createState() => _CustomTolyMenuItemState();
@override
// TODO: implement height
double get height => kMinInteractiveDimension;
@override
bool represents(value) => true;
}
class _CustomTolyMenuItemState<T> extends State<CustomTolyMenuItem> {
@override
void didUpdateWidget(covariant CustomTolyMenuItem oldWidget) {
print('============_CustomTolyMenuItemState#didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return Container(
width: widget.size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6)
]),
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: widget.content,
// color: Colors.lightBlueAccent,
);
}
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
/// A builder that includes an Offset to draw the context menu at.
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
class Popover extends StatefulWidget {
final Widget child;
/// Builds the context menu.
final ContextMenuBuilder contextMenuBuilder;
const Popover({
super.key,
required this.child,
required this.contextMenuBuilder,
});
@override
State<Popover> createState() => _PopoverState();
}
class _PopoverState extends State<Popover> {
final ContextMenuController _contextMenuController = ContextMenuController();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onTap,
child: widget.child,
);
}
void _show(Offset position) {
_contextMenuController.show(
context: context,
contextMenuBuilder: (BuildContext context) {
return widget.contextMenuBuilder(context, position);
},
);
}
final double width = 200;
void _onTap() {
final RenderBox button = context.findRenderObject()! as RenderBox;
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
final Offset offset = Offset(button.size.width / 2, button.size.height);
Offset boxOffset = button.localToGlobal(offset, ancestor: overlay);
_show(boxOffset.translate(-width, 0));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,3 +3,5 @@ export 'navigation/menu_meta.dart';
export 'navigation/toly_breadcrumb.dart'; export 'navigation/toly_breadcrumb.dart';
export 'navigation/toly_navigation_rail.dart'; export 'navigation/toly_navigation_rail.dart';
export 'popable/drop_selectable_widget.dart'; export 'popable/drop_selectable_widget.dart';
export 'popable/popover.dart';
export 'popable/pop_menu.dart';

View File

@@ -0,0 +1,183 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This sample demonstrates allowing a context menu to be shown in a widget
// subtree in response to user gestures.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const ContextMenuControllerExampleApp());
/// A builder that includes an Offset to draw the context menu at.
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
class ContextMenuControllerExampleApp extends StatefulWidget {
const ContextMenuControllerExampleApp({super.key});
@override
State<ContextMenuControllerExampleApp> createState() => _ContextMenuControllerExampleAppState();
}
class _ContextMenuControllerExampleAppState extends State<ContextMenuControllerExampleApp> {
void _showDialog(BuildContext context) {
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked print!')),
),
);
}
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
}
@override
void dispose() {
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Context menu outside of text'),
),
body: _ContextMenuRegion(
contextMenuBuilder: (BuildContext context, Offset offset) {
// The custom context menu will look like the default context menu
// on the current platform with a single 'Print' button.
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: TextSelectionToolbarAnchors(
primaryAnchor: offset,
),
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
_showDialog(context);
},
label: 'Print',
),
],
);
},
// In this case this wraps a big open space in a GestureDetector in
// order to show the context menu, but it could also wrap a single
// widget like an Image to give it a context menu.
child: ListView(
children: <Widget>[
Container(height: 20.0),
const Text(
'Right click (desktop) or long press (mobile) anywhere, not just on this text, to show the custom menu.'),
],
),
),
),
);
}
}
/// Shows and hides the context menu based on user gestures.
///
/// By default, shows the menu on right clicks and long presses.
class _ContextMenuRegion extends StatefulWidget {
/// Creates an instance of [_ContextMenuRegion].
const _ContextMenuRegion({
required this.child,
required this.contextMenuBuilder,
});
/// Builds the context menu.
final ContextMenuBuilder contextMenuBuilder;
/// The child widget that will be listened to for gestures.
final Widget child;
@override
State<_ContextMenuRegion> createState() => _ContextMenuRegionState();
}
class _ContextMenuRegionState extends State<_ContextMenuRegion> {
Offset? _longPressOffset;
final ContextMenuController _contextMenuController = ContextMenuController();
static bool get _longPressEnabled {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
return true;
case TargetPlatform.macOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
}
}
void _onSecondaryTapUp(TapUpDetails details) {
_show(details.globalPosition);
}
void _onTap() {
if (!_contextMenuController.isShown) {
return;
}
_hide();
}
void _onLongPressStart(LongPressStartDetails details) {
_longPressOffset = details.globalPosition;
}
void _onLongPress() {
assert(_longPressOffset != null);
_show(_longPressOffset!);
_longPressOffset = null;
}
void _show(Offset position) {
_contextMenuController.show(
context: context,
contextMenuBuilder: (BuildContext context) {
return widget.contextMenuBuilder(context, position);
},
);
}
void _hide() {
_contextMenuController.remove();
}
@override
void dispose() {
_hide();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onSecondaryTapUp: _onSecondaryTapUp,
onTap: _onTap,
onLongPress: _longPressEnabled ? _onLongPress : null,
onLongPressStart: _longPressEnabled ? _onLongPressStart : null,
child: widget.child,
);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example demonstrates showing the default buttons, but customizing their
// appearance.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
const EditableTextToolbarBuilderExampleApp({super.key});
@override
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
}
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
final TextEditingController _controller = TextEditingController(
text: 'Right click (desktop) or long press (mobile) to see the menu with custom buttons.',
);
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
}
@override
void dispose() {
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Custom button appearance'),
),
body: Center(
child: Column(
children: <Widget>[
const SizedBox(height: 20.0),
TextField(
controller: _controller,
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
return AdaptiveTextSelectionToolbar(
anchors: editableTextState.contextMenuAnchors,
// Build the default buttons, but make them look custom.
// In a real project you may want to build different
// buttons depending on the platform.
children: editableTextState.contextMenuButtonItems.map((ContextMenuButtonItem buttonItem) {
return CupertinoButton(
borderRadius: null,
color: const Color(0xffaaaa00),
disabledColor: const Color(0xffaaaaff),
onPressed: buttonItem.onPressed,
padding: const EdgeInsets.all(10.0),
pressedOpacity: 0.7,
child: SizedBox(
width: 200.0,
child: Text(
CupertinoTextSelectionToolbarButton.getButtonLabel(context, buttonItem),
),
),
);
}).toList(),
);
},
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,109 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example demonstrates showing a custom context menu only when some
// narrowly defined text is selected.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
const String emailAddress = 'me@example.com';
const String text = 'Select the email address and open the menu: $emailAddress';
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
const EditableTextToolbarBuilderExampleApp({super.key});
@override
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
}
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
final TextEditingController _controller = TextEditingController(
text: text,
);
void _showDialog(BuildContext context) {
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked send email!')),
),
);
}
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
}
@override
void dispose() {
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Custom button for emails'),
),
body: Center(
child: Column(
children: <Widget>[
Container(height: 20.0),
TextField(
controller: _controller,
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
final List<ContextMenuButtonItem> buttonItems = editableTextState.contextMenuButtonItems;
// Here we add an "Email" button to the default TextField
// context menu for the current platform, but only if an email
// address is currently selected.
final TextEditingValue value = _controller.value;
if (_isValidEmail(value.selection.textInside(value.text))) {
buttonItems.insert(
0,
ContextMenuButtonItem(
label: 'Send email',
onPressed: () {
ContextMenuController.removeAny();
_showDialog(context);
},
),
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
),
],
),
),
),
);
}
}
bool _isValidEmail(String text) {
return RegExp(
r'(?<name>[a-zA-Z0-9]+)'
r'@'
r'(?<domain>[a-zA-Z0-9]+)'
r'\.'
r'(?<topLevelDomain>[a-zA-Z0-9]+)',
).hasMatch(text);
}

View File

@@ -0,0 +1,132 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example demonstrates how to create a custom toolbar that retains the
// look of the default buttons for the current platform.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
const EditableTextToolbarBuilderExampleApp({super.key});
@override
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
}
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
final TextEditingController _controller = TextEditingController(
text: 'Right click (desktop) or long press (mobile) to see the menu with a custom toolbar.',
);
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
}
@override
void dispose() {
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Custom toolbar, default-looking buttons'),
),
body: Center(
child: Column(
children: <Widget>[
const SizedBox(height: 20.0),
TextField(
controller: _controller,
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
return _MyTextSelectionToolbar(
anchor: editableTextState.contextMenuAnchors.primaryAnchor,
// getAdaptiveButtons creates the default button widgets for
// the current platform.
children: AdaptiveTextSelectionToolbar.getAdaptiveButtons(
context,
// These buttons just close the menu when clicked.
<ContextMenuButtonItem>[
ContextMenuButtonItem(
label: 'One',
onPressed: () => ContextMenuController.removeAny(),
),
ContextMenuButtonItem(
label: 'Two',
onPressed: () => ContextMenuController.removeAny(),
),
ContextMenuButtonItem(
label: 'Three',
onPressed: () => ContextMenuController.removeAny(),
),
ContextMenuButtonItem(
label: 'Four',
onPressed: () => ContextMenuController.removeAny(),
),
ContextMenuButtonItem(
label: 'Five',
onPressed: () => ContextMenuController.removeAny(),
),
],
).toList(),
);
},
),
],
),
),
),
);
}
}
/// A simple, yet totally custom, text selection toolbar.
///
/// Displays its children in a scrollable grid.
class _MyTextSelectionToolbar extends StatelessWidget {
const _MyTextSelectionToolbar({
required this.anchor,
required this.children,
});
final Offset anchor;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: anchor.dy,
left: anchor.dx,
child: Container(
width: 200.0,
height: 200.0,
color: Colors.cyanAccent.withOpacity(0.5),
child: GridView.count(
padding: const EdgeInsets.all(12.0),
crossAxisCount: 2,
children: children,
),
),
),
],
);
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example demonstrates a custom context menu in non-editable text using
// SelectionArea.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const SelectableRegionToolbarBuilderExampleApp());
const String text =
'I am some text inside of SelectionArea. Right click (desktop) or long press (mobile) me to show the customized context menu.';
class SelectableRegionToolbarBuilderExampleApp extends StatefulWidget {
const SelectableRegionToolbarBuilderExampleApp({super.key});
@override
State<SelectableRegionToolbarBuilderExampleApp> createState() => _SelectableRegionToolbarBuilderExampleAppState();
}
class _SelectableRegionToolbarBuilderExampleAppState extends State<SelectableRegionToolbarBuilderExampleApp> {
void _showDialog(BuildContext context) {
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked print!')),
),
);
}
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
}
@override
void dispose() {
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Context menu anywhere'),
),
body: Center(
child: SizedBox(
width: 200.0,
child: SelectionArea(
contextMenuBuilder: (
BuildContext context,
SelectableRegionState selectableRegionState,
) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: selectableRegionState.contextMenuAnchors,
buttonItems: <ContextMenuButtonItem>[
...selectableRegionState.contextMenuButtonItems,
ContextMenuButtonItem(
onPressed: () {
ContextMenuController.removeAny();
_showDialog(context);
},
label: 'Print',
),
],
);
},
child: ListView(
children: const <Widget>[
SizedBox(height: 20.0),
Text(text),
],
),
),
),
),
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'v6/app.dart'; import 'v7/app.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();

View File

@@ -1,8 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'iroute.dart'; import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/empty/empty_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../transition/fade_transition_page.dart';
import '../../../pages/color/color_add_page.dart';
import 'route_history.dart';
const List<String> kDestinationsPaths = [ const List<String> kDestinationsPaths = [
'/color', '/color',
@@ -17,8 +26,65 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color'; String _path = '/color';
String get path => _path; String get path => _path;
AppRouterDelegate() { AppRouterDelegate() {
// keepAlivePath.add('/color'); // keepAlivePath.add('/color');
_histories.add(RouteHistory(path));
}
final List<RouteHistory> _histories = [];
final List<RouteHistory> _backHistories = [];
List<RouteHistory> get histories => _histories.reversed.toList();
bool get hasHistory => _histories.length > 1;
bool get hasBackHistory => _backHistories.isNotEmpty;
/// 历史回退操作
/// 将当前顶层移除,并加入 _backHistories 撤销列表
/// 并转到前一路径
void back() {
if (!hasHistory) return;
RouteHistory top = _histories.removeLast();
_backHistories.add(top);
if (_histories.isNotEmpty) {
_path = _histories.last.path;
if (_histories.last.extra != null) {
_pathExtraMap[_path] = _histories.last.extra;
}
notifyListeners();
}
}
void toHistory(RouteHistory history) {
_path = history.path;
if (history.extra != null) {
_pathExtraMap[_path] = history.extra;
}
notifyListeners();
}
void closeHistory(int index) {
_histories.removeAt(index);
notifyListeners();
}
void clearHistory() {
_histories.clear();
notifyListeners();
}
/// 撤销回退操作
/// 取出回退列表的最后元素,跳转到该路径
void revocation() {
RouteHistory target = _backHistories.removeLast();
_path = target.path;
if (target.extra != null) {
_pathExtraMap[_path] = target.extra;
}
_histories.add(target);
notifyListeners();
} }
int? get activeIndex { int? get activeIndex {
@@ -37,48 +103,58 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
final List<String> keepAlivePath = []; final List<String> keepAlivePath = [];
FutureOr<dynamic> changePath(String value, void setPathKeepLive(String value) {
{bool forResult = false, Object? extra, bool keepAlive = false}) { if (keepAlivePath.contains(value)) {
if (forResult) { keepAlivePath.remove(value);
_completerMap[value] = Completer();
}
if (keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (extra != null) {
_pathExtraMap[value] = extra;
} }
keepAlivePath.add(value);
path = value; path = value;
if (forResult) { }
return _completerMap[value]!.future;
} void setPathForData(String value, dynamic data) {
_pathExtraMap[value] = data;
path = value;
}
Future<dynamic> changePathForResult(String value) async {
Completer<dynamic> completer = Completer();
_completerMap[value] = completer;
path = value;
return completer.future;
} }
set path(String value) { set path(String value) {
if (_path == value) return; if (_path == value) return;
_path = value; _path = value;
/// 将路由加入历史列表
_addPathToHistory(value,_pathExtraMap[path]);
notifyListeners(); notifyListeners();
} }
void _addPathToHistory(String value, Object? extra) {
if (_histories.isNotEmpty && value == _histories.last.path) return;
_histories.add(RouteHistory(
value,
extra: _pathExtraMap[path],
));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Navigator( return Navigator(
onPopPage: _onPopPage, onPopPage: _onPopPage,
pages: _buildPages(context, path), pages: _buildPages(path),
); );
} }
List<Page> _buildPages(BuildContext context,String path) { List<Page> _buildPages(path) {
List<Page> pages = []; List<Page> pages = [];
List<Page> topPages = _buildPageByPath(context,path); List<Page> topPages = _buildPageByPath(path);
if (keepAlivePath.isNotEmpty) { if (keepAlivePath.isNotEmpty) {
for (String alivePath in keepAlivePath) { for (String alivePath in keepAlivePath) {
if (alivePath != path) { if (alivePath != path) {
pages.addAll(_buildPageByPath(context,alivePath)); pages.addAll(_buildPageByPath(alivePath));
} }
} }
@@ -91,36 +167,64 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
return pages; return pages;
} }
Page? _buildPageByPathFromTree(BuildContext context, String path) { List<Page> _buildPageByPath(String path) {
// 1. 根据 path 在 iroute 树中查询节点 Widget? child;
IRoute? iroute = root.match(path); if (path.startsWith('/color')) {
Object? extra = _pathExtraMap[path]; return buildColorPages(path);
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
Page? page;
if (iroute != null) {
page = iroute.builder?.call(
context,
IRouteData(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
),
);
} }
return page;
if (path == kDestinationsPaths[1]) {
child = const CounterPage();
}
if (path == kDestinationsPaths[2]) {
child = const UserPage();
}
if (path == kDestinationsPaths[3]) {
child = const SettingPage();
}
return [
FadeTransitionPage(
key: ValueKey(path),
child: child ?? const EmptyPage(),
)
];
} }
List<Page> _buildPageByPath(BuildContext context,String path) { List<Page> buildColorPages(String path) {
List<Page> result = []; List<Page> result = [];
Uri uri = Uri.parse(path); Uri uri = Uri.parse(path);
String dist = '';
for (String segment in uri.pathSegments) { for (String segment in uri.pathSegments) {
dist += '/$segment'; if (segment == 'color') {
Page? page = _buildPageByPathFromTree(context,dist); result.add(const FadeTransitionPage(
if(page!=null){ key: ValueKey('/color'),
result.add(page); child: ColorPage(),
));
}
if (segment == 'detail') {
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add(FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
));
} else {
Color? selectedColor = _pathExtraMap[path];
if (selectedColor != null) {
result.add(FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: selectedColor),
));
_pathExtraMap.remove(path);
}
}
}
if (segment == 'add') {
result.add(const FadeTransitionPage(
key: ValueKey('/color/add'),
child: ColorAddPage(),
));
} }
} }
return result; return result;

View File

@@ -1,131 +1,37 @@
import 'package:flutter/material.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute { class IRoute {
final String path; final String path;
final IRoutePageBuilder? builder;
final List<IRoute> children; final List<IRoute> children;
const IRoute({ const IRoute({required this.path, this.children = const []});
required this.path,
this.children = const [],
this.builder,
});
@override @override
String toString() { String toString() {
return 'IRoute{path: $path, children: $children}'; return 'IRoute{path: $path, children: $children}';
} }
IRoute? match(String path) { List<String> list(){
return matchRoute(this, path);
return [];
} }
IRoute? matchRoute(IRoute route, String path) {
if (route.path == path) {
return route;
} else {
if (route.children.isNotEmpty) {
for (int i = 0; i < route.children.length; i++) {
IRoute current = route.children[i];
IRoute? target = matchRoute(current, path);
if (target != null) {
return target;
}
}
} else {
return null;
}
}
return null;
}
} }
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData { const List<IRoute> kDestinationsIRoutes = [
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
this.extra,
required this.uri,
this.forResult = false,
this.keepAlive = false,
});
}
IRoute root = IRoute(path: '/', children: kDestinationsIRoutes);
List<IRoute> kDestinationsIRoutes = [
IRoute( IRoute(
path: '/color', path: '/color',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color'),
child: ColorPage(),
);
},
children: [ children: [
IRoute( IRoute(path: '/color/add'),
path: '/color/detail', IRoute(path: '/color/detail'),
builder: (ctx, 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 FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
},
),
IRoute(
path: '/color/add',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color/add'),
child: ColorAddPage(),
);
}),
], ],
), ),
IRoute( IRoute(
path: '/counter', path: '/counter',
builder: (ctx, data) { ),
return const FadeTransitionPage(
key: ValueKey('/counter'),
child: CounterPage(),
);
}),
IRoute( IRoute(
path: '/user', path: '/user',
builder: (ctx, data) { ),
return const FadeTransitionPage(
key: ValueKey('/user'),
child: UserPage(),
);
}),
IRoute( IRoute(
path: '/settings', path: '/settings',
builder: (ctx, data) { ),
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: SettingPage(),
);
}),
]; ];

View File

@@ -0,0 +1,6 @@
class RouteHistory{
final String path;
final Object? extra;
RouteHistory(this.path, { this.extra});
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../router/app_router_delegate.dart'; import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart'; import 'app_navigation_rail.dart';
import 'app_top_bar.dart'; import 'app_top_bar/app_top_bar.dart';
class AppNavigation extends StatelessWidget { class AppNavigation extends StatelessWidget {
const AppNavigation({super.key}); const AppNavigation({super.key});

View File

@@ -41,7 +41,7 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
), ),
tail: Padding( tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0), padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),), child: Text('V0.0.6',style: TextStyle(color: Colors.white,fontSize: 12),),
), ),
backgroundColor: const Color(0xff3975c6), backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected, onDestinationSelected: _onDestinationSelected,
@@ -53,9 +53,9 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
void _onDestinationSelected(int index) { void _onDestinationSelected(int index) {
if(index==1){ if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true); router.setPathKeepLive(kDestinationsPaths[index]);
}else{ }else{
router.path = kDestinationsPaths[index]; router.path=kDestinationsPaths[index];
} }
} }

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,110 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../router/app_router_delegate.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 RouterIndicator(),
Expanded(
child: Row(children: [
const Spacer(),
RouteHistoryButton(),
const SizedBox(width: 12,),
SizedBox(
width: 250,
child: AppRouterEditor(
onSubmit: (path) => router.path = 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': '计数器',
'/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.path = 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,156 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../router/app_router_delegate.dart';
import '../../router/route_history.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 RouteHistory 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) {
if(router.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: router.histories.length,
itemBuilder: (_, index) =>
HistoryItem(
onDelete: (){
int fixIndex = router.histories.length - 1 - index;
router.closeHistory(fixIndex);
},
onPressed: (){
router.toHistory(router.histories[index]);
Navigator.of(context).pop();
},
history: router.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.hasHistory;
bool hasBackHistory = router.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(() {});
}
}

View File

@@ -38,11 +38,12 @@ class _ColorPageState extends State<ColorPage> {
void _selectColor(Color color){ void _selectColor(Color color){
// String value = color.value.toRadixString(16); // String value = color.value.toRadixString(16);
// router.path = '/color/detail?color=$value'; // router.path = '/color/detail?color=$value';
router.changePath('/color/detail',extra: color); router.setPathForData('/color/detail',color);
} }
void _toAddPage() async { void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true); Color? color = await router.changePathForResult('/color/add');
if (color != null) { if (color != null) {
setState(() { setState(() {
_colors.add(color); _colors.add(color);

1
lib/v6_result_/app.dart Normal file
View File

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

View File

@@ -0,0 +1,200 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'iroute.dart';
import 'route_history.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
AppRouterDelegate() {
// keepAlivePath.add('/color');
}
int? get activeIndex {
if (path.startsWith('/color')) return 0;
if (path.startsWith('/counter')) return 1;
if (path.startsWith('/user')) return 2;
if (path.startsWith('/settings')) return 3;
return null;
}
final List<RouteHistory> _histories = [];
final List<RouteHistory> _backHistories = [];
bool get hasHistory => _histories.length > 1;
bool get hasBackHistory => _backHistories.isNotEmpty;
final Map<String, Completer<dynamic>> _completerMap = {};
Completer<dynamic>? completer;
final Map<String, dynamic> _pathExtraMap = {};
final List<String> keepAlivePath = [];
/// 历史回退操作
/// 将当前顶层移除,并加入 _backHistories 撤销列表
/// 并转到前一路径
void back() {
if (!hasHistory) return;
RouteHistory top = _histories.removeLast();
_backHistories.add(top);
if (_histories.isNotEmpty) {
_path = _histories.last.path;
if(_histories.last.extra!=null){
_pathExtraMap[_path] = _histories.last.extra;
}
notifyListeners();
}
}
/// 撤销回退操作
/// 取出回退列表的最后元素,跳转到该路径
void revocation() {
RouteHistory target = _backHistories.removeLast();
_path = target.path;
if(target.extra!=null){
_pathExtraMap[_path] = target.extra;
}
_histories.add(target);
notifyListeners();
// changePath(
// target.path,
// extra: target.extra,
// );
}
FutureOr<dynamic> changePath(
String value, {
bool forResult = false,
Object? extra,
bool keepAlive = false,
bool recordHistory = false,
}) {
if (forResult) {
_completerMap[value] = Completer();
}
if (keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (extra != null) {
_pathExtraMap[value] = extra;
}
if (recordHistory) {
_histories.add(RouteHistory(
value,
extra: extra,
));
}
path = value;
if (forResult) {
return _completerMap[value]!.future;
}
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPages(context, path),
);
}
List<Page> _buildPages(BuildContext context, String path) {
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));
}
pages.addAll(topPages);
return pages;
}
List<Page> _buildPageByPathFromTree(BuildContext context, String path) {
List<Page> result = [];
List<IRoute> iRoutes = root.find(path);
if (iRoutes.isNotEmpty) {
for (int i = 0; i < iRoutes.length; i++) {
IRoute iroute = iRoutes[i];
String path = iroute.path;
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
Page? page = iroute.builder?.call(
context,
IRouteData(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
),
);
if (page != null) {
result.add(page);
}
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if (_completerMap.containsKey(path)) {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path) {
Uri uri = Uri.parse(path);
if (uri.pathSegments.length == 1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}

View File

@@ -0,0 +1,185 @@
import 'package:flutter/material.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute {
final String path;
final IRoutePageBuilder? builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
this.builder,
});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
IRoute? match(String path) {
return matchRoute(this, path);
}
List<IRoute> find(String input){
String fixInput = input.substring(1);
List<IRoute> nodes = findNodes(this,fixInput,0,'/',[]);
if(nodes.isNotEmpty&&nodes.last.path!=input){
return [];
}
return nodes;
}
List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
String separator = '/';
List<String> parts = input.split(separator);
if(deep>parts.length-1){
return result;
}
String target = parts[deep];
if(node.children.isNotEmpty){
List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
bool match = nodes.isNotEmpty;
if(match){
IRoute matched = nodes.first;
result.add(matched);
String nextPrefix = '${matched.path}$separator';
findNodes(matched, input, ++deep,nextPrefix,result);
}
}else{
return result;
}
return result;
}
// List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
// String separator = '/';
// List<String> parts = input.split(separator);
// if(deep>parts.length-1){
// return result;
// }
// String target = parts[deep];
// if(node.children.isNotEmpty){
// List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
// bool match = nodes.isNotEmpty;
// if(match){
// IRoute matched = nodes.first;
// result.add(matched);
// String nextPrefix = '${matched.path}$separator';
// findNodes(matched, input, ++deep,nextPrefix,result);
// }
// }else{
// return result;
// }
// return result;
// }
IRoute? matchRoute(IRoute route, String path) {
if (route.path == path) {
return route;
} else {
if (route.children.isNotEmpty) {
for (int i = 0; i < route.children.length; i++) {
IRoute current = route.children[i];
IRoute? target = matchRoute(current, path);
if (target != null) {
return target;
}
}
} else {
return null;
}
}
return null;
}
}
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
this.extra,
required this.uri,
this.forResult = false,
this.keepAlive = false,
});
}
IRoute root = IRoute(path: 'root', children: kDestinationsIRoutes);
List<IRoute> kDestinationsIRoutes = [
IRoute(
path: '/color',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color'),
child: ColorPage(),
);
},
children: [
IRoute(
path: '/color/detail',
builder: (ctx, data) {
final Map<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 FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
},
),
IRoute(
path: '/color/add',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color/add'),
child: ColorAddPage(),
);
}),
],
),
IRoute(
path: '/counter',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/counter'),
child: CounterPage(),
);
}),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: UserPage(),
);
}),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: SettingPage(),
);
}),
];

View File

@@ -0,0 +1,6 @@
class RouteHistory{
final String path;
final Object? extra;
RouteHistory(this.path, { this.extra});
}

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<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.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,65 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
class _AppNavigationRailState extends State<AppNavigationRail> {
final List<MenuMeta> deskNavBarMenus = const [
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
MenuMeta(label: '计数器', icon: Icons.add_chart),
MenuMeta(label: '我的', icon: Icons.person),
MenuMeta(label: '设置', icon: Icons.settings),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
}else{
router.path = kDestinationsPaths[index];
}
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'navigation/router/app_router_delegate.dart';
import 'navigation/views/app_navigation.dart';
import 'navigation/views/app_navigation_rail.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: "宋体",
scaffoldBackgroundColor: Colors.white,
appBarTheme: const AppBarTheme(
elevation: 0,
iconTheme: IconThemeData(color: Colors.black),
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
))),
debugShowCheckedModeBanner: false,
home: AppNavigation()
);
}
}

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,52 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/project/colors_panel.dart';
import '../../app/navigation/router/app_router_delegate.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<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',extra: color);
}
void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true);
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')));
}
}

File diff suppressed because it is too large Load Diff

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

1
lib/v7/app.dart Normal file
View File

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

View File

@@ -0,0 +1,218 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'iroute.dart';
import 'route_history.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
AppRouterDelegate() {
// keepAlivePath.add('/color');
_histories.add(RouteHistory(path));
}
final List<RouteHistory> _histories = [];
final List<RouteHistory> _backHistories = [];
List<RouteHistory> get histories => _histories.reversed.toList();
bool get hasHistory => _histories.length > 1;
bool get hasBackHistory => _backHistories.isNotEmpty;
/// 历史回退操作
/// 将当前顶层移除,并加入 _backHistories 撤销列表
/// 并转到前一路径
void back() {
if (!hasHistory) return;
RouteHistory top = _histories.removeLast();
_backHistories.add(top);
if (_histories.isNotEmpty) {
_path = _histories.last.path;
if (_histories.last.extra != null) {
_pathExtraMap[_path] = _histories.last.extra;
}
notifyListeners();
}
}
void toHistory(RouteHistory history) {
_path = history.path;
if (history.extra != null) {
_pathExtraMap[_path] = history.extra;
}
notifyListeners();
}
void closeHistory(int index) {
_histories.removeAt(index);
notifyListeners();
}
void clearHistory() {
_histories.clear();
notifyListeners();
}
/// 撤销回退操作
/// 取出回退列表的最后元素,跳转到该路径
void revocation() {
RouteHistory target = _backHistories.removeLast();
_path = target.path;
if (target.extra != null) {
_pathExtraMap[_path] = target.extra;
}
_histories.add(target);
notifyListeners();
}
int? get activeIndex {
if (path.startsWith('/color')) return 0;
if (path.startsWith('/counter')) return 1;
if (path.startsWith('/user')) return 2;
if (path.startsWith('/settings')) return 3;
return null;
}
final Map<String, Completer<dynamic>> _completerMap = {};
Completer<dynamic>? completer;
final Map<String, dynamic> _pathExtraMap = {};
final List<String> keepAlivePath = [];
FutureOr<dynamic> changePath(
String value, {
bool forResult = false,
Object? extra,
bool keepAlive = false,
bool recordHistory = true,
}) {
if (_path == value) null;
if (forResult) {
_completerMap[value] = Completer();
}
if (keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (extra != null) {
_pathExtraMap[value] = extra;
}
if (recordHistory) {
_addPathToHistory(value,extra);
}
_path = value;
notifyListeners();
if (forResult) {
return _completerMap[value]!.future;
}
}
void _addPathToHistory(String value, Object? extra) {
if (_histories.isNotEmpty && value == _histories.last.path) return;
_histories.add(RouteHistory(
value,
extra: _pathExtraMap[path],
));
}
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPages(context, path),
);
}
List<Page> _buildPages(BuildContext context, String path) {
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));
}
pages.addAll(topPages);
return pages;
}
List<Page> _buildPageByPathFromTree(BuildContext context, String path) {
List<Page> result = [];
List<IRoute> iRoutes = root.find(path);
if (iRoutes.isNotEmpty) {
for (int i = 0; i < iRoutes.length; i++) {
IRoute iroute = iRoutes[i];
String path = iroute.path;
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
Page? page = iroute.builder?.call(
context,
IRouteData(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
),
);
if (page != null) {
result.add(page);
}
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if (_completerMap.containsKey(path)) {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
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,185 @@
import 'package:flutter/material.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute {
final String path;
final IRoutePageBuilder? builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
this.builder,
});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
IRoute? match(String path) {
return matchRoute(this, path);
}
List<IRoute> find(String input){
String fixInput = input.substring(1);
List<IRoute> nodes = findNodes(this,fixInput,0,'/',[]);
if(nodes.isNotEmpty&&nodes.last.path!=input){
return [];
}
return nodes;
}
List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
String separator = '/';
List<String> parts = input.split(separator);
if(deep>parts.length-1){
return result;
}
String target = parts[deep];
if(node.children.isNotEmpty){
List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
bool match = nodes.isNotEmpty;
if(match){
IRoute matched = nodes.first;
result.add(matched);
String nextPrefix = '${matched.path}$separator';
findNodes(matched, input, ++deep,nextPrefix,result);
}
}else{
return result;
}
return result;
}
// List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
// String separator = '/';
// List<String> parts = input.split(separator);
// if(deep>parts.length-1){
// return result;
// }
// String target = parts[deep];
// if(node.children.isNotEmpty){
// List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
// bool match = nodes.isNotEmpty;
// if(match){
// IRoute matched = nodes.first;
// result.add(matched);
// String nextPrefix = '${matched.path}$separator';
// findNodes(matched, input, ++deep,nextPrefix,result);
// }
// }else{
// return result;
// }
// return result;
// }
IRoute? matchRoute(IRoute route, String path) {
if (route.path == path) {
return route;
} else {
if (route.children.isNotEmpty) {
for (int i = 0; i < route.children.length; i++) {
IRoute current = route.children[i];
IRoute? target = matchRoute(current, path);
if (target != null) {
return target;
}
}
} else {
return null;
}
}
return null;
}
}
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
this.extra,
required this.uri,
this.forResult = false,
this.keepAlive = false,
});
}
IRoute root = IRoute(path: 'root', children: kDestinationsIRoutes);
List<IRoute> kDestinationsIRoutes = [
IRoute(
path: '/color',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color'),
child: ColorPage(),
);
},
children: [
IRoute(
path: '/color/detail',
builder: (ctx, data) {
final Map<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 FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
},
),
IRoute(
path: '/color/add',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color/add'),
child: ColorAddPage(),
);
}),
],
),
IRoute(
path: '/counter',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/counter'),
child: CounterPage(),
);
}),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: UserPage(),
);
}),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: SettingPage(),
);
}),
];

View File

@@ -0,0 +1,6 @@
class RouteHistory{
final String path;
final Object? extra;
RouteHistory(this.path, { this.extra});
}

View File

@@ -0,0 +1,12 @@
// import 'route_history.dart';
//
// class RouteHistoryManager{
// final List<RouteHistory> _histories = [];
// final List<RouteHistory> _backHistories = [];
//
// List<RouteHistory> get histories => _histories.reversed.toList();
//
// bool get hasHistory => _histories.length > 1;
//
// bool get hasBackHistory => _backHistories.isNotEmpty;
// }

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<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,65 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
class _AppNavigationRailState extends State<AppNavigationRail> {
final List<MenuMeta> deskNavBarMenus = const [
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
MenuMeta(label: '计数器', icon: Icons.add_chart),
MenuMeta(label: '我的', icon: Icons.person),
MenuMeta(label: '设置', icon: Icons.settings),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.6',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
}else{
router.changePath(kDestinationsPaths[index]);
}
}
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
),
)
],
);
}
}

Some files were not shown because too many files have changed in this diff Show More