chore: 汇总2026-05-30全量更新
### 详细变更:
1. **文档与配置**:更新AGENTS.md添加命令超时约束,升级Rive依赖至0.14.7并替换平台插件引用
2. **UI优化**:重构AppInfo页面布局、移除图表冗余配置、锁定部分系统设置项
3. **功能增强**:
- 新增工具面板拖拽状态管理与介绍弹窗
- 新增进度页面编辑/重排/清空用户进度功能
- 新增摇一摇路由作用域拦截逻辑
4. **体验优化**:
- 统一外部链接跳转弹窗,添加文件打开确认逻辑
- 修复设备卡片IP溢出、Android权限声明问题
- 后台任务初始化增加协议校验
5. **代码重构**:拆分工具面板配置、拖拽逻辑与动画参数,优化状态管理代码
6. **工具脚本**:新增协议文件上传脚本
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
/// 创建时间: 2026-05-29
|
||||
/// 更新时间: 2026-05-29
|
||||
/// 作用: 使用Rive动画替代TabIconSprite,实现更丰富的角色动画
|
||||
/// 上次更新: 初始创建
|
||||
/// 上次更新: 迁移至 Rive 0.14.x API
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -41,8 +41,9 @@ class RiveTabIcon extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RiveTabIconState extends State<RiveTabIcon> {
|
||||
SMIInput<bool>? _selectedInput;
|
||||
Artboard? _artboard;
|
||||
RiveWidgetController? _controller;
|
||||
File? _file;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -50,39 +51,49 @@ class _RiveTabIconState extends State<RiveTabIcon> {
|
||||
_loadRive();
|
||||
}
|
||||
|
||||
/// 加载 Rive 文件并初始化 StateMachine
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
_file?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadRive() async {
|
||||
try {
|
||||
final data = await RiveFile.asset(widget.assetPath);
|
||||
final artboard = data.mainArtboard;
|
||||
final controller = StateMachineController.fromArtboard(
|
||||
artboard,
|
||||
widget.stateMachineName,
|
||||
final file = await File.asset(
|
||||
widget.assetPath,
|
||||
riveFactory: Factory.rive,
|
||||
);
|
||||
if (controller != null) {
|
||||
artboard.addController(controller);
|
||||
_selectedInput = controller.findInput<bool>('isSelected');
|
||||
_selectedInput?.value = widget.isSelected;
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() => _artboard = artboard);
|
||||
}
|
||||
if (!mounted) return;
|
||||
_file = file;
|
||||
_controller = RiveWidgetController(
|
||||
file!,
|
||||
stateMachineSelector: StateMachineSelector.byName(
|
||||
widget.stateMachineName,
|
||||
),
|
||||
);
|
||||
_syncSelection();
|
||||
setState(() => _isLoading = false);
|
||||
} catch (e) {
|
||||
// Rive文件加载失败时静默处理,不影响其他功能
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncSelection() {
|
||||
_controller?.stateMachine.boolean('isSelected')?.value = widget.isSelected;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(RiveTabIcon oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.isSelected != widget.isSelected) {
|
||||
_selectedInput?.value = widget.isSelected;
|
||||
_syncSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_artboard == null) {
|
||||
if (_isLoading || _controller == null) {
|
||||
return SizedBox(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
@@ -92,7 +103,7 @@ class _RiveTabIconState extends State<RiveTabIcon> {
|
||||
return SizedBox(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
child: Rive(artboard: _artboard!),
|
||||
child: RiveWidget(controller: _controller!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class SplitViewNotifier extends Notifier<SplitViewState> {
|
||||
@override
|
||||
SplitViewState build() {
|
||||
final savedIndex = KvStorage.getInt(_kNavBarPosition) ?? 0;
|
||||
final positions = NavBarPosition.values;
|
||||
const positions = NavBarPosition.values;
|
||||
return SplitViewState(
|
||||
splitRatio: KvStorage.getDouble(_kSplitRatio) ?? 0.4,
|
||||
navBarPosition: (savedIndex >= 0 && savedIndex < positions.length)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 路由配置(主入口)
|
||||
// 创建时间: 2026-04-20
|
||||
// 更新时间: 2026-05-28
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: go_router 路由表组装 + ShellRoute 布局壳 + iOS 风格转场 + 深链接重定向
|
||||
// 上次更新: 对调inspiration/discover路由,discover为Tab主页面
|
||||
// 上次更新: 新增ShakeScopeObserver监听路由变化,自动更新摇一摇作用域
|
||||
// ============================================================
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
@@ -20,6 +20,7 @@ import '../../features/mine/profile/presentation/profile_page.dart';
|
||||
import '../layout/app_shell.dart';
|
||||
import '../storage/kv_storage.dart';
|
||||
import '../utils/ui/page_transitions.dart';
|
||||
import '../services/device/shake_detector.dart';
|
||||
|
||||
import 'app_routes.dart';
|
||||
import 'settings_routes.dart';
|
||||
@@ -43,9 +44,7 @@ final GoRouter appRouter = GoRouter(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
initialLocation: _resolveInitialLocation(),
|
||||
debugLogDiagnostics: true,
|
||||
observers: pu.isOhos
|
||||
? [_OhosRouteObserver()]
|
||||
: [BotToastNavigatorObserver()],
|
||||
observers: pu.isOhos ? [_OhosRouteObserver()] : [BotToastNavigatorObserver()],
|
||||
redirect: _handleDeepLinkRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
@@ -106,6 +105,12 @@ final GoRouter appRouter = GoRouter(
|
||||
errorBuilder: (context, state) => const NotFoundPage(),
|
||||
);
|
||||
|
||||
/// 摇一摇路由作用域观察者
|
||||
/// 监听路由变化,自动更新 ShakeDetector 的作用域
|
||||
/// 仅当当前路由为 /home 时允许摇一摇触发,3/4级页面自动拦截
|
||||
// ignore: unused_element
|
||||
final _shakeScopeObserver = ShakeScopeObserver(appRouter);
|
||||
|
||||
String? _handleDeepLinkRedirect(BuildContext context, GoRouterState state) {
|
||||
final uri = state.uri;
|
||||
final resolved = AppRouter.resolveDeepLinkUri(uri);
|
||||
@@ -348,3 +353,17 @@ class _OhosRouteObserver extends NavigatorObserver {
|
||||
Log.i('🟢 [OHOS] Route didStartUserGesture: ${route.settings.name}');
|
||||
}
|
||||
}
|
||||
|
||||
/// 摇一摇路由作用域观察者
|
||||
/// 监听 GoRouter 路由变化,自动更新 ShakeDetector 的作用域
|
||||
/// 仅当当前路由为 /home 时允许摇一摇触发
|
||||
class ShakeScopeObserver {
|
||||
ShakeScopeObserver(GoRouter router) {
|
||||
router.routerDelegate.addListener(() {
|
||||
final location =
|
||||
router.routerDelegate.currentConfiguration.last.matchedLocation;
|
||||
final scope = location == AppRoutes.home ? AppRoutes.home : '';
|
||||
ShakeDetector.instance.setScope(scope);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 路由路径常量 + 路由辅助组件
|
||||
// 创建时间: 2026-04-20
|
||||
// 更新时间: 2026-05-28
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: 集中管理所有路由路径字符串,供各路由模块和页面统一引用
|
||||
// 上次更新: 对调inspiration/discover路由路径,inspiration为子页面,discover为Tab主页面
|
||||
// 上次更新: 新增imageCache路由
|
||||
// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -27,6 +27,7 @@ class AppRoutes {
|
||||
static const String passwordSettings = '/settings/password';
|
||||
static const String securityQuestion = '/settings/security-question';
|
||||
static const String dataManagement = '/settings/data';
|
||||
static const String imageCache = '/settings/image-cache';
|
||||
static const String accountDeletion = '/settings/account/deletion';
|
||||
static const String fontManagement = '/settings/fonts';
|
||||
static const String favorites = '/favorites';
|
||||
@@ -94,6 +95,7 @@ class AppRoutes {
|
||||
static const String ttsPlugin = '/settings/plugin/tts';
|
||||
static const String permissionManagement = '/permission-management';
|
||||
static const String privacyPolicy = '/privacy-policy';
|
||||
static const String dataCollectionInfo = '/data-collection-info';
|
||||
static const String logViewer = '/log-viewer';
|
||||
static const String crashLog = '/crash-log';
|
||||
static const String knowledgeGraph = '/knowledge-graph';
|
||||
@@ -120,7 +122,9 @@ class AppRoutes {
|
||||
static const String translateSettings = '/translate-settings';
|
||||
static const String leisure = '/leisure';
|
||||
static const String leisureSettings = '/leisure/settings';
|
||||
static const String toolCenterSettings = '/tool-center/settings';
|
||||
static const String onboarding = '/onboarding';
|
||||
static const String experimentalFeatures = '/settings/experimental-features';
|
||||
|
||||
// ---- Deep Link 路由 ----
|
||||
static const String deepFortune = '/fortune';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 设置模块路由
|
||||
// 创建时间: 2026-05-22
|
||||
// 更新时间: 2026-05-24
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: 设置相关页面的 GoRoute 定义
|
||||
// 上次更新: 新增插件系统路由(plugin/translatePlugin/ttsPlugin)
|
||||
// 上次更新: 新增imageCache路由
|
||||
// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -24,12 +24,14 @@ import '../../features/mine/settings/presentation/more_settings_page.dart';
|
||||
import '../../features/mine/settings/presentation/other_settings_page.dart';
|
||||
import '../../features/mine/settings/presentation/privacy/permission_management_page.dart';
|
||||
import '../../features/mine/settings/presentation/privacy/privacy_policy_page.dart';
|
||||
import '../../features/mine/settings/presentation/privacy/data_collection_info_page.dart';
|
||||
import '../../features/mine/settings/presentation/privacy/log_viewer_page.dart';
|
||||
import '../../features/mine/settings/presentation/privacy/crash_log_page.dart';
|
||||
import '../../features/mine/settings/presentation/lock/app_lock_settings_page.dart';
|
||||
import '../../features/mine/settings/presentation/plugin/plugin_page.dart';
|
||||
import '../../features/mine/settings/presentation/plugin/translate_plugin_page.dart';
|
||||
import '../../features/mine/settings/presentation/plugin/tts_plugin_page.dart';
|
||||
import '../../features/mine/settings/presentation/experimental_features_page.dart';
|
||||
import '../utils/ui/page_transitions.dart';
|
||||
import 'app_routes.dart';
|
||||
|
||||
@@ -145,6 +147,15 @@ List<GoRoute> buildSettingsRoutes(
|
||||
pageBuilder: (context, state) =>
|
||||
iosSlideTransition(state: state, child: const PrivacyPolicyPage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.dataCollectionInfo,
|
||||
name: 'data-collection-info',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
pageBuilder: (context, state) => iosSlideTransition(
|
||||
state: state,
|
||||
child: const DataCollectionInfoPage(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.logViewer,
|
||||
name: 'log-viewer',
|
||||
@@ -187,4 +198,13 @@ List<GoRoute> buildSettingsRoutes(
|
||||
pageBuilder: (context, state) =>
|
||||
iosSlideTransition(state: state, child: const TtsPluginPage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.experimentalFeatures,
|
||||
name: 'experimental-features',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
pageBuilder: (context, state) => iosSlideTransition(
|
||||
state: state,
|
||||
child: const ExperimentalFeaturesPage(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 工具中心路由
|
||||
// 创建时间: 2026-05-22
|
||||
// 更新时间: 2026-05-28
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: 工具中心、AI聊天、翻译、文件传输、协作相关 GoRoute 定义
|
||||
// 上次更新: 更新import路径 tool_center/inspiration → discover
|
||||
// 上次更新: 新增工具中心设置页路由
|
||||
// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -15,6 +15,7 @@ import '../../features/discover/presentation/pages/tool/china_colors_page.dart';
|
||||
import '../../features/discover/presentation/pages/tool_list_page.dart';
|
||||
import '../../features/discover/presentation/pages/tool/ocr_tool_page.dart';
|
||||
import '../../features/discover/presentation/pages/tool/pinyin_tool_page.dart';
|
||||
import '../../features/discover/presentation/pages/tool_center_settings_page.dart';
|
||||
import '../../features/discover/presentation/pages/chat/chat_flow_page.dart';
|
||||
import '../../features/discover/models/chat_session.dart';
|
||||
import '../../features/discover/presentation/pages/chat/chat_settings_page.dart';
|
||||
@@ -258,4 +259,11 @@ List<GoRoute> buildToolRoutes(GlobalKey<NavigatorState> rootNavigatorKey) => [
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.toolCenterSettings,
|
||||
name: 'tool-center-settings',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
pageBuilder: (context, state) =>
|
||||
iosSlideTransition(state: state, child: const ToolCenterSettingsPage()),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 权限管理服务
|
||||
/// 创建时间: 2026-04-23
|
||||
/// 更新时间: 2026-05-26
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 作用: 统一管理应用权限请求,支持相机/相册/通知/位置/蓝牙/附近设备/麦克风/存储/网络/剪贴板/分享权限
|
||||
/// 上次更新: 新增权限使用统计功能(recordUsage/getUsageStats)
|
||||
/// 上次更新: 更新存储权限描述(区分READ/WRITE的API级别必要性)
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:io';
|
||||
@@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import '../../storage/kv_storage.dart';
|
||||
import '../device/shake_detector.dart';
|
||||
|
||||
/// 权限状态枚举
|
||||
enum AppPermissionStatus {
|
||||
@@ -117,9 +118,14 @@ enum AppPermission {
|
||||
'存储空间',
|
||||
Permission.storage,
|
||||
CupertinoIcons.folder_fill,
|
||||
'用于保存编辑的卡片、壁纸到本地,导出字体文件和数据。Android 12及以下版本需要此权限。',
|
||||
'用于保存编辑的卡片、壁纸到本地,导出字体文件和数据。仅Android 9及以下(API≤29)需要写入权限,Android 10+使用分区存储;Android 12及以下(API≤32)需要读取权限,Android 13+由相册权限替代。',
|
||||
Color(0xFFFF9500),
|
||||
usageScenes: ['保存卡片 — 导出到本地', '字体管理 — 下载字体文件', '数据导出 — 导出用户数据'],
|
||||
usageScenes: [
|
||||
'保存卡片 — 导出到本地',
|
||||
'壁纸设置 — 保存壁纸',
|
||||
'字体管理 — 下载字体文件',
|
||||
'数据导出 — 导出用户数据',
|
||||
],
|
||||
),
|
||||
network(
|
||||
'网络连接',
|
||||
@@ -151,6 +157,15 @@ enum AppPermission {
|
||||
isVirtual: true,
|
||||
group: PermissionGroup.system,
|
||||
usageScenes: ['句子分享 — 分享到微信/QQ', '卡片分享 — 分享到社交平台', '日志导出 — 分享日志文件'],
|
||||
),
|
||||
shake(
|
||||
'摇一摇',
|
||||
Permission.notification,
|
||||
CupertinoIcons.arrow_counterclockwise,
|
||||
'摇晃手机触发特定功能,如换句、刷新等',
|
||||
Color(0xFF5856D6),
|
||||
isVirtual: true,
|
||||
usageScenes: ['切换每日推荐句子', '刷新内容', '互动彩蛋'],
|
||||
);
|
||||
|
||||
const AppPermission(
|
||||
@@ -204,6 +219,24 @@ class PermissionService {
|
||||
|
||||
static const _usageStatsKey = 'permission_usage_stats';
|
||||
|
||||
static const _shakeEnabledKey = 'shake_enabled';
|
||||
|
||||
static bool get isShakeEnabled =>
|
||||
KvStorage.getBool(_shakeEnabledKey, box: HiveBoxNames.userPrefs) ?? true;
|
||||
|
||||
static Future<void> setShakeEnabled(bool enabled) async {
|
||||
await KvStorage.setBool(
|
||||
_shakeEnabledKey,
|
||||
enabled,
|
||||
box: HiveBoxNames.userPrefs,
|
||||
);
|
||||
if (!enabled) {
|
||||
try {
|
||||
ShakeDetector.instance.stop();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 记录权限使用
|
||||
static void recordUsage(AppPermission permission) {
|
||||
try {
|
||||
@@ -283,7 +316,9 @@ class PermissionService {
|
||||
final buffer = StringBuffer();
|
||||
stats.forEach((key, value) {
|
||||
if (buffer.isNotEmpty) buffer.write(';');
|
||||
buffer.write('$key=${value['count']},${value['lastUsed']},${value['firstUsed'] ?? ''}');
|
||||
buffer.write(
|
||||
'$key=${value['count']},${value['lastUsed']},${value['firstUsed'] ?? ''}',
|
||||
);
|
||||
});
|
||||
return buffer.toString();
|
||||
}
|
||||
@@ -332,6 +367,10 @@ class PermissionService {
|
||||
return AppPermissionStatus.granted;
|
||||
case AppPermission.share:
|
||||
return AppPermissionStatus.granted;
|
||||
case AppPermission.shake:
|
||||
return isShakeEnabled
|
||||
? AppPermissionStatus.granted
|
||||
: AppPermissionStatus.denied;
|
||||
default:
|
||||
return AppPermissionStatus.granted;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 后台任务服务
|
||||
/// 创建时间: 2026-05-25
|
||||
/// 更新时间: 2026-05-25
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 作用: 基于workmanager实现后台定时任务
|
||||
/// 上次更新: 修复OHOS端原生通道未注册导致PlatformException
|
||||
/// 上次更新: 增加用户协议同意检查,未同意则跳过后台任务初始化
|
||||
/// ============================================================
|
||||
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
import '../../storage/kv_storage.dart';
|
||||
import '../../utils/logger.dart';
|
||||
import '../../utils/platform/platform_utils.dart' as pu;
|
||||
import 'background_callback.dart';
|
||||
@@ -27,6 +28,11 @@ class BackgroundTaskService {
|
||||
Future<void> init() async {
|
||||
if (_initialized) return;
|
||||
|
||||
if (!KvStorage.isOnboardingCompleted) {
|
||||
Log.i('BackgroundTaskService: 用户未同意协议,跳过初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pu.isWeb) {
|
||||
Log.i('BackgroundTaskService: Web平台跳过后台任务初始化');
|
||||
_initialized = true;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 摇一摇检测器
|
||||
// 创建时间: 2026-05-20
|
||||
// 更新时间: 2026-05-29
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: 监听加速度传感器,检测摇一摇手势
|
||||
// 上次更新: 改用处理器栈模式,仅当前可见页面的回调生效
|
||||
// 上次更新: 新增路由作用域检查,仅当前路由在home分支时才响应摇一摇
|
||||
//
|
||||
// 摇一摇生命周期管理(处理器栈模式):
|
||||
//
|
||||
@@ -15,6 +15,12 @@
|
||||
// - 页面 deactivate 时: ShakeDetector.instance.popHandler('/route')
|
||||
// - 仅栈顶 handler 生效,确保只有当前可见页面响应摇一摇
|
||||
//
|
||||
// 路由作用域:
|
||||
// - setScope(scope) 由路由观察者调用,标识当前所在路由分支
|
||||
// - 仅当 _activeScope 匹配栈顶 handler 路由前缀时才触发回调
|
||||
// - 例如: _activeScope='/home' 且栈顶 handler route='/home' → 允许触发
|
||||
// - 例如: _activeScope='/settings' 且栈顶 handler route='/home' → 禁止触发
|
||||
//
|
||||
// 错误用法(已废弃):
|
||||
// - setHomePageActive(true/false) — 在 IndexedStack 中永远为 true
|
||||
// - 在 dispose 中 stop() — 永远不会被调用
|
||||
@@ -48,9 +54,24 @@ class ShakeDetector {
|
||||
|
||||
final List<_ShakeHandlerEntry> _handlerStack = [];
|
||||
|
||||
String? _activeScope;
|
||||
|
||||
bool get isEnabled => _isEnabled;
|
||||
bool get hasActiveHandler => _handlerStack.isNotEmpty;
|
||||
|
||||
void setScope(String? scope) {
|
||||
_activeScope = scope;
|
||||
Log.i('ShakeDetector: setScope scope=$scope');
|
||||
}
|
||||
|
||||
bool _isScopeAllowed() {
|
||||
if (_activeScope == null) return true;
|
||||
if (_activeScope!.isEmpty) return false;
|
||||
if (_handlerStack.isEmpty) return false;
|
||||
final topRoute = _handlerStack.last.route;
|
||||
return topRoute == _activeScope || topRoute.startsWith('$_activeScope/');
|
||||
}
|
||||
|
||||
void pushHandler(String route, ShakeCallback callback) {
|
||||
_handlerStack.removeWhere((e) => e.route == route);
|
||||
_handlerStack.add(_ShakeHandlerEntry(route, callback));
|
||||
@@ -90,6 +111,12 @@ class ShakeDetector {
|
||||
now.difference(_lastShakeTime!) > _minInterval) {
|
||||
_lastShakeTime = now;
|
||||
_consecutiveCount = 0;
|
||||
if (!_isScopeAllowed()) {
|
||||
Log.i(
|
||||
'ShakeDetector: 摇一摇被作用域拦截 (scope=$_activeScope, topRoute=${_handlerStack.last.route})',
|
||||
);
|
||||
return;
|
||||
}
|
||||
Log.i('ShakeDetector: 检测到摇一摇 (acceleration=$acceleration)');
|
||||
_handlerStack.last.callback.call();
|
||||
HapticService.medium();
|
||||
|
||||
132
lib/core/services/post_agreement_initializer.dart
Normal file
132
lib/core/services/post_agreement_initializer.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 协议同意后初始化器
|
||||
// 创建时间: 2026-05-30
|
||||
// 更新时间: 2026-05-30
|
||||
// 作用: 将权限敏感的服务初始化延迟到用户同意协议后执行
|
||||
// 上次更新: 首次创建,从main.dart拆分权限敏感初始化
|
||||
// ============================================================
|
||||
|
||||
import '../storage/kv_storage.dart';
|
||||
import '../utils/logger.dart';
|
||||
import '../utils/platform/platform_utils.dart' as pu;
|
||||
import '../router/app_router.dart' show rootNavigatorKey;
|
||||
import 'network/connectivity_service.dart';
|
||||
import 'clipboard_monitor_service.dart';
|
||||
import 'background/background_task_service.dart';
|
||||
import 'notification/local_notification_service.dart';
|
||||
import 'device/screen_wake_service.dart';
|
||||
import 'device/battery_optimization_service.dart';
|
||||
import 'notification/readlater_reminder_service.dart';
|
||||
import 'data/home_widget_service.dart';
|
||||
import 'readlater/sharing_receiver_service.dart';
|
||||
import '../../features/discover/services/chat_migration_service.dart';
|
||||
|
||||
class PostAgreementInitializer {
|
||||
PostAgreementInitializer._();
|
||||
|
||||
static bool _initialized = false;
|
||||
|
||||
static bool get isInitialized => _initialized;
|
||||
|
||||
static Future<void> init() async {
|
||||
if (_initialized) return;
|
||||
|
||||
Log.i('PostAgreementInitializer: 开始初始化权限敏感服务...');
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
SharingReceiverService().init();
|
||||
SharingReceiverService().setNavigatorKey(rootNavigatorKey);
|
||||
Log.i('分享接收服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('分享接收服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await LocalNotificationService.init();
|
||||
Log.i('本地通知服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('本地通知服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await ScreenWakeService.init();
|
||||
Log.i('屏幕常亮服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('屏幕常亮服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await BatteryOptimizationService.init();
|
||||
Log.i('电池优化服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('电池优化服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await ReadlaterReminderService.startMonitoring();
|
||||
Log.i('稍后读提醒服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('稍后读提醒服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await ChatMigrationService.migrateIfNeeded();
|
||||
Log.i('聊天数据迁移检查完成');
|
||||
} catch (e, st) {
|
||||
Log.e('聊天数据迁移检查失败', e, st);
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await HomeWidgetService.instance.init();
|
||||
Log.i('桌面小组件服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('桌面小组件服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await ClipboardMonitorService.instance.initFromStore();
|
||||
Log.i('剪贴板监控服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('剪贴板监控服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await ConnectivityService.init();
|
||||
Log.i('网络状态检测服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('网络状态检测服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pu.isWeb) {
|
||||
try {
|
||||
await BackgroundTaskService.instance.init();
|
||||
Log.i('后台任务服务初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('后台任务服务初始化失败', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
Log.i('PostAgreementInitializer: 所有权限敏感服务初始化完成 ✓');
|
||||
}
|
||||
|
||||
static bool shouldInit() {
|
||||
return KvStorage.isOnboardingCompleted && !_initialized;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
//// 闲言APP 统一分享接收服务
|
||||
// 创建时间: 2026-05-15
|
||||
/// 更新时间: 2026-05-26
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 作用: 接收其他App通过系统分享面板发送的内容,写入稍后读会话
|
||||
/// 上次更新: 使用SafeSharingReceiver中间件,防止SharedMediaFile空指针崩溃
|
||||
/// 上次更新: 添加确认弹窗,修复图片视频空白问题
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:xianyan/core/utils/data/pattern_utils.dart';
|
||||
import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
|
||||
import 'package:xianyan/core/router/app_nav_extension.dart';
|
||||
|
||||
import '../../../core/utils/logger.dart';
|
||||
import '../../../core/router/app_router.dart';
|
||||
@@ -170,8 +169,7 @@ class SharingReceiverService {
|
||||
sourceApp: '分享',
|
||||
);
|
||||
Log.i('分享链接已写入稍后读: $text');
|
||||
AppToast.showSuccess('🔗 链接已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('🔗 链接已保存到稍后读');
|
||||
} else {
|
||||
await ChatMessageService.sendText(
|
||||
conversationId: _readLaterConvId,
|
||||
@@ -181,8 +179,7 @@ class SharingReceiverService {
|
||||
Log.i(
|
||||
'分享文本已写入稍后读: ${text.length > 50 ? '${text.substring(0, 50)}...' : text}',
|
||||
);
|
||||
AppToast.showSuccess('📝 文本已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('📝 文本已保存到稍后读');
|
||||
}
|
||||
} catch (e) {
|
||||
Log.e('分享文本处理失败', e);
|
||||
@@ -211,8 +208,7 @@ class SharingReceiverService {
|
||||
},
|
||||
);
|
||||
Log.i('分享图片已写入稍后读: $fileName');
|
||||
AppToast.showSuccess('🖼️ 图片已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('🖼️ 图片已保存到稍后读');
|
||||
} else if (mimeType.startsWith('video/')) {
|
||||
await ChatMessageService.sendVideo(
|
||||
conversationId: _readLaterConvId,
|
||||
@@ -225,8 +221,7 @@ class SharingReceiverService {
|
||||
},
|
||||
);
|
||||
Log.i('分享视频已写入稍后读: $fileName');
|
||||
AppToast.showSuccess('🎬 视频已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('🎬 视频已保存到稍后读');
|
||||
} else if (mimeType.startsWith('application/')) {
|
||||
await ChatMessageService.sendDocument(
|
||||
conversationId: _readLaterConvId,
|
||||
@@ -237,8 +232,7 @@ class SharingReceiverService {
|
||||
meta: {'mimeType': mimeType},
|
||||
);
|
||||
Log.i('分享文档已写入稍后读: $fileName');
|
||||
AppToast.showSuccess('📄 文档已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('📄 文档已保存到稍后读');
|
||||
} else {
|
||||
await ChatMessageService.sendFile(
|
||||
conversationId: _readLaterConvId,
|
||||
@@ -246,8 +240,7 @@ class SharingReceiverService {
|
||||
meta: {'mimeType': mimeType, 'fileName': fileName},
|
||||
);
|
||||
Log.i('分享文件已写入稍后读: $fileName');
|
||||
AppToast.showSuccess('📁 文件已保存到稍后读');
|
||||
_navigateToReadlater();
|
||||
_showConfirmDialog('📁 文件已保存到稍后读');
|
||||
}
|
||||
} catch (e) {
|
||||
Log.e('分享文件处理失败', e);
|
||||
@@ -259,17 +252,30 @@ class SharingReceiverService {
|
||||
// 工具方法
|
||||
// ============================================================
|
||||
|
||||
void _navigateToReadlater() {
|
||||
void _showConfirmDialog(String message) {
|
||||
try {
|
||||
final ctx = rootNavigatorKey.currentContext;
|
||||
if (ctx != null && ctx.mounted) {
|
||||
ctx.appGo(AppRoutes.readlaterChat);
|
||||
Log.i('已自动导航到稍后读会话');
|
||||
showCupertinoDialog<void>(
|
||||
context: ctx,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: const Text('已添加到稍后读'),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: const Text('好的'),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Log.w('自动导航到稍后读失败: context不可用');
|
||||
AppToast.showSuccess(message);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.w('自动导航到稍后读失败: $e');
|
||||
Log.w('确认弹窗显示失败: $e');
|
||||
AppToast.showSuccess(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 统一 KV 本地存储
|
||||
/// 创建时间: 2026-04-20
|
||||
/// 更新时间: 2026-05-27
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 作用: 基于 Hive 的统一 KV 存储,合并原 KvStorage(SP) + AppKVStore(Hive)
|
||||
/// 上次更新: 新增leisure box命名空间
|
||||
/// 上次更新: 新增featureFlags box命名空间
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:convert';
|
||||
@@ -31,6 +31,7 @@ class HiveBoxNames {
|
||||
static const String wallpaperFavorites = 'wallpaper_favorites';
|
||||
static const String translateHistory = 'translateHistory';
|
||||
static const String leisure = 'leisure';
|
||||
static const String imageCacheMeta = 'image_cache_meta';
|
||||
|
||||
static const List<String> all = [
|
||||
app,
|
||||
@@ -43,6 +44,7 @@ class HiveBoxNames {
|
||||
chatMessages,
|
||||
wallpaperFavorites,
|
||||
leisure,
|
||||
imageCacheMeta,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user