feat(leisure): 新增闲情逸致模块与多项功能优化

本次提交完成多项核心更新:
1. 新增闲情逸致功能模块,包含时间线、收藏标注、季节主题等基础框架
2. 替换hive为社区维护的hive_ce包,修复依赖兼容问题
3. 统一替换"开发中"提示为"当前设备不支持",优化用户提示文案
4. 新增多项功能开关与特性标志,统一管理不可用功能提示
5. 完善用户账户洞察系统,新增头像审核中状态检测
6. 优化TTS语音朗读服务,修复Android端引擎初始化问题
7. 重构知识图谱缩放手势逻辑,解决缩放不跟手问题
8. 新增精灵头像组件,替换默认聊天头像样式
9. 新增外部链接跳转确认弹窗,提升使用安全性
10. 升级后端API接口,新增签到配置获取与补签积分规则动态读取
11. 完善多语言翻译覆盖率限制,非中文语言仅显示最高50%进度
12. 新增HTTP缓存拦截器,优化网络请求性能
13. 新增恢复出厂设置选项,完善数据管理功能

同时修复了多处代码细节问题:简化字符串拼接、优化布局代码、移除多余代码等。
This commit is contained in:
Developer
2026-05-27 08:06:54 +08:00
parent c44457f94c
commit 355191aaf6
144 changed files with 23600 additions and 1464 deletions

View File

@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — TTS语音朗读公共类
// 创建时间: 2026-05-20
/// 更新时间: 2026-05-26
/// 更新时间: 2026-05-27
/// 作用: 统一TTS语音朗读管理跨平台兼容
/// 上次更新: 替换官方flutter_tts v4.2.5,增加鸿蒙端判断逻辑
/// 上次更新: 修复Android端TTS引擎未就绪导致朗读失败的问题
// ============================================================
import 'dart:async';
@@ -160,7 +160,17 @@ class TtsService {
Log.d('TtsService: Windows平台使用SAPI/WinRT TTS引擎');
break;
case TargetPlatform.android:
Log.d('TtsService: Android平台使用系统TTS引擎');
Log.d('TtsService: Android平台初始化TTS引擎');
await _flutterTts!.awaitSpeakCompletion(true);
try {
final engines = await _flutterTts!.getEngines;
Log.d('TtsService: Android TTS引擎: $engines');
if (engines == null || (engines is List && engines.isEmpty)) {
Log.w('TtsService: Android未检测到TTS引擎朗读可能不可用');
}
} catch (e) {
Log.w('TtsService: Android获取TTS引擎失败: $e');
}
break;
default:
break;

View File

@@ -283,9 +283,7 @@ 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();
}

View File

@@ -11,7 +11,7 @@ import 'dart:io';
import 'package:archive/archive.dart';
import 'package:crypto/crypto.dart';
import 'package:hive/hive.dart';
import 'package:hive_ce/hive.dart';
import '../../storage/database/app_database.dart';
import '../../storage/kv_storage.dart';

View File

@@ -11,7 +11,7 @@ import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive/hive.dart';
import 'package:hive_ce/hive.dart';
import '../../utils/logger.dart';
import '../../utils/platform/platform_utils.dart' as pu;

View File

