diff --git a/CHANGELOG.md b/CHANGELOG.md
index 498fdcc9..daed0822 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
***
+<<<<<<< Updated upstream
## [v6.20.17] - 2026-06-07
### 🔍 改进 — Spotlight搜索扩展
@@ -358,11 +359,45 @@
#### 修改文件
- `lib/features/daily_fortune/presentation/daily_fortune_page.dart` — 移除 AnimatedCrossFade,改用条件渲染;移除 .animate();移除 GlobalKey
- `lib/shared/widgets/charts/safe_chart_widget.dart` — deactivate 中增加 setState 强制重建
+=======
+## [v6.20.8] - 2026-06-06
+
+### 🛡️ 后端API返回值类型安全修复
+
+#### 问题
+后端API返回的JSON数值字段可能是 `int`、`double` 或 `String` 类型,使用 `as int?` 强转会在遇到非int类型时抛出 TypeError,导致应用崩溃。
+
+#### 修复规则
+- `json['field'] as int? ?? 0` → `(json['field'] as num?)?.toInt() ?? 0`
+- `data['field'] as int? ?? 0` → `(data['field'] as num?)?.toInt() ?? 0`
+- `data?['field'] as int? ?? 0` → `(data?['field'] as num?)?.toInt() ?? 0`
+- `json['field'] as int?` (无默认值) → `(json['field'] as num?)?.toInt()`
+
+#### 修复范围(30+文件)
+- **auth/models/** — `user_model.dart` (UserModel/UserTitle/UserVerification/UserVip/UserCloudSpace/UserDevice/UserExtra)
+- **auth/services/** — `user_security_service.dart` (code/expireTime/expireSeconds/id字段)
+- **mine/user_center/models/** — `user_center_models.dart` (PublicProfileModel/InteractionRecord/HeatmapEntry/DashboardModel)
+- **mine/user_center/presentation/** — `public_profile_page.dart`, `learning_center_page.dart`, `learning_heatmap.dart`
+- **mine/user_center/providers/** — `coin_provider.dart`, `interaction_provider.dart`, `learning_progress_provider.dart`, `tag_cloud_provider.dart`
+- **mine/user_center/services/** — `user_center_service.dart`, `account_insights_service.dart`
+- **mine/achievement/** — `achievement_models.dart`, `badge_provider.dart`, `achievement_service.dart`, `achievement_page.dart`, `checkin_page.dart`
+- **mine/settings/** — `permission_management_page.dart`
+- **core/network/** — `api_interceptor.dart`
+- **core/services/** — `token_service.dart`, `permission_service.dart`, `settings_export_service.dart`, `feature_flag_service.dart`, `image_cache_metadata_service.dart`, `ip_location_service.dart`
+- **core/services/readlater/** — `readlater_device_sync_service.dart`, `readlater_collab_service.dart`, `readlater_sync_service.dart`, `safe_sharing_receiver.dart`
+- **discover/** — `hot_api_service.dart`, `rss_service.dart`, `tool_search_page.dart`, `tool_list_page.dart`, `china_colors_page.dart`, `ocr_tool_page.dart`, `readlater_folder_service.dart`, `chat_migration_service.dart`, `chat_message.dart`, `hot_item.dart`, `custom_translate_api.dart`, `hanzi_result.dart`, `chat_sentence_card_bubble.dart`
+- **file_transfer/** — `ws_p2p_service.dart`, `transfer_signaling_handler.dart`, `usb_transport_service.dart`, `webrtc_service.dart`, `ws_relay_service.dart`, `ws_relay_resume_handler.dart`, `ws_relay_chunk_assembler.dart`, `tcp_socket_service.dart`, `transfer_api_service.dart`, `transfer_task.dart`, `transfer_message.dart`, `transfer_device.dart`, `signaling_service.dart`, `ip_location_service.dart`, `ip_location_result.dart`, `voice_message_data.dart`, `cloud_cache_record.dart`, `offline_queue_item.dart`, `cloud_cache_service.dart`, `delivery_receipt_service.dart`, `localsend_dto.dart`, `canvas_document.dart`, `stroke.dart`, `qr_pairing_service.dart`, `file_transfer_device_actions.dart`
+- **home/providers/readlater/** — `readlater_provider.dart`
+
+#### 特殊处理
+- `ip_location_result.dart` — 字段名 `num` 与Dart类型名冲突,改用 `_parseIntField()` 辅助方法
+>>>>>>> Stashed changes
***
## [v6.20.7] - 2026-06-06
+<<<<<<< Updated upstream
### 🐛 Bug修复 — 图表disposed全量修复 + 每日心情GlobalKey冲突 + 搜索页返回按钮
#### 修复1:全量替换 DeferredBuilder → SafeChartWidget,彻底解决 RenderChartFadeTransition disposed 报错
@@ -393,11 +428,49 @@
- `lib/features/tool_center/statistics/presentation/widgets/learning_stats_tab.dart` — DeferredBuilder → SafeChartWidget(2处)
- `lib/features/daily_fortune/presentation/daily_fortune_page.dart` — 移除 GlobalKey,改用 visitChildElements 查找 RepaintBoundary
- `lib/features/search/presentation/search_page.dart` — 添加 AdaptiveBackButton
+=======
+### 🌐 UserCenter子组件国际化 & Catcher2弹窗优化
+
+#### 问题1: UserCenter子组件硬编码中文
+- `account_section.dart` — 5处硬编码中文(账户与数据/账户设置/离线模式/缓存管理/调试信息)
+- `profile_header_row.dart` — 12处硬编码中文(审核中/编辑/编辑资料/修改用户名/修改昵称/修改签名/修改头像/用文字点亮生活的每一刻等)
+- `edit_field_bottom_sheet.dart` — 9处硬编码中文(取消/修改xxx/保存/请输入xxx/成功/失败/好的/xxx修改成功/修改失败)
+- `public_profile_page.dart` — 40处硬编码中文(用户主页/返回/用户不存在/重试/匿名用户/积分/签到/文章/收藏/关注/私信/分享主页/屏蔽用户/个人简介/头衔等级/新手/学徒/熟练工/专家/大师/当前积分/活跃数据/签到x次/笔记x篇/点赞x次/评论x条/浏览x次/稍后读x等)
+
+#### 修复1
+- **t_profile.dart** — 新增40个翻译键(accountAndData/editProfile/edit/editBio/save/pleaseInput/modifySuccess/modifyFailed/userProfile/goBack/userNotExist/retry/anonymousUser/articles/follow/followed/theUser/privateMessage/gotIt/shareProfile/blockUser/personalBio/titleLevel/currentPoints/activeData/beginner/apprentice/skilled/expert/master/signInCount/noteCount/likeCount/commentCount/viewCount/readLaterCount/modifyField/pleaseInputField/fieldModifySuccess/fieldModifyFailed/debugInfo/defaultBio)
+- **14种语言文件** — zh_cn/en/zh_tw/ja/ko/fr/de/es/it/pt/ru/ar/hi/bn 均已添加对应翻译
+- **4个组件文件** — 全部替换硬编码中文为 `tp.xxx` 翻译键引用
+
+#### 问题2: Catcher2溢出捕获弹窗优化
+- 错误信息显示字数上限300太短,大部分错误信息被截断
+- 复制确认弹窗显示冗余信息(错误ID)
+- 弹窗中文面向开发者不够通用
+
+#### 修复2
+- **显示字数上限** — 从300提升至2000,完整显示大部分错误信息
+- **复制确认弹窗** — 简化为"已复制到剪贴板",移除错误ID显示
+- **弹窗英文化** — '⚠️ 应用异常'→'App Error'、'📋 复制详情'→'Copy Details'、'忽略'→'Dismiss'、'确认'→'Confirm'、'好的'→'OK'、'时间:'→'Time:'
+
+#### 修改文件
+- `lib/l10n/types/t_profile.dart` — 新增40个翻译键+toMap+fromMap
+- `lib/l10n/languages/zh_cn.dart` — 新增中文翻译
+- `lib/l10n/languages/en.dart` — 新增英文翻译
+- `lib/l10n/languages/zh_tw.dart` — 新增繁体中文翻译
+- `lib/l10n/languages/ja.dart` / `ko.dart` / `fr.dart` / `de.dart` / `es.dart` / `it.dart` / `pt.dart` / `ru.dart` / `ar.dart` / `hi.dart` / `bn.dart` — 新增英文占位翻译
+- `lib/features/mine/user_center/presentation/widgets/account_section.dart` — 5处硬编码→翻译键,新增tp参数
+- `lib/features/mine/user_center/presentation/widgets/profile_header_row.dart` — 12处硬编码→翻译键,新增tp参数
+- `lib/features/mine/user_center/presentation/widgets/edit_field_bottom_sheet.dart` — 9处硬编码→翻译键
+- `lib/features/mine/user_center/presentation/public_profile_page.dart` — 40处硬编码→翻译键,新增tp参数
+- `lib/features/mine/user_center/presentation/user_center_page.dart` — 更新组件调用传递tp参数
+- `lib/core/services/catcher2_config_service.dart` — 字数上限300→2000、弹窗英文化、复制确认简化
+>>>>>>> Stashed changes
***
## [v6.20.6] - 2026-06-06
+<<<<<<< Updated upstream
### 🐛 Bug修复 — 日签卡片布局 + TiltController + Syncfusion图表 + 引导页路由
#### 修复1:日签卡片页面 ParentDataWidget 嵌套 Expanded 错误
@@ -432,11 +505,33 @@
- `lib/features/mine/profile/presentation/about_page.dart` — 使用 onboardingReview
- `lib/features/mine/settings/presentation/general/general_settings_page.dart` — 使用 onboardingReview
- `lib/features/mine/settings/presentation/more_settings_page.dart` — 使用 onboardingReview
+=======
+### 🐛 安卓端长按桌面图标快捷方式闪退修复
+
+#### 问题
+安卓端长按桌面图标,点击弹出的快捷方式(主题个性化/通用设置),App直接卡死闪退。鸿蒙端和iOS端正常。
+
+#### 根因
+1. `quick_actions_android` 插件在冷启动时,`initialize()` 回调可能在 GoRouter 完全初始化之前被调用
+2. 此时 `appRouter.push(route)` 执行时路由系统未就绪,抛出未捕获异常导致闪退
+3. `_handleAction` 方法同步执行 `onAction!(route)`,无延迟和异常保护
+
+#### 修复
+- **`_handleAction` 添加延迟机制** — 使用 `Future.delayed(500ms)` 确保路由系统初始化完成后再执行导航
+- **`_handleAction` 添加异常捕获** — `try-catch` 包裹 `onAction!` 调用,防止未捕获异常闪退
+- **`app.dart` 回调添加 `addPostFrameCallback`** — 安卓端使用 `WidgetsBinding.instance.addPostFrameCallback` 确保首帧渲染完成后再导航
+- **`app.dart` 回调添加异常保护** — 整个回调用 `try-catch` 包裹,双重保险
+
+#### 修改文件
+- `lib/core/services/device/quick_actions_service.dart` — `_handleAction` 延迟+异常捕获
+- `lib/app/app.dart` — `_initQuickActions` 回调 addPostFrameCallback+异常保护
+>>>>>>> Stashed changes
***
## [v6.20.5] - 2026-06-06
+<<<<<<< Updated upstream
### 🐛 Bug修复 — 句子详情GlobalKey重复崩溃 + 进度美化LateInitializationError
#### 修复1:句子详情面板GlobalKey重复导致iOS启动卡死崩溃
@@ -460,11 +555,34 @@
- `lib/features/home/presentation/panels/sentence_detail_content.dart` — 接收 `cardKey` 参数替代全局 Provider
- `lib/features/home/presentation/panels/sentence_detail_actions.dart` — 接收 `cardKey` 参数替代全局 Provider
- `lib/features/progress/presentation/progress_beautify_page.dart` — 修复 `_args` late 初始化错误
+=======
+### 🐛 鸿蒙端多场景卡死修复
+
+#### 问题
+1. **SearchType.validateAll 逐条日志导致鸿蒙端IDE卡死** — 每个不支持的类型单独调用 `Log.w()`,产生大量日志输出(11条重复格式警告),在鸿蒙端debug模式下导致IDE卡死
+2. **MissingPluginException 卡死** — `checkPendingManageStorage` 方法在鸿蒙端没有原生实现,调用时抛出 `MissingPluginException`
+3. **个人中心页面卡死** — `_loadDashboard()` 静默吞掉异常;`_dashboardData?['score'] as int?` 后端返回String时抛TypeError;UserStatsBar中同样存在类型安全问题;debug日志在鸿蒙端加剧卡死
+
+#### 修复
+- **SearchType.validateAll 合并日志输出** — 将逐条 `Log.w()` 改为合并为单条日志输出,避免鸿蒙端debug模式IDE卡死
+- **鸿蒙端跳过数据管理通道初始化** — `_initDataManagementChannel()` 在鸿蒙端直接 return,避免 `MissingPluginException`
+- **_loadDashboard() 异常日志** — `catch (_)` 改为 `catch (e, st)` 并调用 `Log.e()` 记录异常,便于排查
+- **score 类型安全** — `_dashboardData?['score'] as int?` 改为 `(_dashboardData?['score'] as num?)?.toInt()`,兼容后端返回String/int/double
+- **UserStatsBar 类型安全** — 所有 dashboard 数据访问改为 `(data['key'] as num?)?.toInt() ?? defaultValue`,防止类型转换崩溃
+- **删除 UserStatsBar debug 日志** — 移除每次构建时的 `Log.d()` 调用,减少鸿蒙端日志输出压力
+
+#### 修改文件
+- `lib/features/discover/models/search_type.dart` — validateAll 合并日志
+- `lib/app/app.dart` — 鸿蒙端跳过数据管理通道
+- `lib/features/mine/user_center/presentation/user_center_page.dart` — 异常日志 + score类型安全
+- `lib/features/mine/user_center/presentation/widgets/user_stats_bar.dart` — 类型安全 + 删除debug日志
+>>>>>>> Stashed changes
***
## [v6.20.4] - 2026-06-06
+<<<<<<< Updated upstream
### 🐛 Bug修复 — 设备在线数量显示0 + 每日运势GlobalKey崩溃
#### 修复1:设备概览在线数量始终显示0
@@ -484,11 +602,23 @@
- `lib/features/mine/user_center/presentation/devices/device_overview_card.dart` — currentDevice 获取添加兜底
- `lib/features/mine/user_center/presentation/my_devices_page.dart` — 设备分组逻辑添加兜底
- `lib/features/daily_fortune/presentation/daily_fortune_page.dart` — GlobalKey 改为按日期索引的 Map
+=======
+### 🐛 引导页相关Bug修复
+
+#### 修复
+- **鸿蒙端/安卓端通用设置"重新打开引导页"无效** — 鸿蒙端 onboarding 路由未在 OHOS 路由注册表中注册,`appGo` 导航失败。改为鸿蒙端直接使用 `Navigator.push` 推送 `OnboardingPage`,安卓端保持原有路由导航
+- **鸿蒙端杀后台重启后引导页重复弹出** — `OhosAppShell._checkOnboarding()` 未检查 `KvStorage.isReady`,Hive 未初始化完成时 `isFirstLaunch`/`shouldShowOnboarding` 默认返回 true,导致引导页每次都弹出。添加 `isReady` 检查,未就绪时延迟 300ms 重试
+
+#### 修改文件
+- `lib/features/mine/settings/presentation/general/general_settings_page.dart` — `_reopenOnboarding()` 增加鸿蒙端平台判断
+- `lib/core/layout/ohos_app_shell.dart` — `_checkOnboarding()` 添加 `KvStorage.isReady` 检查
+>>>>>>> Stashed changes
***
## [v6.20.3] - 2026-06-06
+<<<<<<< Updated upstream
### 🧹 依赖清理 — 移除8个未使用的三方库
#### 移除的依赖
@@ -515,6 +645,29 @@
- `pubspec.ohos.yaml` — 移除8个未使用依赖(含flutter_blue_plus)
- `pubspec.macos.yaml` — 移除7个未使用依赖
- `iOS_macOS_Developer_Guide.md` — 更新差异对照表和版本日志
+=======
+### 🔧 鸿蒙端快捷方式修复 + 布局溢出弹窗静默处理 + 权限管理修复 + 注册弹窗多语言 + 日志精简
+
+#### 修复
+- **鸿蒙端桌面长按快捷方式不生效** — `module.json5` 中 metadata name 从 `ohos.shortcut.config` 修正为 `ohos.ability.shortcuts`(符合华为官方文档规范)
+- **shortcuts.json 缺少 moduleName** — 两个快捷方式的 wants 中补充 `moduleName: "entry"` 字段,确保鸿蒙系统能正确解析快捷方式目标
+- **冷启动快捷方式不跳转** — `EntryAbility.ets` 的 `configureFlutterEngine` 末尾增加待处理快捷方式检查,冷启动时也能正确通知 Flutter 侧跳转
+- **布局溢出错误弹窗干扰** — Catcher2 的 `_ConsoleLogHandler` 和 `CopyableDialogReportMode` 均增加 overflow/RenderFlex 错误过滤,溢出错误不再弹窗、不打印详情,静默处理
+- **鸿蒙端权限管理页面点击相机无反应/定位卡死** — `isPlatformRelevant` 鸿蒙端过滤不支持的权限(location/storage);`checkStatus()` 和 `requestPermission()` 添加鸿蒙端超时保护(3秒/5秒)
+- **注册页面弹窗多语言** — `_showExperimentalFeatureDialog()` 和 `_showRegisterInfo()` 中7处硬编码中文替换为翻译键,14种语言文件同步更新
+- **日志量精简** — `appLogger` printer 改为简洁模式(关闭颜色/emoji/调用栈);`LogCategory` 默认级别提高(ui/network/router/storage/device/search/provider/onboarding/general 从debug/info提高到warning,chart/haptic/push提高到error)
+
+#### 修改文件
+- `ohos/entry/src/main/module.json5` — metadata name 修正
+- `ohos/entry/src/main/resources/base/profile/shortcuts.json` — 补充 moduleName
+- `ohos/entry/src/main/ets/entryability/EntryAbility.ets` — 冷启动快捷方式通知
+- `lib/core/services/catcher2_config_service.dart` — 布局溢出错误静默处理
+- `lib/core/services/auth/permission_service.dart` — isPlatformRelevant 鸿蒙端过滤 + 超时保护
+- `lib/features/auth/presentation/login_page.dart` — 注册弹窗多语言
+- `lib/l10n/types/t_auth.dart` — 新增7个翻译键
+- `lib/l10n/languages/*.dart` — 14种语言文件同步更新
+- `lib/core/utils/logger.dart` — 日志精简
+>>>>>>> Stashed changes
***
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 51055296..d78d5bf1 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -63,13 +63,6 @@
-
-
-
-
-
-
-
diff --git a/iOS_macOS_Developer_Guide.md b/iOS_macOS_Developer_Guide.md
index f928bb53..64fbef27 100644
--- a/iOS_macOS_Developer_Guide.md
+++ b/iOS_macOS_Developer_Guide.md
@@ -10,9 +10,13 @@
| 日期 | 版本 | 变更内容 |
|---|---|---|
+<<<<<<< Updated upstream
| 2026-06-07 | v10 | 修正 §2.3 dependency_overrides 行数(4→5行/40+→46行);修正 §2.6 补丁引用(§2.8→§2.9);简化 §2.8.1 pro_image_editor 过时回退建议;删除 §5.4 pro_image_editor 本地包条目和 bitsdojo_window 废弃条目;简化 §3.3 pubspec.yaml 处理策略(git stash → 双模板脚本生成);更新 §3.2/§3.5/§6 与双模板机制对齐 |
| 2026-06-06 | v9 | 清理未使用依赖:移除 animations、animate_do、value_layout_builder、flutter_advanced_canvas_editor、flutter_blue_plus、http_cache_file_store、dartx、vector_math;删除差异对照表中 flutter_nfc_kit 过时条目 |
| 2026-06-06 | v8 | 新增 `app_tracking_transparency` 差异对照条目;新增 `nearby_connections` 鸿蒙端本地stub包说明;新增 §2.10 nearby_connections鸿蒙适配说明 |
+=======
+| 2026-06-06 | v8 | 移除 `nearby_connections` 库及P2P功能(本地stub包影响Android构建);更新差异对照表;新增 §2.8.7 app_tracking_transparency说明 |
+>>>>>>> Stashed changes
| 2026-06-02 | v7 | **重大变更**:pubspec.yaml 拆分为双模板(pubspec.ohos.yaml + pubspec.macos.yaml),pubspec.yaml 不再提交到 Git;新增三方库变更通知机制;新增 setup_pubspec.ps1 脚本 |
| 2026-06-02 | v6 | 鸿蒙端 pubspec.yaml 同步 bitsdojo_window → window_manager 迁移;更新 file_picker 本地包版本注释(v8.3.7→v11.0.0-ohos.1);更新 speech_to_text(^7.0.0→^7.4.0)、live_activities(^2.0.0→^2.4.9) 远程版本号;补充 dependency_overrides 中 bitsdojo_window_windows 移除说明 |
| 2026-06-01 | v5 | 新增 §2.6 pub cache 补丁说明;标记 bitsdojo_window 迁移完成;file_picker 升级到 12.x |
@@ -176,7 +180,6 @@ Error: The getter 'ohos' isn't defined for the class 'TargetPlatform'
| mobile_scanner | `path: packages/mobile_scanner` | `^7.1.4` |
| wifi_iot | `path: packages/wifi_iot` | `^0.3.19` |
| nearby_service | `path: packages/nearby_service` | `^0.2.1` |
-| nearby_connections | `path: packages/nearby_connections` (stub) | `^4.1.1` |
| sqflite | `path: packages/sqflite` | `^2.4.1` |
| workmanager | `path: packages/workmanager` | `^0.9.0` |
| flutter_tts | `path: packages/flutter_tts` | `^4.2.0` |
@@ -344,24 +347,13 @@ MacBook Pro 端使用 pub.dev 版本 `^0.9.1`,鸿蒙端的 `ohosName` 参数
home_widget: ^0.9.1
```
-#### 2.8.7 nearby_connections(鸿蒙端本地stub包)
+#### 2.8.7 nearby_connections(已移除)
-`nearby_connections` 仅支持 Android/iOS 平台(Google Nearby Connections API),不支持鸿蒙。
-鸿蒙端使用本地 stub 包 `packages/nearby_connections/`,提供与 4.x API 一致的类型定义和方法签名,
-但所有方法调用抛出 `UnsupportedError`。代码中通过 `isP2pSupported` 守卫,鸿蒙端不会实际调用 P2P 方法。
+`nearby_connections`(Google Nearby Connections API)已从项目中完全移除。
+原因:该库仅支持 Android/iOS,鸿蒙端需要本地 stub 包,但本地包会影响 Android 端构建。
+近场通信功能由 `nearby_service` 统一承担(Android Wi-Fi Direct / iOS MultipeerConnectivity / 鸿蒙)。
-```yaml
-# 鸿蒙端使用本地 stub 包
- nearby_connections:
- path: packages/nearby_connections
-
-# MacBook Pro 端使用远程版本
- nearby_connections: ^4.1.1
-```
-
-> **注意**:鸿蒙端 stub 包的 Dart API 与远程版本完全一致(枚举、类型、方法签名),
-> 编译不会报错。运行时由 `NearbyServiceAdapter.isP2pSupported` 守卫,
-> 鸿蒙端 P2P 功能不可用,仅 `nearby_service` 原生引擎可用。
+> **注意**:移除后,蓝牙 P2P 发现功能不再可用,设备配对通过配对码/扫码/雷达/Wi-Fi Direct 进行。
#### 2.8.8 app_tracking_transparency(两端均使用远程版本)
diff --git a/lib/app/app.dart b/lib/app/app.dart
index ff0f764b..98daf2d9 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 应用根组件
/// 创建时间: 2026-04-20
-/// 更新时间: 2026-06-05
+/// 更新时间: 2026-06-06
/// 作用: MaterialApp.router + Riverpod 主题管理 + GlassTheme + flutter_animate + AppLockOverlay
-/// 上次更新: 初始化AccessibilityService,build时同步系统无障碍状态
+/// 上次更新: 修复安卓端冷启动快捷方式闪退,添加addPostFrameCallback和异常保护
/// ============================================================
import 'dart:async';
@@ -113,16 +113,26 @@ class _XianyanAppState extends ConsumerState
void _initQuickActions() {
QuickActionsService.init(
onActionCallback: (String route) {
- final context = rootNavigatorKey.currentContext;
- if (context == null) {
- Log.w('🚀 [QuickActions] context不可用,延迟导航');
- return;
- }
- if (pu.isOhos) {
- OhosNavBridge.push(context, route);
- } else {
- // 使用push而非go,保留导航栈以便返回
- appRouter.push(route);
+ try {
+ final context = rootNavigatorKey.currentContext;
+ if (context == null) {
+ Log.w('🚀 [QuickActions] context不可用,延迟导航');
+ return;
+ }
+ if (pu.isOhos) {
+ OhosNavBridge.push(context, route);
+ } else {
+ // 安卓端:确保路由系统已就绪再导航,防止冷启动时GoRouter未初始化导致闪退
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ try {
+ appRouter.push(route);
+ } catch (e) {
+ Log.e('🚀 [QuickActions] Android端快捷方式导航失败', e);
+ }
+ });
+ }
+ } catch (e) {
+ Log.e('🚀 [QuickActions] 快捷方式回调异常', e);
}
},
);
@@ -137,7 +147,9 @@ class _XianyanAppState extends ConsumerState
}
void _initDataManagementChannel() {
- if (pu.isWeb || !pu.isOhos) return;
+ if (pu.isWeb) return;
+ // 鸿蒙端暂不支持数据管理通道,跳过初始化避免 MissingPluginException
+ if (pu.isOhos) return;
_dataManagementChannel.setMethodCallHandler((call) async {
if (call.method == 'open_data_management') {
diff --git a/lib/core/layout/ohos_app_shell.dart b/lib/core/layout/ohos_app_shell.dart
index c9cd037c..58c438ed 100644
--- a/lib/core/layout/ohos_app_shell.dart
+++ b/lib/core/layout/ohos_app_shell.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 鸿蒙端专用布局壳
/// 创建时间: 2026-05-18
-/// 更新时间: 2026-05-22
+/// 更新时间: 2026-06-06
/// 作用: 鸿蒙端使用 Scaffold+GlassBottomBar 替代 GoRouter+StatefulShellRoute
-/// 上次更新: 修复引导页在鸿蒙端不显示的问题,initState检查onboarding状态
+/// 上次更新: 修复鸿蒙端杀后台重启时引导页重复弹出的问题(添加KvStorage.isReady检查)
/// ============================================================
///
/// 根因: 鸿蒙端 Flutter 引擎中 MaterialApp.router + 额外包导入 = 白屏
@@ -66,6 +66,17 @@ class _OhosAppShellState extends ConsumerState {
void _checkOnboarding() {
if (!mounted) return;
+
+ // 等待KvStorage初始化完成再检查引导页状态
+ // 避免Hive未初始化时默认值导致引导页重复弹出
+ if (!KvStorage.isReady) {
+ Log.i('🟢 [OHOS] 引导页检查: KvStorage未就绪,延迟检查');
+ Future.delayed(const Duration(milliseconds: 300), () {
+ if (mounted) _checkOnboarding();
+ });
+ return;
+ }
+
final shouldShow =
KvStorage.isFirstLaunch || KvStorage.shouldShowOnboarding;
if (shouldShow) {
diff --git a/lib/core/network/api_interceptor.dart b/lib/core/network/api_interceptor.dart
index 93b34481..078f465c 100644
--- a/lib/core/network/api_interceptor.dart
+++ b/lib/core/network/api_interceptor.dart
@@ -46,7 +46,7 @@ class ApiInterceptor extends Interceptor {
final data = response.data;
if (data is Map && data.containsKey('code')) {
- final code = data['code'] as int?;
+ final code = (data['code'] as num?)?.toInt();
if (code == -1 || code == 401) {
_logger.w('业务层Token过期: code=$code');
_handleAuthExpired();
diff --git a/lib/core/router/route_registry.dart b/lib/core/router/route_registry.dart
index 3ff763d4..ca788c0d 100644
--- a/lib/core/router/route_registry.dart
+++ b/lib/core/router/route_registry.dart
@@ -350,7 +350,8 @@ final List routeRegistry = [
module: RouteModule.user,
page: () => const SourcePage(),
),
- // 注意:onboarding 已在 app_router.dart 中直接定义(位于 ShellRoute 之前,需要 rootNavigatorKey),此处不再重复注册
+ // onboarding 路由已在 app_router.dart 中手动定义(需 parentNavigatorKey + iOS 转场),
+ // 此处不再重复注册,避免 GoRouter duplicate name 断言崩溃
// ============================================================
// Tool module
diff --git a/lib/core/services/auth/permission_service.dart b/lib/core/services/auth/permission_service.dart
index cf5638e3..0b7ddf34 100644
--- a/lib/core/services/auth/permission_service.dart
+++ b/lib/core/services/auth/permission_service.dart
@@ -3,12 +3,14 @@
/// 创建时间: 2026-04-23
/// 更新时间: 2026-06-06
/// 作用: 统一管理应用权限请求,支持相机/相册/通知/位置/蓝牙/附近设备/麦克风/存储/网络/剪贴板/分享权限 + iOS ATT授权
-/// 上次更新: 新增requestTrackingPermission()方法,支持iOS App Tracking Transparency授权
+/// 上次更新: 鸿蒙端permission_handler不支持时引导用户去系统设置+MissingPluginException捕获
/// ============================================================
+import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -247,6 +249,7 @@ enum AppPermission {
/// Android 13+ 不需要 storage 权限(由 photos 替代)
/// tracking 权限仅 iOS 展示
+ /// 鸿蒙端:过滤 permission_handler 不支持或不需要的权限
bool get isPlatformRelevant {
if (this == AppPermission.storage) {
if (!pu.isAndroid) return false;
@@ -256,6 +259,26 @@ enum AppPermission {
if (this == AppPermission.tracking) {
return Platform.isIOS;
}
+ // 鸿蒙端:过滤不支持的权限,防止 permission_handler 挂起或卡死
+ if (pu.isOhos) {
+ return switch (this) {
+ // 鸿蒙端支持的权限
+ AppPermission.camera => true,
+ AppPermission.photos => true,
+ AppPermission.notification => true,
+ AppPermission.microphone => true,
+ AppPermission.nearbyDevices => true,
+ // 鸿蒙端不需要/不支持的权限
+ AppPermission.location => false, // 闲言APP鸿蒙端不需要定位权限
+ AppPermission.storage => false, // 鸿蒙端使用 READ_MEDIA/WRITE_MEDIA 替代
+ AppPermission.tracking => false, // 鸿蒙端不支持tracking权限
+ // 虚拟权限
+ AppPermission.network => true,
+ AppPermission.clipboard => true,
+ AppPermission.share => true,
+ AppPermission.shake => true,
+ };
+ }
return true;
}
@@ -407,6 +430,7 @@ class PermissionService {
}
/// 检查单个权限状态
+ /// 鸿蒙端添加超时保护,防止 permission_handler 挂起
static Future checkStatus(AppPermission perm) async {
if (perm.isVirtual) {
return _checkVirtualStatus(perm);
@@ -416,6 +440,14 @@ class PermissionService {
return _checkTrackingStatus();
}
try {
+ // 鸿蒙端添加超时保护,防止 permission_handler 挂起
+ if (pu.isOhos) {
+ final status = await perm.permission.status.timeout(
+ const Duration(seconds: 3),
+ onTimeout: () => PermissionStatus.denied,
+ );
+ return AppPermissionStatus.fromPermissionStatus(status);
+ }
final status = await perm.permission.status;
return AppPermissionStatus.fromPermissionStatus(status);
} catch (e) {
@@ -485,7 +517,13 @@ class PermissionService {
return _requestTrackingPermission(context);
}
try {
- final status = await perm.permission.status;
+ // 鸿蒙端添加超时保护,防止 permission_handler 挂起
+ final status = pu.isOhos
+ ? await perm.permission.status.timeout(
+ const Duration(seconds: 3),
+ onTimeout: () => PermissionStatus.denied,
+ )
+ : await perm.permission.status;
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
@@ -504,6 +542,33 @@ class PermissionService {
if (!userConfirmed) return false;
}
+ // 鸿蒙端:如果permission_handler不支持,引导用户去系统设置
+ if (pu.isOhos) {
+ try {
+ final result = await perm.permission.request().timeout(
+ const Duration(seconds: 5),
+ onTimeout: () => PermissionStatus.denied,
+ );
+ if (result.isGranted || result.isLimited) {
+ _log.i('✅ ${perm.name} 权限已授予');
+ return true;
+ }
+ // 鸿蒙端:请求后仍是denied/notDetermined,引导用户去系统设置
+ _log.w('⚠️ 鸿蒙端 ${perm.name} 权限未授予(${result.name}),引导用户前往系统设置');
+ if (context.mounted) {
+ _showSettingsDialog(context, perm);
+ }
+ return false;
+ } on MissingPluginException {
+ // permission_handler 未实现该权限,引导去系统设置
+ _log.w('⚠️ 鸿蒙端 ${perm.name} permission_handler通道未实现,引导去系统设置');
+ if (context.mounted) {
+ _showSettingsDialog(context, perm);
+ }
+ return false;
+ }
+ }
+
final result = await perm.permission.request();
if (result.isGranted) {
_log.i('✅ ${perm.name} 权限已授予');
@@ -618,7 +683,8 @@ class PermissionService {
if (!Platform.isIOS) return true;
try {
// 先检查当前状态
- final currentStatus = await AppTrackingTransparency.trackingAuthorizationStatus;
+ final currentStatus =
+ await AppTrackingTransparency.trackingAuthorizationStatus;
if (currentStatus == TrackingStatus.authorized) {
_log.i('ATT已授权,无需重复请求');
return true;
@@ -842,7 +908,7 @@ class PermissionUsageStat {
factory PermissionUsageStat.fromJson(Map json) {
return PermissionUsageStat(
- count: json['count'] as int? ?? 0,
+ count: (json['count'] as num?)?.toInt() ?? 0,
lastUsed: json['lastUsed'] as String? ?? '',
firstUsed: json['firstUsed'] as String?,
);
diff --git a/lib/core/services/auth/token_service.dart b/lib/core/services/auth/token_service.dart
index 20a858ec..45cb5779 100644
--- a/lib/core/services/auth/token_service.dart
+++ b/lib/core/services/auth/token_service.dart
@@ -48,11 +48,11 @@ class TokenService {
'/api/token/check',
);
final data = response.data as Map;
- final code = data['code'] as int? ?? 0;
+ final code = (data['code'] as num?)?.toInt() ?? 0;
if (code == 1) {
final tokenData = data['data'] as Map? ?? {};
- final expiresIn = tokenData['expires_in'] as int? ?? 0;
+ final expiresIn = (tokenData['expires_in'] as num?)?.toInt() ?? 0;
return TokenCheckResult(
valid: true,
expiresIn: expiresIn,
@@ -76,7 +76,7 @@ class TokenService {
'/api/token/refresh',
);
final data = response.data as Map;
- final code = data['code'] as int? ?? 0;
+ final code = (data['code'] as num?)?.toInt() ?? 0;
if (code == 1) {
final tokenData = data['data'] as Map?;
diff --git a/lib/core/services/catcher2_config_service.dart b/lib/core/services/catcher2_config_service.dart
index 186ca37d..227c33b3 100644
--- a/lib/core/services/catcher2_config_service.dart
+++ b/lib/core/services/catcher2_config_service.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — Catcher2 配置服务
/// 创建时间: 2026-05-21
-/// 更新时间: 2026-06-05
+/// 更新时间: 2026-06-06
/// 作用: 统一管理 Catcher2 异常捕获开关与动态配置更新
-/// 上次更新: 修复Zone mismatch,不使用runAppFunction,手动调用runApp
+/// 上次更新: 错误弹窗中文化改英文、复制显示字数上限提升至2000
/// ============================================================
import 'package:catcher_2/catcher_2.dart';
@@ -101,6 +101,13 @@ class CopyableDialogReportMode extends ReportMode {
}
Future _showDialog(Report report, BuildContext? context) async {
+ // 布局溢出错误不弹窗
+ final errorStr = report.error.toString();
+ if (errorStr.contains('overflowed') || errorStr.contains('RenderFlex')) {
+ onActionConfirmed(report); // 自动确认,不弹窗
+ return;
+ }
+
await Future.delayed(Duration.zero);
if (context != null && context.mounted) {
showCupertinoDialog(
@@ -157,7 +164,12 @@ class _CopyableErrorDialog extends StatelessWidget {
}
void _copyToClipboard(BuildContext context) {
- Clipboard.setData(ClipboardData(text: _errorText));
+ // 限制剪贴板内容不超过400字,避免超长文本占用剪贴板
+ final text = _errorText;
+ final clipboardText = text.length > 400
+ ? '${text.substring(0, 400)}...'
+ : text;
+ Clipboard.setData(ClipboardData(text: clipboardText));
HapticFeedback.lightImpact();
showCupertinoDialog(
context: context,
@@ -165,15 +177,12 @@ class _CopyableErrorDialog extends StatelessWidget {
builder: (ctx) => CupertinoAlertDialog(
content: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
- child: Text(
- '✅ 已复制到剪贴板\n标识: $_errorId',
- style: const TextStyle(fontSize: 14),
- ),
+ child: Text('已复制到剪贴板', style: const TextStyle(fontSize: 14)),
),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.of(ctx).pop(),
- child: const Text('好的'),
+ child: const Text('OK'),
),
],
),
@@ -183,14 +192,14 @@ class _CopyableErrorDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final errorStr = report.error.toString();
- final displayError = errorStr.length > 300
- ? '${errorStr.substring(0, 300)}...'
+ final displayError = errorStr.length > 2000
+ ? '${errorStr.substring(0, 2000)}...'
: errorStr;
return CupertinoAlertDialog(
title: Column(
children: [
- const Text('⚠️ 应用异常'),
+ const Text('App Error'),
const SizedBox(height: 4),
Text(
_errorId,
@@ -220,7 +229,7 @@ class _CopyableErrorDialog extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
- '时间: ${report.dateTime.toIso8601String()}',
+ 'Time: ${report.dateTime.toIso8601String()}',
style: TextStyle(
fontSize: 11,
color: CupertinoColors.secondaryLabel.resolveFrom(context),
@@ -231,17 +240,17 @@ class _CopyableErrorDialog extends StatelessWidget {
actions: [
CupertinoDialogAction(
onPressed: () => _copyToClipboard(context),
- child: const Text('📋 复制详情'),
+ child: const Text('Copy Details'),
),
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: onReject,
- child: const Text('忽略'),
+ child: const Text('Dismiss'),
),
CupertinoDialogAction(
isDefaultAction: true,
onPressed: onAccept,
- child: const Text('确认'),
+ child: const Text('Confirm'),
),
],
);
diff --git a/lib/core/services/data/image_cache_metadata_service.dart b/lib/core/services/data/image_cache_metadata_service.dart
index 0bf8f051..a0a3e336 100644
--- a/lib/core/services/data/image_cache_metadata_service.dart
+++ b/lib/core/services/data/image_cache_metadata_service.dart
@@ -377,7 +377,7 @@ class ImageCacheMetadataService {
static int getCacheSizeLimit() {
final box = _safeBox();
if (box == null) return 100;
- return box.get('_cache_size_limit') as int? ?? 100;
+ return (box.get('_cache_size_limit') as num?)?.toInt() ?? 100;
}
static Future setCacheSizeLimit(int limitMB) async {
diff --git a/lib/core/services/data/settings_export_service.dart b/lib/core/services/data/settings_export_service.dart
index 9ee1f06d..187f161e 100644
--- a/lib/core/services/data/settings_export_service.dart
+++ b/lib/core/services/data/settings_export_service.dart
@@ -85,7 +85,7 @@ class SettingsExportService {
return false;
}
- final version = decoded['export_version'] as int?;
+ final version = (decoded['export_version'] as num?)?.toInt();
if (version == null) {
Log.w('设置导入: 缺少版本号');
return false;
diff --git a/lib/core/services/device/calendar_service.dart b/lib/core/services/device/calendar_service.dart
index 6c2adcc7..8987b732 100644
--- a/lib/core/services/device/calendar_service.dart
+++ b/lib/core/services/device/calendar_service.dart
@@ -1,19 +1,22 @@
/// ============================================================
/// 闲言APP — 日历同步服务
/// 创建时间: 2026-05-29
-/// 更新时间: 2026-05-29
+/// 更新时间: 2026-06-06
/// 作用: 跨平台日历事件同步(Android/iOS/HarmonyOS/macOS/Windows)
-/// 上次更新: 修复analyze错误(unnecessary_import/prefer_final_locals/errorMessages→errors)
+/// 上次更新: 鸿蒙端MethodChannel添加超时保护+MissingPluginException捕获+平台判断早期返回
/// ============================================================
import 'dart:io';
+import 'dart:async';
+
import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:xianyan/core/utils/logger.dart';
+import 'package:xianyan/core/utils/platform/platform_utils.dart' as pu;
/// 日历事件数据模型
class CalendarEvent {
@@ -65,8 +68,10 @@ class CalendarService {
// ============================================================
/// 请求日历权限,成功后自动创建/获取日历
+ /// 鸿蒙端:日历通道可能未实现,通过_requestPermissionOhos()处理
Future requestPermission() async {
try {
+ // 鸿蒙端:直接走鸿蒙通道,通道未实现时返回false
if (_isOhos()) {
return await _requestPermissionOhos();
}
@@ -92,14 +97,20 @@ class CalendarService {
}
}
- /// HarmonyOS权限请求
+ /// HarmonyOS权限请求 — 添加超时保护+MissingPluginException捕获
Future _requestPermissionOhos() async {
try {
- final result = await _ohosChannel.invokeMethod('requestPermission');
+ final result = await _ohosChannel
+ .invokeMethod('requestPermission')
+ .timeout(const Duration(seconds: 5), onTimeout: () => false);
_isAvailable = result ?? false;
return _isAvailable;
+ } on MissingPluginException {
+ Log.w('CalendarService: 鸿蒙端日历权限通道未实现');
+ _isAvailable = false;
+ return false;
} catch (e) {
- Log.e('CalendarService: OHOS日历权限请求失败', e);
+ Log.e('CalendarService: 鸿蒙端请求日历权限失败', e);
_isAvailable = false;
return false;
}
@@ -136,7 +147,9 @@ class CalendarService {
// ============================================================
/// 添加日历事件
+ /// 鸿蒙端:通过_addEventOhos()桥接,通道未实现时返回false
Future addEvent(CalendarEvent event) async {
+ // 鸿蒙端:直接走鸿蒙通道
if (_isOhos()) {
return _addEventOhos(event);
}
@@ -178,20 +191,25 @@ class CalendarService {
}
}
- /// HarmonyOS添加事件
+ /// HarmonyOS添加事件 — 添加超时保护+MissingPluginException捕获
Future _addEventOhos(CalendarEvent event) async {
try {
- final result = await _ohosChannel.invokeMethod('addEvent', {
- 'title': event.title,
- 'description': event.description,
- 'startTime': event.start.millisecondsSinceEpoch,
- 'endTime': event.end.millisecondsSinceEpoch,
- 'reminderMinutes': event.reminderMinutesBefore,
- 'location': event.location,
- });
+ final result = await _ohosChannel
+ .invokeMethod('addEvent', {
+ 'title': event.title,
+ 'description': event.description,
+ 'startTime': event.start.millisecondsSinceEpoch,
+ 'endTime': event.end.millisecondsSinceEpoch,
+ 'reminderMinutes': event.reminderMinutesBefore,
+ 'location': event.location,
+ })
+ .timeout(const Duration(seconds: 5), onTimeout: () => false);
return result ?? false;
+ } on MissingPluginException {
+ Log.w('CalendarService: 鸿蒙端日历事件通道未实现');
+ return false;
} catch (e) {
- Log.e('CalendarService: OHOS日历桥接失败', e);
+ Log.e('CalendarService: 鸿蒙端添加日历事件失败', e);
return false;
}
}
@@ -213,11 +231,5 @@ class CalendarService {
// ============================================================
/// 是否为HarmonyOS平台
- bool _isOhos() {
- try {
- return Platform.operatingSystem == 'ohos';
- } catch (_) {
- return false;
- }
- }
+ bool _isOhos() => pu.isOhos;
}
diff --git a/lib/core/services/device/quick_actions_service.dart b/lib/core/services/device/quick_actions_service.dart
index 6830685b..1c62be4e 100644
--- a/lib/core/services/device/quick_actions_service.dart
+++ b/lib/core/services/device/quick_actions_service.dart
@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 快捷操作服务
// 创建时间: 2026-05-31
-/// 更新时间: 2026-06-05
+/// 更新时间: 2026-06-06
/// 作用: 管理主屏幕快捷操作(Quick Actions / App Shortcuts)
-/// 上次更新: 新增PlatformCapabilities能力查询补充quickActions判断
+/// 上次更新: 修复安卓端冷启动快捷方式闪退,添加延迟和异常捕获
// 跨平台: iOS(UIApplicationShortcutItems) + Android(App Shortcuts)
// + 鸿蒙(module.json5 shortcuts + MethodChannel)
// ============================================================
@@ -119,7 +119,14 @@ class QuickActionsService {
if (route == null) return;
if (onAction != null) {
- onAction!(route);
+ // 延迟执行,确保路由系统已完全初始化(冷启动时回调可能早于GoRouter就绪)
+ Future.delayed(const Duration(milliseconds: 500), () {
+ try {
+ onAction!(route);
+ } catch (e) {
+ Log.e('🚀 [QuickActions] 快捷方式导航失败', e);
+ }
+ });
_pendingAction = null;
return;
}
diff --git a/lib/core/services/feature/feature_flag_service.dart b/lib/core/services/feature/feature_flag_service.dart
index 842289ba..579f8dff 100644
--- a/lib/core/services/feature/feature_flag_service.dart
+++ b/lib/core/services/feature/feature_flag_service.dart
@@ -398,7 +398,7 @@ class RemoteFeatureFlagService {
.map((f) => FeatureFlagItem.fromJson(f as Map))
.toList();
- final timestamp = box.get(_cacheTimestampKey) as int?;
+ final timestamp = (box.get(_cacheTimestampKey) as num?)?.toInt();
if (timestamp != null) {
final cacheTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
if (DateTime.now().difference(cacheTime) > _cacheExpiry) {
diff --git a/lib/core/services/network/ip_location_service.dart b/lib/core/services/network/ip_location_service.dart
index ba664c0e..86211433 100644
--- a/lib/core/services/network/ip_location_service.dart
+++ b/lib/core/services/network/ip_location_service.dart
@@ -125,7 +125,7 @@ class IpLocationService {
final body = response.data;
if (body == null) return null;
- final code = body['code'] as int? ?? 0;
+ final code = (body['code'] as num?)?.toInt() ?? 0;
if (code != 1) {
Log.w('IpLocation: API返回错误 ${body['msg']}');
return null;
@@ -154,7 +154,7 @@ class IpLocationService {
if (raw == null || raw.isEmpty) return null;
final map = jsonDecode(raw) as Map;
- final cachedTime = map['_cache_time'] as int? ?? 0;
+ final cachedTime = (map['_cache_time'] as num?)?.toInt() ?? 0;
final now = DateTime.now().millisecondsSinceEpoch;
if (now - cachedTime > _cacheExpiry.inMilliseconds) {
diff --git a/lib/core/services/readlater/readlater_collab_service.dart b/lib/core/services/readlater/readlater_collab_service.dart
index 6f4f12a7..742c808f 100644
--- a/lib/core/services/readlater/readlater_collab_service.dart
+++ b/lib/core/services/readlater/readlater_collab_service.dart
@@ -52,7 +52,7 @@ class SharedReadlaterList {
?.map((e) => e.toString())
.toList() ??
[],
- messageCount: json['message_count'] as int? ?? 0,
+ messageCount: (json['message_count'] as num?)?.toInt() ?? 0,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'] as String)
: DateTime.now(),
diff --git a/lib/core/services/readlater/readlater_device_sync_service.dart b/lib/core/services/readlater/readlater_device_sync_service.dart
index 30a725b2..e43746a7 100644
--- a/lib/core/services/readlater/readlater_device_sync_service.dart
+++ b/lib/core/services/readlater/readlater_device_sync_service.dart
@@ -234,8 +234,8 @@ class ReadlaterDeviceSyncService {
source: source,
feedType: meta['feedType'] as String?,
feedName: meta['feedName'] as String?,
- likeCount: meta['likeCount'] as int?,
- views: meta['views'] as int?,
+ likeCount: (meta['likeCount'] as num?)?.toInt(),
+ views: (meta['views'] as num?)?.toInt(),
sentenceId: meta['sentenceId'] as String?,
);
} else if (msgType == 'link') {
@@ -253,7 +253,7 @@ class ReadlaterDeviceSyncService {
fileName: meta['fileName'] as String? ?? '未知文档',
filePath: meta['filePath'] as String? ?? '',
fileType: meta['mimeType'] as String? ?? 'application/octet-stream',
- fileSize: meta['fileSize'] as int? ?? 0,
+ fileSize: (meta['fileSize'] as num?)?.toInt() ?? 0,
);
} else {
await ChatMessageService.sendText(
diff --git a/lib/core/services/readlater/readlater_sync_service.dart b/lib/core/services/readlater/readlater_sync_service.dart
index e15e412d..3f3f784c 100644
--- a/lib/core/services/readlater/readlater_sync_service.dart
+++ b/lib/core/services/readlater/readlater_sync_service.dart
@@ -155,7 +155,7 @@ class ReadlaterSyncService {
DateTime.now().toIso8601String(),
),
isRead: metaMap['isRead'] as bool? ?? false,
- readCount: metaMap['readCount'] as int? ?? 0,
+ readCount: (metaMap['readCount'] as num?)?.toInt() ?? 0,
meta: metaMap['meta'] as Map?,
ext: metaMap['ext'] as Map?,
attachments:
diff --git a/lib/core/services/readlater/safe_sharing_receiver.dart b/lib/core/services/readlater/safe_sharing_receiver.dart
index 2fa19622..164ae186 100644
--- a/lib/core/services/readlater/safe_sharing_receiver.dart
+++ b/lib/core/services/readlater/safe_sharing_receiver.dart
@@ -64,7 +64,7 @@ class SafeSharedMediaFile {
path: rawPath as String,
type: mediaType,
thumbnail: json['thumbnail'] as String?,
- duration: json['duration'] as int?,
+ duration: (json['duration'] as num?)?.toInt(),
mimeType: json['mimeType'] as String?,
message: json['message'] as String?,
uri: json['uri'] as String?,
diff --git a/lib/core/storage/kv_storage.dart b/lib/core/storage/kv_storage.dart
index 50cd75d2..3b9095ce 100644
--- a/lib/core/storage/kv_storage.dart
+++ b/lib/core/storage/kv_storage.dart
@@ -534,8 +534,12 @@ class KvStorage {
static Future markOnboardingCompleted() =>
setBool(StorageKeys.onboardingCompleted, true);
- static bool get shouldShowOnboarding =>
- getBool(StorageKeys.showOnboarding) ?? true;
+ /// 是否应显示引导页
+ /// KvStorage未就绪时返回false(安全默认值:不显示引导),避免引导页重复弹出
+ static bool get shouldShowOnboarding {
+ if (!isReady) return false;
+ return getBool(StorageKeys.showOnboarding) ?? true;
+ }
static Future setShowOnboarding(bool value) =>
setBool(StorageKeys.showOnboarding, value);
diff --git a/lib/core/utils/logger.dart b/lib/core/utils/logger.dart
index a1b21f74..566a539a 100644
--- a/lib/core/utils/logger.dart
+++ b/lib/core/utils/logger.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 日志工具
/// 创建时间: 2026-04-20
-/// 更新时间: 2026-06-05
+/// 更新时间: 2026-06-06
/// 作用: 统一日志封装,支持分级与格式化 + 日志查看器 + 日志导出 + 按模块分类控制
-/// 上次更新: 添加 LogCategory 结构化日志,按模块控制日志级别
+/// 上次更新: 优化日志输出:简洁Printer模式 + 提高高频分类默认日志级别
/// ============================================================
import 'dart:convert';
@@ -15,8 +15,16 @@ import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
/// 全局日志器(release模式仅输出error级别,避免敏感信息泄露)
+/// 简洁模式:减少调用栈、关闭颜色和emoji、缩短行宽
final appLogger = Logger(
- printer: PrettyPrinter(),
+ printer: PrettyPrinter(
+ methodCount: 0, // 不打印调用栈
+ errorMethodCount: 5, // 错误时只打印5层调用栈
+ lineLength: 80, // 缩短行宽
+ // colors: true, // 颜色(减少ANSI转义序列)
+ printEmojis: false, // 关闭emoji(减少输出)
+ dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart, // 只显示时间
+ ),
level: _isDebugMode ? Level.debug : Level.error,
);
@@ -43,6 +51,7 @@ enum LogLevel {
/// 级别中文名
final String label;
+
/// 级别数值(越大越严格)
final int value;
}
@@ -53,28 +62,29 @@ enum LogLevel {
/// 作用: 支持按模块设置日志级别,调试时可只关注特定模块
/// ============================================================
enum LogCategory {
- ui('UI', LogLevel.debug), // UI渲染、布局
- network('网络', LogLevel.info), // API请求、响应
- router('路由', LogLevel.info), // 页面导航
- storage('存储', LogLevel.info), // 数据库、KV存储
- device('设备', LogLevel.info), // 设备信息、传感器
- auth('认证', LogLevel.info), // 登录、鉴权
- transfer('传输', LogLevel.info), // 文件传输
- search('搜索', LogLevel.info), // 搜索功能
- chart('图表', LogLevel.warning), // Syncfusion图表(减少日志噪音)
- haptic('触觉', LogLevel.warning), // 震动反馈(高频调用)
- provider('状态', LogLevel.info), // Riverpod Provider
- service('服务', LogLevel.info), // 后台服务
- sync('同步', LogLevel.info), // 数据同步
- offline('离线', LogLevel.info), // 离线模式
- onboarding('引导', LogLevel.info), // 引导页
- push('推送', LogLevel.warning), // 推送通知
- general('通用', LogLevel.debug); // 其他
+ ui('UI', LogLevel.warning), // UI渲染、布局(减少高频UI日志)
+ network('网络', LogLevel.warning), // API请求、响应(减少网络日志噪音)
+ router('路由', LogLevel.warning), // 页面导航(减少路由日志)
+ storage('存储', LogLevel.warning), // 数据库、KV存储(减少存储日志)
+ device('设备', LogLevel.warning), // 设备信息、传感器(减少设备日志)
+ auth('认证', LogLevel.info), // 登录、鉴权(保留info级别,关注认证流程)
+ transfer('传输', LogLevel.info), // 文件传输(保留info级别)
+ search('搜索', LogLevel.warning), // 搜索功能(减少搜索日志)
+ chart('图表', LogLevel.error), // Syncfusion图表(仅输出错误)
+ haptic('触觉', LogLevel.error), // 震动反馈(仅输出错误,极高频调用)
+ provider('状态', LogLevel.warning), // Riverpod Provider(减少状态日志)
+ service('服务', LogLevel.info), // 后台服务(保留info级别)
+ sync('同步', LogLevel.info), // 数据同步(保留info级别)
+ offline('离线', LogLevel.info), // 离线模式(保留info级别)
+ onboarding('引导', LogLevel.warning), // 引导页(减少引导日志)
+ push('推送', LogLevel.error), // 推送通知(仅输出错误)
+ general('通用', LogLevel.warning); // 其他(减少通用日志)
const LogCategory(this.label, this.defaultLevel);
/// 分类中文名
final String label;
+
/// 默认日志级别
final LogLevel defaultLevel;
@@ -111,17 +121,19 @@ enum LogCategory {
/// 获取所有自定义级别(用于持久化)
static Map exportCustomLevels() {
- return _customLevels.map(
- (k, v) => MapEntry(k.name, v.name),
- );
+ return _customLevels.map((k, v) => MapEntry(k.name, v.name));
}
/// 从持久化数据恢复自定义级别
static void importCustomLevels(Map data) {
_customLevels.clear();
for (final entry in data.entries) {
- final cat = LogCategory.values.where((c) => c.name == entry.key).firstOrNull;
- final level = LogLevel.values.where((l) => l.name == entry.value).firstOrNull;
+ final cat = LogCategory.values
+ .where((c) => c.name == entry.key)
+ .firstOrNull;
+ final level = LogLevel.values
+ .where((l) => l.name == entry.value)
+ .firstOrNull;
if (cat != null && level != null) {
_customLevels[cat] = level;
}
@@ -143,6 +155,7 @@ class LogEntry {
final LogLevel level;
final String message;
final DateTime time;
+
/// 日志分类
final LogCategory category;
final dynamic error;
@@ -212,18 +225,32 @@ class Log {
/// 调试日志(release模式下不输出)
/// [category] 日志分类,默认为 LogCategory.general
- static void d(dynamic message, [dynamic error, StackTrace? stackTrace, LogCategory? category]) {
+ static void d(
+ dynamic message, [
+ dynamic error,
+ StackTrace? stackTrace,
+ LogCategory? category,
+ ]) {
if (!_isDebugMode) return;
final cat = category ?? LogCategory.general;
if (!_shouldLog(LogLevel.debug, cat)) return;
- appLogger.d(_formatMessage(message, cat), error: error, stackTrace: stackTrace);
+ appLogger.d(
+ _formatMessage(message, cat),
+ error: error,
+ stackTrace: stackTrace,
+ );
_addEntry(LogLevel.debug, message, error, stackTrace, cat);
}
/// 信息日志(release模式下不输出)
/// 添加节流机制:同一消息5秒内不重复输出到控制台
/// [category] 日志分类,默认为 LogCategory.general
- static void i(dynamic message, [dynamic error, StackTrace? stackTrace, LogCategory? category]) {
+ static void i(
+ dynamic message, [
+ dynamic error,
+ StackTrace? stackTrace,
+ LogCategory? category,
+ ]) {
if (!_isDebugMode) return;
final cat = category ?? LogCategory.general;
if (!_shouldLog(LogLevel.info, cat)) {
@@ -236,7 +263,11 @@ class Log {
final now = DateTime.now();
final lastTime = _infoThrottleMap[msgStr];
if (lastTime == null || now.difference(lastTime) > _infoThrottleDuration) {
- appLogger.i(_formatMessage(message, cat), error: error, stackTrace: stackTrace);
+ appLogger.i(
+ _formatMessage(message, cat),
+ error: error,
+ stackTrace: stackTrace,
+ );
_infoThrottleMap[msgStr] = now;
// 清理过期的节流记录,避免内存泄漏
_infoThrottleMap.removeWhere(
@@ -248,30 +279,57 @@ class Log {
/// 警告日志(release模式下不输出)
/// [category] 日志分类,默认为 LogCategory.general
- static void w(dynamic message, [dynamic error, StackTrace? stackTrace, LogCategory? category]) {
+ static void w(
+ dynamic message, [
+ dynamic error,
+ StackTrace? stackTrace,
+ LogCategory? category,
+ ]) {
if (!_isDebugMode) return;
final cat = category ?? LogCategory.general;
if (!_shouldLog(LogLevel.warning, cat)) {
_addEntry(LogLevel.warning, message, error, stackTrace, cat);
return;
}
- appLogger.w(_formatMessage(message, cat), error: error, stackTrace: stackTrace);
+ appLogger.w(
+ _formatMessage(message, cat),
+ error: error,
+ stackTrace: stackTrace,
+ );
_addEntry(LogLevel.warning, message, error, stackTrace, cat);
}
/// 错误日志(始终输出,错误必须记录)
/// [category] 日志分类,默认为 LogCategory.general
- static void e(dynamic message, [dynamic error, StackTrace? stackTrace, LogCategory? category]) {
+ static void e(
+ dynamic message, [
+ dynamic error,
+ StackTrace? stackTrace,
+ LogCategory? category,
+ ]) {
final cat = category ?? LogCategory.general;
- appLogger.e(_formatMessage(message, cat), error: error, stackTrace: stackTrace);
+ appLogger.e(
+ _formatMessage(message, cat),
+ error: error,
+ stackTrace: stackTrace,
+ );
_addEntry(LogLevel.error, message, error, stackTrace, cat);
}
/// 致命错误日志(始终输出,错误必须记录)
/// [category] 日志分类,默认为 LogCategory.general
- static void f(dynamic message, [dynamic error, StackTrace? stackTrace, LogCategory? category]) {
+ static void f(
+ dynamic message, [
+ dynamic error,
+ StackTrace? stackTrace,
+ LogCategory? category,
+ ]) {
final cat = category ?? LogCategory.general;
- appLogger.f(_formatMessage(message, cat), error: error, stackTrace: stackTrace);
+ appLogger.f(
+ _formatMessage(message, cat),
+ error: error,
+ stackTrace: stackTrace,
+ );
_addEntry(LogLevel.error, message, error, stackTrace, cat);
}
diff --git a/lib/features/auth/models/user_model.dart b/lib/features/auth/models/user_model.dart
index c690634b..18a0daf5 100644
--- a/lib/features/auth/models/user_model.dart
+++ b/lib/features/auth/models/user_model.dart
@@ -164,24 +164,24 @@ class UserModel {
factory UserModel.fromJson(Map json) {
return UserModel(
- id: json['id'] as int? ?? 0,
+ id: (json['id'] as num?)?.toInt() ?? 0,
username: json['username'] as String? ?? '',
nickname: json['nickname'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
avatarUrl: json['avatar_url'] as String? ?? '',
email: json['email'] as String? ?? '',
mobile: json['mobile'] as String? ?? '',
- score: json['score'] as int? ?? 0,
- level: json['level'] as int? ?? 1,
- exp: json['exp'] as int? ?? 0,
- expToNext: json['exp_to_next'] as int? ?? 0,
+ score: (json['score'] as num?)?.toInt() ?? 0,
+ level: (json['level'] as num?)?.toInt() ?? 1,
+ exp: (json['exp'] as num?)?.toInt() ?? 0,
+ expToNext: (json['exp_to_next'] as num?)?.toInt() ?? 0,
expProgress: (json['exp_progress'] as num?)?.toDouble() ?? 0.0,
money: json['money']?.toString() ?? '0.00',
- titleId: json['title_id'] as int? ?? 1,
- signinDays: json['signin_days'] as int? ?? 0,
+ titleId: (json['title_id'] as num?)?.toInt() ?? 1,
+ signinDays: (json['signin_days'] as num?)?.toInt() ?? 0,
lastSigninDate: json['last_signin_date'] as String?,
- noteLimit: json['note_limit'] as int? ?? 50,
- articleCount: json['article_count'] as int? ?? 0,
+ noteLimit: (json['note_limit'] as num?)?.toInt() ?? 50,
+ articleCount: (json['article_count'] as num?)?.toInt() ?? 0,
bio: json['bio'] as String? ?? '',
token: json['token'] as String?,
title: json['title'] != null
@@ -356,8 +356,8 @@ class UserVerification {
factory UserVerification.fromJson(Map json) {
return UserVerification(
- email: json['email'] as int? ?? 0,
- mobile: json['mobile'] as int? ?? 0,
+ email: (json['email'] as num?)?.toInt() ?? 0,
+ mobile: (json['mobile'] as num?)?.toInt() ?? 0,
);
}
}
@@ -430,9 +430,9 @@ class UserCloudSpace {
factory UserCloudSpace.fromJson(Map json) {
return UserCloudSpace(
- total: json['total'] as int? ?? 0,
- used: json['used'] as int? ?? 0,
- free: json['free'] as int? ?? 0,
+ total: (json['total'] as num?)?.toInt() ?? 0,
+ used: (json['used'] as num?)?.toInt() ?? 0,
+ free: (json['free'] as num?)?.toInt() ?? 0,
totalHuman: json['total_human'] as String? ?? '',
usedHuman: json['used_human'] as String? ?? '',
usagePercent: (json['usage_percent'] as num?)?.toDouble() ?? 0.0,
@@ -514,7 +514,7 @@ class UserDevice {
factory UserDevice.fromJson(Map json) {
return UserDevice(
- id: json['id'] as int? ?? 0,
+ id: (json['id'] as num?)?.toInt() ?? 0,
deviceName: json['device_name'] as String? ?? '',
deviceModel: json['device_model'] as String? ?? '',
platform: json['platform'] as String? ?? '',
@@ -522,12 +522,12 @@ class UserDevice {
ip: json['ip'] as String? ?? '',
ipCity: json['ip_city'] as String? ?? '',
ipRange: json['ip_range'] as String? ?? '',
- lastActiveTime: json['last_active_time'] as int? ?? 0,
- isOnline: json['is_online'] as int? ?? 0,
- createtime: json['createtime'] as int? ?? 0,
+ lastActiveTime: (json['last_active_time'] as num?)?.toInt() ?? 0,
+ isOnline: (json['is_online'] as num?)?.toInt() ?? 0,
+ createtime: (json['createtime'] as num?)?.toInt() ?? 0,
lastActiveText: json['last_active_text'] as String? ?? '',
createtimeText: json['createtime_text'] as String? ?? '',
- isActiveRecently: json['is_active_recently'] as int? ?? 0,
+ isActiveRecently: (json['is_active_recently'] as num?)?.toInt() ?? 0,
);
}
@@ -591,7 +591,7 @@ class UserExtra {
factory UserExtra.fromJson(Map json) {
return UserExtra(
money: json['money']?.toString() ?? '0.00',
- noteLimit: json['note_limit'] as int? ?? 50,
+ noteLimit: (json['note_limit'] as num?)?.toInt() ?? 50,
verification: json['verification'] != null
? UserVerification.fromJson(
json['verification'] as Map,
diff --git a/lib/features/auth/presentation/login_page.dart b/lib/features/auth/presentation/login_page.dart
index 23c9b674..6af6db08 100644
--- a/lib/features/auth/presentation/login_page.dart
+++ b/lib/features/auth/presentation/login_page.dart
@@ -820,7 +820,7 @@ class _LoginPageState extends ConsumerState
),
const SizedBox(height: AppSpacing.xs),
Text(
- '用户批次标志:${RegisterConfig.batchFlag}',
+ t.auth.userBatchFlag.replaceAll('{flag}', RegisterConfig.batchFlag),
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
fontFamily: 'monospace',
@@ -828,14 +828,14 @@ class _LoginPageState extends ConsumerState
),
const SizedBox(height: AppSpacing.xs),
Text(
- '开放时间:${RegisterConfig.openPeriod}',
+ t.auth.openPeriod.replaceAll('{period}', RegisterConfig.openPeriod),
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
),
),
const SizedBox(height: AppSpacing.xs),
Text(
- '到期后关闭通道(${RegisterConfig.expireYear})',
+ t.auth.expireNotice.replaceAll('{year}', RegisterConfig.expireYear),
style: AppTypography.caption1.copyWith(color: ext.textHint),
),
const SizedBox(height: AppSpacing.sm),
@@ -873,6 +873,7 @@ class _LoginPageState extends ConsumerState
if (!mounted) return;
final ext = AppTheme.ext(context);
+ final t = ref.read(translationsProvider);
showCupertinoDialog(
context: context,
@@ -882,12 +883,10 @@ class _LoginPageState extends ConsumerState
children: [
Icon(Icons.science, size: 20, color: ext.accent),
const SizedBox(width: 8),
- const Text('实验中的功能'),
+ Text(t.auth.experimentalFeature),
],
),
- content: const Text(
- '闲言账号为实验中的功能,注册登录对用户使用影响不大,且不推荐用户注册登录。大部分功能无需登录即可使用。',
- ),
+ content: Text(t.auth.experimentalFeatureDesc),
actions: [
// 不再提示 — 存储偏好后继续注册流程
CupertinoDialogAction(
@@ -899,7 +898,7 @@ class _LoginPageState extends ConsumerState
if (ctx.mounted) Navigator.pop(ctx);
if (mounted) setState(() => _isRegisterMode = true);
},
- child: Text('不再提示', style: TextStyle(color: ext.textSecondary)),
+ child: Text(t.auth.dontShowAgain, style: TextStyle(color: ext.textSecondary)),
),
// 查看实验中的功能 — 继续注册流程并导航
CupertinoDialogAction(
@@ -909,13 +908,13 @@ class _LoginPageState extends ConsumerState
setState(() => _isRegisterMode = true);
context.appPush('/settings/experimental-features');
},
- child: Text('查看实验中的功能', style: TextStyle(color: ext.accent)),
+ child: Text(t.auth.viewExperimentalFeatures, style: TextStyle(color: ext.accent)),
),
// 取消 — 不进入注册模式
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: () => Navigator.pop(ctx),
- child: const Text('取消'),
+ child: Text(t.common.cancel),
),
],
),
diff --git a/lib/features/auth/services/user_security_service.dart b/lib/features/auth/services/user_security_service.dart
index 74ae3e35..d0c622ff 100644
--- a/lib/features/auth/services/user_security_service.dart
+++ b/lib/features/auth/services/user_security_service.dart
@@ -587,8 +587,8 @@ class UserSecurityService {
final data = apiResp.data ?? {};
return QrcodeGenerateResult(
code: data['code'] as String? ?? '',
- expireTime: data['expire_time'] as int? ?? 0,
- expireSeconds: data['expire_seconds'] as int? ?? 300,
+ expireTime: (data['expire_time'] as num?)?.toInt() ?? 0,
+ expireSeconds: (data['expire_seconds'] as num?)?.toInt() ?? 300,
qrcodeUrl: data['qrcode_url'] as String? ?? '',
);
} on DioException catch (e) {
@@ -633,7 +633,7 @@ class UserSecurityService {
queryParameters: {'code': code},
);
final respData = response.data as Map;
- final respCode = respData['code'] as int? ?? 0;
+ final respCode = (respData['code'] as num?)?.toInt() ?? 0;
final data = respData['data'] as Map? ?? {};
final status = data['status'] as String? ?? 'pending';
final message = data['message'] as String? ?? '';
@@ -1032,7 +1032,7 @@ class UserSecurityService {
try {
final data = e.response!.data as Map;
return ApiException(
- code: data['code'] as int? ?? e.response?.statusCode ?? -5,
+ code: (data['code'] as num?)?.toInt() ?? e.response?.statusCode ?? -5,
message: data['msg'] as String? ?? '请求失败',
);
} catch (_) {}
@@ -1104,7 +1104,7 @@ class SecQuestionItem {
factory SecQuestionItem.fromJson(Map json) {
return SecQuestionItem(
- id: json['id'] as int? ?? 0,
+ id: (json['id'] as num?)?.toInt() ?? 0,
question: json['question'] as String? ?? '',
);
}
diff --git a/lib/features/discover/models/chat_message.dart b/lib/features/discover/models/chat_message.dart
index 48631987..eac8a43c 100644
--- a/lib/features/discover/models/chat_message.dart
+++ b/lib/features/discover/models/chat_message.dart
@@ -1,4 +1,4 @@
-// ============================================================
+// ============================================================
// 闲言APP — 会话消息模型
// 创建时间: 2026-04-30
// 更新时间: 2026-05-15
@@ -296,7 +296,7 @@ class ChatMessage {
? DateTime.parse(json['timestamp'] as String)
: DateTime.now(),
isRead: json['isRead'] as bool? ?? false,
- readCount: json['readCount'] as int? ?? 0,
+ readCount: (json['readCount'] as num?)?.toInt() ?? 0,
meta: json['meta'] as Map?,
ext: json['ext'] as Map?,
attachments:
diff --git a/lib/features/discover/models/custom_translate_api.dart b/lib/features/discover/models/custom_translate_api.dart
index defb0e49..bccdbe06 100644
--- a/lib/features/discover/models/custom_translate_api.dart
+++ b/lib/features/discover/models/custom_translate_api.dart
@@ -119,9 +119,9 @@ class CustomTranslateApi {
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
url: json['url'] as String? ?? '',
- method: CustomApiMethod.values[json['method'] as int? ?? 0],
+ method: CustomApiMethod.values[(json['method'] as num?)?.toInt() ?? 0],
responseFormat:
- CustomApiResponseFormat.values[json['responseFormat'] as int? ?? 0],
+ CustomApiResponseFormat.values[(json['responseFormat'] as num?)?.toInt() ?? 0],
textParam: json['textParam'] as String? ?? 'text',
sourceParam: json['sourceParam'] as String? ?? 'source',
targetParam: json['targetParam'] as String? ?? 'target',
diff --git a/lib/features/discover/models/hanzi_result.dart b/lib/features/discover/models/hanzi_result.dart
index 69203584..acce8ba8 100644
--- a/lib/features/discover/models/hanzi_result.dart
+++ b/lib/features/discover/models/hanzi_result.dart
@@ -69,8 +69,8 @@ class HanziResult {
type: json['type'] as String? ?? '',
query: json['query'] as String? ?? '',
data: json['data'],
- total: json['total'] as int? ?? 0,
- page: json['page'] as int? ?? 1,
+ total: (json['total'] as num?)?.toInt() ?? 0,
+ page: (json['page'] as num?)?.toInt() ?? 1,
);
}
@@ -137,8 +137,8 @@ class HanziQueryRecord {
type: json['type'] as String? ?? '',
query: json['query'] as String? ?? '',
timestamp: DateTime.fromMillisecondsSinceEpoch(
- json['timestamp'] as int? ?? 0,
+ (json['timestamp'] as num?)?.toInt() ?? 0,
),
- resultCount: json['resultCount'] as int? ?? 0,
+ resultCount: (json['resultCount'] as num?)?.toInt() ?? 0,
);
}
diff --git a/lib/features/discover/models/hot_item.dart b/lib/features/discover/models/hot_item.dart
index e6c5148b..54431dea 100644
--- a/lib/features/discover/models/hot_item.dart
+++ b/lib/features/discover/models/hot_item.dart
@@ -79,6 +79,6 @@ class HotItem {
title: json['title'] as String? ?? '',
hotNum: json['num']?.toString() ?? '',
url: json['url'] as String? ?? '',
- rank: json['rank'] as int? ?? 0,
+ rank: (json['rank'] as num?)?.toInt() ?? 0,
);
}
diff --git a/lib/features/discover/models/search_type.dart b/lib/features/discover/models/search_type.dart
index adfd58f8..0f09ed9a 100644
--- a/lib/features/discover/models/search_type.dart
+++ b/lib/features/discover/models/search_type.dart
@@ -1,11 +1,9 @@
/// ============================================================
/// 闲言APP — 搜索类型枚举
/// 创建时间: 2026-05-30
-/// 更新时间: 2026-06-05
+/// 更新时间: 2026-06-06
/// 作用: 替代搜索类型字符串硬编码,提供类型安全约束 + 后端类型校验
-/// 上次更新: changshi改用hanziSearch(value='changshi'); 新增cs/searchAll类型;
-/// 新增hitokoto/efs/composition/wine/article/chengyu/hanzi/cidian/prescription/
-/// word/abbr/jiufang/lunyu/hdnj/jgj/mz/zz/zuozhuan/sj/sgz/sbbf/warring/wlyh/bot
+/// 上次更新: validateAll合并日志输出,避免鸿蒙端debug模式逐条日志导致IDE卡死
/// ============================================================
import 'package:xianyan/core/utils/logger.dart';
@@ -139,10 +137,9 @@ enum SearchType {
}
if (results.isNotEmpty) {
- Log.w('SearchType 校验发现 ${results.length} 个问题:');
- for (final r in results) {
- Log.w(' - ${r.searchType.name}: ${r.issue}');
- }
+ // 合并输出所有校验问题,避免逐条日志导致鸿蒙端IDE卡死
+ final issues = results.map((r) => '${r.searchType.name}: ${r.issue}').join('\n ');
+ Log.w('SearchType 校验发现 ${results.length} 个问题:\n $issues');
} else {
Log.i('SearchType 校验通过,所有类型与后端一致 ✅');
}
diff --git a/lib/features/discover/presentation/pages/tool/china_colors_page.dart b/lib/features/discover/presentation/pages/tool/china_colors_page.dart
index 0b60a4aa..72d33173 100644
--- a/lib/features/discover/presentation/pages/tool/china_colors_page.dart
+++ b/lib/features/discover/presentation/pages/tool/china_colors_page.dart
@@ -177,7 +177,7 @@ class _ChinaColorsPageState extends ConsumerState {
name: map['name']?.toString() ?? '',
hex: map['hex']?.toString() ?? '#000000',
colors: colors,
- id: map['id'] as int? ?? 0,
+ id: (map['id'] as num?)?.toInt() ?? 0,
);
}).toList();
diff --git a/lib/features/discover/presentation/pages/tool/ocr_tool_page.dart b/lib/features/discover/presentation/pages/tool/ocr_tool_page.dart
index a2d18189..3c681936 100644
--- a/lib/features/discover/presentation/pages/tool/ocr_tool_page.dart
+++ b/lib/features/discover/presentation/pages/tool/ocr_tool_page.dart
@@ -1,4 +1,4 @@
-/// ============================================================
+/// ============================================================
/// 闲言APP — OCR图片识别工具页
/// 创建时间: 2026-04-28
/// 更新时间: 2026-04-28
@@ -331,7 +331,7 @@ class _OcrToolPageState extends ConsumerState {
try {
final data = await ToolApiService.ocrRecognize(imageFile);
- final code = data['code'] as int? ?? 0;
+ final code = (data['code'] as num?)?.toInt() ?? 0;
if (code == 1) {
final text = data['data']?.toString() ?? '';
setState(() {
diff --git a/lib/features/discover/presentation/pages/tool_list_page.dart b/lib/features/discover/presentation/pages/tool_list_page.dart
index c26e03a5..8ed19a23 100644
--- a/lib/features/discover/presentation/pages/tool_list_page.dart
+++ b/lib/features/discover/presentation/pages/tool_list_page.dart
@@ -172,7 +172,7 @@ class _ToolListPageState extends ConsumerState {
resultData['data'] as List? ??
[];
_items = list.map((e) => e as Map).toList();
- _total = resultData['total'] as int? ?? _items.length;
+ _total = (resultData['total'] as num?)?.toInt() ?? _items.length;
}
if (_tabs == null) {
final catData = await ToolApiService.hitokotoCategories();
@@ -206,7 +206,7 @@ class _ToolListPageState extends ConsumerState {
..._items,
...list.map((e) => e as Map).toList(),
];
- _total = resultData['total'] as int? ?? _items.length;
+ _total = (resultData['total'] as num?)?.toInt() ?? _items.length;
case 'jiufang':
data = await ToolApiService.jiufangSearch(
@@ -221,7 +221,7 @@ class _ToolListPageState extends ConsumerState {
..._items,
...list.map((e) => e as Map).toList(),
];
- _total = resultData['total'] as int? ?? _items.length;
+ _total = (resultData['total'] as num?)?.toInt() ?? _items.length;
_buildJiufangGroups();
case 'daily_recommend':
@@ -282,7 +282,7 @@ class _ToolListPageState extends ConsumerState {
.map((e) => e as Map)
.toList(),
];
- _total = searchallResult['total'] as int? ?? _items.length;
+ _total = (searchallResult['total'] as num?)?.toInt() ?? _items.length;
default:
if (_searchQuery.isEmpty) {
@@ -304,7 +304,7 @@ class _ToolListPageState extends ConsumerState {
..._items,
...list.map((e) => e as Map).toList(),
];
- _total = resultData['total'] as int? ?? _items.length;
+ _total = (resultData['total'] as num?)?.toInt() ?? _items.length;
}
setState(() {
diff --git a/lib/features/discover/presentation/pages/tool_search_page.dart b/lib/features/discover/presentation/pages/tool_search_page.dart
index 1c5f4ac6..3ddd17ec 100644
--- a/lib/features/discover/presentation/pages/tool_search_page.dart
+++ b/lib/features/discover/presentation/pages/tool_search_page.dart
@@ -116,7 +116,7 @@ class _ToolSearchPageState extends ConsumerState {
data = await ToolApiService.hanziZi(query);
}
- final code = data['code'] as int? ?? 0;
+ final code = (data['code'] as num?)?.toInt() ?? 0;
if (code != 1) {
setState(() {
_isLoading = false;
diff --git a/lib/features/discover/presentation/widgets/chat_bubble/chat_sentence_card_bubble.dart b/lib/features/discover/presentation/widgets/chat_bubble/chat_sentence_card_bubble.dart
index ba2ba6c6..b83e3be5 100644
--- a/lib/features/discover/presentation/widgets/chat_bubble/chat_sentence_card_bubble.dart
+++ b/lib/features/discover/presentation/widgets/chat_bubble/chat_sentence_card_bubble.dart
@@ -55,9 +55,9 @@ class ChatSentenceCardBubble extends StatelessWidget {
final meta = message.meta ?? {};
final feedType = meta['feedType'] as String? ?? 'hot';
final feedName = meta['feedName'] as String? ?? '';
- final likeCount = meta['likeCount'] as int? ?? 0;
- final commentCount = meta['commentCount'] as int? ?? 0;
- final favoriteCount = meta['favoriteCount'] as int? ?? 0;
+ final likeCount = (meta['likeCount'] as num?)?.toInt() ?? 0;
+ final commentCount = (meta['commentCount'] as num?)?.toInt() ?? 0;
+ final favoriteCount = (meta['favoriteCount'] as num?)?.toInt() ?? 0;
final colors = _gradientColors(feedType);
return GestureDetector(
diff --git a/lib/features/discover/services/chat_migration_service.dart b/lib/features/discover/services/chat_migration_service.dart
index e2c679ed..31289e58 100644
--- a/lib/features/discover/services/chat_migration_service.dart
+++ b/lib/features/discover/services/chat_migration_service.dart
@@ -1,4 +1,4 @@
-/// ============================================================
+/// ============================================================
/// 闲言APP — 聊天数据迁移服务
/// 创建时间: 2026-05-08
/// 更新时间: 2026-05-08
@@ -83,7 +83,7 @@ class ChatMigrationService {
final isRead = msgJson['readCount'] != null
? (msgJson['isRead'] as bool? ?? false)
: (msgJson['isRead'] as bool? ?? false);
- final readCount = msgJson['readCount'] as int? ?? 0;
+ final readCount = (msgJson['readCount'] as num?)?.toInt() ?? 0;
final timestampStr = msgJson['timestamp'] as String?;
final timestamp = timestampStr != null
? DateTime.tryParse(timestampStr) ?? DateTime.now()
diff --git a/lib/features/discover/services/hot_api_service.dart b/lib/features/discover/services/hot_api_service.dart
index 2e663e84..d46794f8 100644
--- a/lib/features/discover/services/hot_api_service.dart
+++ b/lib/features/discover/services/hot_api_service.dart
@@ -1,4 +1,4 @@
-/// ============================================================
+/// ============================================================
/// 闲言APP — 热搜榜API服务层
/// 创建时间: 2026-04-28
/// 更新时间: 2026-04-28
@@ -65,7 +65,7 @@ class HotApiService {
queryParameters: {'type': type},
);
final data = response.data as Map;
- return (data['code'] as int?) == 1;
+ return (data['code'] as num?)?.toInt() == 1;
} catch (e) {
Log.e('刷新热搜失败: $type', e);
return false;
diff --git a/lib/features/discover/services/readlater_folder_service.dart b/lib/features/discover/services/readlater_folder_service.dart
index bd2494d9..577cf120 100644
--- a/lib/features/discover/services/readlater_folder_service.dart
+++ b/lib/features/discover/services/readlater_folder_service.dart
@@ -1,4 +1,4 @@
-/// ============================================================
+/// ============================================================
/// 闲言APP — 稍后读文件夹管理服务
/// 创建时间: 2026-05-15
/// 更新时间: 2026-05-15
@@ -48,7 +48,7 @@ class ReadlaterFolder {
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
emoji: json['emoji'] as String? ?? '📁',
- count: json['count'] as int? ?? 0,
+ count: (json['count'] as num?)?.toInt() ?? 0,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: DateTime.now(),
diff --git a/lib/features/discover/services/rss_service.dart b/lib/features/discover/services/rss_service.dart
index 673e3cca..7341a90b 100644
--- a/lib/features/discover/services/rss_service.dart
+++ b/lib/features/discover/services/rss_service.dart
@@ -98,7 +98,7 @@ class RssSubscription {
orElse: () => RssCategory.general,
),
lastUpdated: RssService.parseDateTime(map['lastUpdated'] as String?),
- unreadCount: map['unreadCount'] as int? ?? 0,
+ unreadCount: (map['unreadCount'] as num?)?.toInt() ?? 0,
addedAt: RssService.parseDateTime(map['addedAt'] as String?),
);
}
diff --git a/lib/features/file_transfer/collaboration/canvas/models/canvas_document.dart b/lib/features/file_transfer/collaboration/canvas/models/canvas_document.dart
index 5eb64774..e1200e01 100644
--- a/lib/features/file_transfer/collaboration/canvas/models/canvas_document.dart
+++ b/lib/features/file_transfer/collaboration/canvas/models/canvas_document.dart
@@ -61,15 +61,15 @@ class CanvasDocument {
?.map((s) => Stroke.fromJson(s as Map))
.toList() ??
[],
- lamportClock: json['lamportClock'] as int? ?? 0,
+ lamportClock: (json['lamportClock'] as num?)?.toInt() ?? 0,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: DateTime.now(),
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: DateTime.now(),
- width: json['width'] as int? ?? 1920,
- height: json['height'] as int? ?? 1080,
+ width: (json['width'] as num?)?.toInt() ?? 1920,
+ height: (json['height'] as num?)?.toInt() ?? 1080,
);
}
diff --git a/lib/features/file_transfer/collaboration/canvas/models/stroke.dart b/lib/features/file_transfer/collaboration/canvas/models/stroke.dart
index 9ee5d578..941c6013 100644
--- a/lib/features/file_transfer/collaboration/canvas/models/stroke.dart
+++ b/lib/features/file_transfer/collaboration/canvas/models/stroke.dart
@@ -81,7 +81,7 @@ class Stroke {
))
.toList() ??
[],
- lamportClock: json['lamportClock'] as int? ?? 0,
+ lamportClock: (json['lamportClock'] as num?)?.toInt() ?? 0,
type: StrokeType.fromId(json['type'] as String? ?? 'pen'),
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
diff --git a/lib/features/file_transfer/models/cloud_cache_record.dart b/lib/features/file_transfer/models/cloud_cache_record.dart
index cb18912e..df4e35ec 100644
--- a/lib/features/file_transfer/models/cloud_cache_record.dart
+++ b/lib/features/file_transfer/models/cloud_cache_record.dart
@@ -180,7 +180,7 @@ class CloudCacheRecord {
return CloudCacheRecord(
id: json['id'] as String? ?? '',
fileName: json['fileName'] as String? ?? '',
- fileSize: json['fileSize'] as int? ?? 0,
+ fileSize: (json['fileSize'] as num?)?.toInt() ?? 0,
mimeType: json['mimeType'] as String? ?? 'application/octet-stream',
localPath: json['localPath'] as String?,
cloudUrl: json['cloudUrl'] as String?,
diff --git a/lib/features/file_transfer/models/ip_location_result.dart b/lib/features/file_transfer/models/ip_location_result.dart
index 4001b7bf..b8dfa8e9 100644
--- a/lib/features/file_transfer/models/ip_location_result.dart
+++ b/lib/features/file_transfer/models/ip_location_result.dart
@@ -37,7 +37,7 @@ class IpLocationResult {
domain: json['domain'] as String? ?? '',
city: json['city'] as String? ?? '',
fw: json['fw'] as String?,
- num: json['num'] as int? ?? 0,
+ num: _parseIntField(json['num']),
queryTime: DateTime.now(),
);
}
@@ -70,6 +70,14 @@ class IpLocationResult {
);
}
+ /// 安全解析int字段,兼容后端返回int/double/String
+ static int _parseIntField(dynamic value) {
+ if (value is int) return value;
+ if (value is double) return value.toInt();
+ if (value is String) return int.tryParse(value) ?? 0;
+ return 0;
+ }
+
@override
String toString() => 'IpLocationResult(ip: $displayIp, city: $displayCity)';
}
diff --git a/lib/features/file_transfer/models/localsend_dto.dart b/lib/features/file_transfer/models/localsend_dto.dart
index c027c40e..8ea7ad4f 100644
--- a/lib/features/file_transfer/models/localsend_dto.dart
+++ b/lib/features/file_transfer/models/localsend_dto.dart
@@ -54,7 +54,7 @@ class MulticastDto {
version: json['version'] as String?,
deviceModel: json['deviceModel'] as String?,
deviceType: json['deviceType'] as String?,
- port: json['port'] as int?,
+ port: (json['port'] as num?)?.toInt(),
protocol: json['protocol'] as String?,
download: json['download'] as bool?,
announce: json['announce'] as bool?,
@@ -105,7 +105,7 @@ class InfoDto {
version: json['version'] as String?,
deviceModel: json['deviceModel'] as String?,
deviceType: json['deviceType'] as String?,
- port: json['port'] as int?,
+ port: (json['port'] as num?)?.toInt(),
protocol: json['protocol'] as String?,
download: json['download'] as bool?,
);
@@ -139,7 +139,7 @@ class FileDto {
return FileDto(
id: json['id'] as String? ?? '',
fileName: json['fileName'] as String? ?? '',
- size: json['size'] as int? ?? 0,
+ size: (json['size'] as num?)?.toInt() ?? 0,
sha256: json['sha256'] as String?,
preview: json['preview'] as String?,
);
diff --git a/lib/features/file_transfer/models/offline_queue_item.dart b/lib/features/file_transfer/models/offline_queue_item.dart
index c29aaaff..e11e13bc 100644
--- a/lib/features/file_transfer/models/offline_queue_item.dart
+++ b/lib/features/file_transfer/models/offline_queue_item.dart
@@ -171,8 +171,8 @@ class OfflineQueueItem {
content: json['content'] as String?,
filePath: json['filePath'] as String?,
fileName: json['fileName'] as String?,
- fileSize: json['fileSize'] as int?,
- retryCount: json['retryCount'] as int? ?? 0,
+ fileSize: (json['fileSize'] as num?)?.toInt(),
+ retryCount: (json['retryCount'] as num?)?.toInt() ?? 0,
errorMessage: json['errorMessage'] as String?,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
diff --git a/lib/features/file_transfer/models/transfer_device.dart b/lib/features/file_transfer/models/transfer_device.dart
index f6b6f701..56ed704e 100644
--- a/lib/features/file_transfer/models/transfer_device.dart
+++ b/lib/features/file_transfer/models/transfer_device.dart
@@ -155,7 +155,7 @@ class TransferDevice {
deviceModel: json['deviceModel'] as String?,
deviceType: DeviceType.fromId(json['deviceType'] as String? ?? 'mobile'),
ip: json['ip'] as String?,
- port: json['port'] as int? ?? 53317,
+ port: (json['port'] as num?)?.toInt() ?? 53317,
pairingMethod: PairingMethod.fromId(
json['pairingMethod'] as String? ?? 'lan',
),
diff --git a/lib/features/file_transfer/models/transfer_enums.dart b/lib/features/file_transfer/models/transfer_enums.dart
index 6bc9576b..ceab55e5 100644
--- a/lib/features/file_transfer/models/transfer_enums.dart
+++ b/lib/features/file_transfer/models/transfer_enums.dart
@@ -14,8 +14,7 @@ enum PairingMethod {
usb('usb', 'USB有线', '🔌'),
hotspot('hotspot', 'WiFi热点', '📶'),
wifiDirect('wifi_direct', 'Wi-Fi直连', '📶'),
- harmonyNearby('harmony_nearby', '鸿蒙Nearby', '📡'),
- nearbyP2p('nearby_p2p', '附近P2P', '📱');
+ harmonyNearby('harmony_nearby', '鸿蒙Nearby', '📡');
const PairingMethod(this.id, this.label, this.emoji);
final String id;
diff --git a/lib/features/file_transfer/models/transfer_message.dart b/lib/features/file_transfer/models/transfer_message.dart
index d47c5f28..fd971612 100644
--- a/lib/features/file_transfer/models/transfer_message.dart
+++ b/lib/features/file_transfer/models/transfer_message.dart
@@ -266,7 +266,7 @@ class TransferMessage {
peerDeviceId: json['peerDeviceId'] as String?,
transferTaskId: json['transferTaskId'] as String?,
fileName: json['fileName'] as String?,
- fileSize: json['fileSize'] as int?,
+ fileSize: (json['fileSize'] as num?)?.toInt(),
mimeType: json['mimeType'] as String?,
thumbnailPath: json['thumbnailPath'] as String?,
filePath: json['filePath'] as String?,
@@ -285,7 +285,7 @@ class TransferMessage {
readAt: json['readAt'] != null
? DateTime.parse(json['readAt'] as String)
: null,
- voiceDuration: json['voiceDuration'] as int?,
+ voiceDuration: (json['voiceDuration'] as num?)?.toInt(),
voiceWaveform: (json['voiceWaveform'] as List?)
?.map((e) => (e as num).toDouble())
.toList(),
@@ -363,7 +363,7 @@ class PairingInfo {
alias: json['alias'] as String? ?? '未知设备',
method: PairingMethod.fromId(json['method'] as String? ?? 'lan'),
ip: json['ip'] as String?,
- port: json['port'] as int? ?? 53317,
+ port: (json['port'] as num?)?.toInt() ?? 53317,
fingerprint: json['fingerprint'] as String?,
publicKey: json['publicKey'] as String?,
isTrusted: json['isTrusted'] as bool? ?? false,
diff --git a/lib/features/file_transfer/models/transfer_task.dart b/lib/features/file_transfer/models/transfer_task.dart
index 07dcb604..d081f55f 100644
--- a/lib/features/file_transfer/models/transfer_task.dart
+++ b/lib/features/file_transfer/models/transfer_task.dart
@@ -269,8 +269,8 @@ class TransferTask {
),
status: TransferTaskStatus.fromId(json['status'] as String? ?? 'waiting'),
fileName: json['fileName'] as String? ?? 'unknown',
- fileSize: json['fileSize'] as int? ?? 0,
- transferredBytes: json['transferredBytes'] as int? ?? 0,
+ fileSize: (json['fileSize'] as num?)?.toInt() ?? 0,
+ transferredBytes: (json['transferredBytes'] as num?)?.toInt() ?? 0,
speed: (json['speed'] as num?)?.toDouble() ?? 0.0,
mimeType: json['mimeType'] as String?,
filePath: json['filePath'] as String?,
@@ -285,13 +285,13 @@ class TransferTask {
fileSha256: json['fileSha256'] as String?,
hashVerified: json['hashVerified'] as bool?,
fileId: json['fileId'] as String?,
- chunkSize: json['chunkSize'] as int? ?? 65536,
- totalChunks: json['totalChunks'] as int?,
+ chunkSize: (json['chunkSize'] as num?)?.toInt() ?? 65536,
+ totalChunks: (json['totalChunks'] as num?)?.toInt(),
receivedChunks: (json['receivedChunks'] as List?)
?.map((e) => e as int)
.toSet(),
- retryCount: json['retryCount'] as int? ?? 0,
- maxRetries: json['maxRetries'] as int? ?? 3,
+ retryCount: (json['retryCount'] as num?)?.toInt() ?? 0,
+ maxRetries: (json['maxRetries'] as num?)?.toInt() ?? 3,
isResumable: json['isResumable'] as bool? ?? false,
pausedAt: json['pausedAt'] != null
? DateTime.parse(json['pausedAt'] as String)
@@ -336,7 +336,7 @@ class TransferFileInfo {
return TransferFileInfo(
id: json['id'] as String?,
name: json['fileName'] as String? ?? 'unknown',
- size: json['size'] as int? ?? 0,
+ size: (json['size'] as num?)?.toInt() ?? 0,
mimeType: json['mimeType'] as String? ?? 'application/octet-stream',
path: json['path'] as String?,
sha256: json['sha256'] as String?,
diff --git a/lib/features/file_transfer/models/voice_message_data.dart b/lib/features/file_transfer/models/voice_message_data.dart
index d6692180..7a6270cc 100644
--- a/lib/features/file_transfer/models/voice_message_data.dart
+++ b/lib/features/file_transfer/models/voice_message_data.dart
@@ -84,12 +84,12 @@ class VoiceMessageData {
messageId: json['messageId'] as String? ?? '',
sessionId: json['sessionId'] as String? ?? '',
filePath: json['filePath'] as String? ?? '',
- duration: json['duration'] as int? ?? 0,
+ duration: (json['duration'] as num?)?.toInt() ?? 0,
waveform: (json['waveform'] as List?)
?.map((e) => (e as num).toDouble())
.toList() ??
[],
- sampleRate: json['sampleRate'] as int? ?? 44100,
+ sampleRate: (json['sampleRate'] as num?)?.toInt() ?? 44100,
codec: json['codec'] as String? ?? 'aac',
isRemote: json['isRemote'] as bool? ?? false,
);
diff --git a/lib/features/file_transfer/presentation/pages/device_pairing_page.dart b/lib/features/file_transfer/presentation/pages/device_pairing_page.dart
index d3790382..55c96126 100644
--- a/lib/features/file_transfer/presentation/pages/device_pairing_page.dart
+++ b/lib/features/file_transfer/presentation/pages/device_pairing_page.dart
@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 设备配对页面
// 创建时间: 2026-05-09
-// 更新时间: 2026-06-05
+// 更新时间: 2026-06-06
// 作用: 设备配对 — 配对码/扫码/雷达/其他方式 + DegradationManager降级提示
-// 上次更新: 修复返回按钮在GoRouter导航时可能不显示的问题
+// 上次更新: 移除nearby_connections P2P相关UI
// ============================================================
import 'package:flutter/cupertino.dart';
@@ -24,7 +24,6 @@ import 'package:xianyan/features/file_transfer/presentation/pages/pairing_code_t
import 'package:xianyan/features/file_transfer/presentation/pages/qr_code_tab.dart';
import 'package:xianyan/features/file_transfer/presentation/pages/radar_scan_tab.dart';
import 'package:xianyan/features/file_transfer/services/degradation_manager.dart';
-import 'package:xianyan/features/file_transfer/services/transport/nearby_service_adapter.dart';
class DevicePairingPage extends ConsumerStatefulWidget {
const DevicePairingPage({super.key});
@@ -36,7 +35,6 @@ class DevicePairingPage extends ConsumerStatefulWidget {
class _DevicePairingPageState extends ConsumerState
with SingleTickerProviderStateMixin {
late final TabController _tabController;
- bool _isScanning = false;
@override
void initState() {
@@ -71,11 +69,7 @@ class _DevicePairingPageState extends ConsumerState
Navigator.of(context).maybePop();
}
},
- child: Icon(
- CupertinoIcons.chevron_left,
- color: ext.accent,
- size: 24,
- ),
+ child: Icon(CupertinoIcons.chevron_left, color: ext.accent, size: 24),
),
backgroundColor: ext.bgPrimary.withValues(alpha: 0.85),
border: null,
@@ -142,7 +136,10 @@ class _DevicePairingPageState extends ConsumerState
tabs: const [
Tab(icon: Icon(CupertinoIcons.number, size: 16), text: '配对码'),
Tab(icon: Icon(CupertinoIcons.camera, size: 16), text: '扫码'),
- Tab(icon: Icon(CupertinoIcons.antenna_radiowaves_left_right, size: 16), text: '雷达'),
+ Tab(
+ icon: Icon(CupertinoIcons.antenna_radiowaves_left_right, size: 16),
+ text: '雷达',
+ ),
Tab(icon: Icon(CupertinoIcons.ellipsis_circle, size: 16), text: '其他'),
],
),
@@ -153,7 +150,8 @@ class _DevicePairingPageState extends ConsumerState
final discoveryState = ref.watch(deviceDiscoveryProvider);
final usbCap = DegradationManager.usbTransfer;
- final usbAvailable = discoveryState.isUsbAvailable &&
+ final usbAvailable =
+ discoveryState.isUsbAvailable &&
usbCap.level != FeatureLevel.unavailable;
final nearbyCap = DegradationManager.harmonyNearby;
@@ -217,18 +215,6 @@ class _DevicePairingPageState extends ConsumerState
available: true,
onTap: () => _startAccountPairing(),
),
- // 附近设备P2P发现(仅Android/iOS)
- if (NearbyServiceAdapter.isP2pSupported) ...[
- const SizedBox(height: AppSpacing.sm),
- _buildOtherMethodCard(
- ext,
- iconData: CupertinoIcons.device_phone_portrait,
- title: '📱 附近设备',
- subtitle: _isScanning ? '正在搜索附近设备...' : '蓝牙+Wi-Fi发现附近设备',
- available: !_isScanning,
- onTap: () => _startNearbyDiscovery(),
- ),
- ],
],
),
);
@@ -247,8 +233,8 @@ class _DevicePairingPageState extends ConsumerState
final displaySubtitle = !available && fallbackHint != null
? fallbackHint
: available
- ? subtitle
- : '此设备不支持';
+ ? subtitle
+ : '此设备不支持';
return GestureDetector(
onTap: available ? onTap : null,
@@ -324,7 +310,10 @@ class _DevicePairingPageState extends ConsumerState
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
- title: Text('手动IP配对', style: AppTypography.headline.copyWith(color: ext.textPrimary)),
+ title: Text(
+ '手动IP配对',
+ style: AppTypography.headline.copyWith(color: ext.textPrimary),
+ ),
content: Padding(
padding: const EdgeInsets.only(top: AppSpacing.md),
child: Column(
@@ -332,7 +321,9 @@ class _DevicePairingPageState extends ConsumerState
CupertinoTextField(
controller: ipController,
placeholder: '192.168.1.100',
- placeholderStyle: AppTypography.body.copyWith(color: ext.textHint),
+ placeholderStyle: AppTypography.body.copyWith(
+ color: ext.textHint,
+ ),
style: AppTypography.body.copyWith(color: ext.textPrimary),
decoration: BoxDecoration(
color: ext.bgCard,
@@ -342,13 +333,17 @@ class _DevicePairingPageState extends ConsumerState
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
+ keyboardType: const TextInputType.numberWithOptions(
+ decimal: true,
+ ),
),
const SizedBox(height: AppSpacing.sm),
CupertinoTextField(
controller: portController,
placeholder: '53317',
- placeholderStyle: AppTypography.body.copyWith(color: ext.textHint),
+ placeholderStyle: AppTypography.body.copyWith(
+ color: ext.textHint,
+ ),
style: AppTypography.body.copyWith(color: ext.textPrimary),
decoration: BoxDecoration(
color: ext.bgCard,
@@ -372,7 +367,10 @@ class _DevicePairingPageState extends ConsumerState
isDefaultAction: true,
onPressed: () {
Navigator.pop(ctx);
- _pairWithManualIp(ipController.text.trim(), portController.text.trim());
+ _pairWithManualIp(
+ ipController.text.trim(),
+ portController.text.trim(),
+ );
},
child: const Text('连接'),
),
@@ -388,7 +386,9 @@ class _DevicePairingPageState extends ConsumerState
return;
}
try {
- await ref.read(transferProvider.notifier).pairWithManualIp(ip, port: port);
+ await ref
+ .read(transferProvider.notifier)
+ .pairWithManualIp(ip, port: port);
if (!mounted) return;
Navigator.of(context).pop();
} catch (e) {
@@ -432,67 +432,12 @@ class _DevicePairingPageState extends ConsumerState
void _startHarmonyNearbyPairing() {
if (!PlatformHelper.isHarmonyOS) return;
- ref.read(deviceDiscoveryProvider.notifier).startScan({PairingMethod.harmonyNearby});
+ ref.read(deviceDiscoveryProvider.notifier).startScan({
+ PairingMethod.harmonyNearby,
+ });
}
void _startAccountPairing() {
ref.read(transferProvider.notifier).connectSignaling();
}
-
- /// 启动附近设备P2P发现(nearby_connections)
- Future _startNearbyDiscovery() async {
- final nearby = ref.read(transferProvider.notifier).transportRouter.nearbyServiceAdapter;
-
- setState(() => _isScanning = true);
-
- // 先请求权限
- final permGranted = await nearby.requestP2pPermissions();
- if (!permGranted) {
- if (mounted) {
- _showAlert('权限不足', '附近设备发现需要位置和蓝牙权限');
- }
- setState(() => _isScanning = false);
- return;
- }
-
- // 同时启动广播和发现
- final deviceInfo = await nearby.getCurrentDeviceInfo();
- final deviceName = deviceInfo?.displayName ?? '闲言设备';
-
- await nearby.startP2pAdvertising(deviceName);
- final success = await nearby.startP2pDiscovery();
-
- if (!success) {
- if (mounted) {
- _showAlert('启动失败', '无法启动附近设备发现,请检查蓝牙和位置是否开启');
- }
- setState(() => _isScanning = false);
- return;
- }
-
- // 监听P2P设备发现
- nearby.onP2pDevicesChanged.listen((devices) {
- if (mounted && devices.isNotEmpty) {
- setState(() {});
- final connected = devices
- .where((d) => d.state == NearbyP2pConnectionState.connected)
- .toList();
- if (connected.isNotEmpty) {
- _showAlert(
- '已连接',
- '已连接到 ${connected.map((d) => d.endpointName).join(", ")}',
- );
- }
- }
- });
-
- // 30秒后自动停止
- Future.delayed(const Duration(seconds: 30), () {
- if (mounted && _isScanning) {
- nearby.stopP2pDiscovery();
- nearby.stopP2pAdvertising();
- setState(() => _isScanning = false);
- }
- });
- }
}
diff --git a/lib/features/file_transfer/presentation/pages/file_transfer_device_actions.dart b/lib/features/file_transfer/presentation/pages/file_transfer_device_actions.dart
index 523f5b9f..6ed29857 100644
--- a/lib/features/file_transfer/presentation/pages/file_transfer_device_actions.dart
+++ b/lib/features/file_transfer/presentation/pages/file_transfer_device_actions.dart
@@ -472,7 +472,7 @@ mixin FileTransferDeviceActions
try {
final data = jsonDecode(payload) as Map;
final ip = data['ip'] as String? ?? '';
- final port = data['port'] as int? ?? 53317;
+ final port = (data['port'] as num?)?.toInt() ?? 53317;
final alias = data['alias'] as String? ?? '';
final fp = data['fp'] as String? ?? '';
final readable = '闲言设备配对\n名称: $alias\nIP: $ip:$port\n指纹: $fp';
diff --git a/lib/features/file_transfer/providers/device_discovery_provider.dart b/lib/features/file_transfer/providers/device_discovery_provider.dart
index a9d2d2ce..9453048d 100644
--- a/lib/features/file_transfer/providers/device_discovery_provider.dart
+++ b/lib/features/file_transfer/providers/device_discovery_provider.dart
@@ -185,8 +185,6 @@ class DeviceDiscoveryNotifier extends Notifier
break;
case PairingMethod.harmonyNearby:
break;
- case PairingMethod.nearbyP2p:
- break;
}
}
diff --git a/lib/features/file_transfer/providers/transfer_signaling_handler.dart b/lib/features/file_transfer/providers/transfer_signaling_handler.dart
index f098a362..68014e1f 100644
--- a/lib/features/file_transfer/providers/transfer_signaling_handler.dart
+++ b/lib/features/file_transfer/providers/transfer_signaling_handler.dart
@@ -833,9 +833,9 @@ class TransferSignalingHandler {
void _handleChunkAck(SignalingMessage message) {
final taskId = message.payload?['taskId'] as String? ?? '';
- final chunkIndex = message.payload?['chunkIndex'] as int? ?? -1;
- final receivedCount = message.payload?['receivedCount'] as int? ?? 0;
- final totalChunks = message.payload?['totalChunks'] as int? ?? 0;
+ final chunkIndex = (message.payload?['chunkIndex'] as num?)?.toInt() ?? -1;
+ final receivedCount = (message.payload?['receivedCount'] as num?)?.toInt() ?? 0;
+ final totalChunks = (message.payload?['totalChunks'] as num?)?.toInt() ?? 0;
Log.d(
'Transfer: ChunkAck taskId=$taskId chunk=$chunkIndex '
'progress=$receivedCount/$totalChunks',
diff --git a/lib/features/file_transfer/services/api/transfer_api_service.dart b/lib/features/file_transfer/services/api/transfer_api_service.dart
index 246981ab..d459a833 100644
--- a/lib/features/file_transfer/services/api/transfer_api_service.dart
+++ b/lib/features/file_transfer/services/api/transfer_api_service.dart
@@ -326,7 +326,7 @@ class TurnCredentials {
urls:
(json['urls'] as List?)?.map((e) => e as String).toList() ??
[],
- ttl: json['ttl'] as int? ?? 86400,
+ ttl: (json['ttl'] as num?)?.toInt() ?? 86400,
iceServers: iceServersRaw
.map(
(e) => (e as Map).map(
@@ -414,7 +414,7 @@ class LocalSendInfo {
deviceModel: json['deviceModel'] as String? ?? '',
deviceType: json['deviceType'] as String? ?? 'server',
fingerprint: json['fingerprint'] as String? ?? '',
- port: json['port'] as int? ?? 53317,
+ port: (json['port'] as num?)?.toInt() ?? 53317,
);
}
}
diff --git a/lib/features/file_transfer/services/cloud_cache_service.dart b/lib/features/file_transfer/services/cloud_cache_service.dart
index 518fed51..2745aa57 100644
--- a/lib/features/file_transfer/services/cloud_cache_service.dart
+++ b/lib/features/file_transfer/services/cloud_cache_service.dart
@@ -499,7 +499,7 @@ class CloudCacheService {
try {
final content = await entity.readAsString();
final meta = jsonDecode(content) as Map;
- final createdAt = meta['createdAt'] as int? ?? 0;
+ final createdAt = (meta['createdAt'] as num?)?.toInt() ?? 0;
final cacheId = meta['cacheId'] as String? ?? '';
if (DateTime.fromMillisecondsSinceEpoch(createdAt).isBefore(cutoff)) {
diff --git a/lib/features/file_transfer/services/degradation_manager.dart b/lib/features/file_transfer/services/degradation_manager.dart
index 4dfe093e..03a9727b 100644
--- a/lib/features/file_transfer/services/degradation_manager.dart
+++ b/lib/features/file_transfer/services/degradation_manager.dart
@@ -1,19 +1,14 @@
// ============================================================
// 闲言APP — 功能降级管理器
// 创建时间: 2026-05-19
-// 更新时间: 2026-05-19
+// 更新时间: 2026-06-06
// 作用: 跨平台功能降级策略,优雅回退不可用功能
-// 上次更新: HarmonyOS适配 — screenCapture始终full(RepaintBoundary) + 新增harmonyNearby
+// 上次更新: 移除nearbyP2p(nearby_connections已从项目移除)
// ============================================================
import 'package:xianyan/core/utils/platform/platform_helper.dart';
-enum FeatureLevel {
- full,
- limited,
- minimal,
- unavailable,
-}
+enum FeatureLevel { full, limited, minimal, unavailable }
class FeatureCapability {
const FeatureCapability({
@@ -99,33 +94,17 @@ class DegradationManager {
);
}
- /// nearby_connections P2P传输(仅Android/iOS)
- static FeatureCapability get nearbyP2p {
- if (PlatformHelper.isAndroid || PlatformHelper.isIOS) {
- return const FeatureCapability(
- name: 'nearbyP2p',
- level: FeatureLevel.full,
- );
- }
- return const FeatureCapability(
- name: 'nearbyP2p',
- level: FeatureLevel.unavailable,
- fallbackHint: '附近P2P仅支持Android和iOS',
- alternativeMethod: '使用配对码或扫码配对',
- );
- }
-
static List get allCapabilities => [
screenCapture,
wifiDirect,
usbTransfer,
remoteInput,
harmonyNearby,
- nearbyP2p,
];
- static List get unavailableFeatures =>
- allCapabilities.where((c) => c.level == FeatureLevel.unavailable).toList();
+ static List get unavailableFeatures => allCapabilities
+ .where((c) => c.level == FeatureLevel.unavailable)
+ .toList();
static bool isFeatureAvailable(String featureName) {
switch (featureName) {
@@ -139,8 +118,6 @@ class DegradationManager {
return remoteInput.level != FeatureLevel.unavailable;
case 'harmonyNearby':
return harmonyNearby.level != FeatureLevel.unavailable;
- case 'nearbyP2p':
- return nearbyP2p.level != FeatureLevel.unavailable;
default:
return true;
}
diff --git a/lib/features/file_transfer/services/delivery_receipt_service.dart b/lib/features/file_transfer/services/delivery_receipt_service.dart
index 72660491..74ee9b9c 100644
--- a/lib/features/file_transfer/services/delivery_receipt_service.dart
+++ b/lib/features/file_transfer/services/delivery_receipt_service.dart
@@ -83,7 +83,7 @@ class DeliveryReceiptService {
final messageId = payload['messageId'] as String? ?? '';
final statusId = payload['status'] as String? ?? '';
- final timestamp = payload['timestamp'] as int? ?? 0;
+ final timestamp = (payload['timestamp'] as num?)?.toInt() ?? 0;
if (messageId.isEmpty || statusId.isEmpty) {
Log.w('DeliveryReceiptService: invalid delivery-ack payload');
diff --git a/lib/features/file_transfer/services/discovery/hotspot_service.dart b/lib/features/file_transfer/services/discovery/hotspot_service.dart
index ec0609de..987b15a0 100644
--- a/lib/features/file_transfer/services/discovery/hotspot_service.dart
+++ b/lib/features/file_transfer/services/discovery/hotspot_service.dart
@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — WiFi热点服务
// 创建时间: 2026-05-09
-// 更新时间: 2026-05-10
+// 更新时间: 2026-06-06
// 作用: WiFi热点创建/连接 — 无联网传输场景
-// 上次更新: 集成wifi_iot实现Android热点+跨平台兼容+LocalOnlyHotspot平台Channel
+// 上次更新: 鸿蒙端MethodChannel添加超时保护+MissingPluginException捕获+平台判断
// ============================================================
import 'dart:async';
@@ -136,9 +136,12 @@ class HotspotService {
Future _startLocalOnlyHotspotViaChannel() async {
try {
- final result = await _hotspotChannel.invokeMethod(
- 'startLocalOnlyHotspot',
- );
+ // 鸿蒙端:添加超时保护,防止通道未实现时挂起
+ final result = pu.isOhos
+ ? await _hotspotChannel
+ .invokeMethod('startLocalOnlyHotspot')
+ .timeout(const Duration(seconds: 5), onTimeout: () => false)
+ : await _hotspotChannel.invokeMethod('startLocalOnlyHotspot');
if (result == true) {
_isHotspotActive = true;
_updateState(HotspotState.active);
@@ -155,7 +158,9 @@ class HotspotService {
return false;
} on MissingPluginException {
Log.w(
- 'Hotspot: Platform channel not implemented. Add native Android code for LocalOnlyHotspot.',
+ pu.isOhos
+ ? 'Hotspot: 鸿蒙端热点通道未实现'
+ : 'Hotspot: Platform channel not implemented. Add native Android code for LocalOnlyHotspot.',
);
_updateState(HotspotState.unsupported);
return false;
@@ -174,7 +179,14 @@ class HotspotService {
} catch (_) {}
try {
- await _hotspotChannel.invokeMethod('stopLocalOnlyHotspot');
+ // 鸿蒙端:添加超时保护
+ if (pu.isOhos) {
+ await _hotspotChannel
+ .invokeMethod('stopLocalOnlyHotspot')
+ .timeout(const Duration(seconds: 3), onTimeout: () => false);
+ } else {
+ await _hotspotChannel.invokeMethod('stopLocalOnlyHotspot');
+ }
} catch (_) {}
}
@@ -259,8 +271,16 @@ class HotspotService {
Future _getAndroidSdkVersion() async {
if (!Platform.isAndroid && !pu.isOhos) return null;
try {
- final version = await _hotspotChannel.invokeMethod('getSdkVersion');
+ // 鸿蒙端:添加超时保护
+ final version = pu.isOhos
+ ? await _hotspotChannel
+ .invokeMethod('getSdkVersion')
+ .timeout(const Duration(seconds: 3), onTimeout: () => null)
+ : await _hotspotChannel.invokeMethod('getSdkVersion');
return version;
+ } on MissingPluginException {
+ Log.w('Hotspot: 鸿蒙端获取SDK版本通道未实现');
+ return null;
} catch (_) {
return null;
}
diff --git a/lib/features/file_transfer/services/discovery/qr_pairing_service.dart b/lib/features/file_transfer/services/discovery/qr_pairing_service.dart
index 3da7fbcb..f60328d8 100644
--- a/lib/features/file_transfer/services/discovery/qr_pairing_service.dart
+++ b/lib/features/file_transfer/services/discovery/qr_pairing_service.dart
@@ -60,7 +60,7 @@ class QrPairingService {
final type = data['type'] as String?;
if (type != 'xianyan-pair') return null;
- final expiresAt = data['expiresAt'] as int?;
+ final expiresAt = (data['expiresAt'] as num?)?.toInt();
if (expiresAt != null) {
final expiry = DateTime.fromMillisecondsSinceEpoch(expiresAt);
if (DateTime.now().isAfter(expiry)) {
@@ -70,7 +70,7 @@ class QrPairingService {
}
final ip = data['ip'] as String?;
- final port = data['port'] as int?;
+ final port = (data['port'] as num?)?.toInt();
final alias = data['alias'] as String?;
final fp = data['fp'] as String?;
final methodId = data['method'] as String? ?? 'qr_code';
@@ -103,7 +103,7 @@ class QrPairingService {
if (data['ip'] == null || data['alias'] == null || data['fp'] == null) {
return false;
}
- final expiresAt = data['expiresAt'] as int?;
+ final expiresAt = (data['expiresAt'] as num?)?.toInt();
if (expiresAt != null) {
return DateTime.now().isBefore(
DateTime.fromMillisecondsSinceEpoch(expiresAt),
diff --git a/lib/features/file_transfer/services/ip_location_service.dart b/lib/features/file_transfer/services/ip_location_service.dart
index 08a4f824..25b4b8d9 100644
--- a/lib/features/file_transfer/services/ip_location_service.dart
+++ b/lib/features/file_transfer/services/ip_location_service.dart
@@ -98,7 +98,7 @@ class IpLocationService {
final body = response.data;
if (body == null) return null;
- final code = body['code'] as int? ?? 0;
+ final code = (body['code'] as num?)?.toInt() ?? 0;
if (code != 1) {
Log.w('IpLocation: API返回错误 ${body['msg']}');
return null;
diff --git a/lib/features/file_transfer/services/pairing_service.dart b/lib/features/file_transfer/services/pairing_service.dart
index 92c95112..9cde50ac 100644
--- a/lib/features/file_transfer/services/pairing_service.dart
+++ b/lib/features/file_transfer/services/pairing_service.dart
@@ -143,8 +143,6 @@ class PairingService {
break;
case PairingMethod.harmonyNearby:
break;
- case PairingMethod.nearbyP2p:
- break;
}
}
diff --git a/lib/features/file_transfer/services/signaling_service.dart b/lib/features/file_transfer/services/signaling_service.dart
index b3d097e7..182c04e2 100644
--- a/lib/features/file_transfer/services/signaling_service.dart
+++ b/lib/features/file_transfer/services/signaling_service.dart
@@ -920,7 +920,7 @@ class SignalingService {
id: map['fingerprint'] as String? ?? map['id'] as String? ?? '',
alias: map['alias'] as String? ?? 'Unknown',
deviceType: DeviceType.fromId(map['deviceType'] as String? ?? 'mobile'),
- port: map['port'] as int? ?? 53317,
+ port: (map['port'] as num?)?.toInt() ?? 53317,
pairingMethod: PairingMethod.account,
preferredTransport: TransportType.webrtcP2p,
lastSeen: DateTime.now(),
diff --git a/lib/features/file_transfer/services/transport/nearby_connections_web.dart b/lib/features/file_transfer/services/transport/nearby_connections_web.dart
deleted file mode 100644
index 6bedc301..00000000
--- a/lib/features/file_transfer/services/transport/nearby_connections_web.dart
+++ /dev/null
@@ -1,165 +0,0 @@
-// ============================================================
-// 闲言APP — nearby_connections Web Stub
-// 创建时间: 2026-06-06
-// 更新时间: 2026-06-06
-// 作用: nearby_connections包不支持Web平台,提供空实现桩
-// 类型签名与nearby_connections 4.x保持一致
-// 上次更新: 初始创建,匹配nearby_connections 4.x API
-// ============================================================
-
-import 'dart:typed_data';
-
-/// 策略枚举桩(与nearby_connections Strategy一致)
-enum Strategy { P2P_CLUSTER, P2P_STAR, P2P_POINT_TO_POINT }
-
-/// 连接状态枚举桩
-enum Status { CONNECTED, REJECTED, ERROR }
-
-/// Payload传输状态枚举桩
-enum PayloadStatus { NONE, SUCCESS, FAILURE, IN_PROGRESS, CANCELED }
-
-/// Payload类型枚举桩
-enum PayloadType { NONE, BYTES, FILE, STREAM }
-
-/// 连接信息桩
-class ConnectionInfo {
- final String endpointName;
- final String authenticationToken;
- final bool isIncomingConnection;
-
- ConnectionInfo(
- this.endpointName, this.authenticationToken, this.isIncomingConnection);
-}
-
-/// Payload桩
-class Payload {
- final int id;
- final PayloadType type;
- final Uint8List? bytes;
- final String? filePath;
- final String? uri;
-
- Payload({
- required this.id,
- this.bytes,
- this.type = PayloadType.NONE,
- this.filePath,
- this.uri,
- });
-}
-
-/// Payload传输更新桩
-class PayloadTransferUpdate {
- final int id;
- final int bytesTransferred;
- final int totalBytes;
- final PayloadStatus status;
-
- PayloadTransferUpdate({
- required this.id,
- required this.bytesTransferred,
- required this.totalBytes,
- this.status = PayloadStatus.NONE,
- });
-}
-
-/// 回调类型定义桩
-typedef OnConnectionInitiated = void Function(
- String endpointId, ConnectionInfo connectionInfo);
-typedef OnConnectionResult = void Function(String endpointId, Status status);
-typedef OnDisconnected = void Function(String endpointId);
-typedef OnEndpointFound = void Function(
- String endpointId, String endpointName, String serviceId);
-typedef OnEndpointLost = void Function(String? endpointId);
-typedef OnPayloadReceived = void Function(
- String endpointId, Payload payload);
-typedef OnPayloadTransferUpdate = void Function(
- String endpointId, PayloadTransferUpdate payloadTransferUpdate);
-
-/// Nearby单例桩(factory构造器,与nearby_connections一致)
-class Nearby {
- static Nearby? _instance;
-
- factory Nearby() {
- _instance ??= Nearby._();
- return _instance!;
- }
-
- Nearby._();
-
- Future startAdvertising(
- String userNickName,
- Strategy strategy, {
- required OnConnectionInitiated onConnectionInitiated,
- required OnConnectionResult onConnectionResult,
- required OnDisconnected onDisconnected,
- String serviceId = "com.pkmnapps.nearby_connections",
- }) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future stopAdvertising() async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future startDiscovery(
- String userNickName,
- Strategy strategy, {
- required OnEndpointFound onEndpointFound,
- required OnEndpointLost onEndpointLost,
- String serviceId = "com.pkmnapps.nearby_connections",
- }) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future stopDiscovery() async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future requestConnection(
- String userNickName,
- String endpointId, {
- required OnConnectionInitiated onConnectionInitiated,
- required OnConnectionResult onConnectionResult,
- required OnDisconnected onDisconnected,
- }) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future acceptConnection(
- String endpointId, {
- required OnPayloadReceived onPayLoadRecieved,
- OnPayloadTransferUpdate? onPayloadTransferUpdate,
- }) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future rejectConnection(String endpointId) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future disconnectFromEndpoint(String endpointId) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future sendFilePayload(String endpointId, String filePath) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future sendBytesPayload(String endpointId, Uint8List bytes) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future stopAllEndpoints() async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future copyFileAndDeleteOriginal(
- String sourceUri, String destinationFilepath) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-
- Future cancelPayload(int payloadId) async {
- throw UnsupportedError('nearby_connections不支持Web平台');
- }
-}
diff --git a/lib/features/file_transfer/services/transport/nearby_service_adapter_io.dart b/lib/features/file_transfer/services/transport/nearby_service_adapter_io.dart
index de2d6bd9..7b476529 100644
--- a/lib/features/file_transfer/services/transport/nearby_service_adapter_io.dart
+++ b/lib/features/file_transfer/services/transport/nearby_service_adapter_io.dart
@@ -2,20 +2,16 @@
// 闲言APP — NearbyService适配器
// 创建时间: 2026-05-13
// 更新时间: 2026-06-06
-// 作用: 封装nearby_service + nearby_connections三方库,适配项目传输体系
+// 作用: 封装nearby_service三方库,适配项目传输体系
// nearby_service: Android Wi-Fi Direct | iOS/macOS MultipeerConnectivity | 鸿蒙
-// nearby_connections: Google Nearby Connections(蓝牙发现+Wi-Fi Direct传输, 仅Android/iOS)
-// 上次更新: 跨平台兼容修复:鸿蒙端使用ohos属性替代android属性,P2P引擎鸿蒙端跳过
+// 上次更新: 移除nearby_connections依赖,仅保留nearby_service原生引擎
// ============================================================
import 'dart:async';
import 'dart:io';
-import 'dart:typed_data';
import 'package:mime/mime.dart';
-import 'package:nearby_connections/nearby_connections.dart';
import 'package:nearby_service/nearby_service.dart';
-import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
import 'package:xianyan/core/utils/logger.dart';
@@ -25,81 +21,17 @@ import 'package:xianyan/features/file_transfer/models/transfer_enums.dart';
import 'package:xianyan/features/file_transfer/models/transfer_message.dart';
import 'package:xianyan/features/file_transfer/models/transfer_task.dart';
-/// ============================================================
-/// 附近P2P设备信息(nearby_connections)
-/// ============================================================
-class NearbyP2pDeviceInfo {
- const NearbyP2pDeviceInfo({
- required this.endpointId,
- required this.endpointName,
- this.serviceId,
- this.state = NearbyP2pConnectionState.discovered,
- });
-
- /// 端点ID
- final String endpointId;
-
- /// 端点名称
- final String endpointName;
-
- /// 服务ID
- final String? serviceId;
-
- /// 连接状态
- final NearbyP2pConnectionState state;
-
- NearbyP2pDeviceInfo copyWith({NearbyP2pConnectionState? state}) {
- return NearbyP2pDeviceInfo(
- endpointId: endpointId,
- endpointName: endpointName,
- serviceId: serviceId,
- state: state ?? this.state,
- );
- }
-}
-
-/// P2P连接状态
-enum NearbyP2pConnectionState {
- discovered,
- connecting,
- connected,
- disconnected,
- failed,
-}
-
-/// P2P传输进度
-class NearbyP2pTransferProgress {
- const NearbyP2pTransferProgress({
- required this.transferId,
- required this.fileName,
- required this.totalBytes,
- required this.transferredBytes,
- this.isComplete = false,
- this.error,
- });
-
- final String transferId;
- final String fileName;
- final int totalBytes;
- final int transferredBytes;
- final bool isComplete;
- final String? error;
-
- double get progress => totalBytes > 0 ? transferredBytes / totalBytes : 0;
-}
-
/// ============================================================
/// NearbyService适配器
-/// 双引擎: nearby_service(原生平台) + nearby_connections(Google P2P)
+/// 引擎: nearby_service(原生平台: Wi-Fi Direct / MultipeerConnectivity / 鸿蒙)
/// ============================================================
class NearbyServiceAdapter {
NearbyServiceAdapter() {
_initPlatformService();
- _initNearby();
}
// ============================================================
- // nearby_service 引擎(原生平台: Wi-Fi Direct / MultipeerConnectivity)
+ // nearby_service 引擎
// ============================================================
NearbyService? _service;
bool _isInitialized = false;
@@ -109,18 +41,7 @@ class NearbyServiceAdapter {
StreamSubscription? _channelStateSub;
// ============================================================
- // nearby_connections 引擎(Google P2P: 蓝牙发现 + Wi-Fi Direct传输)
- // ============================================================
- static const String _p2pServiceId = 'com.xianyan.file_transfer';
- bool _p2pIsAdvertising = false;
- bool _p2pIsDiscovering = false;
- final Map _p2pDiscoveredDevices = {};
- StreamSubscription? _p2pDiscoverySub;
- StreamSubscription? _p2pConnectionSub;
- StreamSubscription? _p2pPayloadSub;
-
- // ============================================================
- // 公共流控制器
+ // 流控制器
// ============================================================
final _devicesController = StreamController>.broadcast();
final _incomingMessageController =
@@ -129,12 +50,6 @@ class NearbyServiceAdapter {
final _connectionStateController =
StreamController.broadcast();
- // P2P专用流控制器
- final _p2pDevicesController =
- StreamController>.broadcast();
- final _p2pTransferProgressController =
- StreamController.broadcast();
-
final _uuid = const Uuid();
// ============================================================
@@ -145,29 +60,14 @@ class NearbyServiceAdapter {
static bool get isPlatformSupported =>
pu.isOhos || Platform.isAndroid || Platform.isIOS || Platform.isMacOS;
- /// nearby_connections P2P平台支持(仅Android/iOS)
- static bool get isP2pSupported => Platform.isAndroid || Platform.isIOS;
-
// ============================================================
- // nearby_service 属性
+ // 属性
// ============================================================
bool get isInitialized => _isInitialized;
bool get isDiscovering => _isDiscovering;
bool get isConnected => _connectedDeviceId != null;
String? get connectedDeviceId => _connectedDeviceId;
- // ============================================================
- // nearby_connections P2P 属性
- // ============================================================
- bool get p2pIsAdvertising => _p2pIsAdvertising;
- bool get p2pIsDiscovering => _p2pIsDiscovering;
- List get p2pDiscoveredDevices =>
- _p2pDiscoveredDevices.values.toList();
- List get p2pConnectedDevices => _p2pDiscoveredDevices
- .values
- .where((d) => d.state == NearbyP2pConnectionState.connected)
- .toList();
-
// ============================================================
// 公共流
// ============================================================
@@ -179,16 +79,8 @@ class NearbyServiceAdapter {
Stream get onConnectionStateChanged =>
_connectionStateController.stream;
- /// P2P设备发现流
- Stream> get onP2pDevicesChanged =>
- _p2pDevicesController.stream;
-
- /// P2P传输进度流
- Stream get onP2pTransferProgress =>
- _p2pTransferProgressController.stream;
-
// ============================================================
- // nearby_service 初始化与操作
+ // 初始化与操作
// ============================================================
void _initPlatformService() {
@@ -254,7 +146,7 @@ class NearbyServiceAdapter {
});
}
- /// nearby_service: 开始发现设备
+ /// 开始发现设备
Future startDiscovery() async {
if (!_isInitialized || _service == null) return false;
if (_isDiscovering) return true;
@@ -270,7 +162,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 停止发现
+ /// 停止发现
Future stopDiscovery() async {
if (!_isInitialized || _service == null) return false;
@@ -285,7 +177,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 连接设备
+ /// 连接设备
Future connect(String deviceId) async {
if (!_isInitialized || _service == null) return false;
@@ -302,7 +194,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 断开连接
+ /// 断开连接
Future disconnect([String? deviceId]) async {
if (!_isInitialized || _service == null) return false;
@@ -317,7 +209,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 启动通信通道
+ /// 启动通信通道
Future startCommunicationChannel() async {
if (!_isInitialized || _service == null || _connectedDeviceId == null) {
return false;
@@ -352,7 +244,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 结束通信通道
+ /// 结束通信通道
Future endCommunicationChannel() async {
if (!_isInitialized || _service == null) return false;
@@ -366,7 +258,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 发送文本消息
+ /// 发送文本消息
Future sendTextMessage({
required String deviceId,
required String text,
@@ -391,7 +283,7 @@ class NearbyServiceAdapter {
}
}
- /// nearby_service: 发送文件
+ /// 发送文件
Future sendFile({
required String deviceId,
required String filePath,
@@ -424,7 +316,7 @@ class NearbyServiceAdapter {
}
// ============================================================
- // nearby_service 内部处理
+ // 内部处理
// ============================================================
void _handleIncomingMessage(ReceivedNearbyMessage message) {
@@ -570,6 +462,7 @@ class NearbyServiceAdapter {
);
}
+ /// 获取当前设备信息
Future getCurrentDeviceInfo() async {
if (_service == null) return null;
try {
@@ -580,18 +473,15 @@ class NearbyServiceAdapter {
}
}
+ /// 请求权限(Android使用android属性,鸿蒙端通过permission_handler统一请求)
Future requestPermissions() async {
if (!Platform.isAndroid && !pu.isOhos) return true;
if (_service == null) return false;
try {
- // Android使用android属性,鸿蒙使用ohos属性
if (Platform.isAndroid) {
return await _service!.android?.requestPermissions() ?? false;
}
- if (pu.isOhos) {
- // 鸿蒙端权限通过permission_handler统一请求,nearby_service.ohos无独立权限方法
- return true;
- }
+ // 鸿蒙端权限通过permission_handler统一请求
return true;
} catch (e) {
Log.e('NearbyService: Request permissions failed: $e');
@@ -599,22 +489,19 @@ class NearbyServiceAdapter {
}
}
+ /// 检查Wi-Fi服务(仅Android)
Future checkWifiService() async {
- if (!Platform.isAndroid && !pu.isOhos) return true;
+ if (!Platform.isAndroid) return true;
if (_service == null) return true;
try {
- // 仅Android端支持Wi-Fi服务检查
- if (Platform.isAndroid) {
- return await _service!.android?.checkWifiService() ?? false;
- }
- // 鸿蒙/iOS/macOS端无需检查Wi-Fi服务
- return true;
+ return await _service!.android?.checkWifiService() ?? false;
} catch (e) {
Log.e('NearbyService: Check WiFi failed: $e');
return false;
}
}
+ /// 打开系统设置
Future openServicesSettings() async {
if (_service == null) return;
try {
@@ -624,640 +511,23 @@ class NearbyServiceAdapter {
}
}
- // ============================================================
- // nearby_connections P2P 引擎
- // ============================================================
-
- /// 初始化nearby_connections引擎
- void _initNearby() {
- if (!isP2pSupported) {
- Log.w('NearbyP2p: Platform not supported (${Platform.operatingSystem})');
- return;
- }
- Log.i('NearbyP2p: Engine ready for ${Platform.operatingSystem}');
- }
-
- /// P2P: 请求必要权限(位置+蓝牙+附近设备)
- Future requestP2pPermissions() async {
- if (!isP2pSupported) return false;
-
- try {
- // 请求位置权限
- final locationStatus = await Permission.locationWhenInUse.status;
- if (!locationStatus.isGranted) {
- final result = await Permission.locationWhenInUse.request();
- if (!result.isGranted) {
- Log.w('NearbyP2p: Location permission denied');
- return false;
- }
- }
-
- // Android需要蓝牙和附近设备权限
- if (Platform.isAndroid) {
- final bluetoothStatus = await Permission.bluetooth.status;
- if (!bluetoothStatus.isGranted) {
- final result = await Permission.bluetooth.request();
- if (!result.isGranted) {
- Log.w('NearbyP2p: Bluetooth permission denied');
- return false;
- }
- }
-
- // Android 13+ 需要附近Wi-Fi设备权限
- final nearbyStatus = await Permission.nearbyWifiDevices.status;
- if (!nearbyStatus.isGranted) {
- final result = await Permission.nearbyWifiDevices.request();
- if (!result.isGranted) {
- Log.w('NearbyP2p: Nearby devices permission denied');
- return false;
- }
- }
- }
-
- Log.i('NearbyP2p: All permissions granted');
- return true;
- } catch (e) {
- Log.e('NearbyP2p: Request permissions failed: $e');
- return false;
- }
- }
-
- /// P2P: 开始广播(等待其他设备发现)
- Future startP2pAdvertising(String deviceName) async {
- if (!isP2pSupported) {
- Log.w('NearbyP2p: 当前平台不支持nearby_connections');
- return false;
- }
-
- if (_p2pIsAdvertising) return true;
-
- try {
- await Nearby().startAdvertising(
- deviceName,
- Strategy.P2P_CLUSTER,
- onConnectionInitiated: (String endpointId, ConnectionInfo connectionInfo) {
- Log.i(
- 'NearbyP2p: Connection initiated by ${connectionInfo.endpointName} ($endpointId)',
- );
- _onP2pConnectionInitiated(endpointId, connectionInfo);
- },
- onConnectionResult: (String endpointId, Status status) {
- Log.i('NearbyP2p: Connection result for $endpointId: $status');
- _onP2pConnectionResult(endpointId, status);
- },
- onDisconnected: (String endpointId) {
- Log.i('NearbyP2p: Disconnected from $endpointId');
- _onP2pDisconnected(endpointId);
- },
- );
-
- _p2pIsAdvertising = true;
- Log.i('NearbyP2p: 开始广播: $deviceName');
- return true;
- } catch (e) {
- Log.e('NearbyP2p: 启动广播失败: $e');
- return false;
- }
- }
-
- /// P2P: 停止广播
- Future stopP2pAdvertising() async {
- if (!_p2pIsAdvertising) return;
-
- try {
- await Nearby().stopAdvertising();
- _p2pIsAdvertising = false;
- Log.i('NearbyP2p: 停止广播');
- } catch (e) {
- Log.e('NearbyP2p: 停止广播失败: $e');
- }
- }
-
- /// P2P: 开始发现附近设备
- Future startP2pDiscovery() async {
- if (!isP2pSupported) {
- Log.w('NearbyP2p: 当前平台不支持nearby_connections');
- return false;
- }
-
- if (_p2pIsDiscovering) return true;
-
- try {
- await Nearby().startDiscovery(
- 'xianyan_discoverer',
- Strategy.P2P_CLUSTER,
- serviceId: _p2pServiceId,
- onEndpointFound:
- (String endpointId, String endpointName, String serviceId) {
- Log.i(
- 'NearbyP2p: 发现设备: $endpointName ($endpointId, service=$serviceId)',
- );
- _onP2pEndpointFound(endpointId, endpointName, serviceId);
- },
- onEndpointLost: (String? endpointId) {
- if (endpointId != null) {
- Log.i('NearbyP2p: 设备丢失: $endpointId');
- _onP2pEndpointLost(endpointId);
- }
- },
- );
-
- _p2pIsDiscovering = true;
- _p2pDiscoveredDevices.clear();
- Log.i('NearbyP2p: 开始发现附近设备');
- return true;
- } catch (e) {
- Log.e('NearbyP2p: 启动发现失败: $e');
- return false;
- }
- }
-
- /// P2P: 停止发现
- Future stopP2pDiscovery() async {
- if (!_p2pIsDiscovering) return;
-
- try {
- await Nearby().stopDiscovery();
- _p2pIsDiscovering = false;
- Log.i('NearbyP2p: 停止发现');
- } catch (e) {
- Log.e('NearbyP2p: 停止发现失败: $e');
- }
- }
-
- /// P2P: 请求连接设备
- Future requestP2pConnection(String endpointId) async {
- if (!isP2pSupported) return false;
-
- try {
- await Nearby().requestConnection(
- 'xianyan_device',
- endpointId,
- onConnectionInitiated: (String endpointId, ConnectionInfo connectionInfo) {
- Log.i(
- 'NearbyP2p: Connection initiated with ${connectionInfo.endpointName} ($endpointId)',
- );
- _onP2pConnectionInitiated(endpointId, connectionInfo);
- },
- onConnectionResult: (String endpointId, Status status) {
- Log.i('NearbyP2p: Connection result for $endpointId: $status');
- _onP2pConnectionResult(endpointId, status);
- },
- onDisconnected: (String endpointId) {
- Log.i('NearbyP2p: Disconnected from $endpointId');
- _onP2pDisconnected(endpointId);
- },
- );
-
- // 更新设备状态
- final device = _p2pDiscoveredDevices[endpointId];
- if (device != null) {
- _p2pDiscoveredDevices[endpointId] = device.copyWith(
- state: NearbyP2pConnectionState.connecting,
- );
- _notifyP2pDevicesChanged();
- }
-
- Log.i('NearbyP2p: 请求连接: $endpointId');
- return true;
- } catch (e) {
- Log.e('NearbyP2p: 请求连接失败: $e');
- return false;
- }
- }
-
- /// P2P: 接受连接
- Future acceptP2pConnection(String endpointId) async {
- if (!isP2pSupported) return;
-
- try {
- await Nearby().acceptConnection(
- endpointId,
- onPayLoadRecieved: (String endpointId, Payload payload) {
- _onP2pPayloadReceived(endpointId, payload);
- },
- onPayloadTransferUpdate:
- (String endpointId, PayloadTransferUpdate payloadTransferUpdate) {
- _onP2pPayloadTransferUpdate(endpointId, payloadTransferUpdate);
- },
- );
- Log.i('NearbyP2p: 接受连接: $endpointId');
- } catch (e) {
- Log.e('NearbyP2p: 接受连接失败: $e');
- }
- }
-
- /// P2P: 拒绝连接
- Future rejectP2pConnection(String endpointId) async {
- if (!isP2pSupported) return;
-
- try {
- await Nearby().rejectConnection(endpointId);
- Log.i('NearbyP2p: 拒绝连接: $endpointId');
- } catch (e) {
- Log.e('NearbyP2p: 拒绝连接失败: $e');
- }
- }
-
- /// P2P: 断开连接
- Future disconnectP2pEndpoint(String endpointId) async {
- if (!isP2pSupported) return;
-
- try {
- await Nearby().disconnectFromEndpoint(endpointId);
- _p2pDiscoveredDevices.remove(endpointId);
- _notifyP2pDevicesChanged();
- Log.i('NearbyP2p: 断开连接: $endpointId');
- } catch (e) {
- Log.e('NearbyP2p: 断开连接失败: $e');
- }
- }
-
- /// P2P: 发送文件
- Future sendP2pFile(String endpointId, String filePath) async {
- if (!isP2pSupported) return false;
-
- try {
- final file = File(filePath);
- if (!await file.exists()) {
- Log.e('NearbyP2p: 文件不存在: $filePath');
- return false;
- }
-
- final fileSize = await file.length();
- final fileName = filePath.split('/').last;
- final transferId = 'p2p-${DateTime.now().millisecondsSinceEpoch}';
-
- Log.i('NearbyP2p: 开始发送文件: $fileName (${_formatBytes(fileSize)})');
-
- // 通知传输开始
- if (!_p2pTransferProgressController.isClosed) {
- _p2pTransferProgressController.add(
- NearbyP2pTransferProgress(
- transferId: transferId,
- fileName: fileName,
- totalBytes: fileSize,
- transferredBytes: 0,
- ),
- );
- }
-
- // 通过nearby_connections发送文件payload
- await Nearby().sendFilePayload(endpointId, filePath);
-
- return true;
- } catch (e) {
- Log.e('NearbyP2p: 发送文件失败: $e');
- return false;
- }
- }
-
- /// P2P: 发送字节数据
- Future sendP2pBytes(String endpointId, Uint8List data) async {
- if (!isP2pSupported) return false;
-
- try {
- await Nearby().sendBytesPayload(endpointId, data);
- Log.i('NearbyP2p: 发送数据: ${data.length} bytes');
- return true;
- } catch (e) {
- Log.e('NearbyP2p: 发送数据失败: $e');
- return false;
- }
- }
-
- // ============================================================
- // nearby_connections P2P 内部回调
- // ============================================================
-
- /// 发现新端点
- void _onP2pEndpointFound(
- String endpointId,
- String endpointName,
- String serviceId,
- ) {
- _p2pDiscoveredDevices[endpointId] = NearbyP2pDeviceInfo(
- endpointId: endpointId,
- endpointName: endpointName,
- serviceId: serviceId,
- );
- _notifyP2pDevicesChanged();
-
- // 同时通知nearby_service设备流
- _notifyP2pDeviceAsTransferDevice();
- }
-
- /// 端点丢失
- void _onP2pEndpointLost(String endpointId) {
- final device = _p2pDiscoveredDevices[endpointId];
- if (device != null && device.state != NearbyP2pConnectionState.connected) {
- _p2pDiscoveredDevices.remove(endpointId);
- _notifyP2pDevicesChanged();
- _notifyP2pDeviceAsTransferDevice();
- }
- }
-
- /// 连接发起回调
- void _onP2pConnectionInitiated(
- String endpointId,
- ConnectionInfo connectionInfo,
- ) {
- final device = _p2pDiscoveredDevices[endpointId];
- if (device != null) {
- _p2pDiscoveredDevices[endpointId] = device.copyWith(
- state: NearbyP2pConnectionState.connecting,
- );
- _notifyP2pDevicesChanged();
- }
-
- // 自动接受连接(可后续改为用户确认)
- acceptP2pConnection(endpointId);
- }
-
- /// 连接结果回调
- void _onP2pConnectionResult(String endpointId, Status status) {
- final device = _p2pDiscoveredDevices[endpointId];
- if (device == null) return;
-
- if (status == Status.CONNECTED) {
- _p2pDiscoveredDevices[endpointId] = device.copyWith(
- state: NearbyP2pConnectionState.connected,
- );
- Log.i('NearbyP2p: 已连接到 ${device.endpointName}');
-
- // 通知nearby_service连接状态
- if (!_connectionStateController.isClosed) {
- _connectionStateController.add(
- const NearbyConnectionState(isConnected: true),
- );
- }
- } else {
- _p2pDiscoveredDevices[endpointId] = device.copyWith(
- state: NearbyP2pConnectionState.failed,
- );
- Log.w('NearbyP2p: 连接失败 $endpointId, status=$status');
- }
- _notifyP2pDevicesChanged();
- }
-
- /// 断开连接回调
- void _onP2pDisconnected(String endpointId) {
- final device = _p2pDiscoveredDevices[endpointId];
- if (device != null) {
- _p2pDiscoveredDevices[endpointId] = device.copyWith(
- state: NearbyP2pConnectionState.disconnected,
- );
- _notifyP2pDevicesChanged();
- }
-
- // 如果没有已连接的P2P设备,通知断开
- if (p2pConnectedDevices.isEmpty) {
- if (!_connectionStateController.isClosed) {
- _connectionStateController.add(
- const NearbyConnectionState(isConnected: false),
- );
- }
- }
- }
-
- /// 接收Payload回调
- void _onP2pPayloadReceived(String endpointId, Payload payload) {
- if (payload.type == PayloadType.BYTES) {
- // 接收到字节数据
- final bytes = payload.bytes;
- if (bytes != null) {
- final device = _p2pDiscoveredDevices[endpointId];
- final senderName = device?.endpointName ?? endpointId;
-
- final msg = TransferMessage(
- id: _uuid.v4(),
- sessionId: 'p2p-$endpointId',
- type: TransferMessageType.text,
- content: String.fromCharCodes(bytes),
- isRemote: true,
- timestamp: DateTime.now(),
- peerDeviceId: endpointId,
- deviceAlias: senderName,
- deliveryStatus: DeliveryStatus.delivered,
- );
-
- if (!_incomingMessageController.isClosed) {
- _incomingMessageController.add(msg);
- }
- Log.i('NearbyP2p: Received bytes from $senderName');
- }
- } else if (payload.type == PayloadType.FILE) {
- // 接收到文件
- final device = _p2pDiscoveredDevices[endpointId];
- final senderName = device?.endpointName ?? endpointId;
- final sessionId = 'p2p-$endpointId';
-
- final filePath = payload.uri;
- if (filePath != null) {
- final fileName = filePath.split('/').last;
- final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';
- final isImage = mimeType.startsWith('image/');
- final isVideo = mimeType.startsWith('video/');
-
- final task = TransferTask(
- id: 'p2p-recv-${_uuid.v4()}',
- sessionId: sessionId,
- peer: TransferDevice(
- id: endpointId,
- alias: senderName,
- deviceType: DeviceType.mobile,
- port: 0,
- pairingMethod: PairingMethod.nearbyP2p,
- preferredTransport: TransportType.wifiDirect,
- lastSeen: DateTime.now(),
- isOnline: true,
- isVerified: false,
- ),
- transport: TransportType.wifiDirect,
- direction: TransferDirection.receive,
- status: TransferTaskStatus.completed,
- fileName: fileName,
- fileSize: 0,
- transferredBytes: 0,
- speed: 0.0,
- filePath: filePath,
- mimeType: mimeType,
- startTime: DateTime.now(),
- endTime: DateTime.now(),
- );
-
- if (!_incomingTaskController.isClosed) {
- _incomingTaskController.add(task);
- }
-
- final msg = TransferMessage(
- id: _uuid.v4(),
- sessionId: sessionId,
- type: isImage
- ? TransferMessageType.image
- : isVideo
- ? TransferMessageType.video
- : TransferMessageType.file,
- content: fileName,
- isRemote: true,
- timestamp: DateTime.now(),
- peerDeviceId: endpointId,
- deviceAlias: senderName,
- fileName: fileName,
- filePath: filePath,
- mimeType: mimeType,
- transferStatus: TransferTaskStatus.completed,
- deliveryStatus: DeliveryStatus.delivered,
- );
-
- if (!_incomingMessageController.isClosed) {
- _incomingMessageController.add(msg);
- }
-
- Log.i('NearbyP2p: Received file $fileName from $senderName');
- }
- }
- }
-
- /// Payload传输进度回调
- void _onP2pPayloadTransferUpdate(
- String endpointId,
- PayloadTransferUpdate update,
- ) {
- if (update.status == PayloadStatus.IN_PROGRESS) {
- if (!_p2pTransferProgressController.isClosed) {
- _p2pTransferProgressController.add(
- NearbyP2pTransferProgress(
- transferId: 'p2p-payload-${update.id}',
- fileName: 'payload-${update.id}',
- totalBytes: update.totalBytes,
- transferredBytes: update.bytesTransferred,
- ),
- );
- }
- } else if (update.status == PayloadStatus.SUCCESS) {
- if (!_p2pTransferProgressController.isClosed) {
- _p2pTransferProgressController.add(
- NearbyP2pTransferProgress(
- transferId: 'p2p-payload-${update.id}',
- fileName: 'payload-${update.id}',
- totalBytes: update.totalBytes,
- transferredBytes: update.totalBytes,
- isComplete: true,
- ),
- );
- }
- Log.i(
- 'NearbyP2p: Payload ${update.id} transfer complete (${_formatBytes(update.totalBytes)})',
- );
- } else if (update.status == PayloadStatus.FAILURE) {
- if (!_p2pTransferProgressController.isClosed) {
- _p2pTransferProgressController.add(
- NearbyP2pTransferProgress(
- transferId: 'p2p-payload-${update.id}',
- fileName: 'payload-${update.id}',
- totalBytes: update.totalBytes,
- transferredBytes: update.bytesTransferred,
- isComplete: true,
- error: 'Transfer failed',
- ),
- );
- }
- Log.w('NearbyP2p: Payload ${update.id} transfer failed');
- }
- }
-
- // ============================================================
- // P2P 通知辅助
- // ============================================================
-
- /// 通知P2P设备列表变更
- void _notifyP2pDevicesChanged() {
- if (!_p2pDevicesController.isClosed) {
- _p2pDevicesController.add(_p2pDiscoveredDevices.values.toList());
- }
- }
-
- /// 将P2P设备同步到nearby_service设备流(统一设备列表)
- void _notifyP2pDeviceAsTransferDevice() {
- final devices = _p2pDiscoveredDevices.values
- .map(
- (d) => TransferDevice(
- id: d.endpointId,
- alias: d.endpointName,
- deviceType: DeviceType.mobile,
- port: 0,
- pairingMethod: PairingMethod.nearbyP2p,
- preferredTransport: TransportType.wifiDirect,
- lastSeen: DateTime.now(),
- isOnline:
- d.state == NearbyP2pConnectionState.connected ||
- d.state == NearbyP2pConnectionState.discovered,
- isVerified: d.state == NearbyP2pConnectionState.connected,
- ),
- )
- .toList();
-
- if (!_devicesController.isClosed && devices.isNotEmpty) {
- _devicesController.add(devices);
- }
- }
-
- // ============================================================
- // 公共操作
- // ============================================================
-
- /// 停止所有操作(两个引擎)
+ /// 停止所有操作
Future stopAll() async {
- // 停止nearby_service
await stopDiscovery();
-
- // 停止nearby_connections P2P
- await stopP2pAdvertising();
- await stopP2pDiscovery();
-
- // 断开所有P2P连接
- for (final device in _p2pDiscoveredDevices.values.toList()) {
- if (device.state == NearbyP2pConnectionState.connected) {
- await disconnectP2pEndpoint(device.endpointId);
- }
- }
- _p2pDiscoveredDevices.clear();
-
Log.i('NearbyService: 已停止所有操作');
}
- /// 格式化文件大小
- static String _formatBytes(int bytes) {
- if (bytes < 1024) return '$bytes B';
- if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
- if (bytes < 1024 * 1024 * 1024) {
- return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
- }
- return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
- }
-
/// 释放所有资源
Future dispose() async {
- // nearby_service 资源
await _peersSub?.cancel();
await _channelStateSub?.cancel();
- // nearby_connections P2P 资源
- await _p2pDiscoverySub?.cancel();
- await _p2pConnectionSub?.cancel();
- await _p2pPayloadSub?.cancel();
-
- // 停止所有操作
await stopAll();
- // 关闭流控制器
await _devicesController.close();
await _incomingMessageController.close();
await _incomingTaskController.close();
await _connectionStateController.close();
- await _p2pDevicesController.close();
- await _p2pTransferProgressController.close();
_isInitialized = false;
_isDiscovering = false;
@@ -1266,7 +536,7 @@ class NearbyServiceAdapter {
}
/// ============================================================
-/// 连接状态(nearby_service + nearby_connections共用)
+/// 连接状态
/// ============================================================
class NearbyConnectionState {
const NearbyConnectionState({
diff --git a/lib/features/file_transfer/services/transport/nearby_service_adapter_web.dart b/lib/features/file_transfer/services/transport/nearby_service_adapter_web.dart
index b5e5ae08..6ce19736 100644
--- a/lib/features/file_transfer/services/transport/nearby_service_adapter_web.dart
+++ b/lib/features/file_transfer/services/transport/nearby_service_adapter_web.dart
@@ -2,9 +2,8 @@
// 闲言APP — NearbyService适配器 Web Stub
// 创建时间: 2026-06-06
// 更新时间: 2026-06-06
-// 作用: Web平台不支持nearby_service和nearby_connections,
-// 提供与NearbyServiceAdapter相同的公共API空实现
-// 上次更新: 初始创建
+// 作用: Web平台不支持nearby_service,提供与NearbyServiceAdapter相同的公共API空实现
+// 上次更新: 移除nearby_connections依赖,同步移除P2P相关API
// ============================================================
import 'dart:async';
@@ -14,60 +13,6 @@ import 'package:xianyan/features/file_transfer/models/transfer_device.dart';
import 'package:xianyan/features/file_transfer/models/transfer_message.dart';
import 'package:xianyan/features/file_transfer/models/transfer_task.dart';
-/// 附近P2P设备信息桩
-class NearbyP2pDeviceInfo {
- const NearbyP2pDeviceInfo({
- required this.endpointId,
- required this.endpointName,
- this.serviceId,
- this.state = NearbyP2pConnectionState.discovered,
- });
-
- final String endpointId;
- final String endpointName;
- final String? serviceId;
- final NearbyP2pConnectionState state;
-
- NearbyP2pDeviceInfo copyWith({NearbyP2pConnectionState? state}) {
- return NearbyP2pDeviceInfo(
- endpointId: endpointId,
- endpointName: endpointName,
- serviceId: serviceId,
- state: state ?? this.state,
- );
- }
-}
-
-/// P2P连接状态
-enum NearbyP2pConnectionState {
- discovered,
- connecting,
- connected,
- disconnected,
- failed,
-}
-
-/// P2P传输进度桩
-class NearbyP2pTransferProgress {
- const NearbyP2pTransferProgress({
- required this.transferId,
- required this.fileName,
- required this.totalBytes,
- required this.transferredBytes,
- this.isComplete = false,
- this.error,
- });
-
- final String transferId;
- final String fileName;
- final int totalBytes;
- final int transferredBytes;
- final bool isComplete;
- final String? error;
-
- double get progress => totalBytes > 0 ? transferredBytes / totalBytes : 0;
-}
-
/// NearbyService适配器 Web桩实现
/// Web平台不支持近场通信,所有方法返回false/空
class NearbyServiceAdapter {
@@ -77,15 +22,10 @@ class NearbyServiceAdapter {
// 属性
static bool get isPlatformSupported => false;
- static bool get isP2pSupported => false;
bool get isInitialized => false;
bool get isDiscovering => false;
bool get isConnected => false;
String? get connectedDeviceId => null;
- bool get p2pIsAdvertising => false;
- bool get p2pIsDiscovering => false;
- List get p2pDiscoveredDevices => [];
- List get p2pConnectedDevices => [];
// 流
Stream> get onDevicesChanged => const Stream.empty();
@@ -93,12 +33,8 @@ class NearbyServiceAdapter {
Stream get onIncomingTask => const Stream.empty();
Stream get onConnectionStateChanged =>
const Stream.empty();
- Stream> get onP2pDevicesChanged =>
- const Stream.empty();
- Stream get onP2pTransferProgress =>
- const Stream.empty();
- // nearby_service 方法(全部返回false)
+ // 方法(全部返回false)
Future initialize() async => false;
Future startDiscovery() async => false;
Future stopDiscovery() async => false;
@@ -115,19 +51,6 @@ class NearbyServiceAdapter {
required String filePath,
}) async => false;
- // nearby_connections P2P 方法(全部返回false)
- Future requestP2pPermissions() async => false;
- Future startP2pAdvertising(String deviceName) async => false;
- Future stopP2pAdvertising() async {}
- Future startP2pDiscovery() async => false;
- Future stopP2pDiscovery() async {}
- Future requestP2pConnection(String endpointId) async => false;
- Future acceptP2pConnection(String endpointId) async {}
- Future rejectP2pConnection(String endpointId) async {}
- Future disconnectP2pEndpoint(String endpointId) async {}
- Future sendP2pFile(String endpointId, String filePath) async => false;
- Future sendP2pBytes(dynamic endpointId, dynamic data) async => false;
-
// 其他方法
Future getCurrentDeviceInfo() async => null;
Future requestPermissions() async => false;
diff --git a/lib/features/file_transfer/services/transport/tcp_socket_service.dart b/lib/features/file_transfer/services/transport/tcp_socket_service.dart
index b7b041ec..272eff03 100644
--- a/lib/features/file_transfer/services/transport/tcp_socket_service.dart
+++ b/lib/features/file_transfer/services/transport/tcp_socket_service.dart
@@ -328,7 +328,7 @@ class TcpSocketService {
final json = jsonDecode(utf8.decode(payload)) as Map;
final fileId = json['fileId'] as String? ?? '';
final fileName = json['fileName'] as String? ?? 'unknown';
- final fileSize = json['fileSize'] as int? ?? 0;
+ final fileSize = (json['fileSize'] as num?)?.toInt() ?? 0;
final mimeType =
json['mimeType'] as String? ?? 'application/octet-stream';
diff --git a/lib/features/file_transfer/services/transport/usb_transport_service.dart b/lib/features/file_transfer/services/transport/usb_transport_service.dart
index 5706d7bc..26fe4bf9 100644
--- a/lib/features/file_transfer/services/transport/usb_transport_service.dart
+++ b/lib/features/file_transfer/services/transport/usb_transport_service.dart
@@ -60,14 +60,14 @@ class UsbDevice {
factory UsbDevice.fromMap(Map map) {
return UsbDevice(
- deviceId: map['deviceId'] as int? ?? 0,
- vendorId: map['vendorId'] as int? ?? 0,
- productId: map['productId'] as int? ?? 0,
+ deviceId: (map['deviceId'] as num?)?.toInt() ?? 0,
+ vendorId: (map['vendorId'] as num?)?.toInt() ?? 0,
+ productId: (map['productId'] as num?)?.toInt() ?? 0,
deviceName: map['deviceName'] as String? ?? '未知USB设备',
usbVersion: UsbVersion.fromId(map['usbVersion'] as String? ?? 'unknown'),
isConnected: map['isConnected'] as bool? ?? false,
- interfaceCount: map['interfaceCount'] as int? ?? 0,
- endpointCount: map['endpointCount'] as int? ?? 0,
+ interfaceCount: (map['interfaceCount'] as num?)?.toInt() ?? 0,
+ endpointCount: (map['endpointCount'] as num?)?.toInt() ?? 0,
);
}
}
diff --git a/lib/features/file_transfer/services/transport/webrtc_service.dart b/lib/features/file_transfer/services/transport/webrtc_service.dart
index beaa4702..8e967b6d 100644
--- a/lib/features/file_transfer/services/transport/webrtc_service.dart
+++ b/lib/features/file_transfer/services/transport/webrtc_service.dart
@@ -334,7 +334,7 @@ class WebRtcService {
void _handleFileMeta(Map data) {
final taskId = data['taskId'] as String? ?? '';
final fileName = data['fileName'] as String? ?? 'unknown';
- final fileSize = data['fileSize'] as int? ?? 0;
+ final fileSize = (data['fileSize'] as num?)?.toInt() ?? 0;
final mimeType = data['mimeType'] as String? ?? 'application/octet-stream';
final saveDir = Directory.systemTemp;
@@ -483,7 +483,7 @@ class WebRtcService {
final rtcCandidate = RTCIceCandidate(
candidate['candidate'] as String?,
candidate['sdpMid'] as String?,
- candidate['sdpMLineIndex'] as int?,
+ (candidate['sdpMLineIndex'] as num?)?.toInt(),
);
await _peerConnection!.addCandidate(rtcCandidate);
}
@@ -746,7 +746,7 @@ class WebRtcService {
final rtcCandidate = RTCIceCandidate(
candidate['candidate'] as String?,
candidate['sdpMid'] as String?,
- candidate['sdpMLineIndex'] as int?,
+ (candidate['sdpMLineIndex'] as num?)?.toInt(),
);
await _screenSharePc!.addCandidate(rtcCandidate);
}
diff --git a/lib/features/file_transfer/services/transport/ws_p2p_service.dart b/lib/features/file_transfer/services/transport/ws_p2p_service.dart
index 40c59959..98be89e4 100644
--- a/lib/features/file_transfer/services/transport/ws_p2p_service.dart
+++ b/lib/features/file_transfer/services/transport/ws_p2p_service.dart
@@ -92,8 +92,8 @@ class WsP2pFileMeta {
WsP2pFileMeta(
taskId: json['taskId'] as String? ?? '',
fileName: json['fileName'] as String? ?? 'unknown',
- fileSize: json['fileSize'] as int? ?? 0,
- totalChunks: json['totalChunks'] as int? ?? 0,
+ fileSize: (json['fileSize'] as num?)?.toInt() ?? 0,
+ totalChunks: (json['totalChunks'] as num?)?.toInt() ?? 0,
mimeType: json['mimeType'] as String?,
checksum: json['checksum'] as String?,
);
diff --git a/lib/features/file_transfer/services/transport/ws_relay_chunk_assembler.dart b/lib/features/file_transfer/services/transport/ws_relay_chunk_assembler.dart
index 883b6427..0359ed84 100644
--- a/lib/features/file_transfer/services/transport/ws_relay_chunk_assembler.dart
+++ b/lib/features/file_transfer/services/transport/ws_relay_chunk_assembler.dart
@@ -96,9 +96,9 @@ class ChunkAssembler {
return ChunkAssembler(
taskId: state['taskId'] as String? ?? '',
fileName: state['fileName'] as String? ?? 'unknown',
- fileSize: state['fileSize'] as int? ?? 0,
+ fileSize: (state['fileSize'] as num?)?.toInt() ?? 0,
checksum: state['checksum'] as String? ?? '',
- totalChunks: state['totalChunks'] as int? ?? 0,
+ totalChunks: (state['totalChunks'] as num?)?.toInt() ?? 0,
fromId: state['fromId'] as String? ?? '',
);
}
diff --git a/lib/features/file_transfer/services/transport/ws_relay_resume_handler.dart b/lib/features/file_transfer/services/transport/ws_relay_resume_handler.dart
index ebbe051f..006dc19d 100644
--- a/lib/features/file_transfer/services/transport/ws_relay_resume_handler.dart
+++ b/lib/features/file_transfer/services/transport/ws_relay_resume_handler.dart
@@ -77,7 +77,7 @@ class TransferResumeState {
?.cast() ??
[],
lastActiveTime: DateTime.fromMillisecondsSinceEpoch(
- json['lastActiveTime'] as int? ?? 0,
+ (json['lastActiveTime'] as num?)?.toInt() ?? 0,
),
isPaused: json['isPaused'] as bool? ?? false,
);
@@ -247,9 +247,9 @@ class WsRelayResumeHandler {
if (payload == null) return;
final taskId = payload['taskId'] as String? ?? '';
- final chunkIndex = payload['chunkIndex'] as int? ?? -1;
- final receivedCount = payload['receivedCount'] as int? ?? 0;
- final totalChunks = payload['totalChunks'] as int? ?? 0;
+ final chunkIndex = (payload['chunkIndex'] as num?)?.toInt() ?? -1;
+ final receivedCount = (payload['receivedCount'] as num?)?.toInt() ?? 0;
+ final totalChunks = (payload['totalChunks'] as num?)?.toInt() ?? 0;
Log.d(
'ResumeHandler: received chunkAck taskId=$taskId '
@@ -265,8 +265,8 @@ class WsRelayResumeHandler {
final missingChunks = (payload['missingChunks'] as List?)
?.cast() ??
[];
- final receivedCount = payload['receivedCount'] as int? ?? 0;
- final totalChunks = payload['totalChunks'] as int? ?? 0;
+ final receivedCount = (payload['receivedCount'] as num?)?.toInt() ?? 0;
+ final totalChunks = (payload['totalChunks'] as num?)?.toInt() ?? 0;
Log.i(
'ResumeHandler: received resumeRequest from=${message.from} '
diff --git a/lib/features/file_transfer/services/transport/ws_relay_service.dart b/lib/features/file_transfer/services/transport/ws_relay_service.dart
index 17e0c492..efe3358c 100644
--- a/lib/features/file_transfer/services/transport/ws_relay_service.dart
+++ b/lib/features/file_transfer/services/transport/ws_relay_service.dart
@@ -350,9 +350,9 @@ class WsRelayService {
void _handleFileMeta(String fromId, Map data) {
final taskId = data['taskId'] as String? ?? '';
final fileName = data['fileName'] as String? ?? 'unknown';
- final fileSize = data['fileSize'] as int? ?? 0;
+ final fileSize = (data['fileSize'] as num?)?.toInt() ?? 0;
final checksum = data['checksum'] as String? ?? '';
- final totalChunks = data['totalChunks'] as int? ?? 0;
+ final totalChunks = (data['totalChunks'] as num?)?.toInt() ?? 0;
_assemblers[taskId] = ChunkAssembler(
taskId: taskId,
@@ -383,7 +383,7 @@ class WsRelayService {
void _handleFileChunk(String fromId, Map data) {
final taskId = data['taskId'] as String? ?? '';
- final chunkIndex = data['chunkIndex'] as int? ?? 0;
+ final chunkIndex = (data['chunkIndex'] as num?)?.toInt() ?? 0;
final base64Data = data['data'] as String? ?? '';
final assembler = _assemblers[taskId];
diff --git a/lib/features/home/presentation/providers/readlater/readlater_provider.dart b/lib/features/home/presentation/providers/readlater/readlater_provider.dart
index f05961e5..a715a927 100644
--- a/lib/features/home/presentation/providers/readlater/readlater_provider.dart
+++ b/lib/features/home/presentation/providers/readlater/readlater_provider.dart
@@ -246,7 +246,7 @@ class ReadLaterNotifier extends Notifier {
final metaThumb = msg.meta?['thumbnail'] as String?;
final metaFileName = msg.meta?['fileName'] as String?;
final metaMimeType = msg.meta?['mimeType'] as String?;
- final metaFileSize = msg.meta?['fileSize'] as int?;
+ final metaFileSize = (msg.meta?['fileSize'] as num?)?.toInt();
final metaFilePath = msg.meta?['filePath'] as String?;
return ReadLaterEntry(
id: msg.id,
@@ -271,7 +271,7 @@ class ReadLaterNotifier extends Notifier {
final metaThumb = msg.meta?['thumbnail'] as String?;
final metaFileName = msg.meta?['fileName'] as String?;
final metaMimeType = msg.meta?['mimeType'] as String?;
- final metaFileSize = msg.meta?['fileSize'] as int?;
+ final metaFileSize = (msg.meta?['fileSize'] as num?)?.toInt();
final metaFilePath = msg.meta?['filePath'] as String?;
return ReadLaterEntry(
id: msg.id,
@@ -323,7 +323,7 @@ class ReadLaterNotifier extends Notifier {
final att = msg.attachments.firstOrNull;
final metaMimeType = msg.meta?['mimeType'] as String?;
final metaFileName = msg.meta?['fileName'] as String?;
- final metaFileSize = msg.meta?['fileSize'] as int?;
+ final metaFileSize = (msg.meta?['fileSize'] as num?)?.toInt();
final metaFilePath = msg.meta?['filePath'] as String?;
final mimeType =
att?.fileType ??
diff --git a/lib/features/mine/achievement/models/achievement_models.dart b/lib/features/mine/achievement/models/achievement_models.dart
index 367b8930..bd9cc046 100644
--- a/lib/features/mine/achievement/models/achievement_models.dart
+++ b/lib/features/mine/achievement/models/achievement_models.dart
@@ -38,16 +38,16 @@ class Achievement {
: 0.0;
factory Achievement.fromJson(Map json) => Achievement(
- id: json['id'] as int? ?? 0,
+ id: (json['id'] as num?)?.toInt() ?? 0,
name: json['name'] as String? ?? '',
icon: json['icon'] as String? ?? '🏅',
type: json['type'] as String? ?? '',
description: json['description'] as String? ?? '',
- conditionValue: json['condition_value'] as int? ?? 0,
- currentValue: json['current_value'] as int? ?? 0,
+ conditionValue: (json['condition_value'] as num?)?.toInt() ?? 0,
+ currentValue: (json['current_value'] as num?)?.toInt() ?? 0,
isAchieved: json['is_achieved'] as bool? ?? false,
isClaimed: json['is_claimed'] as bool? ?? false,
- reward: (json['reward_score'] ?? json['reward']) as int? ?? 0,
+ reward: ((json['reward_score'] ?? json['reward']) as num?)?.toInt() ?? 0,
);
}
@@ -132,8 +132,8 @@ class MyAchievementData {
?.map((e) => UserTitle.fromJson(e as Map))
.toList() ??
[],
- score: json['score'] as int? ?? 0,
- signinDays: json['signin_days'] as int? ?? 0,
+ score: (json['score'] as num?)?.toInt() ?? 0,
+ signinDays: (json['signin_days'] as num?)?.toInt() ?? 0,
);
}
diff --git a/lib/features/mine/achievement/presentation/achievement_page.dart b/lib/features/mine/achievement/presentation/achievement_page.dart
index ddf77497..44639465 100644
--- a/lib/features/mine/achievement/presentation/achievement_page.dart
+++ b/lib/features/mine/achievement/presentation/achievement_page.dart
@@ -488,7 +488,7 @@ class _AchievementPageState extends ConsumerState
if (success) {
_confettiController.play();
final state = ref.read(achievementProvider);
- final reward = state.claimResult?['reward_score'] as int? ?? 0;
+ final reward = (state.claimResult?['reward_score'] as num?)?.toInt() ?? 0;
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
@@ -1573,7 +1573,7 @@ class _CheckinSuccessBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final reward = result['reward'] as int? ?? 0;
+ final reward = (result['reward'] as num?)?.toInt() ?? 0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
child: Container(
diff --git a/lib/features/mine/achievement/presentation/checkin_page.dart b/lib/features/mine/achievement/presentation/checkin_page.dart
index b7fda954..3ff5d1f5 100644
--- a/lib/features/mine/achievement/presentation/checkin_page.dart
+++ b/lib/features/mine/achievement/presentation/checkin_page.dart
@@ -363,7 +363,7 @@ class _CheckinPageState extends ConsumerState
_comboCount++;
_confettiController.play();
final state = ref.read(checkinProvider);
- final reward = state.checkinResult?['reward'] as int? ?? 0;
+ final reward = (state.checkinResult?['reward'] as num?)?.toInt() ?? 0;
final ext = Theme.of(context).extension()!;
showCupertinoDialog(
context: context,
diff --git a/lib/features/mine/achievement/providers/badge_provider.dart b/lib/features/mine/achievement/providers/badge_provider.dart
index 52a5d92d..34eb3557 100644
--- a/lib/features/mine/achievement/providers/badge_provider.dart
+++ b/lib/features/mine/achievement/providers/badge_provider.dart
@@ -50,26 +50,22 @@ class Badge {
final int scoreReward;
factory Badge.fromJson(Map json) => Badge(
- id: json['id'] as int? ?? 0,
- name: json['name'] as String? ?? '',
- icon: json['icon'] as String? ?? '🎖️',
- description: json['description'] as String? ?? '',
- rarity: json['rarity'] as String? ?? 'common',
- type: json['type'] as String? ?? '',
- condition: json['condition'] as String? ?? '',
- conditionValue: json['condition_value'] as int? ?? 0,
- isUnlocked: json['is_unlocked'] as bool? ?? false,
- isDisplayed: json['is_displayed'] as bool? ?? false,
- unlockedAt: json['unlocked_at'] as String?,
- expReward: json['exp_reward'] as int? ?? 0,
- scoreReward: json['score_reward'] as int? ?? 0,
- );
+ id: (json['id'] as num?)?.toInt() ?? 0,
+ name: json['name'] as String? ?? '',
+ icon: json['icon'] as String? ?? '🎖️',
+ description: json['description'] as String? ?? '',
+ rarity: json['rarity'] as String? ?? 'common',
+ type: json['type'] as String? ?? '',
+ condition: json['condition'] as String? ?? '',
+ conditionValue: (json['condition_value'] as num?)?.toInt() ?? 0,
+ isUnlocked: json['is_unlocked'] as bool? ?? false,
+ isDisplayed: json['is_displayed'] as bool? ?? false,
+ unlockedAt: json['unlocked_at'] as String?,
+ expReward: (json['exp_reward'] as num?)?.toInt() ?? 0,
+ scoreReward: (json['score_reward'] as num?)?.toInt() ?? 0,
+ );
- Badge copyWith({
- bool? isUnlocked,
- bool? isDisplayed,
- String? unlockedAt,
- }) =>
+ Badge copyWith({bool? isUnlocked, bool? isDisplayed, String? unlockedAt}) =>
Badge(
id: id,
name: name,
@@ -105,24 +101,23 @@ class BadgeStats {
final int maxDisplay;
factory BadgeStats.fromJson(Map json) => BadgeStats(
- total: json['total'] as int? ?? 0,
- unlocked: json['unlocked'] as int? ?? 0,
- displayedCount: json['displayed_count'] as int? ?? 0,
- maxDisplay: json['max_display'] as int? ?? 3,
- );
+ total: (json['total'] as num?)?.toInt() ?? 0,
+ unlocked: (json['unlocked'] as num?)?.toInt() ?? 0,
+ displayedCount: (json['displayed_count'] as num?)?.toInt() ?? 0,
+ maxDisplay: (json['max_display'] as num?)?.toInt() ?? 3,
+ );
BadgeStats copyWith({
int? total,
int? unlocked,
int? displayedCount,
int? maxDisplay,
- }) =>
- BadgeStats(
- total: total ?? this.total,
- unlocked: unlocked ?? this.unlocked,
- displayedCount: displayedCount ?? this.displayedCount,
- maxDisplay: maxDisplay ?? this.maxDisplay,
- );
+ }) => BadgeStats(
+ total: total ?? this.total,
+ unlocked: unlocked ?? this.unlocked,
+ displayedCount: displayedCount ?? this.displayedCount,
+ maxDisplay: maxDisplay ?? this.maxDisplay,
+ );
}
// ============================================================
@@ -146,11 +141,9 @@ class BadgeState {
final List displayBadgeIds;
final bool isSettingDisplay;
- List get unlockedBadges =>
- badges.where((b) => b.isUnlocked).toList();
+ List get unlockedBadges => badges.where((b) => b.isUnlocked).toList();
- List get lockedBadges =>
- badges.where((b) => !b.isUnlocked).toList();
+ List get lockedBadges => badges.where((b) => !b.isUnlocked).toList();
List get displayedBadges =>
badges.where((b) => b.isDisplayed).toList();
@@ -163,15 +156,14 @@ class BadgeState {
BadgeStats? stats,
List? displayBadgeIds,
bool? isSettingDisplay,
- }) =>
- BadgeState(
- isLoading: isLoading ?? this.isLoading,
- error: clearError ? null : (error ?? this.error),
- badges: badges ?? this.badges,
- stats: stats ?? this.stats,
- displayBadgeIds: displayBadgeIds ?? this.displayBadgeIds,
- isSettingDisplay: isSettingDisplay ?? this.isSettingDisplay,
- );
+ }) => BadgeState(
+ isLoading: isLoading ?? this.isLoading,
+ error: clearError ? null : (error ?? this.error),
+ badges: badges ?? this.badges,
+ stats: stats ?? this.stats,
+ displayBadgeIds: displayBadgeIds ?? this.displayBadgeIds,
+ isSettingDisplay: isSettingDisplay ?? this.isSettingDisplay,
+ );
}
// ============================================================
@@ -203,7 +195,9 @@ class BadgeNotifier extends Notifier {
final list = (data['list'] as List? ?? [])
.map((e) => Badge.fromJson(e as Map))
.toList();
- final stats = BadgeStats.fromJson(data['stats'] as Map? ?? {});
+ final stats = BadgeStats.fromJson(
+ data['stats'] as Map? ?? {},
+ );
final displayIds = list
.where((b) => b.isDisplayed)
.map((b) => b.id)
@@ -254,9 +248,7 @@ class BadgeNotifier extends Notifier {
badges: updatedBadges,
displayBadgeIds: badgeIds,
isSettingDisplay: false,
- stats: state.stats.copyWith(
- displayedCount: badgeIds.length,
- ),
+ stats: state.stats.copyWith(displayedCount: badgeIds.length),
);
return true;
} on DioException catch (e) {
@@ -284,7 +276,7 @@ class BadgeNotifier extends Notifier {
final data = e.response?.data;
if (data is Map) {
return ApiException(
- code: data['code'] as int? ?? e.response?.statusCode ?? -5,
+ code: (data['code'] as num?)?.toInt() ?? e.response?.statusCode ?? -5,
message: data['msg'] as String? ?? '请求失败',
);
}
@@ -299,5 +291,6 @@ class BadgeNotifier extends Notifier {
// Provider
// ============================================================
-final badgeProvider =
- NotifierProvider(BadgeNotifier.new);
+final badgeProvider = NotifierProvider(
+ BadgeNotifier.new,
+);
diff --git a/lib/features/mine/achievement/services/achievement_service.dart b/lib/features/mine/achievement/services/achievement_service.dart
index bead296c..7311236c 100644
--- a/lib/features/mine/achievement/services/achievement_service.dart
+++ b/lib/features/mine/achievement/services/achievement_service.dart
@@ -66,7 +66,7 @@ class AchievementService {
data: {'achievement_id': achievementId},
);
final respData = response.data as Map;
- final code = respData['code'] as int? ?? 0;
+ final code = (respData['code'] as num?)?.toInt() ?? 0;
if (code != 1) {
throw ApiException(
code: code,
@@ -95,7 +95,7 @@ class AchievementService {
},
);
final respData = response.data as Map;
- final code = respData['code'] as int? ?? 0;
+ final code = (respData['code'] as num?)?.toInt() ?? 0;
if (code != 1) {
throw ApiException(
code: code,
diff --git a/lib/features/mine/settings/presentation/general/general_settings_page.dart b/lib/features/mine/settings/presentation/general/general_settings_page.dart
index 23522725..3a83875d 100644
--- a/lib/features/mine/settings/presentation/general/general_settings_page.dart
+++ b/lib/features/mine/settings/presentation/general/general_settings_page.dart
@@ -3,7 +3,7 @@
/// 创建时间: 2026-04-28
/// 更新时间: 2026-06-07
/// 作用: 声音/震动/通知/显示/性能/隐私/高级等设置
-/// 上次更新: i18n支持,替换硬编码中文为翻译调用
+/// 上次更新: i18n支持,替换硬编码中文为翻译调用;修复鸿蒙端重新打开引导页无法跳转的问题
/// ============================================================
import 'package:flutter/cupertino.dart';
@@ -25,6 +25,8 @@ import '../../../../../../core/theme/app_typography.dart';
import '../../../../../../core/theme/app_radius.dart';
import '../../../../../../l10n/translations.dart';
import '../../../../../../shared/widgets/adaptive/adaptive_back_button.dart';
+import '../../../../onboarding/presentation/onboarding_page.dart';
+import '../../../../../../core/utils/platform/platform_utils.dart' as pu;
import '../../../../../../shared/widgets/containers/glass_container.dart';
import '../../providers/general_settings_provider.dart';
import '../../providers/plugin_provider.dart';
@@ -565,7 +567,8 @@ class _GeneralSettingsPageState extends ConsumerState
behavior: HitTestBehavior.opaque,
onTap: () {
HapticService.selection();
- final message = flag?.unsupportedMessage ?? t.common.featureNotSupported;
+ final message =
+ flag?.unsupportedMessage ?? t.common.featureNotSupported;
showCupertinoDialog(
context: context,
builder: (_) => CupertinoAlertDialog(
@@ -1023,7 +1026,17 @@ class _GeneralSettingsPageState extends ConsumerState
await KvStorage.setBool(StorageKeys.onboardingCompleted, false);
await KvStorage.setShowOnboarding(true);
if (mounted) {
- context.appGo(AppRoutes.onboardingReview);
+ if (pu.isOhos) {
+ // 鸿蒙端直接push引导页,因为onboarding路由未在OHOS路由注册表中注册
+ Navigator.of(context).push(
+ CupertinoPageRoute(
+ fullscreenDialog: true,
+ builder: (_) => const OnboardingPage(),
+ ),
+ );
+ } else {
+ context.appGo(AppRoutes.onboarding);
+ }
}
}
}
diff --git a/lib/features/mine/settings/presentation/privacy/permission_management_page.dart b/lib/features/mine/settings/presentation/privacy/permission_management_page.dart
index f4874d95..c21dc6a5 100644
--- a/lib/features/mine/settings/presentation/privacy/permission_management_page.dart
+++ b/lib/features/mine/settings/presentation/privacy/permission_management_page.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 权限管理页面
/// 创建时间: 2026-05-07
-/// 更新时间: 2026-05-31
+/// 更新时间: 2026-06-06
/// 作用: 展示所有权限状态,支持请求/跳转系统设置,区分虚拟权限和真实权限
-/// 上次更新: 添加多语言支持,使用翻译键替代硬编码中文
+/// 上次更新: 修复RenderChartFadeTransition disposed错误—添加mounted检查+dispose保护
/// ============================================================
import 'package:flutter/cupertino.dart';
@@ -75,6 +75,12 @@ class PermissionManagementPage extends ConsumerStatefulWidget {
class _PermissionManagementPageState
extends ConsumerState {
+ @override
+ void dispose() {
+ // 确保在页面销毁时取消所有动画,避免 RenderChartFadeTransition disposed 错误
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
@@ -249,7 +255,9 @@ class _PermissionManagementPageState
.read(generalFieldsProvider.notifier)
.setShakeToSwitch(enabled);
// 使用静默刷新,避免全页面loading导致开关组件被销毁
- ref.read(permissionStatusProvider.notifier)._refreshSilently();
+ ref
+ .read(permissionStatusProvider.notifier)
+ ._refreshSilently();
},
perm: perm,
)
@@ -283,6 +291,7 @@ class _PermissionManagementPageState
/// 请求权限
Future _requestPermission(AppPermission perm) async {
final granted = await PermissionService.requestPermission(context, perm);
+ if (!mounted) return; // 防止页面已销毁时更新状态
if (granted) {
PermissionService.recordUsage(perm);
ref.read(permissionStatusProvider.notifier).refresh();
@@ -299,7 +308,7 @@ class _PermissionManagementPageState
final chartData = <_PermissionChartEntry>[];
for (final ap in permissions) {
final data = stats[ap.name];
- final count = data != null ? (data['count'] as int? ?? 0) : 0;
+ final count = data != null ? ((data['count'] as num?)?.toInt() ?? 0) : 0;
if (count > 0) {
chartData.add(_PermissionChartEntry(permission: ap, count: count));
}
@@ -369,35 +378,38 @@ class _PermissionManagementPageState
SizedBox(
width: 160,
height: 160,
- child: SafeChartWidget(chartName: '权限分布', chartBuilder: (context) => SfCircularChart(
- series: >[
- DoughnutSeries<_PermissionChartEntry, String>(
- animationDuration: 0,
- dataSource: chartData,
- xValueMapper: (_PermissionChartEntry d, _) =>
- d.permission.label(context),
- yValueMapper: (_PermissionChartEntry d, _) =>
- d.count.toDouble(),
- pointColorMapper: (_PermissionChartEntry d, _) =>
- d.permission.color,
- innerRadius: '38%',
- dataLabelSettings: DataLabelSettings(
- isVisible: true,
- textStyle: AppTypography.caption2.copyWith(
- color: CupertinoColors.white,
- fontWeight: FontWeight.w700,
+ child: SafeChartWidget(
+ chartName: '权限分布',
+ chartBuilder: (context) => SfCircularChart(
+ series: >[
+ DoughnutSeries<_PermissionChartEntry, String>(
+ animationDuration: 0,
+ dataSource: chartData,
+ xValueMapper: (_PermissionChartEntry d, _) =>
+ d.permission.label(context),
+ yValueMapper: (_PermissionChartEntry d, _) =>
+ d.count.toDouble(),
+ pointColorMapper: (_PermissionChartEntry d, _) =>
+ d.permission.color,
+ innerRadius: '38%',
+ dataLabelSettings: DataLabelSettings(
+ isVisible: true,
+ textStyle: AppTypography.caption2.copyWith(
+ color: CupertinoColors.white,
+ fontWeight: FontWeight.w700,
+ ),
),
+ dataLabelMapper: (_PermissionChartEntry d, _) {
+ final percentage = d.count / totalCount * 100;
+ return percentage >= 8
+ ? '${percentage.toStringAsFixed(0)}%'
+ : '';
+ },
),
- dataLabelMapper: (_PermissionChartEntry d, _) {
- final percentage = d.count / totalCount * 100;
- return percentage >= 8
- ? '${percentage.toStringAsFixed(0)}%'
- : '';
- },
- ),
- ],
+ ],
+ ),
),
- )),
+ ),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
diff --git a/lib/features/mine/user_center/models/user_center_models.dart b/lib/features/mine/user_center/models/user_center_models.dart
index 905ad094..f65412f4 100644
--- a/lib/features/mine/user_center/models/user_center_models.dart
+++ b/lib/features/mine/user_center/models/user_center_models.dart
@@ -61,22 +61,22 @@ class PublicProfileModel {
factory PublicProfileModel.fromJson(Map json) {
return PublicProfileModel(
- id: json['id'] as int? ?? 0,
+ id: (json['id'] as num?)?.toInt() ?? 0,
username: json['username'] as String? ?? '',
nickname: json['nickname'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
avatarUrl: json['avatar_url'] as String? ?? '',
bio: json['bio'] as String? ?? '',
- score: json['score'] as int? ?? 0,
- level: json['level'] as int? ?? 1,
- exp: json['exp'] as int? ?? 0,
- expToNext: json['exp_to_next'] as int? ?? 0,
+ score: (json['score'] as num?)?.toInt() ?? 0,
+ level: (json['level'] as num?)?.toInt() ?? 1,
+ exp: (json['exp'] as num?)?.toInt() ?? 0,
+ expToNext: (json['exp_to_next'] as num?)?.toInt() ?? 0,
expProgress: (json['exp_progress'] as num?)?.toDouble() ?? 0.0,
levelTitle: json['level_title'] as String? ?? '',
- signinDays: json['signin_days'] as int? ?? 0,
- articleCount: json['article_count'] as int? ?? 0,
- favoriteCount: json['favorite_count'] as int? ?? 0,
- likeCount: json['like_count'] as int? ?? 0,
+ signinDays: (json['signin_days'] as num?)?.toInt() ?? 0,
+ articleCount: (json['article_count'] as num?)?.toInt() ?? 0,
+ favoriteCount: (json['favorite_count'] as num?)?.toInt() ?? 0,
+ likeCount: (json['like_count'] as num?)?.toInt() ?? 0,
jointime: json['jointime']?.toString() ?? '',
);
}
@@ -172,22 +172,22 @@ class DashboardModel {
factory DashboardModel.fromJson(Map json) {
return DashboardModel(
- userId: json['user_id'] as int? ?? 0,
- score: json['score'] as int? ?? 0,
- level: json['level'] as int? ?? 1,
- exp: json['exp'] as int? ?? 0,
- expToNext: json['exp_to_next'] as int? ?? 0,
+ userId: (json['user_id'] as num?)?.toInt() ?? 0,
+ score: (json['score'] as num?)?.toInt() ?? 0,
+ level: (json['level'] as num?)?.toInt() ?? 1,
+ exp: (json['exp'] as num?)?.toInt() ?? 0,
+ expToNext: (json['exp_to_next'] as num?)?.toInt() ?? 0,
expProgress: (json['exp_progress'] as num?)?.toDouble() ?? 0.0,
levelTitle: json['level_title'] as String? ?? '',
- signinDays: json['signin_days'] as int? ?? 0,
- signinCount: json['signin_count'] as int? ?? 0,
- favoriteCount: json['favorite_count'] as int? ?? 0,
- noteCount: json['note_count'] as int? ?? 0,
- likeCount: json['like_count'] as int? ?? 0,
- articleCount: json['article_count'] as int? ?? 0,
- commentCount: json['comment_count'] as int? ?? 0,
- viewCount: json['view_count'] as int? ?? 0,
- readlaterCount: json['readlater_count'] as int? ?? 0,
+ signinDays: (json['signin_days'] as num?)?.toInt() ?? 0,
+ signinCount: (json['signin_count'] as num?)?.toInt() ?? 0,
+ favoriteCount: (json['favorite_count'] as num?)?.toInt() ?? 0,
+ noteCount: (json['note_count'] as num?)?.toInt() ?? 0,
+ likeCount: (json['like_count'] as num?)?.toInt() ?? 0,
+ articleCount: (json['article_count'] as num?)?.toInt() ?? 0,
+ commentCount: (json['comment_count'] as num?)?.toInt() ?? 0,
+ viewCount: (json['view_count'] as num?)?.toInt() ?? 0,
+ readlaterCount: (json['readlater_count'] as num?)?.toInt() ?? 0,
last7days: json['last_7days'] as List? ?? [],
);
}
diff --git a/lib/features/mine/user_center/presentation/learning_center_page.dart b/lib/features/mine/user_center/presentation/learning_center_page.dart
index 7f2f22bd..00134f7f 100644
--- a/lib/features/mine/user_center/presentation/learning_center_page.dart
+++ b/lib/features/mine/user_center/presentation/learning_center_page.dart
@@ -1069,12 +1069,12 @@ class _DashboardHero extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final score = data?['score'] as int? ?? 0;
- final signinDays = data?['signin_days'] as int? ?? 0;
- final signinCount = data?['signin_count'] as int? ?? 0;
- final favoriteCount = data?['favorite_count'] as int? ?? 0;
- final likeCount = data?['like_count'] as int? ?? 0;
- final commentCount = data?['comment_count'] as int? ?? 0;
+ final score = (data?['score'] as num?)?.toInt() ?? 0;
+ final signinDays = (data?['signin_days'] as num?)?.toInt() ?? 0;
+ final signinCount = (data?['signin_count'] as num?)?.toInt() ?? 0;
+ final favoriteCount = (data?['favorite_count'] as num?)?.toInt() ?? 0;
+ final likeCount = (data?['like_count'] as num?)?.toInt() ?? 0;
+ final commentCount = (data?['comment_count'] as num?)?.toInt() ?? 0;
final nextThreshold = _getNextTitleThreshold(score);
final progress = nextThreshold > 0
diff --git a/lib/features/mine/user_center/presentation/public_profile_page.dart b/lib/features/mine/user_center/presentation/public_profile_page.dart
index 06944c5e..c536a249 100644
--- a/lib/features/mine/user_center/presentation/public_profile_page.dart
+++ b/lib/features/mine/user_center/presentation/public_profile_page.dart
@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 公开用户主页
// 创建时间: 2026-04-29
-// 更新时间: 2026-05-23
+// 更新时间: 2026-06-06
// 作用: 用户公开主页+视差头部+互动按钮+成就展示+文章预览
-// 上次更新: v14.105.0 CachedNetworkImage替换为SafeCachedImage防止数据库异常
+// 上次更新: 硬编码中文替换为翻译键
// ============================================================
import 'package:flutter/cupertino.dart';
@@ -16,6 +16,9 @@ import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_typography.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/services/feature/feature_flag_service.dart';
+import '../../../../l10n/translation_resolver.dart';
+import '../../../../l10n/types/t_common.dart';
+import '../../../../l10n/types/t_profile.dart';
import '../../../../shared/widgets/containers/glass_container.dart';
import '../../../../shared/widgets/media/safe_cached_image.dart';
import '../services/user_center_service.dart';
@@ -72,12 +75,13 @@ class _PublicProfilePageState extends ConsumerState {
@override
Widget build(BuildContext context) {
final ext = Theme.of(context).extension()!;
+ final tp = ref.read(translationsProvider).profile;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: const AdaptiveBackButton(),
- middle: Text(_profile?['display_name'] as String? ?? '用户主页'),
- previousPageTitle: '返回',
+ middle: Text(_profile?['display_name'] as String? ?? tp.userProfile),
+ previousPageTitle: tp.goBack,
backgroundColor: ext.bgElevated.withValues(alpha: 0.85),
),
child: SafeArea(
@@ -97,6 +101,7 @@ class _PublicProfilePageState extends ConsumerState {
profile: _profile!,
offset: _headerOffset,
ext: ext,
+ tp: tp,
).animate().fadeIn(duration: 500.ms),
),
const SliverToBoxAdapter(
@@ -105,6 +110,7 @@ class _PublicProfilePageState extends ConsumerState {
child: _ProfileStatsRow(
profile: _profile!,
ext: ext,
+ tp: tp,
).animate().fadeIn(duration: 400.ms, delay: 100.ms).slideY(
begin: 0.06,
end: 0,
@@ -118,6 +124,8 @@ class _PublicProfilePageState extends ConsumerState {
child: _InteractionButtons(
profile: _profile!,
ext: ext,
+ tp: tp,
+ tc: ref.read(translationsProvider).common,
).animate().fadeIn(duration: 400.ms, delay: 180.ms),
),
const SliverToBoxAdapter(
@@ -128,6 +136,7 @@ class _PublicProfilePageState extends ConsumerState {
child: _BioSection(
bio: _profile?['bio'] as String? ?? '',
ext: ext,
+ tp: tp,
).animate().fadeIn(duration: 400.ms, delay: 220.ms).slideY(
begin: 0.06,
end: 0,
@@ -140,6 +149,7 @@ class _PublicProfilePageState extends ConsumerState {
child: _TitleBadgeSection(
profile: _profile!,
ext: ext,
+ tp: tp,
).animate().fadeIn(duration: 400.ms, delay: 280.ms).slideY(
begin: 0.06,
end: 0,
@@ -151,6 +161,7 @@ class _PublicProfilePageState extends ConsumerState {
child: _ActivitySummary(
profile: _profile!,
ext: ext,
+ tp: tp,
).animate().fadeIn(duration: 400.ms, delay: 340.ms).slideY(
begin: 0.06,
end: 0,
@@ -168,19 +179,20 @@ class _PublicProfilePageState extends ConsumerState {
}
Widget _buildErrorState(AppThemeExtension ext) {
+ final tp = ref.read(translationsProvider).profile;
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('😕', style: TextStyle(fontSize: 48)),
const SizedBox(height: AppSpacing.sm),
- Text('用户不存在',
+ Text(tp.userNotExist,
style: AppTypography.body.copyWith(color: ext.textHint)),
const SizedBox(height: AppSpacing.md),
CupertinoButton(
color: ext.accent,
borderRadius: AppRadius.pillBorder,
- child: const Text('🔄 重试'),
+ child: Text('🔄 ${tp.retry}'),
onPressed: _loadProfile,
),
],
@@ -194,15 +206,17 @@ class _ParallaxHeader extends StatelessWidget {
required this.profile,
required this.offset,
required this.ext,
+ required this.tp,
});
final Map profile;
final double offset;
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
final avatarUrl = profile['avatar'] as String? ?? '';
- final displayName = profile['display_name'] as String? ?? '匿名用户';
+ final displayName = profile['display_name'] as String? ?? tp.anonymousUser;
final title = profile['title'] as String? ?? '';
return SizedBox(
@@ -357,9 +371,11 @@ class _ProfileStatsRow extends StatelessWidget {
const _ProfileStatsRow({
required this.profile,
required this.ext,
+ required this.tp,
});
final Map profile;
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
@@ -375,7 +391,7 @@ class _ProfileStatsRow extends StatelessWidget {
_AnimatedStat(
emoji: '💰',
value: '${profile['score'] ?? 0}',
- label: '积分',
+ label: tp.points,
color: CupertinoColors.systemOrange,
ext: ext,
),
@@ -383,7 +399,7 @@ class _ProfileStatsRow extends StatelessWidget {
_AnimatedStat(
emoji: '📅',
value: '${profile['signin_days'] ?? 0}',
- label: '签到',
+ label: tp.checkin,
color: CupertinoColors.systemBlue,
ext: ext,
),
@@ -391,7 +407,7 @@ class _ProfileStatsRow extends StatelessWidget {
_AnimatedStat(
emoji: '📄',
value: '${profile['article_count'] ?? 0}',
- label: '文章',
+ label: tp.articles,
color: CupertinoColors.systemTeal,
ext: ext,
),
@@ -399,7 +415,7 @@ class _ProfileStatsRow extends StatelessWidget {
_AnimatedStat(
emoji: '💕',
value: '${profile['favorite_count'] ?? 0}',
- label: '收藏',
+ label: tp.favorites,
color: CupertinoColors.systemPink,
ext: ext,
),
@@ -467,9 +483,13 @@ class _InteractionButtons extends StatelessWidget {
const _InteractionButtons({
required this.profile,
required this.ext,
+ required this.tp,
+ required this.tc,
});
final Map profile;
final AppThemeExtension ext;
+ final TProfile tp;
+ final TCommon tc;
@override
Widget build(BuildContext context) {
@@ -487,7 +507,7 @@ class _InteractionButtons extends StatelessWidget {
children: [
const Text('👋', style: TextStyle(fontSize: 14)),
const SizedBox(width: 6),
- Text('关注',
+ Text(tp.follow,
style: AppTypography.callout.copyWith(
color: ext.textInverse,
fontWeight: FontWeight.w600)),
@@ -497,13 +517,13 @@ class _InteractionButtons extends StatelessWidget {
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
- title: const Text('👋 关注'),
- content: Text('已关注 ${profile['display_name'] ?? '该用户'}'),
+ title: Text('👋 ${tp.follow}'),
+ content: Text('${tp.followed} ${profile['display_name'] ?? tp.theUser}'),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(ctx),
- child: const Text('好的'),
+ child: Text(tp.ok),
),
],
),
@@ -522,7 +542,7 @@ class _InteractionButtons extends StatelessWidget {
children: [
const Text('💬', style: TextStyle(fontSize: 14)),
const SizedBox(width: 6),
- Text('私信',
+ Text(tp.privateMessage,
style: AppTypography.callout.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600)),
@@ -532,13 +552,13 @@ class _InteractionButtons extends StatelessWidget {
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
- title: const Text('💬 私信'),
+ title: Text('💬 ${tp.privateMessage}'),
content: Text(FeatureFlag.privateMessage.unsupportedMessage),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(ctx),
- child: const Text('知道了'),
+ child: Text(tp.gotIt),
),
],
),
@@ -565,21 +585,21 @@ class _InteractionButtons extends StatelessWidget {
onPressed: () {
Navigator.pop(ctx);
},
- child: Text('🔗 分享主页',
+ child: Text('🔗 ${tp.shareProfile}',
style: TextStyle(color: ext.accent)),
),
CupertinoActionSheetAction(
onPressed: () {
Navigator.pop(ctx);
},
- child: const Text('🚫 屏蔽用户',
- style: TextStyle(color: CupertinoColors.systemRed)),
+ child: Text('🚫 ${tp.blockUser}',
+ style: const TextStyle(color: CupertinoColors.systemRed)),
),
],
cancelButton: CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () => Navigator.pop(ctx),
- child: const Text('取消'),
+ child: Text(tc.cancel),
),
),
);
@@ -596,9 +616,11 @@ class _BioSection extends StatelessWidget {
const _BioSection({
required this.bio,
required this.ext,
+ required this.tp,
});
final String bio;
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
@@ -623,7 +645,7 @@ class _BioSection extends StatelessWidget {
child: Text('📝', style: TextStyle(fontSize: 14))),
),
const SizedBox(width: AppSpacing.sm),
- Text('个人简介',
+ Text(tp.personalBio,
style: AppTypography.callout
.copyWith(color: ext.textSecondary)),
],
@@ -645,9 +667,11 @@ class _TitleBadgeSection extends StatelessWidget {
const _TitleBadgeSection({
required this.profile,
required this.ext,
+ required this.tp,
});
final Map profile;
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
@@ -676,7 +700,7 @@ class _TitleBadgeSection extends StatelessWidget {
child: Text('🎖️', style: TextStyle(fontSize: 14))),
),
const SizedBox(width: AppSpacing.sm),
- Text('头衔等级',
+ Text(tp.titleLevel,
style: AppTypography.callout
.copyWith(color: ext.textSecondary)),
],
@@ -686,29 +710,29 @@ class _TitleBadgeSection extends StatelessWidget {
children: [
_TitleBadge(
emoji: '🌱',
- name: '新手',
+ name: tp.beginner,
isActive: title == '新手',
ext: ext,
),
const SizedBox(width: AppSpacing.xs),
_TitleBadge(
emoji: '📖',
- name: '学徒',
+ name: tp.apprentice,
isActive: title == '学徒',
ext: ext,
),
const SizedBox(width: AppSpacing.xs),
_TitleBadge(
emoji: '🔧',
- name: '熟练工',
+ name: tp.skilled,
isActive: title == '熟练工',
ext: ext,
),
const SizedBox(width: AppSpacing.xs),
_TitleBadge(
emoji: '⭐',
- name: '达人',
- isActive: title == '达人',
+ name: tp.expert,
+ isActive: title == '专家',
ext: ext,
),
],
@@ -724,8 +748,8 @@ class _TitleBadgeSection extends StatelessWidget {
),
const SizedBox(width: AppSpacing.xs),
_TitleBadge(
- emoji: '👑',
- name: '大师',
+ emoji: '🏆',
+ name: tp.master,
isActive: title == '大师',
ext: ext,
),
@@ -734,7 +758,7 @@ class _TitleBadgeSection extends StatelessWidget {
const SizedBox(height: AppSpacing.md),
Row(
children: [
- Text('当前积分',
+ Text(tp.currentPoints,
style: AppTypography.caption1
.copyWith(color: ext.textHint)),
const Spacer(),
@@ -797,18 +821,20 @@ class _ActivitySummary extends StatelessWidget {
const _ActivitySummary({
required this.profile,
required this.ext,
+ required this.tp,
});
final Map profile;
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
- final signinCount = profile['signin_count'] as int? ?? 0;
- final noteCount = profile['note_count'] as int? ?? 0;
- final likeCount = profile['like_count'] as int? ?? 0;
- final commentCount = profile['comment_count'] as int? ?? 0;
- final viewCount = profile['view_count'] as int? ?? 0;
- final readlaterCount = profile['readlater_count'] as int? ?? 0;
+ final signinCount = (profile['signin_count'] as num?)?.toInt() ?? 0;
+ final noteCount = (profile['note_count'] as num?)?.toInt() ?? 0;
+ final likeCount = (profile['like_count'] as num?)?.toInt() ?? 0;
+ final commentCount = (profile['comment_count'] as num?)?.toInt() ?? 0;
+ final viewCount = (profile['view_count'] as num?)?.toInt() ?? 0;
+ final readlaterCount = (profile['readlater_count'] as num?)?.toInt() ?? 0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md)
@@ -832,7 +858,7 @@ class _ActivitySummary extends StatelessWidget {
child: Text('📊', style: TextStyle(fontSize: 14))),
),
const SizedBox(width: AppSpacing.sm),
- Text('活跃数据',
+ Text(tp.activeData,
style: AppTypography.callout
.copyWith(color: ext.textSecondary)),
],
@@ -844,37 +870,37 @@ class _ActivitySummary extends StatelessWidget {
children: [
_ActivityChip(
emoji: '📅',
- label: '签到 $signinCount 次',
+ label: tp.signInCount.replaceAll('{count}', '$signinCount'),
color: CupertinoColors.systemBlue,
ext: ext,
),
_ActivityChip(
emoji: '📝',
- label: '笔记 $noteCount 篇',
+ label: tp.noteCount.replaceAll('{count}', '$noteCount'),
color: CupertinoColors.systemPurple,
ext: ext,
),
_ActivityChip(
emoji: '❤️',
- label: '点赞 $likeCount 次',
+ label: tp.likeCount.replaceAll('{count}', '$likeCount'),
color: CupertinoColors.systemPink,
ext: ext,
),
_ActivityChip(
emoji: '💬',
- label: '评论 $commentCount 条',
+ label: tp.commentCount.replaceAll('{count}', '$commentCount'),
color: CupertinoColors.systemIndigo,
ext: ext,
),
_ActivityChip(
emoji: '👁️',
- label: '浏览 $viewCount 次',
+ label: tp.viewCount.replaceAll('{count}', '$viewCount'),
color: CupertinoColors.systemTeal,
ext: ext,
),
_ActivityChip(
emoji: '🔖',
- label: '稍后读 $readlaterCount',
+ label: tp.readLaterCount.replaceAll('{count}', '$readlaterCount'),
color: CupertinoColors.systemOrange,
ext: ext,
),
diff --git a/lib/features/mine/user_center/presentation/user_center_page.dart b/lib/features/mine/user_center/presentation/user_center_page.dart
index 86eb4f3a..0f60a019 100644
--- a/lib/features/mine/user_center/presentation/user_center_page.dart
+++ b/lib/features/mine/user_center/presentation/user_center_page.dart
@@ -3,7 +3,7 @@
/// 创建时间: 2026-05-01
/// 更新时间: 2026-06-07
/// 作用: 用户个人中心 — 头像信息 + 统计栏 + 快捷入口 + 可编辑信息 + 账户设置 + 调试
-/// 上次更新: 修复accountInsightsProvider未初始化导致Bad state崩溃,增加authState守卫
+/// 上次更新: 修复accountInsightsProvider未初始化导致Bad state崩溃,增加authState守卫;修复_loadDashboard异常静默吞掉问题;修复score类型安全
/// ============================================================
import 'package:flutter/cupertino.dart';
@@ -11,6 +11,7 @@ import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xianyan/core/router/app_nav_extension.dart';
import 'package:xianyan/core/router/navigator_extension.dart';
+import 'package:xianyan/core/utils/logger.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_spacing.dart';
@@ -60,7 +61,8 @@ class _UserCenterPageState extends ConsumerState {
_isDashboardLoading = false;
});
}
- } catch (_) {
+ } catch (e, st) {
+ Log.e('UserCenterPage: 加载面板数据失败', e, st);
if (mounted) setState(() => _isDashboardLoading = false);
}
}
@@ -177,7 +179,10 @@ class _UserCenterPageState extends ConsumerState {
children: [
Icon(CupertinoIcons.lock, size: 18, color: ext.textHint),
const SizedBox(width: 8),
- Text(t.profile.selectFromAlbum, style: TextStyle(color: ext.textHint)),
+ Text(
+ t.profile.selectFromAlbum,
+ style: TextStyle(color: ext.textHint),
+ ),
],
),
),
@@ -293,7 +298,10 @@ class _UserCenterPageState extends ConsumerState {
} catch (e) {
if (mounted) {
Navigator.of(context, rootNavigator: true).pop();
- _showAvatarResult(false, '${t.profile.avatarChangeFailed}: $e');
+ _showAvatarResult(
+ false,
+ '${t.profile.avatarChangeFailed}: $e',
+ );
}
}
},
@@ -391,17 +399,20 @@ class _UserCenterPageState extends ConsumerState {
CupertinoSliverRefreshControl(onRefresh: _refresh),
SliverToBoxAdapter(
child: RepaintBoundary(
- child: ProfileHeaderRow(
- ext: ext,
- user: user,
- displayScore:
- _dashboardData?['score'] as int? ??
- user.score,
- onAvatarTap: _showAvatarPicker,
- )
- .animate()
- .fadeIn(duration: 300.ms)
- .slideY(begin: 0.05, end: 0),
+ child:
+ ProfileHeaderRow(
+ ext: ext,
+ user: user,
+ displayScore:
+ (_dashboardData?['score'] as num?)
+ ?.toInt() ??
+ user.score,
+ tp: t.profile,
+ onAvatarTap: _showAvatarPicker,
+ )
+ .animate()
+ .fadeIn(duration: 300.ms)
+ .slideY(begin: 0.05, end: 0),
),
),
SliverToBoxAdapter(
@@ -413,10 +424,15 @@ class _UserCenterPageState extends ConsumerState {
).animate().fadeIn(duration: 300.ms, delay: 80.ms),
),
SliverToBoxAdapter(
- child: QuickActionGrid(ext: ext, ref: ref, profileTranslations: t.profile)
- .animate()
- .fadeIn(duration: 300.ms, delay: 130.ms)
- .slideY(begin: 0.05, end: 0),
+ child:
+ QuickActionGrid(
+ ext: ext,
+ ref: ref,
+ profileTranslations: t.profile,
+ )
+ .animate()
+ .fadeIn(duration: 300.ms, delay: 130.ms)
+ .slideY(begin: 0.05, end: 0),
),
SliverToBoxAdapter(
child:
@@ -430,13 +446,13 @@ class _UserCenterPageState extends ConsumerState {
.slideY(begin: 0.05, end: 0),
),
SliverToBoxAdapter(
- child: AccountSection(ext: ext)
+ child: AccountSection(ext: ext, tp: t.profile)
.animate()
.fadeIn(duration: 300.ms, delay: 180.ms)
.slideY(begin: 0.05, end: 0),
),
SliverToBoxAdapter(
- child: DebugSection(ext: ext)
+ child: DebugSection(ext: ext, tp: t.profile)
.animate()
.fadeIn(duration: 300.ms, delay: 220.ms)
.slideY(begin: 0.05, end: 0),
diff --git a/lib/features/mine/user_center/presentation/widgets/account_section.dart b/lib/features/mine/user_center/presentation/widgets/account_section.dart
index b68f8b78..919cf7c8 100644
--- a/lib/features/mine/user_center/presentation/widgets/account_section.dart
+++ b/lib/features/mine/user_center/presentation/widgets/account_section.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 个人中心-账户设置/调试/底部信息/通用行组件
/// 创建时间: 2026-05-14
-/// 更新时间: 2026-05-24
+/// 更新时间: 2026-06-06
/// 作用: 账户设置区 + 调试入口 + App信息底部 + SettingRow/SettingsDivider通用组件
-/// 上次更新: AppInfoFooter版本号改为动态获取+图标替换为应用图标图片
+/// 上次更新: 硬编码中文替换为翻译键
/// ============================================================
import 'package:flutter/cupertino.dart';
@@ -17,6 +17,7 @@ import '../../../../../../core/theme/app_typography.dart';
import '../../../../../../core/theme/app_radius.dart';
import '../../../../../../core/constants/app_constants.dart';
import '../../../../../../core/router/app_routes.dart';
+import '../../../../../../l10n/types/t_profile.dart';
import '../../../../../../shared/widgets/containers/glass_container.dart';
// ============================================================
@@ -24,9 +25,10 @@ import '../../../../../../shared/widgets/containers/glass_container.dart';
// ============================================================
class AccountSection extends StatelessWidget {
- const AccountSection({super.key, required this.ext});
+ const AccountSection({super.key, required this.ext, required this.tp});
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
@@ -52,7 +54,7 @@ class AccountSection extends StatelessWidget {
),
const SizedBox(width: 4),
Text(
- '账户与数据',
+ tp.accountAndData,
style: AppTypography.caption1.copyWith(
color: ext.textSecondary,
fontWeight: FontWeight.w600,
@@ -68,21 +70,21 @@ class AccountSection extends StatelessWidget {
children: [
SettingRow(
icon: CupertinoIcons.person_crop_circle,
- title: '账户设置',
+ title: tp.accountSettings,
ext: ext,
onTap: () => context.appPush(AppRoutes.accountSettings),
),
SettingsDivider(ext: ext),
SettingRow(
icon: CupertinoIcons.wifi_slash,
- title: '离线模式',
+ title: tp.offlineMode,
ext: ext,
onTap: () => context.appPush(AppRoutes.offline),
),
SettingsDivider(ext: ext),
SettingRow(
icon: CupertinoIcons.archivebox_fill,
- title: '缓存管理',
+ title: tp.cacheManagement,
ext: ext,
onTap: () => context.appPush(AppRoutes.cacheManagement),
),
@@ -100,9 +102,10 @@ class AccountSection extends StatelessWidget {
// ============================================================
class DebugSection extends StatelessWidget {
- const DebugSection({super.key, required this.ext});
+ const DebugSection({super.key, required this.ext, required this.tp});
final AppThemeExtension ext;
+ final TProfile tp;
@override
Widget build(BuildContext context) {
@@ -115,7 +118,7 @@ class DebugSection extends StatelessWidget {
padding: EdgeInsets.zero,
child: SettingRow(
icon: CupertinoIcons.ant_fill,
- title: '调试信息',
+ title: tp.debugInfo,
ext: ext,
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
diff --git a/lib/features/mine/user_center/presentation/widgets/edit_field_bottom_sheet.dart b/lib/features/mine/user_center/presentation/widgets/edit_field_bottom_sheet.dart
index 6e0026d2..5722e050 100644
--- a/lib/features/mine/user_center/presentation/widgets/edit_field_bottom_sheet.dart
+++ b/lib/features/mine/user_center/presentation/widgets/edit_field_bottom_sheet.dart
@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 个人中心-编辑字段底部弹窗
/// 创建时间: 2026-05-14
-/// 更新时间: 2026-05-14
+/// 更新时间: 2026-06-06
/// 作用: 编辑用户名/昵称/个性签名的底部弹窗组件
-/// 上次更新: 从 user_center_page.dart 拆分提取
+/// 上次更新: 硬编码中文替换为翻译键
/// ============================================================
import 'package:flutter/cupertino.dart';
@@ -13,6 +13,7 @@ import '../../../../../../core/theme/app_theme.dart';
import '../../../../../../core/theme/app_spacing.dart';
import '../../../../../../core/theme/app_typography.dart';
import '../../../../../../core/theme/app_radius.dart';
+import '../../../../../../l10n/translation_resolver.dart';
import '../../../../auth/providers/auth_provider.dart';
import '../../services/user_center_service.dart';
@@ -62,6 +63,7 @@ class _EditFieldBottomSheetState extends ConsumerState {
@override
Widget build(BuildContext context) {
final ext = widget.ext;
+ final tp = ref.read(translationsProvider).profile;
final isBio = widget.field == 'bio';
final maxLen = isBio ? 500 : (widget.field == 'username' ? 30 : 50);
@@ -86,14 +88,14 @@ class _EditFieldBottomSheetState extends ConsumerState {
padding: EdgeInsets.zero,
onPressed: () => Navigator.pop(context),
child: Text(
- '取消',
+ ref.read(translationsProvider).common.cancel,
style: AppTypography.body.copyWith(
color: ext.textSecondary,
),
),
),
Text(
- '修改${widget.title}',
+ tp.modifyField.replaceAll('{field}', widget.title),
style: AppTypography.title3.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
@@ -105,7 +107,7 @@ class _EditFieldBottomSheetState extends ConsumerState