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

@@ -1,8 +1,17 @@
import 'dart:async';
import 'dart:io';
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 = [
'/color',
@@ -17,8 +26,65 @@ 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 {
@@ -37,48 +103,58 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
final List<String> keepAlivePath = [];
FutureOr<dynamic> changePath(String value,
{bool forResult = false, Object? extra, bool keepAlive = false}) {
if (forResult) {
_completerMap[value] = Completer();
}
if (keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (extra != null) {
_pathExtraMap[value] = extra;
void setPathKeepLive(String value) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(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) {
if (_path == value) return;
_path = value;
/// 将路由加入历史列表
_addPathToHistory(value,_pathExtraMap[path]);
notifyListeners();
}
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),
pages: _buildPages(path),
);
}
List<Page> _buildPages(BuildContext context,String path) {
List<Page> _buildPages(path) {
List<Page> pages = [];
List<Page> topPages = _buildPageByPath(context,path);
List<Page> topPages = _buildPageByPath(path);
if (keepAlivePath.isNotEmpty) {
for (String alivePath in keepAlivePath) {
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;
}
Page? _buildPageByPathFromTree(BuildContext context, String path) {
// 1. 根据 path 在 iroute 树中查询节点
IRoute? iroute = root.match(path);
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
Page? page;
if (iroute != null) {
page = iroute.builder?.call(
context,
IRouteData(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
),
);
List<Page> _buildPageByPath(String path) {
Widget? child;
if (path.startsWith('/color')) {
return buildColorPages(path);
}
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 = [];
Uri uri = Uri.parse(path);
String dist = '';
for (String segment in uri.pathSegments) {
dist += '/$segment';
Page? page = _buildPageByPathFromTree(context,dist);
if(page!=null){
result.add(page);
if (segment == 'color') {
result.add(const FadeTransitionPage(
key: ValueKey('/color'),
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;

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 {
final String path;
final IRoutePageBuilder? builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
this.builder,
});
const IRoute({required this.path, this.children = const []});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
IRoute? match(String path) {
return matchRoute(this, path);
List<String> list(){
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 {
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 = [
const 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: '/color/add'),
IRoute(path: '/color/detail'),
],
),
IRoute(
path: '/counter',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/counter'),
child: CounterPage(),
);
}),
path: '/counter',
),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: UserPage(),
);
}),
path: '/user',
),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: SettingPage(),
);
}),
path: '/settings',
),
];

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 '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
import 'app_top_bar/app_top_bar.dart';
class AppNavigation extends StatelessWidget {
const AppNavigation({super.key});

View File

@@ -41,7 +41,7 @@ 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.6',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
@@ -53,9 +53,9 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
router.setPathKeepLive(kDestinationsPaths[index]);
}else{
router.path = kDestinationsPaths[index];
router.path=kDestinationsPaths[index];
}
}

View File

@@ -1,7 +1,7 @@
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';
import '../../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.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});
@@ -19,11 +21,15 @@ class AppTopBar extends StatelessWidget {
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(

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