@@ -0,0 +1,221 @@
/// ============================================================
/// 闲言APP — 功能标志服务
/// 创建时间: 2026-05-27
/// 更新时间: 2026-05-27
/// 作用: 统一管理功能可用性,替代分散的"设备不支持"硬编码
/// 上次更新: v6.5.57 初始创建包含15个功能标志
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// 功能标志枚举
enum FeatureFlag {
nearbyDiscovery(
id: 'nearby_discovery',
title: '近场发现',
unsupportedMessage: '近场发现功能正在开发中,敬请期待 📡',
),
shaderBackground(
id: 'shader_background',
title: '特效背景',
unsupportedMessage: '特效背景功能正在开发中,敬请期待 ✨',
),
exportTranslate(
id: 'export_translate',
title: '导出翻译',
unsupportedMessage: '导出翻译功能正在开发中,敬请期待 📤',
),
ttsRead(
id: 'tts_read',
title: '朗读功能',
unsupportedMessage: '朗读功能正在开发中,敬请期待 🔊',
),
importChat(
id: 'import_chat',
title: '导入会话',
unsupportedMessage: '导入会话功能正在开发中,敬请期待 📥',
),
downloadWallpaper(
id: 'download_wallpaper',
title: '下载壁纸',
unsupportedMessage: '下载壁纸功能正在开发中,敬请期待 🖼️',
),
editWithApp(
id: 'edit_with_app',
title: '编辑器应用',
unsupportedMessage: '编辑器应用功能正在开发中,敬请期待 🎨',
),
privateMessage(
id: 'private_message',
title: '私信',
unsupportedMessage: '私信功能正在开发中,敬请期待 💬',
),
tagDelete(
id: 'tag_delete',
title: '标签删除',
unsupportedMessage: '标签删除功能正在开发中,敬请期待 🏷️',
),
shareSentence(
id: 'share_sentence',
title: '分享句子',
unsupportedMessage: '分享句子功能正在开发中,敬请期待 🔗',
),
forwardMessage(
id: 'forward_message',
title: '转发消息',
unsupportedMessage: '转发消息功能正在开发中,敬请期待 ➡️',
),
handwritingFont(
id: 'handwriting_font',
title: '手写字体',
unsupportedMessage: '手写字体功能正在开发中,敬请期待 ✍️',
),
qrScan(
id: 'qr_scan',
title: '通用扫码',
unsupportedMessage: '通用扫码功能正在开发中,敬请期待 📷',
),
quickTransfer(
id: 'quick_transfer',
title: '面对面快传',
unsupportedMessage: '面对面快传功能正在开发中,敬请期待 📱',
),
payment(
id: 'payment',
title: '收付款',
unsupportedMessage: '收付款功能正在开发中,敬请期待 💰',
);
const FeatureFlag({
required this.id,
required this.title,
required this.unsupportedMessage,
});
/// 标识符
final String id;
/// 功能名称
final String title;
/// 不支持时的提示文案
final String unsupportedMessage;
/// 是否启用默认false可通过远程配置开启
static final Map<String, bool> _overrides = {};
/// 设置功能标志覆盖
static void setOverride(String flagId, bool enabled) {
_overrides[flagId] = enabled;
}
/// 清除所有覆盖
static void clearOverrides() {
_overrides.clear();
}
/// 检查功能是否启用
bool get isEnabled => _overrides[id] ?? false;
}
/// 功能标志状态
class FeatureFlagState {
const FeatureFlagState();
/// 检查功能是否启用
bool isEnabled(FeatureFlag flag) => flag.isEnabled;
/// 获取所有功能标志及其状态
Map<FeatureFlag, bool> get allFlags {
return Map.fromEntries(
FeatureFlag.values.map((f) => MapEntry(f, f.isEnabled)),
);
}
}
/// 功能标志 Notifier
class FeatureFlagNotifier extends Notifier<FeatureFlagState> {
@override
FeatureFlagState build() => const FeatureFlagState();
/// 设置功能标志覆盖
void setOverride(FeatureFlag flag, bool enabled) {
FeatureFlag.setOverride(flag.id, enabled);
ref.invalidateSelf();
}
/// 清除所有覆盖
void clearOverrides() {
FeatureFlag.clearOverrides();
ref.invalidateSelf();
}
}
/// 功能标志 Provider
final featureFlagProvider =
NotifierProvider<FeatureFlagNotifier, FeatureFlagState>(
FeatureFlagNotifier.new,
);
/// 功能标志服务 — 提供静态方法供各页面调用
class FeatureFlagService {
FeatureFlagService._();
/// 检查功能是否可用
/// 如果功能未启用,弹出 CupertinoAlertDialog 并返回 false
/// 如果功能已启用,返回 true
static bool check(BuildContext context, FeatureFlag flag) {
if (flag.isEnabled) return true;
showCupertinoDialog<void>(
context: context,
builder: (_) => CupertinoAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
CupertinoIcons.lock_shield_fill,
size: 20,
color: CupertinoColors.systemGrey,
),
const SizedBox(width: 8),
Text(flag.title),
],
),
content: Text(flag.unsupportedMessage),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: const Text('知道了'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
return false;
}
/// 静默检查功能是否可用(不弹窗)
static bool isEnabled(FeatureFlag flag) => flag.isEnabled;
/// 根据 setting id 获取对应的 FeatureFlag
/// 用于 general_settings_page 的 isPlaceholder 映射
static FeatureFlag? fromSettingId(String id) {
switch (id) {
case 'nearby_discovery':
return FeatureFlag.nearbyDiscovery;
case 'shader_background':
return FeatureFlag.shaderBackground;
default:
return null;
}
}
/// 根据 FeatureFlag 判断是否为 placeholder
/// 用于 general_settings_sections 中替换 isPlaceholder: true
static bool isPlaceholder(FeatureFlag? flag) {
if (flag == null) return false;
return !flag.isEnabled;
}
}

