chore: 完成多平台兼容性优化与资源更新

本次提交包含多项改进:
1. 新增Android启动页资源与配色配置,完善多版本主题适配
2. 全量替换Platform.pathSeparator为硬编码斜杠,修复Web平台路径兼容问题
3. 为大量文件系统操作添加kIsWeb守卫,优化Web端表现
4. 替换硬编码平台判断为platform_utils封装,统一平台检测逻辑
5. 移除冗余代码与默认参数,优化小部件性能
6. 新增Web端适配逻辑,处理不支持的原生功能
7. 更新鸿蒙兼容性工具,完善平台识别与路径处理
8. 优化设备注册错误捕获,避免非致命崩溃
9. 添加启动页图标与背景配置,优化首屏体验
This commit is contained in:
Developer
2026-06-05 02:31:34 +08:00
parent a2266913af
commit a5d8483797
79 changed files with 762 additions and 759 deletions

View File

@@ -1,15 +1,16 @@
/// ============================================================
/// 闲言APP — API 客户端
/// 创建时间: 2026-04-20
/// 更新时间: 2026-05-27
/// 更新时间: 2026-06-05
/// 作用: Dio 网络请求封装,统一拦截器与错误处理
/// 上次更新: 集成dio_cache_interceptor缓存拦截器GET请求自动缓存
/// 上次更新: 修复Web平台兼容性createFormData添加kIsWeb守卫
/// ============================================================
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:logger/logger.dart';
import 'api_interceptor.dart';
@@ -180,6 +181,7 @@ class ApiClient {
// ============================================================
Future<FormData> createFormData(Map<String, dynamic> fields) async {
if (kIsWeb) throw UnsupportedError('Web端不支持文件上传');
final formFields = <MapEntry<String, dynamic>>[];
for (final entry in fields.entries) {
if (entry.value is File) {

View File

@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 在线语音合成服务(Edge TTS)
// 创建时间: 2026-05-25
// 更新时间: 2026-05-26
// 更新时间: 2026-06-05
// 作用: 通过WebSocket连接Edge TTS实现在线语音合成
// 上次更新: 修复Path正则—\w+无法匹配turn.start/turn.end中的点号导致合成完成事件丢失
// 上次更新: Web兼容—getTemporaryDirectory添加kIsWeb保护
// ============================================================
import 'dart:async';
@@ -14,6 +14,7 @@ import 'dart:typed_data';
import 'package:audioplayers/audioplayers.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';
import 'package:xianyan/core/storage/kv_storage.dart';
@@ -843,6 +844,7 @@ class OnlineTtsService {
try {
// 写入临时文件
if (kIsWeb) return;
final tempDir = await getTemporaryDirectory();
final filePath =
'${tempDir.path}/edge_tts_${DateTime.now().millisecondsSinceEpoch}.mp3';

View File

@@ -1,18 +1,17 @@
/// ============================================================
/// 闲言APP — 权限管理服务
/// 创建时间: 2026-04-23
/// 更新时间: 2026-05-31
/// 更新时间: 2026-06-05
/// 作用: 统一管理应用权限请求,支持相机/相册/通知/位置/蓝牙/附近设备/麦克风/存储/网络/剪贴板/分享权限
/// 上次更新: 枚举label/description/usageScenes改为从翻译系统获取移除硬编码中文
/// 上次更新: Web平台兼容性修复-Platform.isAndroid→pu.isAndroid+Platform.version→pu
/// ============================================================
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../storage/kv_storage.dart';
import '../../utils/platform/platform_utils.dart' as pu;
import '../../../l10n/translation_resolver.dart';
import '../../../l10n/types/t_settings_permission.dart';
import '../device/shake_detector.dart';
@@ -253,7 +252,7 @@ enum AppPermission {
/// Android 13+ 不需要 storage 权限(由 photos 替代)
bool get isPlatformRelevant {
if (this == AppPermission.storage) {
if (!Platform.isAndroid) return false;
if (!pu.isAndroid) return false;
final sdkInt = _androidSdkInt;
return sdkInt != null && sdkInt <= 32;
}
@@ -262,7 +261,7 @@ enum AppPermission {
static int? get _androidSdkInt {
try {
return int.tryParse(Platform.version.split('.').first);
return int.tryParse(pu.platformVersion.split('.').first);
} catch (_) {
return null;
}

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — Catcher2 配置服务
/// 创建时间: 2026-05-21
/// 更新时间: 2026-05-21
/// 更新时间: 2026-06-05
/// 作用: 统一管理 Catcher2 异常捕获开关与动态配置更新
/// 上次更新: 自定义 CopyableDialogReportMode 支持复制/标识/控制台输出
/// 上次更新: 修复Zone mismatch不使用runAppFunction手动调用runApp
/// ============================================================
import 'package:catcher_2/catcher_2.dart';
@@ -13,6 +13,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show SelectableText;
import 'package:flutter/services.dart';
import '../router/app_router.dart' show rootNavigatorKey;
import '../storage/kv_storage.dart';
import '../utils/logger.dart';
import 'crash_log_service.dart';
@@ -30,7 +31,8 @@ class Catcher2ConfigService {
return KvStorage.getBool('general_catcher_enabled') ?? true;
}
void init({required void Function() runAppFunction}) {
/// 初始化 Catcher2使用 rootWidget 而非 runAppFunction避免 Zone mismatch
void init({required Widget rootWidget}) {
final enabled = isEnabled;
final debugConfig = enabled
@@ -47,11 +49,14 @@ class Catcher2ConfigService {
? Catcher2Options(SilentReportMode(), [_ConsoleLogHandler()])
: Catcher2Options(SilentReportMode(), []);
// 使用 rootWidget 而非 runAppFunctionCatcher2 会在内部调用 runApp
// 但不会创建新的 Zone避免 Zone mismatch 警告
_catcher2 = Catcher2(
runAppFunction: runAppFunction,
rootWidget: rootWidget,
debugConfig: debugConfig,
releaseConfig: releaseConfig,
profileConfig: Catcher2Options(SilentReportMode(), []),
navigatorKey: rootNavigatorKey,
);
_initialized = true;

View File

@@ -1,14 +1,15 @@
/// ============================================================
/// 闲言APP — 崩溃日志服务
/// 创建时间: 2026-05-21
/// 更新时间: 2026-05-21
/// 更新时间: 2026-06-05
/// 作用: 持久化存储崩溃/异常日志,支持增删查清空,自动限制数量
/// 上次更新: 初始创建
/// 上次更新: 修复 Web 平台兼容性kIsWeb 时跳过文件操作,使用内存存储
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
@@ -81,20 +82,30 @@ class CrashLogService {
bool _loaded = false;
Future<File> _getFile() async {
/// 获取日志文件Web 端返回 null
Future<File?> _getFile() async {
if (kIsWeb) return null;
final dir = await getApplicationSupportDirectory();
return File('${dir.path}/$_fileName');
}
/// 确保日志已加载
Future<void> ensureLoaded() async {
if (_loaded) return;
await load();
}
/// 加载日志Web 端仅使用内存存储(无持久化)
Future<void> load() async {
if (kIsWeb) {
_logs = [];
_loaded = true;
Log.d('CrashLogService: Web 平台,跳过文件加载');
return;
}
try {
final file = await _getFile();
if (!await file.exists()) {
if (file == null || !await file.exists()) {
_logs = [];
_loaded = true;
return;
@@ -114,9 +125,12 @@ class CrashLogService {
}
}
/// 保存日志到文件Web 端跳过
Future<void> _save() async {
if (kIsWeb) return;
try {
final file = await _getFile();
if (file == null) return;
final json = jsonEncode(_logs.map((e) => e.toJson()).toList());
await file.writeAsString(json);
} catch (e) {

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 自动备份服务
/// 创建时间: 2026-05-04
/// 更新时间: 2026-05-20
/// 更新时间: 2026-06-05
/// 作用: 定时自动备份用户数据到本地,支持备份管理和恢复
/// 上次更新: 修复Web端path_provider MissingPluginException(增加isWeb守卫)
/// 上次更新: 完善Web端守卫,所有公共方法添加isWeb早期返回
/// ============================================================
import 'dart:convert';
@@ -94,6 +94,7 @@ class BackupService {
}
static Future<List<BackupFileInfo>> getBackupList() async {
if (pu.isWeb) return [];
final dirPath = await backupDirPath;
final dir = Directory(dirPath);
final files = await dir
@@ -127,6 +128,7 @@ class BackupService {
}
static Future<String> performBackup() async {
if (pu.isWeb) throw UnsupportedError('Web端不支持备份操作');
Log.i('开始自动备份…');
final db = AppDatabase.instance;
@@ -258,6 +260,7 @@ class BackupService {
}
static Future<bool> deleteBackup(String path) async {
if (pu.isWeb) return false;
try {
final file = File(path);
if (await file.exists()) {
@@ -273,6 +276,7 @@ class BackupService {
}
static Future<void> deleteAllBackups() async {
if (pu.isWeb) return;
final backups = await getBackupList();
for (final backup in backups) {
try {
@@ -285,6 +289,7 @@ class BackupService {
}
static Future<int> getTotalBackupSize() async {
if (pu.isWeb) return 0;
final backups = await getBackupList();
int total = 0;
for (final backup in backups) {

View File

@@ -1,14 +1,15 @@
/// ============================================================
/// 闲言APP — 数据导出服务
/// 创建时间: 2026-05-07
/// 更新时间: 2026-05-07
/// 更新时间: 2026-06-05
/// 作用: 导出个人数据为JSON支持收藏/历史/笔记/设置
/// 上次更新: 接入AppDatabase真实数据查询
/// 上次更新: Web兼容—getTemporaryDirectory添加kIsWeb保护
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
@@ -119,6 +120,7 @@ class DataExportService {
static Future<String> exportToFile() async {
try {
if (kIsWeb) throw UnsupportedError('Web端不支持文件系统');
final data = await collectAllData();
final json = toJson(data);
final dir = await getTemporaryDirectory();

View File

@@ -1,13 +1,14 @@
/// ============================================================
/// 闲言APP — 图片缓存元数据服务
/// 创建时间: 2026-05-30
/// 更新时间: 2026-05-30
/// 更新时间: 2026-06-05
/// 作用: 基于Hive的图片缓存元数据索引支持按类型/日期分组、过期清理
/// 上次更新: 新增getCacheSizeLimit/setCacheSizeLimitclearAll/rebuildIndex保留_cache_size_limit
/// 上次更新: 修复Web平台兼容性添加kIsWeb守卫保护文件系统操作
/// ============================================================
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
@@ -213,6 +214,7 @@ class ImageCacheMetadataService {
String? sourceUrl,
String? category,
}) async {
if (kIsWeb) return;
final box = _safeBox();
if (box == null) return;
@@ -241,6 +243,7 @@ class ImageCacheMetadataService {
}
static Future<void> indexFromCacheManager() async {
if (kIsWeb) return;
final box = _safeBox();
if (box == null) return;
@@ -385,6 +388,7 @@ class ImageCacheMetadataService {
}
static Future<void> autoCleanExpired() async {
if (kIsWeb) return;
final policy = getAutoCleanPolicy();
final days = AutoCleanPolicy.toDays(policy);
if (days == null) return;
@@ -439,6 +443,7 @@ class ImageCacheMetadataService {
// ============================================================
static Future<void> rebuildIndex() async {
if (kIsWeb) return;
final box = _safeBox();
if (box == null) return;

View File

@@ -1,14 +1,15 @@
// ============================================================
// 闲言APP — 设置导出/导入服务
// 创建时间: 2026-05-16
// 更新时间: 2026-05-26
// 更新时间: 2026-06-05
// 作用: 设置项的导出分享与导入恢复
// 上次更新: 完善exportToJson/importFromJson支持全量设置键值导出与版本兼容导入
// 上次更新: 修复Web平台兼容性添加kIsWeb守卫保护文件操作
// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
@@ -28,6 +29,7 @@ class SettingsExportService {
];
static Future<void> shareSettings() async {
if (kIsWeb) return;
try {
final json = await exportToJson();
final path = await _writeTempFile(json);
@@ -149,6 +151,7 @@ class SettingsExportService {
}
static Future<String> _writeTempFile(String json) async {
if (kIsWeb) throw UnsupportedError('Web端不支持文件系统操作');
final dir = await getTemporaryDirectory();
final timestamp = DateTime.now().millisecondsSinceEpoch;
final file = File('${dir.path}/xianyan_settings_$timestamp.json');

View File

@@ -1,12 +1,11 @@
// ============================================================
// 闲言APP — 设备信息服务
// 创建时间: 2026-05-10
// 更新时间: 2026-05-22
// 更新时间: 2026-06-05
// 作用: 采集设备信息并自动注册到服务端
// 上次更新: 新增refresh()/clearCache()方法,支持强制刷新设备缓存
// 上次更新: 修复Web平台兼容性移除dart:io依赖使用platform_utils替代Platform
// ============================================================
import 'dart:io';
import 'dart:math';
import 'package:device_info_plus/device_info_plus.dart';
@@ -390,14 +389,15 @@ class DeviceInfoService {
// ============================================================
static Future<String> getDeviceId() async {
if (kIsWeb) return 'web_device';
try {
if (pu.isOhos) {
final android = await _deviceInfoPlugin.androidInfo;
return 'ohos_${android.id}';
} else if (Platform.isAndroid) {
} else if (pu.isAndroid) {
final android = await _deviceInfoPlugin.androidInfo;
return 'android_${android.id}';
} else if (Platform.isIOS) {
} else if (pu.isIOS) {
final ios = await _deviceInfoPlugin.iosInfo;
return 'ios_${ios.identifierForVendor ?? "unknown"}';
}
@@ -419,6 +419,7 @@ class DeviceInfoService {
// ============================================================
static Future<String> getDeviceName() async {
if (kIsWeb) return 'Web Browser';
if (_cachedDeviceName != null) return _cachedDeviceName!;
try {
if (pu.isOhos) {
@@ -430,14 +431,14 @@ class DeviceInfoService {
if (android.display.isNotEmpty) return android.display;
if (android.product.isNotEmpty) return android.product;
return '鸿蒙设备';
} else if (Platform.isAndroid) {
} else if (pu.isAndroid) {
final android = await _deviceInfoPlugin.androidInfo;
final friendly = _buildFriendlyName(android.brand, android.model);
if (friendly != null) return friendly;
if (android.model.isNotEmpty) return android.model;
if (android.brand.isNotEmpty) return getBrandChineseName(android.brand);
return 'Android 设备';
} else if (Platform.isIOS) {
} else if (pu.isIOS) {
final ios = await _deviceInfoPlugin.iosInfo;
final machine = ios.utsname.machine;
if (machine.isNotEmpty) {
@@ -458,6 +459,7 @@ class DeviceInfoService {
// ============================================================
static Future<String> getDeviceModel() async {
if (kIsWeb) return 'Web';
if (_cachedDeviceModel != null) return _cachedDeviceModel!;
try {
if (pu.isOhos) {
@@ -469,7 +471,7 @@ class DeviceInfoService {
android.product,
isOhos: true,
);
} else if (Platform.isAndroid) {
} else if (pu.isAndroid) {
final android = await _deviceInfoPlugin.androidInfo;
return _buildDeviceModel(
android.brand,
@@ -477,7 +479,7 @@ class DeviceInfoService {
android.display,
android.product,
);
} else if (Platform.isIOS) {
} else if (pu.isIOS) {
final ios = await _deviceInfoPlugin.iosInfo;
final machine = ios.utsname.machine;
if (machine.isNotEmpty) {
@@ -546,11 +548,11 @@ class DeviceInfoService {
static String getPlatform() {
if (kIsWeb) return 'web';
if (pu.isOhos) return 'ohos';
if (Platform.isAndroid) return 'android';
if (Platform.isIOS) return 'ios';
if (Platform.isMacOS) return 'mac';
if (Platform.isWindows) return 'windows';
if (Platform.isLinux) return 'linux';
if (pu.isAndroid) return 'android';
if (pu.isIOS) return 'ios';
if (pu.isMacOS) return 'mac';
if (pu.isWindows) return 'windows';
if (pu.isLinux) return 'linux';
return 'other';
}

View File

@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 快捷操作服务
// 创建时间: 2026-05-31
// 更新时间: 2026-05-31
// 更新时间: 2026-06-05
// 作用: 管理主屏幕快捷操作(Quick Actions / App Shortcuts)
// 上次更新: 初始创建,支持主题个性化+通用设置两个快捷操作
// 上次更新: 桌面端跳过初始化,避免 MissingPluginException
// 跨平台: iOS(UIApplicationShortcutItems) + Android(App Shortcuts)
// + 鸿蒙(module.json5 shortcuts + MethodChannel)
// ============================================================
@@ -37,6 +37,8 @@ class QuickActionsService {
static void init({QuickActionCallback? onActionCallback}) {
if (_initialized) return;
if (pu.isWeb) return;
// 桌面端不支持快捷操作,跳过初始化
if (pu.isDesktop) return;
onAction = onActionCallback;

View File

@@ -206,7 +206,7 @@ class NfcShareService {
_state = _isAvailable ? NfcShareState.available : NfcShareState.unavailable;
if (_isAvailable) {
_emitProgress(NfcProgressInfo(
_emitProgress(const NfcProgressInfo(
state: NfcShareState.available,
message: 'NFC已就绪可以开始分享',
progress: 1.0,
@@ -301,7 +301,7 @@ class NfcShareService {
// 阶段2等待标签
_state = NfcShareState.waitingForTag;
_emitProgress(NfcProgressInfo(
_emitProgress(const NfcProgressInfo(
state: NfcShareState.waitingForTag,
message: '请将手机背面靠近NFC标签',
progress: 0.3,
@@ -338,7 +338,7 @@ class NfcShareService {
// 阶段4成功
_state = NfcShareState.success;
_emitProgress(NfcProgressInfo(
_emitProgress(const NfcProgressInfo(
state: NfcShareState.success,
message: '写入成功!',
progress: 1.0,

View File

@@ -1,13 +1,11 @@
/// ============================================================
/// 闲言APP — 本地通知服务
/// 创建时间: 2026-05-02
/// 更新时间: 2026-05-25
/// 更新时间: 2026-06-05
/// 作用: 本地推送通知管理 (初始化/调度/取消/点击处理/多渠道)
/// 上次更新: 新增多通知渠道(daily_sentence/reminder/pomodoro)+便捷方法+scheduleDailyNotification
/// 上次更新: Web平台兼容性修复-Platform.isIOS/isAndroid→pu.isIOS/pu.isAndroid
/// ============================================================
import 'dart:io';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xianyan/core/router/app_nav_extension.dart';
@@ -131,7 +129,7 @@ class LocalNotificationService {
if (pu.isOhos) {
return requestOhosNotificationPermission(_plugin);
}
if (Platform.isIOS) {
if (pu.isIOS) {
final result = await _plugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
@@ -139,7 +137,7 @@ class LocalNotificationService {
?.requestPermissions(alert: true, badge: true, sound: true);
return result ?? false;
}
if (Platform.isAndroid) {
if (pu.isAndroid) {
final android = _plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
@@ -276,8 +274,8 @@ class LocalNotificationService {
}) async {
final body = sentence != null && sentence.isNotEmpty
? (author != null && author.isNotEmpty
? '$sentence —— $author'
: sentence)
? '$sentence —— $author'
: sentence)
: '新的一天,送你一句好话 ✨';
final details = _buildDetails(
@@ -300,9 +298,7 @@ class LocalNotificationService {
// 签到提醒通知
// ============================================================
static Future<void> showCheckinReminder({
int streakDays = 0,
}) async {
static Future<void> showCheckinReminder({int streakDays = 0}) async {
final body = streakDays > 0
? '已连续签到 $streakDays 天,别忘了今日签到哦 📝'
: '别忘了今日签到哦 📝';
@@ -332,9 +328,7 @@ class LocalNotificationService {
bool isBreak = false,
}) async {
final title = isBreak ? '闲言 · 休息结束' : '闲言 · 番茄钟完成';
final body = isBreak
? '休息结束,继续专注吧 💪'
: '专注了 $focusMinutes 分钟,休息一下吧 🍅';
final body = isBreak ? '休息结束,继续专注吧 💪' : '专注了 $focusMinutes 分钟,休息一下吧 🍅';
final details = _buildDetails(
NotificationChannels.pomodoroId,

View File

@@ -1,13 +1,11 @@
/// ============================================================
/// 闲言APP — 本地通知服务
/// 创建时间: 2026-05-10
/// 更新时间: 2026-05-17
/// 更新时间: 2026-06-05
/// 作用: 管理本地推送通知(每日推荐/签到提醒/即时通知)
/// 上次更新: 鸿蒙适配-使用桥接方法隔离OhosInitializationSettings
/// 上次更新: Web平台兼容性修复-Platform.isIOS/isMacOS→pu.isIOS/pu.isMacOS
/// ============================================================
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -91,14 +89,14 @@ class NotificationService {
// 鸿蒙端:通过桥接方法动态请求权限
await requestOhosNotificationPermission(_plugin);
}
if (Platform.isIOS) {
if (pu.isIOS) {
await _plugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
>()
?.requestPermissions(alert: true, badge: true, sound: true);
}
if (Platform.isMacOS) {
if (pu.isMacOS) {
await _plugin
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin

View File

@@ -1,14 +1,15 @@
/// ============================================================
/// 闲言APP — 稍后读跨设备同步服务
/// 创建时间: 2026-05-15
/// 更新时间: 2026-05-15
/// 更新时间: 2026-06-05
/// 作用: 通过文件传输助手的同账号/局域网设备同步稍后读内容
/// 上次更新: 初始创建,支持发现设备、打包发送、接收导入
/// 上次更新: Web兼容—getApplicationDocumentsDirectory添加kIsWeb保护
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
@@ -123,6 +124,7 @@ class ReadlaterDeviceSyncService {
/// 扫描传输目录中未处理的稍后读同步文件
Future<int> scanAndImportPendingSyncFiles() async {
try {
if (kIsWeb) return 0;
final appDocDir = await getApplicationDocumentsDirectory();
final syncDir = Directory('${appDocDir.path}/$_syncDirName');
if (!await syncDir.exists()) return 0;

View File

@@ -1,8 +1,8 @@
//// 闲言APP 统一分享接收服务
// 创建时间: 2026-05-15
/// 更新时间: 2026-05-31
/// 更新时间: 2026-06-05
/// 作用: 接收其他App通过系统分享面板发送的内容写入稍后读会话
/// 上次更新: 迁移notifyReadlaterRefresh至DataSyncEventBus统一事件总线
/// 上次更新: Web兼容—Platform.pathSeparator→'/'
/// ============================================================
import 'dart:async';
@@ -386,7 +386,7 @@ class SharingReceiverService {
/// 从路径提取文件名
String _extractFileName(String path) {
if (path.isEmpty) return '未知文件';
return path.split(Platform.pathSeparator).last;
return path.split('/').last;
}
/// 根据文件扩展名猜测MIME类型

View File

@@ -1,15 +1,16 @@
// ============================================================
// 闲言APP — Hive 安全访问工具类
// 创建时间: 2026-06-04
/// 更新时间: 2026-06-04
/// 更新时间: 2026-06-05
/// 作用: 统一 Hive Box 访问守卫,解决 OHOS 冷启动时序问题
/// 提供 lazy-init 守卫、自动重试、日志追踪
/// 上次更新: Hive.initFlutter()失败时降级使用Hive.init()+手动路径解决iOS模拟器objective_c库问题
/// 上次更新: 修复Web平台兼容性降级方案中添加kIsWeb保护避免MissingPluginException
// ============================================================
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:hive_ce/hive.dart';
import 'package:hive_flutter/hive_flutter.dart' as flutter_hive;
import 'package:path_provider/path_provider.dart';
@@ -65,15 +66,25 @@ class HiveSafeAccess {
// 方案2: 降级使用Hive.init() + 手动获取路径绕过objective_c依赖
// Hive.initFlutter()内部调用getApplicationDocumentsDirectory()
// 在iOS模拟器上因objective_c库问题会失败这里手动获取路径并降级
// Web端: getApplicationDocumentsDirectory()不可用需kIsWeb保护
try {
Log.i('[HiveSafe] 降级方案: 使用Hive.init()手动指定路径...');
String hivePath;
try {
if (kIsWeb) {
// Web端不需要手动指定路径Hive.initFlutter()应该已经处理了
// 如果走到这里说明initFlutter也失败了Web端无法继续
throw UnsupportedError('Hive init failed on web');
}
final dir = await getApplicationDocumentsDirectory();
hivePath = dir.path;
} catch (_) {
// path_provider也不可用时使用系统临时目录
hivePath = Directory.systemTemp.path;
if (kIsWeb) {
hivePath = '/tmp'; // Web端不会执行到这里但作为安全措施
} else {
// path_provider也不可用时使用系统临时目录
hivePath = Directory.systemTemp.path;
}
}
Hive.init(hivePath);
_instance._hiveInitialized = true;

View File

@@ -255,7 +255,7 @@ class KvStorage {
}
// 优先从 HiveSafeAccess 缓存获取
final cached = HiveSafeAccess.tryGetBox<dynamic>(boxName);
if (cached != null) return cached as Box<dynamic>;
if (cached != null) return cached;
// fallback: 直接访问(兼容旧逻辑)
try {
return Hive.box<dynamic>(boxName);

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 安全存储
/// 创建时间: 2026-04-20
/// 更新时间: 2026-05-22
/// 更新时间: 2026-06-05
/// 作用: flutter_secure_storage 封装,用于敏感数据存储
/// 上次更新: macOS Keychain 兼容性修复,临时使用 shared_preferences 替代
/// 上次更新: Windows/macOS 使用 shared_preferences 替代,避免 MethodChannel 异常
/// ============================================================
import 'package:flutter/foundation.dart';
@@ -28,7 +28,7 @@ class SecureKeys {
///
/// 用于存储敏感数据如 Token、密码等
/// 底层使用 Keychain (iOS) / EncryptedSharedPreferences (Android)。
/// macOS 临时使用 shared_preferences 避免 Keychain 配置问题
/// Windows/macOS 使用 shared_preferences 替代,避免 MethodChannel 异常
class SecureStorage {
SecureStorage._();
@@ -46,14 +46,17 @@ class SecureStorage {
mOptions: _macosOptions,
);
// macOS 临时使用 shared_preferences 替代
// Windows/macOS 使用 shared_preferences 替代
static SharedPreferences? _prefs;
static Future<void> _ensurePrefs() async {
_prefs ??= await SharedPreferences.getInstance();
}
static bool get _isMacOS => defaultTargetPlatform == TargetPlatform.macOS;
/// macOS 和 Windows 使用 SharedPreferences 降级方案
static bool get _useSharedPreferences =>
defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows;
// ============================================================
// 通用读写
@@ -61,7 +64,7 @@ class SecureStorage {
/// 读取
static Future<String?> read(String key) async {
if (_isMacOS) {
if (_useSharedPreferences) {
await _ensurePrefs();
return _prefs!.getString(key);
} else {
@@ -71,7 +74,7 @@ class SecureStorage {
/// 写入
static Future<void> write(String key, String value) async {
if (_isMacOS) {
if (_useSharedPreferences) {
await _ensurePrefs();
await _prefs!.setString(key, value);
} else {
@@ -81,7 +84,7 @@ class SecureStorage {
/// 删除
static Future<void> delete(String key) async {
if (_isMacOS) {
if (_useSharedPreferences) {
await _ensurePrefs();
await _prefs!.remove(key);
} else {
@@ -91,7 +94,7 @@ class SecureStorage {
/// 是否包含
static Future<bool> containsKey(String key) async {
if (_isMacOS) {
if (_useSharedPreferences) {
await _ensurePrefs();
return _prefs!.containsKey(key);
} else {
@@ -101,7 +104,7 @@ class SecureStorage {
/// 清空所有
static Future<void> deleteAll() async {
if (_isMacOS) {
if (_useSharedPreferences) {
await _ensurePrefs();
final keys = _prefs!.getKeys();
for (final key in keys) {

View File

@@ -1,14 +1,15 @@
/// ============================================================
/// 闲言APP — 日志工具
/// 创建时间: 2026-04-20
/// 更新时间: 2026-05-30
/// 更新时间: 2026-06-05
/// 作用: 统一日志封装,支持分级与格式化 + 日志查看器 + 日志导出
/// 上次更新: 新增生产环境日志级别控制release模式下d/i/w不输出仅保留e/f
/// 上次更新: 修复Web平台兼容性添加kIsWeb守卫保护文件操作
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
@@ -267,12 +268,13 @@ class Log {
return value;
}
/// 导出日志到文件
/// 导出日志到文件Web端不可用
static Future<String> exportToFile({
LogLevel level = LogLevel.all,
bool asJson = true,
bool asCsv = false,
}) async {
if (kIsWeb) throw UnsupportedError('日志导出在Web端不可用');
try {
final String content;
final String ext;
@@ -302,8 +304,9 @@ class Log {
}
}
/// 分享日志文件
/// 分享日志文件Web端不可用
static Future<void> shareLogs({LogLevel level = LogLevel.all}) async {
if (kIsWeb) return;
try {
final path = await exportToFile(level: level);
await SharePlus.instance.share(

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 鸿蒙端兼容性增强工具
/// 创建时间: 2026-06-04
/// 更新时间: 2026-06-04
/// 更新时间: 2026-06-05
/// 作用: 解决鸿蒙端的已知兼容性问题
/// 上次更新: 初始版本集中处理OHOS平台特殊逻辑
/// 上次更新: 修复Web平台兼容性Platform.xxx替换为pu.xxxWeb端方法提前返回
/// ============================================================
import 'dart:io';
@@ -76,11 +76,11 @@ class OhosCompatibilityHelper {
static String getEnhancedPlatform() {
if (kIsWeb) return 'web';
if (isOhos) return 'harmonyos'; // 使用标准名称而非ohos
if (Platform.isAndroid) return 'android';
if (Platform.isIOS) return 'ios';
if (Platform.isMacOS) return 'macos';
if (Platform.isWindows) return 'windows';
if (Platform.isLinux) return 'linux';
if (pu.isAndroid) return 'android';
if (pu.isIOS) return 'ios';
if (pu.isMacOS) return 'macos';
if (pu.isWindows) return 'windows';
if (pu.isLinux) return 'linux';
return 'other';
}
@@ -105,6 +105,7 @@ class OhosCompatibilityHelper {
/// 获取图片缓存目录
/// 鸿蒙端path_provider返回值可能不同
static String normalizeCachePath(String path) {
if (kIsWeb) return path;
if (!isOhos || path.isEmpty) return path;
// 确保路径使用正斜杠
@@ -121,6 +122,7 @@ class OhosCompatibilityHelper {
/// 安全读取目录大小
/// 处理权限不足或目录不存在的情况
static Future<int> safeGetDirectorySize(String dirPath) async {
if (kIsWeb) return 0;
try {
final dir = Directory(normalizeCachePath(dirPath));
if (!await dir.exists()) {
@@ -195,6 +197,7 @@ class OhosCompatibilityHelper {
/// OHOS平台兼容的图片保存方法
/// gal插件不支持OHOS使用系统分享作为替代方案
static Future<bool> saveImageToGalleryCompat(Uint8List imageBytes) async {
if (kIsWeb) return false;
if (!isOhos) return false;
try {

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 平台IO原生实现 (Android/iOS/macOS/Windows/Linux/Ohos)
/// 创建时间: 2026-04-25
/// 更新时间: 2026-05-17
/// 更新时间: 2026-06-05
/// 作用: 原生平台使用dart:io获取平台信息支持鸿蒙检测
/// 上次更新: 增加isOhosImpl鸿蒙平台检测Platform.operatingSystem=='ohos'
/// 上次更新: 新增platformVersionImpl封装Platform.version
/// ============================================================
import 'dart:io' show Platform;
@@ -41,3 +41,4 @@ bool get supportsGPU3DImpl =>
Platform.isAndroid || Platform.isIOS || Platform.isMacOS || _isOhos();
bool get supportsWebView3DImpl =>
Platform.isAndroid || Platform.isIOS || Platform.isMacOS || _isOhos();
String get platformVersionImpl => Platform.version;

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 平台IO Web Stub
/// 创建时间: 2026-04-25
/// 更新时间: 2026-05-17
/// 更新时间: 2026-06-05
/// 作用: Web端不使用dart:io提供空实现
/// 上次更新: 增加isOhosImpl=false
/// 上次更新: 新增platformVersionImpl Web端返回空字符串
/// ============================================================
bool get isWebImpl => true;
@@ -20,3 +20,4 @@ String get platformNameImpl => 'Web';
bool get supportsFilesystemImpl => false;
bool get supportsGPU3DImpl => false;
bool get supportsWebView3DImpl => true;
String get platformVersionImpl => '';

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 平台工具类
/// 创建时间: 2026-04-25
/// 更新时间: 2026-05-20
/// 更新时间: 2026-06-05
/// 作用: 封装平台相关操作隔离dart:io/dart:html支持鸿蒙
/// 上次更新: 增加safeGetAppDir/safeGetTempDir安全路径获取(Web端返回null)
/// 上次更新: 新增platformVersion封装Platform.version
/// ============================================================
import 'package:flutter/widgets.dart';
@@ -21,6 +21,7 @@ bool get isLinux => isLinuxImpl;
bool get isMobile => isMobileImpl;
bool get isDesktop => isDesktopImpl;
String get platformName => platformNameImpl;
String get platformVersion => platformVersionImpl;
bool get supportsFilesystem => supportsFilesystemImpl;
bool get supportsGPU3D => supportsGPU3DImpl;
bool get supportsWebView3D => supportsWebView3DImpl;