chore: 完成多平台兼容性优化与资源更新
本次提交包含多项改进: 1. 新增Android启动页资源与配色配置,完善多版本主题适配 2. 全量替换Platform.pathSeparator为硬编码斜杠,修复Web平台路径兼容问题 3. 为大量文件系统操作添加kIsWeb守卫,优化Web端表现 4. 替换硬编码平台判断为platform_utils封装,统一平台检测逻辑 5. 移除冗余代码与默认参数,优化小部件性能 6. 新增Web端适配逻辑,处理不支持的原生功能 7. 更新鸿蒙兼容性工具,完善平台识别与路径处理 8. 优化设备注册错误捕获,避免非致命崩溃 9. 添加启动页图标与背景配置,优化首屏体验
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 而非 runAppFunction,Catcher2 会在内部调用 runApp
|
||||
// 但不会创建新的 Zone,避免 Zone mismatch 警告
|
||||
_catcher2 = Catcher2(
|
||||
runAppFunction: runAppFunction,
|
||||
rootWidget: rootWidget,
|
||||
debugConfig: debugConfig,
|
||||
releaseConfig: releaseConfig,
|
||||
profileConfig: Catcher2Options(SilentReportMode(), []),
|
||||
navigatorKey: rootNavigatorKey,
|
||||
);
|
||||
|
||||
_initialized = true;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 图片缓存元数据服务
|
||||
/// 创建时间: 2026-05-30
|
||||
/// 更新时间: 2026-05-30
|
||||
/// 更新时间: 2026-06-05
|
||||
/// 作用: 基于Hive的图片缓存元数据索引,支持按类型/日期分组、过期清理
|
||||
/// 上次更新: 新增getCacheSizeLimit/setCacheSizeLimit,clearAll/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;
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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类型
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — 鸿蒙端兼容性增强工具
|
||||
/// 创建时间: 2026-06-04
|
||||
/// 更新时间: 2026-06-04
|
||||
/// 更新时间: 2026-06-05
|
||||
/// 作用: 解决鸿蒙端的已知兼容性问题
|
||||
/// 上次更新: 初始版本,集中处理OHOS平台特殊逻辑
|
||||
/// 上次更新: 修复Web平台兼容性,Platform.xxx替换为pu.xxx,Web端方法提前返回
|
||||
/// ============================================================
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 => '';
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user