View File

@@ -1,204 +1,117 @@
/// ============================================================
/// 闲言APP — 深度链接服务
/// 创建时间: 2026-04-28
/// 更新时间: 2026-04-28
/// 作用: 处理外部深度链接,支持 xianyan:// 和 https:// 链接
/// 上次更新: 修复工具路由路径映射 + 新增更多工具路由
/// 更新时间: 2026-05-27
/// 作用: 使用 app_links 统一处理深度链接,支持冷启动和热恢复
/// 上次更新: 重构为使用 AppRouter.resolveDeepLinkUri 统一路径映射,消除重复逻辑
/// ============================================================
import 'package:app_links/app_links.dart';
import 'package:xianyan/core/router/app_nav_extension.dart';
import 'dart:async';
import '../../../core/router/app_router.dart';
import '../../../core/utils/logger.dart';
import 'package:app_links/app_links.dart';
import 'package:flutter/services.dart';
import '../../router/app_router.dart';
import '../../router/app_nav_extension.dart';
import '../../utils/logger.dart';
class DeepLinkService {
DeepLinkService._();
static AppLinks? _appLinks;
static StreamSubscription<Uri>? _linkSubscription;
/// 初始化深度链接服务
/// 处理冷启动getInitialLink和热恢复uriLinkStream
static Future<void> init() async {
try {
_appLinks = AppLinks();
final initialLink = await _appLinks!.getInitialLink();
// 冷启动:获取初始链接
final initialLink = await _getInitialLinkSafely();
if (initialLink != null) {
Log.i('深度链接: 初始链接 $initialLink');
_handleLink(initialLink.toString());
Log.i('🔗 [DeepLink] 冷启动链接: $initialLink');
_handleLink(initialLink);
}
_appLinks!.uriLinkStream.listen((uri) {
Log.i('深度链接: 实时链接 $uri');
_handleLink(uri.toString());
});
// 热恢复:监听实时链接流
_linkSubscription = _appLinks!.uriLinkStream.listen(
(uri) {
Log.i('🔗 [DeepLink] 热恢复链接: $uri');
_handleLink(uri);
},
onError: (Object error) {
Log.e('🔗 [DeepLink] 链接流错误', error);
},
);
Log.i('深度链接服务初始化完成');
} on PlatformException catch (e) {
Log.w('🔗 [DeepLink] 平台不支持深度链接: $e');
} catch (e) {
Log.e('深度链接服务初始化失败', e);
}
}
static void _handleLink(String link) {
final uri = Uri.tryParse(link);
if (uri == null) {
Log.w('深度链接: 无法解析 $link');
return;
/// 安全获取初始链接,部分平台可能抛出异常
static Future<Uri?> _getInitialLinkSafely() async {
try {
return await _appLinks!.getInitialLink();
} on PlatformException catch (e) {
Log.w('🔗 [DeepLink] 获取初始链接失败(平台限制): $e');
return null;
}
}
final path = _resolvePath(uri);
if (path == null) {
Log.w('深度链接: 未知路径 $link');
/// 处理单个深度链接 URI
/// 使用 AppRouter.resolveDeepLinkUri 统一路径映射
static void _handleLink(Uri uri) {
final resolved = AppRouter.resolveDeepLinkUri(uri);
if (resolved == null) {
Log.w('🔗 [DeepLink] 无法解析: $uri');
return;
}
final context = rootNavigatorKey.currentContext;
if (context == null) {
Log.w('深度链接: NavigatorContext 不可用');
Log.w('🔗 [DeepLink] NavigatorContext 不可用,延迟导航');
_pendingLink = resolved;
_schedulePendingNavigation();
return;
}
if (context.mounted) {
context.appGo(path);
Log.i('深度链接: 导航到 $path');
context.appGo(resolved);
Log.i('🔗 [DeepLink] 导航到: $resolved');
} else {
Log.w('🔗 [DeepLink] Context 未挂载,延迟导航');
_pendingLink = resolved;
_schedulePendingNavigation();
}
}
static String? _resolvePath(Uri uri) {
if (uri.scheme == 'xianyan') {
return _resolveCustomScheme(uri);
}
if (uri.scheme == 'https' && uri.host.contains('xianyan')) {
return _resolveHttps(uri);
}
return null;
/// 待处理的链接Context 不可用时暂存)
static String? _pendingLink;
/// 延迟导航:等待 Context 就绪后执行
static void _schedulePendingNavigation() {
if (_pendingLink == null) return;
Future.delayed(const Duration(milliseconds: 500), () {
final context = rootNavigatorKey.currentContext;
if (context != null && context.mounted && _pendingLink != null) {
context.appGo(_pendingLink!);
Log.i('🔗 [DeepLink] 延迟导航到: $_pendingLink');
_pendingLink = null;
}
});
}
static String? _resolveCustomScheme(Uri uri) {
final path = uri.host + uri.path;
switch (uri.host) {
case 'home':
return AppRoutes.home;
case 'inspiration':
return AppRoutes.inspiration;
case 'profile':
return AppRoutes.profile;
case 'search':
return AppRoutes.search;
case 'tool':
return _resolveToolPath(uri.path);
case 'editor':
return AppRoutes.editor;
case 'notes':
return AppRoutes.noteList;
case 'signin':
return AppRoutes.signin;
case 'weather':
return AppRoutes.weather;
case 'poetry':
return AppRoutes.poetry;
case 'pomodoro':
return AppRoutes.pomodoro;
case 'countdown':
return AppRoutes.countdown;
case 'solar-term':
return AppRoutes.solarTerm;
case 'knowledge-graph':
return AppRoutes.knowledgeGraph;
case 'study-plan':
return AppRoutes.studyPlan;
case 'notification-settings':
return AppRoutes.notificationSettings;
case 'statistics':
return AppRoutes.statistics;
case 'settings':
return _resolveSettingsPath(uri.path);
default:
Log.w('深度链接: 未知 scheme 路径 $path');
return null;
}
}
static String? _resolveHttps(Uri uri) {
final segments = uri.pathSegments;
if (segments.isEmpty) return AppRoutes.home;
switch (segments[0]) {
case 'tool':
if (segments.length > 1) return _resolveToolPath('/${segments[1]}');
return AppRoutes.inspiration;
case 'search':
return AppRoutes.search;
case 'notes':
return AppRoutes.noteList;
case 'editor':
return AppRoutes.editor;
default:
return null;
}
}
static String? _resolveToolPath(String path) {
switch (path) {
case '/hanzi':
return AppRoutes.hanziTool;
case '/ocr':
return '/tool/ocr';
case '/colors':
return '/tool/china_colors';
case '/hot':
return '/tool/list';
case '/calc':
return '/tool/calc';
case '/list':
return '/tool/list';
case '/offline':
return AppRoutes.offline;
case '/cache':
return AppRoutes.cacheManagement;
case '/readlater':
return AppRoutes.readLater;
case '/favorites':
return AppRoutes.favorites;
case '/history':
return AppRoutes.history;
case '/notes':
return AppRoutes.noteList;
case '/signin':
return AppRoutes.signin;
case '/weather':
return AppRoutes.weather;
case '/poetry':
return AppRoutes.poetry;
case '/pomodoro':
return AppRoutes.pomodoro;
case '/countdown':
return AppRoutes.countdown;
case '/solar-term':
return AppRoutes.solarTerm;
case '/knowledge-graph':
return AppRoutes.knowledgeGraph;
case '/study-plan':
return AppRoutes.studyPlan;
default:
Log.w('深度链接: 未知工具路径 $path');
return AppRoutes.inspiration;
}
}
static String? _resolveSettingsPath(String path) {
switch (path) {
case '/theme':
return AppRoutes.themeSettings;
case '/general':
return AppRoutes.generalSettings;
case '/account':
return AppRoutes.accountSettings;
case '/data':
return AppRoutes.dataManagement;
case '/notifications':
return AppRoutes.notificationSettings;
default:
return AppRoutes.generalSettings;
}
/// 释放资源
static void dispose() {
_linkSubscription?.cancel();
_linkSubscription = null;
_pendingLink = null;
_appLinks = null;
}
}

View File

@@ -91,7 +91,7 @@ class SafeSharingReceiver {
static Stream<List<SharedMediaFile>> getMediaStream() {
try {
final instance = ReceiveSharingIntent.instance;
return instance.getMediaStream().handleError((e) {
return instance.getMediaStream().handleError((Object e) {
Log.e('SafeSharingReceiver: getMediaStream异常', e);
});
} catch (e) {