v10
This commit is contained in:
184
lib/v11/app/navigation/router/app_router_delegate.dart
Normal file
184
lib/v11/app/navigation/router/app_router_delegate.dart
Normal file
@@ -0,0 +1,184 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'views/navigator_scope.dart';
|
||||
import 'iroute.dart';
|
||||
import 'iroute_config.dart';
|
||||
import 'route_history_manager.dart';
|
||||
import 'routes.dart';
|
||||
import 'views/not_find_view.dart';
|
||||
|
||||
AppRouterDelegate router = AppRouterDelegate(node: rootRoute);
|
||||
|
||||
class AppRouterDelegate extends RouterDelegate<IRouteConfig>
|
||||
with ChangeNotifier {
|
||||
/// 核心数据,路由配置数据列表
|
||||
final List<IRouteConfig> _configs = [];
|
||||
|
||||
String get path => current.uri.path;
|
||||
|
||||
IRouteConfig get current => _configs.last;
|
||||
|
||||
final IRoutePageBuilder? notFindPageBuilder;
|
||||
|
||||
final IRouteNode node;
|
||||
|
||||
@override
|
||||
IRouteConfig? get currentConfiguration {
|
||||
if(_configs.isEmpty) return null;
|
||||
return current;
|
||||
}
|
||||
|
||||
AppRouterDelegate({
|
||||
this.notFindPageBuilder,
|
||||
required this.node,
|
||||
});
|
||||
|
||||
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
|
||||
child: Material(child: NotFindPage()),
|
||||
);
|
||||
|
||||
final RouteHistoryManager _historyManager = RouteHistoryManager();
|
||||
|
||||
RouteHistoryManager get historyManager => _historyManager;
|
||||
|
||||
/// 历史回退操作
|
||||
/// 详见: [RouteHistoryManager.back]
|
||||
void back() => _historyManager.back(changeRoute);
|
||||
|
||||
/// 撤销回退操作
|
||||
/// 详见: [RouteHistoryManager.revocation]
|
||||
void revocation() => _historyManager.revocation(changeRoute);
|
||||
|
||||
void closeHistory(int index) {
|
||||
_historyManager.close(index);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearHistory() {
|
||||
_historyManager.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// final List<IRouteConfig> _pathStack = [];
|
||||
|
||||
bool get canPop => _configs.where((e) => e.routeStyle == RouteStyle.push).isNotEmpty;
|
||||
|
||||
final Map<String, Completer<dynamic>> _completerMap = {};
|
||||
|
||||
FutureOr<dynamic> changeRoute(IRouteConfig config) {
|
||||
String value = config.uri.path;
|
||||
if (current == config) null;
|
||||
_handleChangeStyle(config);
|
||||
|
||||
if (config.forResult) {
|
||||
_completerMap[value] = Completer();
|
||||
}
|
||||
|
||||
if (config.recordHistory) {
|
||||
_historyManager.recode(config);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
||||
if (config.forResult) {
|
||||
return _completerMap[value]!.future;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleChangeStyle(IRouteConfig config) {
|
||||
switch (config.routeStyle) {
|
||||
case RouteStyle.push:
|
||||
if (_configs.contains(config)) {
|
||||
_configs.remove(config);
|
||||
}
|
||||
_configs.add(config);
|
||||
break;
|
||||
case RouteStyle.replace:
|
||||
List<IRouteConfig> liveRoutes =
|
||||
_configs.where((e) => e.keepAlive && e != config).toList();
|
||||
_configs.clear();
|
||||
_configs.addAll([...liveRoutes, config]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<dynamic> changePath(
|
||||
String value, {
|
||||
bool forResult = false,
|
||||
Object? extra,
|
||||
bool keepAlive = false,
|
||||
bool recordHistory = false,
|
||||
RouteStyle style = RouteStyle.replace,
|
||||
}) {
|
||||
return changeRoute(IRouteConfig(
|
||||
uri: Uri.parse(value),
|
||||
forResult: forResult,
|
||||
extra: extra,
|
||||
routeStyle: style,
|
||||
keepAlive: keepAlive,
|
||||
recordHistory: recordHistory,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigatorScope(
|
||||
node: node,
|
||||
onPopPage: _onPopPage,
|
||||
configs: _configs,
|
||||
notFindPageBuilder: (notFindPageBuilder ?? _defaultNotFindPageBuilder),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> popRoute() async {
|
||||
print('=======popRoute=========');
|
||||
return true;
|
||||
}
|
||||
|
||||
void backStack() {
|
||||
if (_configs.isNotEmpty) {
|
||||
_configs.removeLast();
|
||||
if (_configs.isNotEmpty) {
|
||||
changeRoute(_configs.last);
|
||||
} else {
|
||||
changeRoute(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _onPopPage(Route route, result) {
|
||||
if (_completerMap.containsKey(path)) {
|
||||
_completerMap[path]?.complete(result);
|
||||
_completerMap.remove(path);
|
||||
}
|
||||
|
||||
if (canPop) {
|
||||
_configs.removeLast();
|
||||
notifyListeners();
|
||||
} else {
|
||||
changePath(backPath(path), recordHistory: false);
|
||||
}
|
||||
return route.didPop(result);
|
||||
}
|
||||
|
||||
String backPath(String path) {
|
||||
Uri uri = Uri.parse(path);
|
||||
if (uri.pathSegments.length == 1) return path;
|
||||
List<String> parts = List.of(uri.pathSegments)..removeLast();
|
||||
return '/${parts.join('/')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(IRouteConfig configuration) async{
|
||||
changeRoute(configuration);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setInitialRoutePath(IRouteConfig configuration) {
|
||||
_configs.add(configuration);
|
||||
_historyManager.recode(configuration);
|
||||
return super.setInitialRoutePath(configuration);
|
||||
}
|
||||
}
|
||||
22
lib/v11/app/navigation/router/app_router_parser.dart
Normal file
22
lib/v11/app/navigation/router/app_router_parser.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'iroute_config.dart';
|
||||
|
||||
class AppRouterParser extends RouteInformationParser<IRouteConfig> {
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(IRouteConfig configuration) {
|
||||
return RouteInformation(uri: configuration.uri);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<IRouteConfig> parseRouteInformationWithDependencies(
|
||||
RouteInformation routeInformation,
|
||||
BuildContext context,
|
||||
) {
|
||||
if(routeInformation.state is IRouteConfig){
|
||||
return SynchronousFuture(routeInformation.state as IRouteConfig);
|
||||
}
|
||||
return SynchronousFuture(IRouteConfig(uri: routeInformation.uri));
|
||||
}
|
||||
}
|
||||
156
lib/v11/app/navigation/router/iroute.dart
Normal file
156
lib/v11/app/navigation/router/iroute.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/v9/app/navigation/router/views/navigator_scope.dart';
|
||||
|
||||
import 'iroute_config.dart';
|
||||
|
||||
typedef IRoutePageBuilder = Page? Function(
|
||||
BuildContext context,
|
||||
IRouteConfig data,
|
||||
);
|
||||
|
||||
|
||||
|
||||
typedef IRouteWidgetBuilder = Widget? Function(
|
||||
BuildContext context,
|
||||
IRouteConfig data,
|
||||
);
|
||||
|
||||
abstract class IRouteNode {
|
||||
final String path;
|
||||
final List<IRouteNode> children;
|
||||
|
||||
const IRouteNode({
|
||||
required this.path,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
Page? createPage(BuildContext context, IRouteConfig config);
|
||||
|
||||
List<IRouteNode> find(
|
||||
String input,
|
||||
) {
|
||||
String prefix = '/';
|
||||
if (this is CellIRoute) {
|
||||
input = input.replaceFirst(path, '');
|
||||
if (path != '/') {
|
||||
prefix = path + "/";
|
||||
}
|
||||
}
|
||||
|
||||
return findNodes(this, Uri.parse(input), 0, prefix, []);
|
||||
}
|
||||
|
||||
List<IRouteNode> findNodes(
|
||||
IRouteNode node,
|
||||
Uri uri,
|
||||
int deep,
|
||||
String prefix,
|
||||
List<IRouteNode> result,
|
||||
) {
|
||||
List<String> parts = uri.pathSegments;
|
||||
if (deep > parts.length - 1) {
|
||||
return result;
|
||||
}
|
||||
String target = parts[deep];
|
||||
if (node.children.isNotEmpty) {
|
||||
target = prefix + target;
|
||||
|
||||
List<IRouteNode> nodes = node.children.where((e) => e.path == target).toList();
|
||||
bool match = nodes.isNotEmpty;
|
||||
if (match) {
|
||||
IRouteNode matched = nodes.first;
|
||||
result.add(matched);
|
||||
String nextPrefix = '${matched.path}/';
|
||||
findNodes(matched, uri, ++deep, nextPrefix, result);
|
||||
} else {
|
||||
result.add(NotFindNode(path: target));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// 优先调用 [pageBuilder] 构建 Page
|
||||
/// 没有 [pageBuilder] 时, 使用 [widgetBuilder] 构建组件
|
||||
/// 没有 [pageBuilder] 和 [widgetBuilder] 时, 使用 [widget] 构建组件
|
||||
class IRoute extends IRouteNode {
|
||||
final IRoutePageBuilder? pageBuilder;
|
||||
final IRouteWidgetBuilder? widgetBuilder;
|
||||
final Widget? widget;
|
||||
|
||||
const IRoute({
|
||||
required super.path,
|
||||
super.children = const [],
|
||||
this.widget,
|
||||
this.pageBuilder,
|
||||
this.widgetBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||
if (pageBuilder != null) {
|
||||
return pageBuilder!(context, config);
|
||||
}
|
||||
Widget? child;
|
||||
if (widgetBuilder != null) {
|
||||
child = widgetBuilder!(context, config);
|
||||
}
|
||||
child ??= widget;
|
||||
if (child != null) {
|
||||
return MaterialPage(child: child, key: config.pageKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 未知路由
|
||||
class NotFindNode extends IRouteNode {
|
||||
NotFindNode({required super.path, super.children = const []});
|
||||
|
||||
@override
|
||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
typedef CellBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
IRouteConfig config,
|
||||
Widget navigator,
|
||||
);
|
||||
|
||||
typedef CellIRoutePageBuilder = Page? Function(
|
||||
BuildContext context,
|
||||
IRouteConfig data,
|
||||
Widget child,
|
||||
);
|
||||
|
||||
class CellIRoute extends IRouteNode {
|
||||
final CellBuilder cellBuilder;
|
||||
final CellIRoutePageBuilder? pageBuilder;
|
||||
|
||||
const CellIRoute({
|
||||
required this.cellBuilder,
|
||||
this.pageBuilder,
|
||||
required super.path,
|
||||
required super.children,
|
||||
});
|
||||
|
||||
@override
|
||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Page? createCellPage(BuildContext context, IRouteConfig config,
|
||||
Widget child) {
|
||||
if (pageBuilder != null) {
|
||||
return pageBuilder!(context, config, child);
|
||||
}
|
||||
print("======CellIRoute#createCellPage${config.pageKey}=================");
|
||||
return MaterialPage(
|
||||
child: child,
|
||||
key: config.pageKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
71
lib/v11/app/navigation/router/iroute_config.dart
Normal file
71
lib/v11/app/navigation/router/iroute_config.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum RouteStyle{
|
||||
push,
|
||||
replace,
|
||||
}
|
||||
|
||||
|
||||
class IRouteConfig {
|
||||
final Object? extra;
|
||||
final bool forResult;
|
||||
final Uri uri;
|
||||
final bool keepAlive;
|
||||
final RouteStyle routeStyle;
|
||||
final bool recordHistory;
|
||||
|
||||
const IRouteConfig({
|
||||
this.extra,
|
||||
required this.uri,
|
||||
this.forResult = false,
|
||||
this.keepAlive = false,
|
||||
this.routeStyle = RouteStyle.replace,
|
||||
this.recordHistory = false,
|
||||
});
|
||||
|
||||
String get path => uri.path;
|
||||
|
||||
IRouteConfig copyWith({
|
||||
Object? extra,
|
||||
bool? forResult,
|
||||
bool? keepAlive,
|
||||
bool? recordHistory,
|
||||
String? path,
|
||||
}) =>
|
||||
IRouteConfig(
|
||||
extra: extra ?? this.extra,
|
||||
forResult: forResult ?? this.forResult,
|
||||
keepAlive: keepAlive ?? this.keepAlive,
|
||||
recordHistory: recordHistory ?? this.recordHistory,
|
||||
uri: path!=null?Uri.parse(path):uri,
|
||||
);
|
||||
|
||||
ValueKey get pageKey => ValueKey(hashCode);
|
||||
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IRouteConfig{extra: $extra, forResult: $forResult, uri: $uri, keepAlive: $keepAlive, routeStyle: $routeStyle, recordHistory: $recordHistory}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is IRouteConfig &&
|
||||
runtimeType == other.runtimeType &&
|
||||
extra == other.extra &&
|
||||
forResult == other.forResult &&
|
||||
uri == other.uri &&
|
||||
keepAlive == other.keepAlive &&
|
||||
routeStyle == other.routeStyle &&
|
||||
recordHistory == other.recordHistory;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
extra.hashCode ^
|
||||
forResult.hashCode ^
|
||||
uri.hashCode ^
|
||||
keepAlive.hashCode ^
|
||||
routeStyle.hashCode ^
|
||||
recordHistory.hashCode;
|
||||
}
|
||||
47
lib/v11/app/navigation/router/route_history_manager.dart
Normal file
47
lib/v11/app/navigation/router/route_history_manager.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'iroute_config.dart';
|
||||
|
||||
typedef OnRouteChange = void Function(IRouteConfig config);
|
||||
|
||||
class RouteHistoryManager{
|
||||
final List<IRouteConfig> _histories = [];
|
||||
final List<IRouteConfig> _backHistories = [];
|
||||
|
||||
List<IRouteConfig> get histories => _histories.reversed.toList();
|
||||
|
||||
bool get hasHistory => _histories.length > 1;
|
||||
|
||||
bool get hasBackHistory => _backHistories.isNotEmpty;
|
||||
|
||||
/// 将 [config] 加入历史记录
|
||||
void recode(IRouteConfig config){
|
||||
if (_histories.isNotEmpty && config.path == _histories.last.path) return;
|
||||
_histories.add(config);
|
||||
}
|
||||
|
||||
/// 历史回退操作
|
||||
/// 将当前顶层移除,并加入 [_backHistories] 撤销列表
|
||||
/// 并转到前一路径 [_histories.last]
|
||||
void back(OnRouteChange callback) {
|
||||
if (!hasHistory) return;
|
||||
IRouteConfig top = _histories.removeLast();
|
||||
_backHistories.add(top);
|
||||
if (_histories.isNotEmpty) {
|
||||
callback(_histories.last);
|
||||
}
|
||||
}
|
||||
|
||||
/// 撤销回退操作
|
||||
/// 取出回退列表的最后元素,跳转到该路径
|
||||
void revocation(OnRouteChange callback) {
|
||||
IRouteConfig target = _backHistories.removeLast();
|
||||
callback(target);
|
||||
}
|
||||
|
||||
void close(int index) {
|
||||
_histories.removeAt(index);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_histories.clear();
|
||||
}
|
||||
}
|
||||
95
lib/v11/app/navigation/router/routes.dart
Normal file
95
lib/v11/app/navigation/router/routes.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import '../../../pages/login/login.dart';
|
||||
import '../transition/no_transition_page.dart';
|
||||
import '../../../pages/sort/views/player/sort_player.dart';
|
||||
import 'iroute_config.dart';
|
||||
import '../views/app_navigation.dart';
|
||||
import 'iroute.dart';
|
||||
import '../../../pages/color/color_add_page.dart';
|
||||
import '../../../pages/color/color_detail_page.dart';
|
||||
import '../../../pages/color/color_page.dart';
|
||||
import '../../../pages/counter/counter_page.dart';
|
||||
import '../../../pages/user/user_page.dart';
|
||||
import '../../../pages/settings/settings_page.dart';
|
||||
import '../../../pages/sort/views/sort_page/sort_page.dart';
|
||||
import '../../../pages/sort/views/settings/sort_setting.dart';
|
||||
|
||||
CellIRoute appRoute = CellIRoute(
|
||||
cellBuilder: (_, __, navigator) => AppNavigation(
|
||||
navigator: navigator,
|
||||
),
|
||||
path: '/app',
|
||||
children: [
|
||||
IRoute(
|
||||
path: '/app/color',
|
||||
widget: ColorPage(),
|
||||
children: [
|
||||
IRoute(path: '/app/color/detail', widgetBuilder: _buildColorDetail),
|
||||
IRoute(path: '/app/color/add', widget: ColorAddPage()),
|
||||
],
|
||||
),
|
||||
const IRoute(path: '/app/counter', widget: CounterPage()),
|
||||
CellIRoute(
|
||||
cellBuilder: (_, __, navigator) => SortNavigation(navigator: navigator),
|
||||
// pageBuilder: (_,config,child)=> NoTransitionPage(
|
||||
// child: child,
|
||||
// key: config.pageKey
|
||||
// ),
|
||||
path: '/app/sort',
|
||||
children: [
|
||||
const IRoute(
|
||||
path: '/app/sort/settings',
|
||||
widget: SortSettings(),
|
||||
),
|
||||
const IRoute(
|
||||
path: '/app/sort/player',
|
||||
widget: SortPlayer(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const IRoute(path: '/app/user', widget: UserPage()),
|
||||
const IRoute(path: '/app/settings', widget: SettingPage()),
|
||||
],
|
||||
);
|
||||
|
||||
IRoute rootRoute = IRoute(path: '/', children: [
|
||||
appRoute,
|
||||
const IRoute(
|
||||
path: '/login',
|
||||
widget: LoginPage()
|
||||
)
|
||||
]);
|
||||
|
||||
Widget? _buildColorDetail(BuildContext context, IRouteConfig data) {
|
||||
final Map<String, String> queryParams = data.uri.queryParameters;
|
||||
String? selectedColor = queryParams['color'];
|
||||
Color color = Colors.black;
|
||||
if (selectedColor != null) {
|
||||
color = Color(int.parse(selectedColor, radix: 16));
|
||||
} else if (data.extra is Color) {
|
||||
color = data.extra as Color;
|
||||
}
|
||||
return ColorDetailPage(color: color);
|
||||
}
|
||||
|
||||
Map<String, String> kRouteLabelMap = {
|
||||
'/app': '',
|
||||
'/app/color': '颜色板',
|
||||
'/app/color/add': '添加颜色',
|
||||
'/app/color/detail': '颜色详情',
|
||||
'/app/counter': '计数器',
|
||||
'/app/sort': '排序算法',
|
||||
'/app/sort/player': '演示',
|
||||
'/app/sort/settings': '排序配置',
|
||||
'/app/user': '我的',
|
||||
'/app/settings': '系统设置',
|
||||
};
|
||||
|
||||
const List<MenuMeta> deskNavBarMenus = [
|
||||
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined, path: '/app/color'),
|
||||
MenuMeta(label: '计数器', icon: Icons.add_chart, path: '/app/counter'),
|
||||
MenuMeta(label: '排序', icon: Icons.sort, path: '/app/sort/player'),
|
||||
MenuMeta(label: '我的', icon: Icons.person, path: '/app/user'),
|
||||
MenuMeta(label: '设置', icon: Icons.settings, path: '/app/settings'),
|
||||
];
|
||||
109
lib/v11/app/navigation/router/views/navigator_scope.dart
Normal file
109
lib/v11/app/navigation/router/views/navigator_scope.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../iroute.dart';
|
||||
|
||||
import '../iroute_config.dart';
|
||||
|
||||
class NavigatorScope extends StatefulWidget {
|
||||
final IRouteNode node;
|
||||
final PopPageCallback onPopPage;
|
||||
final List<IRouteConfig> configs;
|
||||
final IRoutePageBuilder notFindPageBuilder;
|
||||
|
||||
const NavigatorScope({
|
||||
super.key,
|
||||
required this.node,
|
||||
required this.onPopPage,
|
||||
required this.configs,
|
||||
required this.notFindPageBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NavigatorScope> createState() => _NavigatorScopeState();
|
||||
}
|
||||
|
||||
class _NavigatorScopeState extends State<NavigatorScope> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget content = Navigator(
|
||||
onPopPage: widget.onPopPage,
|
||||
pages: _buildPages(context, widget.configs),
|
||||
);
|
||||
|
||||
if(widget.node is CellIRoute){
|
||||
content = (widget.node as CellIRoute).cellBuilder(context,widget.configs.last,content);
|
||||
}
|
||||
return HeroControllerScope(
|
||||
controller: MaterialApp.createMaterialHeroController(),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
List<Page> _buildPages(BuildContext context, List<IRouteConfig> configs) {
|
||||
IRouteConfig top = configs.last;
|
||||
List<IRouteConfig> bottoms =
|
||||
configs.sublist(0, configs.length - 1).toList();
|
||||
List<Page> pages = [];
|
||||
List<Page> topPages = _buildPageByPathFromTree(context, top);
|
||||
pages = _buildLivePageByPathList(context, bottoms, top, topPages);
|
||||
pages.addAll(topPages);
|
||||
return pages;
|
||||
}
|
||||
|
||||
List<Page> _buildLivePageByPathList(
|
||||
BuildContext context,
|
||||
List<IRouteConfig> paths,
|
||||
IRouteConfig curConfig,
|
||||
List<Page> curPages,
|
||||
) {
|
||||
List<Page> pages = [];
|
||||
if (paths.isNotEmpty) {
|
||||
for (IRouteConfig path in paths) {
|
||||
if (path != curConfig) {
|
||||
pages.addAll(_buildPageByPathFromTree(context, path));
|
||||
}
|
||||
}
|
||||
/// 去除和 curPages 中重复的界面
|
||||
pages.removeWhere((page) => curPages.map((e) => e.key).contains(page.key));
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
List<Page> _buildPageByPathFromTree(
|
||||
BuildContext context, IRouteConfig config) {
|
||||
List<Page> result = [];
|
||||
List<IRouteNode> iRoutes = widget.node.find(config.path);
|
||||
|
||||
if (iRoutes.isNotEmpty) {
|
||||
for (int i = 0; i < iRoutes.length; i++) {
|
||||
IRouteNode iroute = iRoutes[i];
|
||||
IRouteConfig fixConfig = config;
|
||||
|
||||
if(iroute.path!=config.uri.path){
|
||||
fixConfig = IRouteConfig(uri: Uri.parse(iroute.path));
|
||||
}
|
||||
|
||||
Page? page;
|
||||
if (iroute is NotFindNode) {
|
||||
page = widget.notFindPageBuilder(context, config);
|
||||
} else if (iroute is CellIRoute) {
|
||||
Widget scope = NavigatorScope(
|
||||
node: iroute,
|
||||
onPopPage: widget.onPopPage,
|
||||
configs: widget.configs,
|
||||
notFindPageBuilder: widget.notFindPageBuilder,
|
||||
);
|
||||
page = iroute.createCellPage(context, fixConfig, scope);
|
||||
} else {
|
||||
page = iroute.createPage(context, fixConfig);
|
||||
}
|
||||
if (page != null) {
|
||||
result.add(page);
|
||||
}
|
||||
if (iroute is CellIRoute) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
25
lib/v11/app/navigation/router/views/not_find_view.dart
Normal file
25
lib/v11/app/navigation/router/views/not_find_view.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotFindPage extends StatelessWidget {
|
||||
const NotFindPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Material(
|
||||
child: Center(
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
direction: Axis.vertical,
|
||||
children: [
|
||||
Icon(Icons.nearby_error,size: 64, color: Colors.redAccent),
|
||||
Text(
|
||||
'404 Page Not Find',
|
||||
style: TextStyle(fontSize: 24, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../app_router_delegate.dart';
|
||||
class RouteBackIndicator extends StatefulWidget {
|
||||
const RouteBackIndicator({super.key});
|
||||
|
||||
@override
|
||||
State<RouteBackIndicator> createState() => _RouteBackIndicatorState();
|
||||
}
|
||||
|
||||
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router.addListener(_onChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.removeListener(_onChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(router.canPop){
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: router.backStack,
|
||||
child: Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
margin: EdgeInsets.only(right: 8),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffE3E5E7),
|
||||
borderRadius: BorderRadius.circular(6)
|
||||
),
|
||||
child: Icon(Icons.arrow_back_ios_new,size: 14,)),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
void _onChange() {
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FadePageTransitionsBuilder extends PageTransitionsBuilder {
|
||||
|
||||
const FadePageTransitionsBuilder();
|
||||
|
||||
@override
|
||||
Widget buildTransitions<T>(
|
||||
PageRoute<T>? route,
|
||||
BuildContext? context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return _FadePagePageTransition(
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FadePagePageTransition extends StatelessWidget {
|
||||
|
||||
const _FadePagePageTransition({
|
||||
required this.animation,
|
||||
required this.secondaryAnimation,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Animation<double> animation;
|
||||
final Animation<double> secondaryAnimation;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(curveTween),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/v11/app/navigation/transition/fade_transition_page.dart
Normal file
52
lib/v11/app/navigation/transition/fade_transition_page.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FadeTransitionPage<T> extends Page<T> {
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
|
||||
const FadeTransitionPage({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.duration = const Duration(milliseconds: 300),
|
||||
});
|
||||
|
||||
@override
|
||||
Route<T> createRoute(BuildContext context) => PageBasedFadeTransitionRoute<T>(this);
|
||||
}
|
||||
|
||||
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
|
||||
final FadeTransitionPage<T> _page;
|
||||
|
||||
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
|
||||
|
||||
@override
|
||||
Color? get barrierColor => null;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => null;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => _page.duration;
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(curveTween),
|
||||
child: (settings as FadeTransitionPage).child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) =>
|
||||
child;
|
||||
}
|
||||
47
lib/v11/app/navigation/transition/no_transition_page.dart
Normal file
47
lib/v11/app/navigation/transition/no_transition_page.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoTransitionPage<T> extends Page<T> {
|
||||
final Widget child;
|
||||
|
||||
const NoTransitionPage({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
|
||||
}
|
||||
|
||||
class NoTransitionRoute<T> extends PageRoute<T> {
|
||||
|
||||
final NoTransitionPage<T> _page;
|
||||
|
||||
NoTransitionRoute(this._page) : super(settings: _page);
|
||||
|
||||
@override
|
||||
Color? get barrierColor => null;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => null;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => const Duration(milliseconds: 0);
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
return (settings as NoTransitionPage).child;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) =>
|
||||
child;
|
||||
}
|
||||
51
lib/v11/app/navigation/views/app_navigation.dart
Normal file
51
lib/v11/app/navigation/views/app_navigation.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
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 StatefulWidget {
|
||||
final Widget navigator;
|
||||
|
||||
const AppNavigation({super.key,required this.navigator});
|
||||
|
||||
@override
|
||||
State<AppNavigation> createState() => _AppNavigationState();
|
||||
}
|
||||
|
||||
class _AppNavigationState extends State<AppNavigation> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
print('======_AppNavigationState#initState==============');
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('======_AppNavigationState#dispose==============');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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: widget.navigator,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
80
lib/v11/app/navigation/views/app_navigation_rail.dart
Normal file
80
lib/v11/app/navigation/views/app_navigation_rail.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import '../router/app_router_delegate.dart';
|
||||
import '../router/iroute_config.dart';
|
||||
import '../router/routes.dart';
|
||||
|
||||
class AppNavigationRail extends StatefulWidget {
|
||||
const AppNavigationRail({super.key});
|
||||
|
||||
@override
|
||||
State<AppNavigationRail> createState() => _AppNavigationRailState();
|
||||
}
|
||||
|
||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||
|
||||
@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.10',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
backgroundColor: const Color(0xff3975c6),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
selectedIndex: activeIndex,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
RegExp _segReg = RegExp(r'/app/\w+');
|
||||
|
||||
int? get activeIndex {
|
||||
String path = router.path;
|
||||
RegExpMatch? match = _segReg.firstMatch(path);
|
||||
if (match == null) return null;
|
||||
String? target = match.group(0);
|
||||
int index = deskNavBarMenus.indexWhere((menu) => menu.path!.contains(target??''));
|
||||
if (index == -1) return null;
|
||||
return index;
|
||||
}
|
||||
|
||||
void _onDestinationSelected(int index) {
|
||||
String path = deskNavBarMenus[index].path!;
|
||||
if (index == 1) {
|
||||
router.changePath(path, keepAlive: true,recordHistory: true);
|
||||
return;
|
||||
}
|
||||
if (index == 4) {
|
||||
router.changePath(path, style: RouteStyle.push,recordHistory: true);
|
||||
return;
|
||||
} else {
|
||||
router.changePath(path,recordHistory: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRouterChange() {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
|
||||
import '../../router/app_router_delegate.dart';
|
||||
|
||||
class AppRouterEditor extends StatefulWidget {
|
||||
final ValueChanged<String>? onSubmit;
|
||||
const AppRouterEditor({super.key, this.onSubmit});
|
||||
|
||||
@override
|
||||
State<AppRouterEditor> createState() => _AppRouterEditorState();
|
||||
}
|
||||
|
||||
class _AppRouterEditorState extends State<AppRouterEditor> {
|
||||
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_onRouteChange();
|
||||
router.addListener(_onRouteChange);
|
||||
}
|
||||
|
||||
void _onRouteChange() {
|
||||
_controller.text=router.path;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
router.removeListener(_onRouteChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: CupertinoTextField(
|
||||
controller: _controller,
|
||||
style: TextStyle(fontSize: 14),
|
||||
padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32),
|
||||
placeholder: '输入路由地址导航',
|
||||
onSubmitted: widget.onSubmit,
|
||||
decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: HoverIconButton(
|
||||
icon: Icons.directions_outlined,
|
||||
defaultColor: Color(0xff68696B),
|
||||
onPressed:()=>widget.onSubmit?.call(_controller.text),
|
||||
size: 20
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
107
lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart
Normal file
107
lib/v11/app/navigation/views/app_top_bar/app_top_bar.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import '../../router/app_router_delegate.dart';
|
||||
import '../../router/routes.dart';
|
||||
import '../../router/views/route_back_indicator.dart';
|
||||
import 'app_router_editor.dart';
|
||||
import 'history_view_icon.dart';
|
||||
import 'route_history_button.dart';
|
||||
|
||||
class AppTopBar extends StatelessWidget {
|
||||
const AppTopBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DragToMoveWrap(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 46,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
const RouteBackIndicator(),
|
||||
const RouterIndicator(),
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
const Spacer(),
|
||||
RouteHistoryButton(),
|
||||
const SizedBox(width: 12,),
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: AppRouterEditor(
|
||||
onSubmit: (path) => router.changePath(path),
|
||||
)),
|
||||
const SizedBox(width: 12,),
|
||||
HistoryViewIcon(),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: VerticalDivider(
|
||||
width: 32,
|
||||
),
|
||||
)
|
||||
])),
|
||||
const WindowButtons()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RouterIndicator extends StatefulWidget {
|
||||
const RouterIndicator({super.key});
|
||||
|
||||
@override
|
||||
State<RouterIndicator> createState() => _RouterIndicatorState();
|
||||
}
|
||||
|
||||
|
||||
class _RouterIndicatorState extends State<RouterIndicator> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router.addListener(_onRouterChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.removeListener(_onRouterChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TolyBreadcrumb(
|
||||
items: pathToBreadcrumbItems(router.path),
|
||||
onTapItem: (item) {
|
||||
if (item.to != null) {
|
||||
router.changePath(item.to!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onRouterChange() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
|
||||
Uri uri = Uri.parse(path);
|
||||
List<BreadcrumbItem> result = [];
|
||||
String to = '';
|
||||
|
||||
String distPath = '';
|
||||
for (String segment in uri.pathSegments) {
|
||||
distPath += '/$segment';
|
||||
}
|
||||
|
||||
for (String segment in uri.pathSegments) {
|
||||
to += '/$segment';
|
||||
String label = kRouteLabelMap[to] ?? '未知路由';
|
||||
if(label.isNotEmpty){
|
||||
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
158
lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart
Normal file
158
lib/v11/app/navigation/views/app_top_bar/history_view_icon.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import '../../router/app_router_delegate.dart';
|
||||
import '../../router/routes.dart';
|
||||
import '../../router/iroute_config.dart';
|
||||
import 'app_top_bar.dart';
|
||||
|
||||
class HistoryViewIcon extends StatelessWidget{
|
||||
const HistoryViewIcon({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: PopPanel(
|
||||
offset: const Offset(0, 10),
|
||||
panel: SizedBox(
|
||||
height: 350,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTopBar(),
|
||||
const Expanded(
|
||||
child:HistoryPanel(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.history,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTopBar() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffFAFAFC),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, left: 12, right: 12, bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
'浏览历史',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(onPressed: router.clearHistory, child: const Text('清空历史'))
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryItem extends StatefulWidget {
|
||||
final IRouteConfig history;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onDelete;
|
||||
|
||||
const HistoryItem({super.key, required this.history, required this.onPressed, required this.onDelete});
|
||||
|
||||
@override
|
||||
State<HistoryItem> createState() => _HistoryItemState();
|
||||
}
|
||||
|
||||
class _HistoryItemState extends State<HistoryItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onPressed,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.history.path),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(kRouteLabelMap[widget.history.path]??'未知路由'),
|
||||
],
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: widget.onDelete,
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: Color(0xff8E92A9),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryPanel extends StatefulWidget {
|
||||
const HistoryPanel({super.key});
|
||||
|
||||
@override
|
||||
State<HistoryPanel> createState() => _HistoryPanelState();
|
||||
}
|
||||
|
||||
class _HistoryPanelState extends State<HistoryPanel> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router.addListener(_onChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.removeListener(_onChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<IRouteConfig> histories = router.historyManager.histories;
|
||||
if(histories.isEmpty){
|
||||
return const Center(
|
||||
child: Text(
|
||||
'暂无浏览历史记录',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
itemExtent: 46,
|
||||
itemCount: histories.length,
|
||||
itemBuilder: (_, index) =>
|
||||
HistoryItem(
|
||||
onDelete: (){
|
||||
int fixIndex = histories.length - 1 - index;
|
||||
router.closeHistory(fixIndex);
|
||||
},
|
||||
onPressed: (){
|
||||
router.changeRoute(histories[index].copyWith(recordHistory: false));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
history: histories[index]),
|
||||
);
|
||||
}
|
||||
|
||||
void _onChange() {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
|
||||
import '../../router/app_router_delegate.dart';
|
||||
|
||||
class RouteHistoryButton extends StatefulWidget {
|
||||
const RouteHistoryButton({super.key});
|
||||
|
||||
@override
|
||||
State<RouteHistoryButton> createState() => _RouteHistoryButtonState();
|
||||
}
|
||||
|
||||
class _RouteHistoryButtonState extends State<RouteHistoryButton> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router.addListener(_onChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.removeListener(_onChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool hasHistory = router.historyManager.hasHistory;
|
||||
bool hasBackHistory = router.historyManager.hasBackHistory;
|
||||
Color activeColor = const Color(0xff9195AC);
|
||||
Color inActiveColor = const Color(0xffC7CAD5);
|
||||
Color historyColor = hasHistory?activeColor:inActiveColor;
|
||||
Color backHistoryColor = hasBackHistory?activeColor:inActiveColor;
|
||||
return Wrap(
|
||||
children: [
|
||||
HoverIconButton(
|
||||
size: 20,
|
||||
hoverColor: historyColor,
|
||||
defaultColor: historyColor,
|
||||
icon: CupertinoIcons.arrow_left_circle,
|
||||
onPressed: hasHistory?router.back:null,
|
||||
),
|
||||
const SizedBox(width: 8,),
|
||||
HoverIconButton(
|
||||
size: 20,
|
||||
hoverColor: backHistoryColor,
|
||||
defaultColor: backHistoryColor,
|
||||
icon: CupertinoIcons.arrow_right_circle,
|
||||
onPressed: hasBackHistory?router.revocation:null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onChange() {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user