1. 新增工作台三栏布局模式,适配宽屏设备 2. 添加跨平台系统托盘支持,新增托盘图标资源 3. 修复工作台模式下导航返回异常问题 4. 统一JSON类型安全解析,替换硬类型转换 5. 增加macOS深度链接支持,统一渠道分发信息 6. 优化部分页面生命周期和状态加载逻辑 7. 移除废弃的nearby_connections依赖
272 lines
8.8 KiB
Dart
272 lines
8.8 KiB
Dart
// ============================================================
|
||
// 闲言APP — 跨平台导航扩展
|
||
// 创建时间: 2026-05-18
|
||
// 更新时间: 2026-06-19
|
||
// 作用: 统一导航API,鸿蒙端使用OhosNavBridge,其他端使用GoRouter;自动记录最近路由
|
||
// 上次更新: 新增 appPop/appCanPop 工作台感知方法,修复右栏页面 Navigator.pop 导致根栈被弹空的白屏问题
|
||
// ============================================================
|
||
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
|
||
import 'package:xianyan/core/router/ohos_nav_bridge.dart';
|
||
import 'package:xianyan/features/settings/providers/general_settings_provider.dart';
|
||
import 'package:xianyan/core/services/recent_route_service.dart';
|
||
import 'package:xianyan/core/router/route_registry.dart';
|
||
import 'package:xianyan/core/layout/workbench/right_panel_navigator.dart';
|
||
import 'package:xianyan/core/layout/adaptive_split_view.dart' show kCompactBreakpoint;
|
||
import 'package:xianyan/core/providers/split_view_provider.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
/// 工作台模式导航辅助
|
||
///
|
||
/// 在宽屏工作台模式下,将非全屏路由重定向到右栏嵌套 Navigator
|
||
class WorkbenchNavHelper {
|
||
/// 判断当前是否处于工作台模式
|
||
///
|
||
/// 所有平台(桌面/移动/Web)在宽度 >= 768px 且工作台开关开启时自动启用
|
||
/// 仅鸿蒙端因独立布局不走工作台模式
|
||
static bool isWorkbenchMode(BuildContext context, WidgetRef ref) {
|
||
// 鸿蒙端不走工作台模式(独立布局)
|
||
if (pu.isOhos) return false;
|
||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||
final splitState = ref.read(splitViewProvider);
|
||
return screenWidth >= kCompactBreakpoint && splitState.workbenchEnabled;
|
||
}
|
||
|
||
/// 判断路由是否应该进入右栏(非全屏路由)
|
||
static bool shouldOpenInRightPanel(String route) {
|
||
return !isFullScreenRoute(route);
|
||
}
|
||
|
||
/// 在右栏 push 一个页面
|
||
static void pushToRightPanel(
|
||
WidgetRef ref,
|
||
int currentTab,
|
||
String route, {
|
||
Object? extra,
|
||
String? title,
|
||
}) {
|
||
final entry = RightPanelEntry(
|
||
route: route,
|
||
title: title ?? inferRouteTitle(route),
|
||
extra: extra,
|
||
);
|
||
ref.read(rightPanelStackProvider.notifier).push(currentTab, entry);
|
||
}
|
||
}
|
||
|
||
extension AppNavExtension on BuildContext {
|
||
Future<T?> appPush<T extends Object?>(
|
||
String route, {
|
||
Object? extra,
|
||
PageTransitionMode? transitionMode,
|
||
String? title,
|
||
}) {
|
||
RecentRouteService.addRecentRoute(route);
|
||
if (pu.isOhos) {
|
||
return OhosNavBridge.push<T>(
|
||
this,
|
||
route,
|
||
extra: extra,
|
||
transitionMode: transitionMode,
|
||
);
|
||
}
|
||
|
||
// 工作台模式判断:宽屏 + 非全屏路由 → push 到右栏嵌套 Navigator
|
||
if (!pu.isOhos) {
|
||
final screenWidth = MediaQuery.sizeOf(this).width;
|
||
if (screenWidth >= kCompactBreakpoint) {
|
||
// 通过 ProviderScope 获取 ProviderContainer
|
||
final container = ProviderScope.containerOf(this);
|
||
final splitState = container.read(splitViewProvider);
|
||
if (splitState.workbenchEnabled && !isFullScreenRoute(route)) {
|
||
// 工作台模式:push 到右栏嵌套 Navigator
|
||
final entry = RightPanelEntry(
|
||
route: route,
|
||
title: title ?? inferRouteTitle(route),
|
||
extra: extra,
|
||
);
|
||
container
|
||
.read(rightPanelStackProvider.notifier)
|
||
.push(splitState.currentTab, entry);
|
||
return Future<T?>.value();
|
||
}
|
||
}
|
||
}
|
||
|
||
return push<T>(route, extra: extra);
|
||
}
|
||
|
||
Future<T?> appReplace<T extends Object?>(String route, {Object? extra}) {
|
||
RecentRouteService.addRecentRoute(route);
|
||
if (pu.isOhos) {
|
||
return OhosNavBridge.replace<T>(this, route, extra: extra);
|
||
}
|
||
pushReplacement(route, extra: extra);
|
||
return Future<T?>.value();
|
||
}
|
||
|
||
void appGo(String route) {
|
||
RecentRouteService.addRecentRoute(route);
|
||
if (pu.isOhos) {
|
||
OhosNavBridge.go(this, route);
|
||
return;
|
||
}
|
||
go(route);
|
||
}
|
||
|
||
// ============================================================
|
||
// 工作台模式感知的 pop / canPop
|
||
// ============================================================
|
||
|
||
/// 工作台模式感知的 pop
|
||
///
|
||
/// - 宽屏工作台模式 → pop 右栏嵌套栈
|
||
/// - 窄屏或非工作台 → 走 Navigator.pop(兼容 go_router)
|
||
///
|
||
/// 修复问题:右栏页面直接调用 `Navigator.pop(context)` 会操作根 GoRouter
|
||
/// Navigator,导致栈被弹空出现白屏断言错误。
|
||
void appPop<T extends Object?>([T? result]) {
|
||
if (pu.isOhos) {
|
||
Navigator.pop(this, result);
|
||
return;
|
||
}
|
||
|
||
// 工作台模式判断:宽屏 + 工作台开关开启 → pop 右栏栈
|
||
final screenWidth = MediaQuery.sizeOf(this).width;
|
||
if (screenWidth >= kCompactBreakpoint) {
|
||
final container = ProviderScope.containerOf(this);
|
||
final splitState = container.read(splitViewProvider);
|
||
if (splitState.workbenchEnabled) {
|
||
// 工作台模式:pop 右栏栈顶页面
|
||
container
|
||
.read(rightPanelStackProvider.notifier)
|
||
.pop(splitState.currentTab);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 正常模式:使用 Navigator.pop
|
||
Navigator.pop(this, result);
|
||
}
|
||
|
||
/// 工作台模式感知的 canPop
|
||
///
|
||
/// - 宽屏工作台模式 → 检查右栏嵌套栈是否非空
|
||
/// - 窄屏或非工作台 → 走 Navigator.canPop
|
||
bool appCanPop() {
|
||
if (pu.isOhos) {
|
||
return Navigator.of(this).canPop();
|
||
}
|
||
|
||
// 工作台模式判断
|
||
final screenWidth = MediaQuery.sizeOf(this).width;
|
||
if (screenWidth >= kCompactBreakpoint) {
|
||
final container = ProviderScope.containerOf(this);
|
||
final splitState = container.read(splitViewProvider);
|
||
if (splitState.workbenchEnabled) {
|
||
// 工作台模式:检查右栏栈是否有页面
|
||
final stackMap = container.read(rightPanelStackProvider);
|
||
final stack = stackMap[splitState.currentTab];
|
||
return stack != null && stack.entries.isNotEmpty;
|
||
}
|
||
}
|
||
|
||
// 正常模式
|
||
return Navigator.of(this).canPop();
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// ConsumerContext 扩展 — 工作台模式感知的导航
|
||
// ============================================================
|
||
|
||
/// 工作台模式感知的导航扩展
|
||
///
|
||
/// 使用方式:
|
||
/// ```dart
|
||
/// ref.appPush(context, '/sentence/123', extra: {...});
|
||
/// ```
|
||
extension WorkbenchNavExtension on WidgetRef {
|
||
/// 工作台模式感知的 push
|
||
///
|
||
/// - 宽屏工作台模式 + 非全屏路由 → push 到右栏嵌套 Navigator
|
||
/// - 窄屏 或 全屏路由 → 走 GoRouter rootNavigator push
|
||
Future<T?> appPush<T extends Object?>(
|
||
BuildContext context,
|
||
String route, {
|
||
Object? extra,
|
||
PageTransitionMode? transitionMode,
|
||
String? title,
|
||
}) {
|
||
RecentRouteService.addRecentRoute(route);
|
||
|
||
// 鸿蒙端走 OhosNavBridge
|
||
if (pu.isOhos) {
|
||
return OhosNavBridge.push<T>(
|
||
context,
|
||
route,
|
||
extra: extra,
|
||
transitionMode: transitionMode,
|
||
);
|
||
}
|
||
|
||
// 工作台模式判断
|
||
final isWorkbench = WorkbenchNavHelper.isWorkbenchMode(context, this);
|
||
final shouldRightPanel = WorkbenchNavHelper.shouldOpenInRightPanel(route);
|
||
|
||
if (isWorkbench && shouldRightPanel) {
|
||
// 宽屏工作台模式:push 到右栏嵌套 Navigator
|
||
final currentTab = read(splitViewProvider).currentTab;
|
||
WorkbenchNavHelper.pushToRightPanel(
|
||
this,
|
||
currentTab,
|
||
route,
|
||
extra: extra,
|
||
title: title,
|
||
);
|
||
return Future<T?>.value();
|
||
}
|
||
|
||
// 窄屏或全屏路由:走 GoRouter push
|
||
return context.push<T>(route, extra: extra);
|
||
}
|
||
|
||
/// 工作台模式感知的 pop(WidgetRef 版本)
|
||
///
|
||
/// - 宽屏工作台模式 → pop 右栏嵌套栈
|
||
/// - 窄屏或非工作台 → 走 Navigator.pop
|
||
void appPop<T extends Object?>(BuildContext context, [T? result]) {
|
||
if (pu.isOhos) {
|
||
Navigator.pop(context, result);
|
||
return;
|
||
}
|
||
|
||
final isWorkbench = WorkbenchNavHelper.isWorkbenchMode(context, this);
|
||
if (isWorkbench) {
|
||
final currentTab = read(splitViewProvider).currentTab;
|
||
read(rightPanelStackProvider.notifier).pop(currentTab);
|
||
return;
|
||
}
|
||
|
||
Navigator.pop(context, result);
|
||
}
|
||
|
||
/// 工作台模式感知的 canPop(WidgetRef 版本)
|
||
bool appCanPop(BuildContext context) {
|
||
if (pu.isOhos) {
|
||
return Navigator.of(context).canPop();
|
||
}
|
||
|
||
if (WorkbenchNavHelper.isWorkbenchMode(context, this)) {
|
||
final currentTab = read(splitViewProvider).currentTab;
|
||
final stackMap = read(rightPanelStackProvider);
|
||
final stack = stackMap[currentTab];
|
||
return stack != null && stack.entries.isNotEmpty;
|
||
}
|
||
|
||
return Navigator.of(context).canPop();
|
||
}
|
||
}
|