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 { child: _isSaving ? CupertinoActivityIndicator(color: ext.accent) : Text( - '保存', + tp.save, style: AppTypography.body.copyWith( color: ext.accent, fontWeight: FontWeight.w600, @@ -117,7 +119,7 @@ class _EditFieldBottomSheetState extends ConsumerState { const SizedBox(height: AppSpacing.md), CupertinoTextField( controller: _controller, - placeholder: '请输入${widget.title}', + placeholder: tp.pleaseInputField.replaceAll('{field}', widget.title), maxLength: maxLen, maxLines: isBio ? 5 : 1, minLines: isBio ? 3 : 1, @@ -150,6 +152,7 @@ class _EditFieldBottomSheetState extends ConsumerState { final value = _controller.text.trim(); if (value.isEmpty) return; + final tp = ref.read(translationsProvider).profile; final user = ref.read(authProvider).user; String? current; switch (widget.field) { @@ -175,17 +178,18 @@ class _EditFieldBottomSheetState extends ConsumerState { await ref.read(authProvider.notifier).refreshUser(); if (mounted) { Navigator.pop(context); - _showResult(true, '${widget.title}修改成功'); + _showResult(true, tp.fieldModifySuccess.replaceAll('{field}', widget.title)); } } catch (e) { if (mounted) { setState(() => _isSaving = false); - _showResult(false, '修改失败: $e'); + _showResult(false, tp.fieldModifyFailed.replaceAll('{error}', e.toString())); } } } void _showResult(bool success, String msg) { + final tp = ref.read(translationsProvider).profile; showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( @@ -202,13 +206,13 @@ class _EditFieldBottomSheetState extends ConsumerState { : CupertinoColors.systemRed, ), const SizedBox(width: 8), - Text(success ? '成功' : '失败'), + Text(success ? tp.success : tp.failed), ], ), content: Text(msg), actions: [ CupertinoDialogAction( - child: const Text('好的'), + child: Text(tp.ok), onPressed: () => Navigator.pop(ctx), ), ], diff --git a/lib/features/mine/user_center/presentation/widgets/learning_heatmap.dart b/lib/features/mine/user_center/presentation/widgets/learning_heatmap.dart index 88bd5b36..61d1ae32 100644 --- a/lib/features/mine/user_center/presentation/widgets/learning_heatmap.dart +++ b/lib/features/mine/user_center/presentation/widgets/learning_heatmap.dart @@ -31,9 +31,9 @@ class LearningHeatmap extends StatelessWidget { @override Widget build(BuildContext context) { final calendar = _parseCalendar(data); - final continuous = data?['current_continuous'] as int? ?? 0; - final total = data?['total_signins'] as int? ?? 0; - final longestStreak = data?['longest_streak'] as int? ?? 0; + final continuous = (data?['current_continuous'] as num?)?.toInt() ?? 0; + final total = (data?['total_signins'] as num?)?.toInt() ?? 0; + final longestStreak = (data?['longest_streak'] as num?)?.toInt() ?? 0; return Padding( padding: const EdgeInsets.fromLTRB( diff --git a/lib/features/mine/user_center/presentation/widgets/profile_header_row.dart b/lib/features/mine/user_center/presentation/widgets/profile_header_row.dart index 8f2778c6..dbc08dca 100644 --- a/lib/features/mine/user_center/presentation/widgets/profile_header_row.dart +++ b/lib/features/mine/user_center/presentation/widgets/profile_header_row.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 个人中心-头像+用户名行 /// 创建时间: 2026-05-14 -/// 更新时间: 2026-05-27 +/// 更新时间: 2026-06-06 /// 作用: 头像+用户名+等级标签+编辑按钮的同行展示组件 -/// 上次更新: LV等级标签移至右侧与编辑按钮上下对齐 +/// 上次更新: 硬编码中文替换为翻译键 /// ============================================================ import 'package:flutter/cupertino.dart'; @@ -13,6 +13,7 @@ import '../../../../../../core/theme/app_spacing.dart'; import '../../../../../../core/theme/app_typography.dart'; import '../../../../../../core/theme/app_radius.dart'; import '../../../../../../core/utils/data/level_utils.dart'; +import '../../../../../../l10n/types/t_profile.dart'; import '../../../../../../shared/widgets/containers/glass_container.dart'; import '../../../../../../shared/widgets/media/safe_cached_image.dart'; import '../../../../auth/models/user_model.dart'; @@ -24,12 +25,14 @@ class ProfileHeaderRow extends StatelessWidget { required this.ext, required this.user, required this.displayScore, + required this.tp, this.onAvatarTap, }); final AppThemeExtension ext; final UserModel user; final int displayScore; + final TProfile tp; final VoidCallback? onAvatarTap; @override @@ -83,9 +86,13 @@ class ProfileHeaderRow extends StatelessWidget { width: 64, height: 64, fit: BoxFit.cover, - placeholder: (_, __) => const CupertinoActivityIndicator(), - errorWidget: (_, __, ___) => - Icon(CupertinoIcons.person, size: 28, color: ext.textHint), + placeholder: (_, __) => + const CupertinoActivityIndicator(), + errorWidget: (_, __, ___) => Icon( + CupertinoIcons.person, + size: 28, + color: ext.textHint, + ), ), ) : const Center( @@ -107,10 +114,14 @@ class ProfileHeaderRow extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(CupertinoIcons.hourglass, size: 14, color: CupertinoColors.white), + const Icon( + CupertinoIcons.hourglass, + size: 14, + color: CupertinoColors.white, + ), const SizedBox(height: 2), Text( - '审核中', + tp.reviewing, style: AppTypography.caption2.copyWith( color: CupertinoColors.white, fontSize: 9, @@ -141,7 +152,7 @@ class ProfileHeaderRow extends StatelessWidget { ), const SizedBox(height: 4), Text( - user.bio.isNotEmpty ? user.bio : '用文字点亮生活的每一刻', + user.bio.isNotEmpty ? user.bio : tp.defaultBio, style: AppTypography.subhead.copyWith(color: ext.textSecondary), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -202,7 +213,7 @@ class ProfileHeaderRow extends StatelessWidget { Icon(CupertinoIcons.pencil, size: 14, color: ext.accent), const SizedBox(width: 4), Text( - '编辑', + tp.edit, style: AppTypography.caption1.copyWith( color: ext.accent, fontWeight: FontWeight.w600, @@ -218,42 +229,42 @@ class ProfileHeaderRow extends StatelessWidget { context: context, builder: (ctx) => CupertinoActionSheet( title: Text( - '编辑资料', + tp.editProfile, style: AppTypography.subhead.copyWith(color: ext.textSecondary), ), actions: [ CupertinoActionSheetAction( onPressed: () { Navigator.pop(ctx); - _showEditFieldSheet(context, '账户', 'username'); + _showEditFieldSheet(context, tp.username, 'username'); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(CupertinoIcons.person, size: 18, color: ext.accent), const SizedBox(width: 8), - const Text('修改账户'), + Text(tp.editUsername), ], ), ), CupertinoActionSheetAction( onPressed: () { Navigator.pop(ctx); - _showEditFieldSheet(context, '昵称', 'nickname'); + _showEditFieldSheet(context, tp.nickname, 'nickname'); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(CupertinoIcons.chat_bubble_2, size: 18, color: ext.accent), const SizedBox(width: 8), - const Text('修改昵称'), + Text(tp.editNickname), ], ), ), CupertinoActionSheetAction( onPressed: () { Navigator.pop(ctx); - _showEditFieldSheet(context, '个性签名', 'bio'); + _showEditFieldSheet(context, tp.bio, 'bio'); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -264,7 +275,7 @@ class ProfileHeaderRow extends StatelessWidget { color: ext.accent, ), const SizedBox(width: 8), - const Text('修改签名'), + Text(tp.editBio), ], ), ), @@ -278,7 +289,7 @@ class ProfileHeaderRow extends StatelessWidget { children: [ Icon(CupertinoIcons.photo, size: 18, color: ext.accent), const SizedBox(width: 8), - const Text('修改头像'), + Text(tp.changeAvatar), ], ), ), diff --git a/lib/features/mine/user_center/presentation/widgets/user_stats_bar.dart b/lib/features/mine/user_center/presentation/widgets/user_stats_bar.dart index 5a703fec..a62bbbb8 100644 --- a/lib/features/mine/user_center/presentation/widgets/user_stats_bar.dart +++ b/lib/features/mine/user_center/presentation/widgets/user_stats_bar.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 个人中心-统计栏组件 /// 创建时间: 2026-05-14 -/// 更新时间: 2026-06-02 +/// 更新时间: 2026-06-06 /// 作用: 显示积分/签到/收藏/笔记/点赞统计栏 -/// 上次更新: 硬编码中文替换为多语言翻译 +/// 上次更新: 修复dashboard数据类型安全(as num?);删除debug日志避免鸿蒙端卡死 /// ============================================================ import 'package:flutter/cupertino.dart'; @@ -15,7 +15,6 @@ import '../../../../../../core/theme/app_spacing.dart'; import '../../../../../../core/theme/app_typography.dart'; import '../../../../../../core/router/app_routes.dart'; import '../../../../../../shared/widgets/containers/glass_container.dart'; -import '../../../../../../core/utils/logger.dart'; import '../../../../auth/models/user_model.dart'; import '../../../../note/providers/note_provider.dart'; import '../../../../../../l10n/translation_resolver.dart'; @@ -37,18 +36,12 @@ class UserStatsBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.read(translationsProvider); - final score = dashboardData?['score'] ?? user.score; - final signinDays = dashboardData?['signin_days'] ?? user.signinDays; - final favCount = dashboardData?['favorite_count'] ?? 0; + final score = (dashboardData?['score'] as num?)?.toInt() ?? user.score; + final signinDays = (dashboardData?['signin_days'] as num?)?.toInt() ?? user.signinDays; + final favCount = (dashboardData?['favorite_count'] as num?)?.toInt() ?? 0; final noteState = ref.watch(noteListProvider); final noteCount = noteState.total; - final likeCount = dashboardData?['like_count'] ?? 0; - - if (dashboardData != null) { - Log.d( - 'UserStatsBar dashboard: score=$score, signin=$signinDays, fav=$favCount, note=$noteCount(dashboard=${dashboardData?['note_count']}, provider=${noteState.total}), like=$likeCount', - ); - } + final likeCount = (dashboardData?['like_count'] as num?)?.toInt() ?? 0; return Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), diff --git a/lib/features/mine/user_center/providers/account_insights_provider.dart b/lib/features/mine/user_center/providers/account_insights_provider.dart index 904c3d75..6cd0adfe 100644 --- a/lib/features/mine/user_center/providers/account_insights_provider.dart +++ b/lib/features/mine/user_center/providers/account_insights_provider.dart @@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../core/utils/logger.dart'; import '../../../auth/providers/auth_provider.dart'; import '../models/account_insight_model.dart'; import '../services/account_insights_service.dart'; @@ -48,14 +49,23 @@ class AccountInsightsState { class AccountInsightsNotifier extends Notifier { @override AccountInsightsState build() { - // 同步加载缓存,直接作为初始状态返回,避免在 build 中访问 state 导致循环依赖 - final cached = AccountInsightsService.loadInsights(); - if (cached.isNotEmpty) { - return AccountInsightsState(insights: _mergeWithExpired(cached)); - } + // 使用 Future.microtask 延迟加载缓存,避免在 build() 中直接修改 state 导致循环依赖 + Future.microtask(() => _loadFromCache()); return const AccountInsightsState(); } + Future _loadFromCache() async { + try { + final cached = AccountInsightsService.loadInsights(); + if (cached.isNotEmpty) { + state = state.copyWith(insights: _mergeWithExpired(cached)); + } + } catch (e) { + // 缓存加载失败不影响正常使用 + Log.w('AccountInsights: 缓存加载失败', e); + } + } + Future refresh() async { state = state.copyWith(isLoading: true, clearError: true); diff --git a/lib/features/mine/user_center/providers/coin_provider.dart b/lib/features/mine/user_center/providers/coin_provider.dart index b505df16..b20928de 100644 --- a/lib/features/mine/user_center/providers/coin_provider.dart +++ b/lib/features/mine/user_center/providers/coin_provider.dart @@ -38,9 +38,9 @@ class CoinLogItem { factory CoinLogItem.fromJson(Map json) { return CoinLogItem( - id: json['id'] as int? ?? 0, + id: (json['id'] as num?)?.toInt() ?? 0, coinType: json['coin_type'] as String? ?? 'score', - amount: json['amount'] as int? ?? 0, + amount: (json['amount'] as num?)?.toInt() ?? 0, before: json['before']?.toString() ?? '0', after: json['after']?.toString() ?? '0', action: json['action'] as String? ?? '', diff --git a/lib/features/mine/user_center/providers/interaction_provider.dart b/lib/features/mine/user_center/providers/interaction_provider.dart index c361121b..ba772b63 100644 --- a/lib/features/mine/user_center/providers/interaction_provider.dart +++ b/lib/features/mine/user_center/providers/interaction_provider.dart @@ -400,7 +400,7 @@ class InteractionNotifier extends Notifier { final list = result['list'] as List? ?? []; return list.map((e) { final map = e as Map; - return map['target_id'] as int? ?? 0; + return (map['target_id'] as num?)?.toInt() ?? 0; }).where((id) => id > 0).toSet(); } catch (e) { return {}; @@ -418,7 +418,7 @@ class InteractionNotifier extends Notifier { final list = result['list'] as List? ?? []; return list.map((e) { final map = e as Map; - return map['target_id'] as int? ?? 0; + return (map['target_id'] as num?)?.toInt() ?? 0; }).where((id) => id > 0).toSet(); } catch (e) { return {}; diff --git a/lib/features/mine/user_center/providers/learning_progress_provider.dart b/lib/features/mine/user_center/providers/learning_progress_provider.dart index 1e107ae4..64d233d6 100644 --- a/lib/features/mine/user_center/providers/learning_progress_provider.dart +++ b/lib/features/mine/user_center/providers/learning_progress_provider.dart @@ -72,7 +72,7 @@ class LearningProgressNotifier extends Notifier { ), ]); final overview = results[0]; - final todayViewCount = overview['today_view_count'] as int? ?? 0; + final todayViewCount = (overview['today_view_count'] as num?)?.toInt() ?? 0; state = state.copyWith( isLoading: false, overviewData: overview, diff --git a/lib/features/mine/user_center/providers/tag_cloud_provider.dart b/lib/features/mine/user_center/providers/tag_cloud_provider.dart index ac37ba16..29845a90 100644 --- a/lib/features/mine/user_center/providers/tag_cloud_provider.dart +++ b/lib/features/mine/user_center/providers/tag_cloud_provider.dart @@ -94,7 +94,7 @@ class TagCloudNotifier extends Notifier { final tagName = map['extra'] as String? ?? map['tag'] as String? ?? ''; if (tagName.isEmpty) continue; - final targetId = map['target_id'] as int?; + final targetId = (map['target_id'] as num?)?.toInt(); final targetType = map['target_type'] as String?; final initial = _getPinyinInitial(tagName); diff --git a/lib/features/mine/user_center/services/account_insights_service.dart b/lib/features/mine/user_center/services/account_insights_service.dart index e8fd385c..98f2b1aa 100644 --- a/lib/features/mine/user_center/services/account_insights_service.dart +++ b/lib/features/mine/user_center/services/account_insights_service.dart @@ -194,7 +194,7 @@ class AccountInsightsService { Map userData, DateTime now, ) { - final secQuestion = userData['sec_question'] as int? ?? 0; + final secQuestion = (userData['sec_question'] as num?)?.toInt() ?? 0; if (secQuestion > 0) return; insights.add( diff --git a/lib/features/mine/user_center/services/user_center_service.dart b/lib/features/mine/user_center/services/user_center_service.dart index 647093d7..a5c44efc 100644 --- a/lib/features/mine/user_center/services/user_center_service.dart +++ b/lib/features/mine/user_center/services/user_center_service.dart @@ -802,7 +802,7 @@ class UserCenterService { 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? ?? '请求失败', ); } diff --git a/lib/features/onboarding/presentation/onboarding_page.dart b/lib/features/onboarding/presentation/onboarding_page.dart index c1c5c86e..e63c31fc 100644 --- a/lib/features/onboarding/presentation/onboarding_page.dart +++ b/lib/features/onboarding/presentation/onboarding_page.dart @@ -109,34 +109,27 @@ class _OnboardingPageState extends ConsumerState { void _goToPage(int page) { if (page < 0 || page >= OnboardingConstants.totalPages) return; - if (!_pageController.hasClients) return; - - final state = ref.read(onboardingProvider); - if (page == 2 && !state.canProceedAgreement) { - HapticService.light(); - showCupertinoDialog( - context: context, - builder: (ctx) => CupertinoAlertDialog( - title: const Text('温馨提示'), - content: const Text('请先阅读并同意软件协议和权限说明,才能继续使用闲言。'), - actions: [ - CupertinoDialogAction( - isDefaultAction: true, - onPressed: () => Navigator.pop(ctx), - child: const Text('我知道了'), - ), - ], - ), - ); + if (!_pageController.hasClients) { + // PageController 没有客户端时,尝试直接设置页码 + ref.read(onboardingProvider.notifier).setPage(page); return; } + // 检查协议是否同意(跳到第2页需要) + if (page >= 2) { + final state = ref.read(onboardingProvider); + if (!state.canProceedAgreement) { + _showAgreementReminder(); + return; + } + } + HapticService.light(); ref.read(onboardingProvider.notifier).setPage(page); _pageController.animateToPage( page, duration: const Duration(milliseconds: 400), - curve: Curves.easeInOutCubic, + curve: Curves.easeInOut, ); setState(() => _showLottie = true); Future.delayed(const Duration(milliseconds: 1500), () { @@ -144,6 +137,25 @@ class _OnboardingPageState extends ConsumerState { }).catchError((_) {}); } + /// 协议未同意时的提醒弹窗 + void _showAgreementReminder() { + HapticService.light(); + showCupertinoDialog( + context: context, + builder: (ctx) => CupertinoAlertDialog( + title: const Text('温馨提示'), + content: const Text('请先阅读并同意软件协议和权限说明,才能继续使用闲言。'), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(ctx), + child: const Text('我知道了'), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final ext = AppTheme.ext(context); diff --git a/lib/features/onboarding/presentation/pages/agreement_page.dart b/lib/features/onboarding/presentation/pages/agreement_page.dart index 23ff4f3a..3542fc64 100644 --- a/lib/features/onboarding/presentation/pages/agreement_page.dart +++ b/lib/features/onboarding/presentation/pages/agreement_page.dart @@ -15,6 +15,7 @@ import '../../../../core/constants/character_expression.dart'; import '../../../../l10n/translations.dart'; import '../../../../core/router/app_nav_extension.dart'; import '../../../../core/router/app_routes.dart'; +import '../../../../core/utils/platform/platform_utils.dart' as pu; import '../../../../l10n/app_locale.dart'; import '../../../../core/services/auth/permission_service.dart'; import '../../../../core/services/device/haptic_service.dart'; @@ -1000,7 +1001,11 @@ class _AgreementPageState extends ConsumerState { HapticService.light(); await notifier.completeOnboarding(); if (context.mounted) { - context.appGo(AppRoutes.home); + if (pu.isOhos) { + Navigator.of(context).pop(); + } else { + context.appGo(AppRoutes.home); + } } } : null, diff --git a/lib/features/onboarding/presentation/pages/personalization_page.dart b/lib/features/onboarding/presentation/pages/personalization_page.dart index 0e642aa9..e06c0a3f 100644 --- a/lib/features/onboarding/presentation/pages/personalization_page.dart +++ b/lib/features/onboarding/presentation/pages/personalization_page.dart @@ -16,6 +16,7 @@ import '../../../../l10n/translations.dart'; import '../../../../core/router/app_nav_extension.dart'; import '../../../../core/router/app_routes.dart'; import '../../../../core/services/device/haptic_service.dart'; +import '../../../../core/utils/platform/platform_utils.dart' as pu; import '../../../../core/theme/app_radius.dart'; import '../../../../core/theme/app_spacing.dart'; import '../../../../core/theme/app_theme.dart'; @@ -748,7 +749,12 @@ class _PersonalizationPageState extends ConsumerState { .completeOnboarding(); _loadingTextTimer?.cancel(); if (mounted) { - context.appGo(AppRoutes.home); + if (pu.isOhos) { + // 鸿蒙端:pop引导页回到主页,OhosAppShell会自动显示 + Navigator.of(context).pop(); + } else { + context.appGo(AppRoutes.home); + } } }, child: isCompleting diff --git a/lib/l10n/languages/ar.dart b/lib/l10n/languages/ar.dart index 1a7371ed..c1e7e7af 100644 --- a/lib/l10n/languages/ar.dart +++ b/lib/l10n/languages/ar.dart @@ -614,16 +614,59 @@ const ar = T( costPoints: 'نقاط التكلفة', currentPoints: 'النقاط الحالية', makeupInfo: 'معلومات التعويض', - makeupLimitInfo: 'تعويض واحد فقط يومياً. نقاط غير كافية = غير ممكن (تحتاج {0} نقطة)', + makeupLimitInfo: + 'تعويض واحد فقط يومياً. نقاط غير كافية = غير ممكن (تحتاج {0} نقطة)', confirmMakeup: 'تأكيد التعويض', insufficientPoints: 'نقاط غير كافية', - insufficientPointsDesc: 'التعويض يحتاج {0} نقطة، الحالي: {1}\n\n💡 اكسب نقاط عبر الحضور اليومي ونشر المقالات إلخ.', + insufficientPointsDesc: + 'التعويض يحتاج {0} نقطة، الحالي: {1}\n\n💡 اكسب نقاط عبر الحضور اليومي ونشر المقالات إلخ.', makeupSuccess: 'نجح التعويض', makeupSuccessDesc: '{0} نجح التعويض، تم خصم {1} نقطة', makeupFailed: 'فشل التعويض', makeupFailedRetry: 'فشل التعويض، حاول لاحقاً', timesUnit: 'مرة', daysUnit: 'يوم', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'اللغة', @@ -905,19 +948,26 @@ const ar = T( allCacheCleared: 'تم مسح كل ذاكرة التخزين المؤقت', cleanFailed2: 'فشل التنظيف: {0}', confirmCleanChatTrashTitle: 'تنظيف سلة محذوفات الدردشة', - confirmCleanChatTrashContent: 'سيتم حذف الرسائل والملفات في سلة المحذوفات التي تزيد عن 30 يوم بشكل دائم. لا يمكن التراجع عن هذا الإجراء.', + confirmCleanChatTrashContent: + 'سيتم حذف الرسائل والملفات في سلة المحذوفات التي تزيد عن 30 يوم بشكل دائم. لا يمكن التراجع عن هذا الإجراء.', confirmCleanChatThumbnailsTitle: 'تنظيف الصور المصغرة للدردشة', - confirmCleanChatThumbnailsContent: 'سيتم تنظيف ذاكرة الصور المصغرة لصور الدردشة. لن يتم حذف الصور الأصلية.', + confirmCleanChatThumbnailsContent: + 'سيتم تنظيف ذاكرة الصور المصغرة لصور الدردشة. لن يتم حذف الصور الأصلية.', confirmClearAllCacheTitle: 'مسح كل ذاكرة التخزين المؤقت', - confirmClearAllCacheContent: 'هل أنت متأكد من مسح جميع بيانات ذاكرة التخزين المؤقت؟ سيتم حذف المحتوى غير المتصل. لا يمكن التراجع عن هذا الإجراء.', + confirmClearAllCacheContent: + 'هل أنت متأكد من مسح جميع بيانات ذاكرة التخزين المؤقت؟ سيتم حذف المحتوى غير المتصل. لا يمكن التراجع عن هذا الإجراء.', confirmCleanTransferCacheTitle: 'تنظيف ذاكرة النقل', - confirmCleanTransferCacheContent: 'سيتم تنظيف الصور المصغرة والملفات المؤقتة وسجلات النقل التي تزيد عن 30 يوم. لن يتم حذف الملفات المستلمة.', + confirmCleanTransferCacheContent: + 'سيتم تنظيف الصور المصغرة والملفات المؤقتة وسجلات النقل التي تزيد عن 30 يوم. لن يتم حذف الملفات المستلمة.', confirmClearAllChatDataTitle: 'مسح جميع بيانات الدردشة', - confirmClearAllChatDataContent: 'سيتم حذف جميع جلسات الدردشة والرسائل والمرفقات وبيانات سلة المحذوفات. لا يمكن التراجع عن هذا الإجراء!', + confirmClearAllChatDataContent: + 'سيتم حذف جميع جلسات الدردشة والرسائل والمرفقات وبيانات سلة المحذوفات. لا يمكن التراجع عن هذا الإجراء!', confirmCleanReadlaterCacheTitle: 'تنظيف ذاكرة "اقرأ لاحقاً"', - confirmCleanReadlaterCacheContent: 'سيتم تنظيف الصور المصغرة والمرفقات والملفات المؤقتة للمزامنة لـ "اقرأ لاحقاً". لن يتم حذف سجلات الرسائل.', + confirmCleanReadlaterCacheContent: + 'سيتم تنظيف الصور المصغرة والمرفقات والملفات المؤقتة للمزامنة لـ "اقرأ لاحقاً". لن يتم حذف سجلات الرسائل.', confirmClearReadlaterDataTitle: 'مسح جميع بيانات "اقرأ لاحقاً"', - confirmClearReadlaterDataContent: 'سيتم حذف جميع الرسائل والمرفقات والصور المصغرة لـ "اقرأ لاحقاً". لا يمكن التراجع عن هذا الإجراء!', + confirmClearReadlaterDataContent: + 'سيتم حذف جميع الرسائل والمرفقات والصور المصغرة لـ "اقرأ لاحقاً". لا يمكن التراجع عن هذا الإجراء!', clearAll: 'مسح الكل', clean2: 'تنظيف', enabled2: 'مفعّل', @@ -1326,7 +1376,8 @@ const ar = T( browser: 'المتصفح', unableOpenBrowser: 'تعذر فتح المتصفح', cannotVerifyEmail: 'لا يمكنك التحقق من البريد؟', - skipEmailVerifyTip: 'يمكنك تخطي التحقق وتعيين سؤال أمني في الخطوة التالية. تستخدم الأسئلة الأمنية لاستعادة كلمة المرور.', + skipEmailVerifyTip: + 'يمكنك تخطي التحقق وتعيين سؤال أمني في الخطوة التالية. تستخدم الأسئلة الأمنية لاستعادة كلمة المرور.', setSecQuestionToContinue: 'تعيين سؤال أمني للمتابعة', warmTips: 'نصائح', tipOpenWithoutLogin: 'شيان يان مفتوح — معظم الميزات تعمل بدون تسجيل دخول', @@ -1343,13 +1394,24 @@ const ar = T( resetPasswordSuccess: 'تم إعادة تعيين كلمة المرور بنجاح', resetPasswordFailed: 'فشل إعادة تعيين كلمة المرور', contactServiceTitle: 'اتصل بالدعم', - contactServiceSubtitle: 'إذا لم تتمكن من إعادة تعيين كلمة المرور بالطرق أعلاه، يرجى الاتصال بالدعم مع المعلومات التالية', + contactServiceSubtitle: + 'إذا لم تتمكن من إعادة تعيين كلمة المرور بالطرق أعلاه، يرجى الاتصال بالدعم مع المعلومات التالية', contactServiceInfoAccount: 'الحساب/اسم المستخدم المسجل', contactServiceInfoEmail: 'البريد الإلكتروني المسجل', contactServiceInfoDevice: 'معلومات الجهاز (الطراز/إصدار النظام)', - contactServiceInfoDescription: 'وصف المشكلة (تاريخ التسجيل، الاستخدام، إلخ)', + contactServiceInfoDescription: + 'وصف المشكلة (تاريخ التسجيل، الاستخدام، إلخ)', contactServiceMethod: 'طريقة الاتصال', - contactServiceMethodDetail: 'يرجى الاتصال بالدعم عبر صفحة "حول" في التطبيق أو عبر البريد الإلكتروني الرسمي، مع تقديم المعلومات أعلاه للتحقق من الهوية والمساعدة.', + contactServiceMethodDetail: + 'يرجى الاتصال بالدعم عبر صفحة "حول" في التطبيق أو عبر البريد الإلكتروني الرسمي، مع تقديم المعلومات أعلاه للتحقق من الهوية والمساعدة.', + experimentalFeature: 'ميزة تجريبية', + experimentalFeatureDesc: + 'حساب شيان يان هو ميزة تجريبية. التسجيل وتسجيل الدخول لهما تأثير ضئيل على الاستخدام ولا يُنصح بهما. معظم الميزات تعمل بدون تسجيل الدخول.', + dontShowAgain: 'عدم الإظهار مرة أخرى', + viewExperimentalFeatures: 'عرض الميزات التجريبية', + userBatchFlag: 'دفعة: {flag}', + openPeriod: 'مفتوح: {period}', + expireNotice: 'يغلق بعد {year}', ), progress: TProgress( title: 'التقدم', @@ -1947,8 +2009,10 @@ const ar = T( title: 'رؤى الحساب', markAllRead: 'تحديد الكل كمقروء', close: 'إغلاق', - testAccountWarning: '⚠️ قد يكون هذا الحساب حساب اختبار مقدمة من 閑言 الرسمي، يستخدمه عدة أشخاص. يرجى عدم تغيير كلمة المرور وعدم إنشاء ملاحظات بهذا الحساب. في حالة الحاجة، يرجى تسجيل حسابك الخاص.', - ohosDeviceWarning: '⚠️ قد تظهر بعض الأجهزة خللاً في التعريف، مثل عرض غير معروف أو خطأ في عامل الأمان', + testAccountWarning: + '⚠️ قد يكون هذا الحساب حساب اختبار مقدمة من 閑言 الرسمي، يستخدمه عدة أشخاص. يرجى عدم تغيير كلمة المرور وعدم إنشاء ملاحظات بهذا الحساب. في حالة الحاجة، يرجى تسجيل حسابك الخاص.', + ohosDeviceWarning: + '⚠️ قد تظهر بعض الأجهزة خللاً في التعريف، مثل عرض غير معروف أو خطأ في عامل الأمان', allNormal: 'كل شيء طبيعي', noSecurityIssues: 'لا توجد مشاكل أمان تتطلب الانتباه', totalCount: '{0} إجمالي · {1} غير مقروء', diff --git a/lib/l10n/languages/bn.dart b/lib/l10n/languages/bn.dart index d20d85f9..2f785e60 100644 --- a/lib/l10n/languages/bn.dart +++ b/lib/l10n/languages/bn.dart @@ -537,7 +537,8 @@ const bn = T( changeAvatar: 'অবতার পরিবর্তন করুন', inputAvatarUrl: 'অবতার URL লিখুন', selectFromAlbum: 'অ্যালবাম থেকে নির্বাচন (শীঘ্রই)', - avatarUrlHint: 'URL ২০৪৮ অক্ষরের বেশি হতে পারবে না, শুধুমাত্র http/https লিংক', + avatarUrlHint: + 'URL ২০৪৮ অক্ষরের বেশি হতে পারবে না, শুধুমাত্র http/https লিংক', pleaseInputUrl: 'অনুগ্রহ করে URL লিখুন', urlMustStartWithHttp: 'URL http:// বা https:// দিয়ে শুরু হতে হবে', urlTooLong: 'URL ২০৪৮ অক্ষরের সীমা ছাড়িয়ে গেছে', @@ -591,7 +592,8 @@ const bn = T( noTasksDesc: 'আজ কোনো কাজ নেই। পরে আবার দেখুন!', great: 'চমৎকার!', loginToCheckin: 'চেক-ইন করতে লগইন করুন', - loginToCheckinDesc: 'দৈনিক চেক-ইনে অংশ নিতে এবং পয়েন্ট অর্জন করতে লগইন করুন', + loginToCheckinDesc: + 'দৈনিক চেক-ইনে অংশ নিতে এবং পয়েন্ট অর্জন করতে লগইন করুন', viewAchievementCenter: 'অর্জন কেন্দ্র দেখুন', todaySigned: 'আজ চেক-ইন করা হয়েছে', tapToCheckin: 'চেক-ইন করতে ট্যাপ করুন', @@ -612,16 +614,59 @@ const bn = T( costPoints: 'খরচ পয়েন্ট', currentPoints: 'বর্তমান পয়েন্ট', makeupInfo: 'পুনরুদ্ধার তথ্য', - makeupLimitInfo: 'দিনে মাত্র ১ বার পুনরুদ্ধার। পর্যাপ্ত পয়েন্ট না থাকলে সম্ভব নয় ({0} পয়েন্ট প্রয়োজন)', + makeupLimitInfo: + 'দিনে মাত্র ১ বার পুনরুদ্ধার। পর্যাপ্ত পয়েন্ট না থাকলে সম্ভব নয় ({0} পয়েন্ট প্রয়োজন)', confirmMakeup: 'পুনরুদ্ধার নিশ্চিত করুন', insufficientPoints: 'পর্যাপ্ত পয়েন্ট নেই', - insufficientPointsDesc: 'পুনরুদ্ধারে {0} পয়েন্ট প্রয়োজন, বর্তমান: {1}\n\n💡 দৈনিক চেক-ইন, নিবন্ধ প্রকাশ ইত্যাদির মাধ্যমে পয়েন্ট অর্জন করুন', + insufficientPointsDesc: + 'পুনরুদ্ধারে {0} পয়েন্ট প্রয়োজন, বর্তমান: {1}\n\n💡 দৈনিক চেক-ইন, নিবন্ধ প্রকাশ ইত্যাদির মাধ্যমে পয়েন্ট অর্জন করুন', makeupSuccess: 'পুনরুদ্ধার সফল', makeupSuccessDesc: '{0} পুনরুদ্ধার সফল, {1} পয়েন্ট কাটা হয়েছে', makeupFailed: 'পুনরুদ্ধার ব্যর্থ', makeupFailedRetry: 'পুনরুদ্ধার ব্যর্থ, পরে আবার চেষ্টা করুন', timesUnit: 'বার', daysUnit: 'দিন', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'ভাষা', @@ -711,7 +756,8 @@ const bn = T( splitViewRatioSubtitle: 'স্প্লিট ভিউতে বাম/ডান প্যানেল অনুপাত', splitViewRatioTitle: '📐 স্প্লিট ভিউ অনুপাত', splitViewEnabled: 'স্প্লিট ভিউ', - splitViewEnabledSubtitle: 'ওয়াইড স্ক্রিনে স্প্লিট ভিউ লেআউট সক্রিয় করুন', + splitViewEnabledSubtitle: + 'ওয়াইড স্ক্রিনে স্প্লিট ভিউ লেআউট সক্রিয় করুন', shaderBackground: 'শেডার ব্যাকগ্রাউন্ড', shaderBackgroundSubtitle: 'উদ্ধৃতি কার্ডে তরল গ্রেডিয়েন্ট প্রভাব', ), @@ -743,7 +789,8 @@ const bn = T( privacyPolicy: 'গোপনীয়তা নীতি', privacyPolicySubtitle: 'গোপনীয়তা নীতি কন্টেন্ট দেখুন', nearbyDiscovery: 'নিকটবর্তী আবিষ্কার', - nearbyDiscoverySubtitle: 'নিকটবর্তী ব্যবহারকারীদের আপনাকে আবিষ্কার করার অনুমতি দিন', + nearbyDiscoverySubtitle: + 'নিকটবর্তী ব্যবহারকারীদের আপনাকে আবিষ্কার করার অনুমতি দিন', ), advanced: TSettingsAdvanced( advanced: 'উন্নত', @@ -904,19 +951,26 @@ const bn = T( allCacheCleared: 'সব ক্যাশে মুছে ফেলা হয়েছে', cleanFailed2: 'পরিষ্কার ব্যর্থ: {0}', confirmCleanChatTrashTitle: 'চ্যাট ট্র্যাশ পরিষ্কার', - confirmCleanChatTrashContent: '৩০ দিনের বেশি পুরনো ট্র্যাশের বার্তা ও ফাইল স্থায়ীভাবে মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না।', + confirmCleanChatTrashContent: + '৩০ দিনের বেশি পুরনো ট্র্যাশের বার্তা ও ফাইল স্থায়ীভাবে মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না।', confirmCleanChatThumbnailsTitle: 'চ্যাট থাম্বনেইল পরিষ্কার', - confirmCleanChatThumbnailsContent: 'সব চ্যাট ছবির থাম্বনেইল ক্যাশে পরিষ্কার হবে, মূল ছবি মুছে যাবে না।', + confirmCleanChatThumbnailsContent: + 'সব চ্যাট ছবির থাম্বনেইল ক্যাশে পরিষ্কার হবে, মূল ছবি মুছে যাবে না।', confirmClearAllCacheTitle: 'সব ক্যাশে মুছুন', - confirmClearAllCacheContent: 'আপনি কি নিশ্চিত সব ক্যাশে ডেটা মুছে ফেলতে চান? অফলাইন কন্টেন্ট মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না।', + confirmClearAllCacheContent: + 'আপনি কি নিশ্চিত সব ক্যাশে ডেটা মুছে ফেলতে চান? অফলাইন কন্টেন্ট মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না।', confirmCleanTransferCacheTitle: 'ট্রান্সফার ক্যাশে পরিষ্কার', - confirmCleanTransferCacheContent: 'ট্রান্সফার থাম্বনেইল, অস্থায়ী ফাইল এবং ৩০ দিনের বেশি পুরনো রেকর্ড পরিষ্কার হবে। প্রাপ্ত ফাইল মুছে যাবে না।', + confirmCleanTransferCacheContent: + 'ট্রান্সফার থাম্বনেইল, অস্থায়ী ফাইল এবং ৩০ দিনের বেশি পুরনো রেকর্ড পরিষ্কার হবে। প্রাপ্ত ফাইল মুছে যাবে না।', confirmClearAllChatDataTitle: 'সব চ্যাট ডেটা মুছুন', - confirmClearAllChatDataContent: 'সব চ্যাট সেশন, বার্তা, সংযুক্তি এবং ট্র্যাশ ডেটা মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না!', + confirmClearAllChatDataContent: + 'সব চ্যাট সেশন, বার্তা, সংযুক্তি এবং ট্র্যাশ ডেটা মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না!', confirmCleanReadlaterCacheTitle: 'পরে পড়ুন ক্যাশে পরিষ্কার', - confirmCleanReadlaterCacheContent: 'পরে পড়ুন থাম্বনেইল, সংযুক্তি এবং সিঙ্ক অস্থায়ী ফাইল পরিষ্কার হবে, বার্তা রেকর্ড মুছে যাবে না।', + confirmCleanReadlaterCacheContent: + 'পরে পড়ুন থাম্বনেইল, সংযুক্তি এবং সিঙ্ক অস্থায়ী ফাইল পরিষ্কার হবে, বার্তা রেকর্ড মুছে যাবে না।', confirmClearReadlaterDataTitle: 'সব পরে পড়ুন ডেটা মুছুন', - confirmClearReadlaterDataContent: 'সব পরে পড়ুন বার্তা, সংযুক্তি এবং থাম্বনেইল মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না!', + confirmClearReadlaterDataContent: + 'সব পরে পড়ুন বার্তা, সংযুক্তি এবং থাম্বনেইল মুছে যাবে, এটি পূর্বাবস্থায় ফেরানো যাবে না!', clearAll: 'সব মুছুন', clean2: 'পরিষ্কার', enabled2: 'সক্রিয়', @@ -1028,7 +1082,8 @@ const bn = T( permShakeUsage: 'দৈনিক উদ্ধৃতি পরিবর্তন|কন্টেন্ট রিফ্রেশ|ইস্টার এগ', permShakeDenial: 'উদ্ধৃতি পরিবর্তন করতে ঝাঁকানো ব্যবহার করতে অসমর্থ', permTrackingLabel: 'ট্র্যাকিং', - permTrackingDesc: 'ব্যক্তিগত কন্টেন্ট সুপারিশের জন্য কার্যকলাপ ট্র্যাকিং অনুমোদন করুন', + permTrackingDesc: + 'ব্যক্তিগত কন্টেন্ট সুপারিশের জন্য কার্যকলাপ ট্র্যাকিং অনুমোদন করুন', permTrackingUsage: 'ব্যক্তিগত সুপারিশ|কন্টেন্ট পছন্দ বিশ্লেষণ', permTrackingDenial: 'ব্যক্তিগত সুপারিশ পাওয়া যাবে না', ), @@ -1331,10 +1386,12 @@ const bn = T( browser: 'ব্রাউজার', unableOpenBrowser: 'ব্রাউজার খোলা যাচ্ছে না', cannotVerifyEmail: 'ইমেইল যাচাই করতে পারছেন না?', - skipEmailVerifyTip: 'আপনি ইমেইল যাচাই এড়িয়ে পরবর্তী ধাপে একটি নিরাপত্তা প্রশ্ন সেট করতে পারেন। নিরাপত্তা প্রশ্ন পাসওয়ার্ড পুনরুদ্ধারের জন্য ব্যবহৃত হয়।', + skipEmailVerifyTip: + 'আপনি ইমেইল যাচাই এড়িয়ে পরবর্তী ধাপে একটি নিরাপত্তা প্রশ্ন সেট করতে পারেন। নিরাপত্তা প্রশ্ন পাসওয়ার্ড পুনরুদ্ধারের জন্য ব্যবহৃত হয়।', setSecQuestionToContinue: 'নিরাপত্তা প্রশ্ন সেট করে চালিয়ে যান', warmTips: 'টিপস', - tipOpenWithoutLogin: 'শিয়ান ইয়ান উন্মুক্ত — লগইন ছাড়াই বেশিরভাগ বৈশিষ্ট্য কাজ করে', + tipOpenWithoutLogin: + 'শিয়ান ইয়ান উন্মুক্ত — লগইন ছাড়াই বেশিরভাগ বৈশিষ্ট্য কাজ করে', tipServerMayFail: 'সার্ভার সমস্যার কারণে মাঝে মাঝে নিবন্ধন ব্যর্থ হতে পারে', tipWillImprove: 'আমরা ভবিষ্যতে আপডেটে নিবন্ধন প্রক্রিয়া উন্নত করব', tips: 'টিপস', @@ -1348,13 +1405,24 @@ const bn = T( resetPasswordSuccess: 'পাসওয়ার্ড সফলভাবে রিসেট হয়েছে', resetPasswordFailed: 'পাসওয়ার্ড রিসেট ব্যর্থ হয়েছে', contactServiceTitle: 'সাপোর্টে যোগাযোগ', - contactServiceSubtitle: 'উপরের পদ্ধতিতে পাসওয়ার্ড রিসেট করতে না পারলে, নিচের তথ্য সহ সাপোর্টে যোগাযোগ করুন', + contactServiceSubtitle: + 'উপরের পদ্ধতিতে পাসওয়ার্ড রিসেট করতে না পারলে, নিচের তথ্য সহ সাপোর্টে যোগাযোগ করুন', contactServiceInfoAccount: 'নিবন্ধিত অ্যাকাউন্ট/ব্যবহারকারী নাম', contactServiceInfoEmail: 'নিবন্ধিত ইমেইল', contactServiceInfoDevice: 'ডিভাইস তথ্য (মডেল/ওএস সংস্করণ)', - contactServiceInfoDescription: 'সমস্যার বিবরণ (নিবন্ধনের তারিখ, ব্যবহার ইত্যাদি)', + contactServiceInfoDescription: + 'সমস্যার বিবরণ (নিবন্ধনের তারিখ, ব্যবহার ইত্যাদি)', contactServiceMethod: 'যোগাযোগের পদ্ধতি', - contactServiceMethodDetail: 'অ্যাপের "সম্পর্কে" পৃষ্ঠা বা অফিসিয়াল ইমেইলের মাধ্যমে সাপোর্টে যোগাযোগ করুন, পরিচয় যাচাই এবং সহায়তার জন্য উপরের তথ্য প্রদান করুন।', + contactServiceMethodDetail: + 'অ্যাপের "সম্পর্কে" পৃষ্ঠা বা অফিসিয়াল ইমেইলের মাধ্যমে সাপোর্টে যোগাযোগ করুন, পরিচয় যাচাই এবং সহায়তার জন্য উপরের তথ্য প্রদান করুন।', + experimentalFeature: 'পরীক্ষামূলক বৈশিষ্ট্য', + experimentalFeatureDesc: + 'Xianyan অ্যাকাউন্ট একটি পরীক্ষামূলক বৈশিষ্ট্য। নিবন্ধন এবং লগইন ব্যবহারে সামান্য প্রভাব ফেলে এবং সুপারিশ করা হয় না। বেশিরভাগ বৈশিষ্ট্য লগইন ছাড়াই কাজ করে।', + dontShowAgain: 'আর দেখাবেন না', + viewExperimentalFeatures: 'পরীক্ষামূলক বৈশিষ্ট্য দেখুন', + userBatchFlag: 'ব্যাচ: {flag}', + openPeriod: 'খোলা: {period}', + expireNotice: '{year} পরে বন্ধ', ), progress: TProgress( title: 'অগ্রগতি', @@ -1956,14 +2024,17 @@ const bn = T( title: 'অ্যাকাউন্ট ইনসাইট', markAllRead: 'সব পড়া হয়েছে', close: 'বন্ধ', - testAccountWarning: '⚠️ এটি সম্ভবত জিয়ানইয়ান অফিসিয়াল প্রদত্ত পরীক্ষা অ্যাকাউন্ট, একাধিক ব্যক্তি ব্যবহার করছেন। পাসওয়ার্ড পরিবর্তন করবেন না, এই অ্যাকাউন্টে নোট তৈরি করবেন না। প্রয়োজনে নিজের অ্যাকাউন্ট নিবন্ধন করুন', - ohosDeviceWarning: '⚠️ কিছু ডিভাইসে শনাক্তকরণ অস্বাভাবিকতা হতে পারে, যেমন অজানা প্রদর্শন বা নিরাপত্তা কারণ ত্রুটি', + testAccountWarning: + '⚠️ এটি সম্ভবত জিয়ানইয়ান অফিসিয়াল প্রদত্ত পরীক্ষা অ্যাকাউন্ট, একাধিক ব্যক্তি ব্যবহার করছেন। পাসওয়ার্ড পরিবর্তন করবেন না, এই অ্যাকাউন্টে নোট তৈরি করবেন না। প্রয়োজনে নিজের অ্যাকাউন্ট নিবন্ধন করুন', + ohosDeviceWarning: + '⚠️ কিছু ডিভাইসে শনাক্তকরণ অস্বাভাবিকতা হতে পারে, যেমন অজানা প্রদর্শন বা নিরাপত্তা কারণ ত্রুটি', allNormal: 'সব স্বাভাবিক', noSecurityIssues: 'কোনো নিরাপত্তা সমস্যা নেই', totalCount: 'মোট {0} টি · {1} টি অপঠিত', refresh: '🔄 রিফ্রেশ', markedAsRead: '"{0}" পড়া হয়েছে হিসেবে চিহ্নিত', - selectReminderMethod: 'রিমাইন্ডার পদ্ধতি নির্বাচন করুন, বা বাতিল করে বর্তমান অবস্থা রাখুন', + selectReminderMethod: + 'রিমাইন্ডার পদ্ধতি নির্বাচন করুন, বা বাতিল করে বর্তমান অবস্থা রাখুন', snooze7Days: '৭ দিনের জন্য স্নুজ', ignoreForever: 'উপেক্ষা · আর মনে করিয়ে দেবেন না', snoozeSet: '🔇 ৭ দিনের জন্য স্নুজ সেট করা হয়েছে', diff --git a/lib/l10n/languages/de.dart b/lib/l10n/languages/de.dart index 219425ad..c07b6fc0 100644 --- a/lib/l10n/languages/de.dart +++ b/lib/l10n/languages/de.dart @@ -622,6 +622,47 @@ const de = T( makeupFailedRetry: 'Nachholen fehlgeschlagen, bitte später erneut versuchen', timesUnit: 'Mal', daysUnit: 'Tage', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Sprache', @@ -1371,6 +1412,13 @@ const de = T( contactServiceInfoDescription: 'Problembeschreibung (z.B. Registrierungsdatum, Nutzung)', contactServiceMethod: 'Kontaktmethode', contactServiceMethodDetail: 'Bitte kontaktieren Sie den Support über die "Über"-Seite in der App oder per offizieller E-Mail und geben Sie die oben genannten Informationen zur Identitätsprüfung und Hilfe beim Zurücksetzen an.', + experimentalFeature: 'Experimentelle Funktion', + experimentalFeatureDesc: 'Das Xianyan-Konto ist eine experimentelle Funktion. Registrierung und Anmeldung haben nur geringen Einfluss auf die Nutzung und werden nicht empfohlen. Die meisten Funktionen sind ohne Anmeldung verfügbar.', + dontShowAgain: 'Nicht mehr anzeigen', + viewExperimentalFeatures: 'Experimentelle Funktionen anzeigen', + userBatchFlag: 'Charge: {flag}', + openPeriod: 'Geöffnet: {period}', + expireNotice: 'Schließt nach {year}', ), progress: TProgress( title: 'Fortschritt', diff --git a/lib/l10n/languages/en.dart b/lib/l10n/languages/en.dart index 85637c5d..72ac48e3 100644 --- a/lib/l10n/languages/en.dart +++ b/lib/l10n/languages/en.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 英语翻译数据 /// 创建时间: 2026-05-29 -/// 更新时间: 2026-06-01 +/// 更新时间: 2026-06-06 /// 作用: 英语(en)翻译文本 -/// 上次更新: 新增accountSettings/dataManagement/source/favorites/offline多语言模块 +/// 上次更新: 新增UserCenter子组件国际化翻译字段 /// ============================================================ import '../types/t.dart'; @@ -546,7 +546,8 @@ const en = T( changeAvatar: 'Change Avatar', inputAvatarUrl: 'Enter Avatar URL', selectFromAlbum: 'Choose from Album (Coming Soon)', - avatarUrlHint: 'URL must not exceed 2048 characters, only http/https image links are supported', + avatarUrlHint: + 'URL must not exceed 2048 characters, only http/https image links are supported', pleaseInputUrl: 'Please enter a URL', urlMustStartWithHttp: 'URL must start with http:// or https://', urlTooLong: 'URL exceeds 2048 character limit, please use a shorter link', @@ -600,7 +601,8 @@ const en = T( noTasksDesc: 'No tasks yet today. Check back later!', great: 'Great!', loginToCheckin: 'Login required to check in', - loginToCheckinDesc: 'Log in to participate in daily check-in and earn points', + loginToCheckinDesc: + 'Log in to participate in daily check-in and earn points', viewAchievementCenter: 'View Achievement Center', todaySigned: 'Already checked in today', tapToCheckin: 'Tap to check in', @@ -621,16 +623,59 @@ const en = T( costPoints: 'Cost Points', currentPoints: 'Current Points', makeupInfo: 'Make-up Info', - makeupLimitInfo: 'Only 1 make-up per day. Insufficient points will prevent make-up (need {0} points)', + makeupLimitInfo: + 'Only 1 make-up per day. Insufficient points will prevent make-up (need {0} points)', confirmMakeup: 'Confirm Make-up', insufficientPoints: 'Insufficient Points', - insufficientPointsDesc: 'Make-up requires {0} points, current: {1}\n\n💡 Earn points via daily check-in, posting articles, etc.', + insufficientPointsDesc: + 'Make-up requires {0} points, current: {1}\n\n💡 Earn points via daily check-in, posting articles, etc.', makeupSuccess: 'Make-up Successful', makeupSuccessDesc: '{0} make-up successful, {1} points deducted', makeupFailed: 'Make-up Failed', makeupFailedRetry: 'Make-up failed, please try again later', timesUnit: 'times', daysUnit: 'days', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Language', @@ -912,19 +957,26 @@ const en = T( allCacheCleared: 'All cache cleared', cleanFailed2: 'Cleanup failed: {0}', confirmCleanChatTrashTitle: 'Clean Chat Trash', - confirmCleanChatTrashContent: 'Permanently delete messages and files in trash older than 30 days. This cannot be undone.', + confirmCleanChatTrashContent: + 'Permanently delete messages and files in trash older than 30 days. This cannot be undone.', confirmCleanChatThumbnailsTitle: 'Clean Chat Thumbnails', - confirmCleanChatThumbnailsContent: 'Clean all chat image thumbnail cache. Original images will not be deleted.', + confirmCleanChatThumbnailsContent: + 'Clean all chat image thumbnail cache. Original images will not be deleted.', confirmClearAllCacheTitle: 'Clear All Cache', - confirmClearAllCacheContent: 'Are you sure you want to clear all cached data? Offline content will be deleted. This cannot be undone.', + confirmClearAllCacheContent: + 'Are you sure you want to clear all cached data? Offline content will be deleted. This cannot be undone.', confirmCleanTransferCacheTitle: 'Clean Transfer Cache', - confirmCleanTransferCacheContent: 'Clean transfer thumbnails, temporary files, and transfer records older than 30 days. Received files will not be deleted.', + confirmCleanTransferCacheContent: + 'Clean transfer thumbnails, temporary files, and transfer records older than 30 days. Received files will not be deleted.', confirmClearAllChatDataTitle: 'Clear All Chat Data', - confirmClearAllChatDataContent: 'Delete all chat sessions, messages, attachments, and trash data. This cannot be undone!', + confirmClearAllChatDataContent: + 'Delete all chat sessions, messages, attachments, and trash data. This cannot be undone!', confirmCleanReadlaterCacheTitle: 'Clean Read Later Cache', - confirmCleanReadlaterCacheContent: 'Clean read later thumbnails, attachments, and sync temp files. Message records will not be deleted.', + confirmCleanReadlaterCacheContent: + 'Clean read later thumbnails, attachments, and sync temp files. Message records will not be deleted.', confirmClearReadlaterDataTitle: 'Clear All Read Later Data', - confirmClearReadlaterDataContent: 'Delete all read later messages, attachments, and thumbnails. This cannot be undone!', + confirmClearReadlaterDataContent: + 'Delete all read later messages, attachments, and thumbnails. This cannot be undone!', clearAll: 'Clear All', clean2: 'Clean', enabled2: 'Enabled', @@ -1034,9 +1086,12 @@ const en = T( permShakeUsage: 'Switch daily quote|Refresh content|Easter egg', permShakeDenial: 'Cannot use shake to switch quotes', permTrackingLabel: 'Tracking', - permTrackingDesc: 'Allow the app to request tracking of your activity for personalized content recommendations', - permTrackingUsage: 'Personalized recommendations|Content preference analysis', - permTrackingDenial: 'Unable to receive personalized recommendations, content suggestions may be less accurate', + permTrackingDesc: + 'Allow the app to request tracking of your activity for personalized content recommendations', + permTrackingUsage: + 'Personalized recommendations|Content preference analysis', + permTrackingDenial: + 'Unable to receive personalized recommendations, content suggestions may be less accurate', ), dataCollection: TSettingsDataCollection( pageTitle: 'Data We Collect', @@ -1343,12 +1398,16 @@ const en = T( browser: 'Browser', unableOpenBrowser: 'Unable to open browser', cannotVerifyEmail: 'Cannot verify email?', - skipEmailVerifyTip: 'You can skip email verification and set a security question in the next step as an alternative credential. Security questions can be used for password recovery and account security verification.', + skipEmailVerifyTip: + 'You can skip email verification and set a security question in the next step as an alternative credential. Security questions can be used for password recovery and account security verification.', setSecQuestionToContinue: 'Set security question to continue', warmTips: 'Tips', - tipOpenWithoutLogin: 'XianYan remains open — most features work without logging in', - tipServerMayFail: 'Server issues may occasionally cause registration to fail', - tipWillImprove: 'We will improve the registration process in future updates', + tipOpenWithoutLogin: + 'XianYan remains open — most features work without logging in', + tipServerMayFail: + 'Server issues may occasionally cause registration to fail', + tipWillImprove: + 'We will improve the registration process in future updates', tips: 'Tips', forgotPasswordTitle: 'Forgot Password', forgotPasswordSubtitle: 'Reset your password via verification', @@ -1360,13 +1419,24 @@ const en = T( resetPasswordSuccess: 'Password reset successfully', resetPasswordFailed: 'Password reset failed', contactServiceTitle: 'Contact Support', - contactServiceSubtitle: 'If you cannot reset your password through the above methods, please contact support with the following information', + contactServiceSubtitle: + 'If you cannot reset your password through the above methods, please contact support with the following information', contactServiceInfoAccount: 'Registered account/username', contactServiceInfoEmail: 'Registered email', contactServiceInfoDevice: 'Device info (model/OS version)', - contactServiceInfoDescription: 'Issue description (e.g. registration time, usage)', + contactServiceInfoDescription: + 'Issue description (e.g. registration time, usage)', contactServiceMethod: 'Contact Method', - contactServiceMethodDetail: 'Please contact support via the "About" page in the app or official email, providing the above information for identity verification and password reset assistance.', + contactServiceMethodDetail: + 'Please contact support via the "About" page in the app or official email, providing the above information for identity verification and password reset assistance.', + experimentalFeature: 'Experimental Feature', + experimentalFeatureDesc: + 'Xianyan account is an experimental feature. Registration and login have minimal impact on usage and are not recommended. Most features work without logging in.', + dontShowAgain: "Don't Show Again", + viewExperimentalFeatures: 'View Experimental Features', + userBatchFlag: 'Batch: {flag}', + openPeriod: 'Open: {period}', + expireNotice: 'Closes after {year}', ), progress: TProgress( title: 'Progress', @@ -1960,22 +2030,27 @@ const en = T( allChannelsCached: 'All channels already cached', preloadFailed: 'Preload failed, please check your connection', preloadError: 'Preload error: {error}', - preloadModeDisabledHint: 'Preload mode is disabled, enable it in smart preload strategy', + preloadModeDisabledHint: + 'Preload mode is disabled, enable it in smart preload strategy', wifiOnlyModeHint: 'WiFi-only mode active, please connect to WiFi', - preloadNoNewContent: 'No new content to preload, all up-to-date or rate limited', + preloadNoNewContent: + 'No new content to preload, all up-to-date or rate limited', ), accountInsights: TAccountInsights( title: 'Account Insights', markAllRead: 'Mark All Read', close: 'Close', - testAccountWarning: '⚠️ This may be a test account provided by Xianyan official, used by multiple people. Please do not change the password or create notes with this account. Please register your own account if needed.', - ohosDeviceWarning: '⚠️ Some devices may experience identification issues, such as showing unknown or security factor errors', + testAccountWarning: + '⚠️ This may be a test account provided by Xianyan official, used by multiple people. Please do not change the password or create notes with this account. Please register your own account if needed.', + ohosDeviceWarning: + '⚠️ Some devices may experience identification issues, such as showing unknown or security factor errors', allNormal: 'All Normal', noSecurityIssues: 'No security issues to pay attention to', totalCount: '{0} total · {1} unread', refresh: '🔄 Refresh', markedAsRead: '"{0}" marked as read', - selectReminderMethod: 'Select reminder method, or cancel to keep current state', + selectReminderMethod: + 'Select reminder method, or cancel to keep current state', snooze7Days: 'Snooze for 7 days', ignoreForever: 'Ignore · Never remind again', snoozeSet: '🔇 Snoozed for 7 days', diff --git a/lib/l10n/languages/es.dart b/lib/l10n/languages/es.dart index e4a0d69f..af09f4ce 100644 --- a/lib/l10n/languages/es.dart +++ b/lib/l10n/languages/es.dart @@ -632,6 +632,47 @@ const es = T( makeupFailedRetry: 'Recuperación fallida, intenta más tarde', timesUnit: 'veces', daysUnit: 'días', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Idioma', @@ -1379,6 +1420,13 @@ const es = T( contactServiceInfoDescription: 'Descripción del problema (fecha de registro, uso, etc.)', contactServiceMethod: 'Método de contacto', contactServiceMethodDetail: 'Contacte al soporte a través de la página "Acerca de" en la app o por correo oficial, proporcionando la información anterior para verificación de identidad y asistencia.', + experimentalFeature: 'Función experimental', + experimentalFeatureDesc: 'La cuenta de Xianyan es una función experimental. El registro y el inicio de sesión tienen poco impacto en el uso y no se recomiendan. La mayoría de las funciones están disponibles sin iniciar sesión.', + dontShowAgain: 'No mostrar de nuevo', + viewExperimentalFeatures: 'Ver funciones experimentales', + userBatchFlag: 'Lote: {flag}', + openPeriod: 'Abierto: {period}', + expireNotice: 'Cierra después de {year}', ), progress: TProgress( title: 'Progreso', diff --git a/lib/l10n/languages/fr.dart b/lib/l10n/languages/fr.dart index 637e0958..6053f29c 100644 --- a/lib/l10n/languages/fr.dart +++ b/lib/l10n/languages/fr.dart @@ -624,6 +624,47 @@ const fr = T( makeupFailedRetry: 'Rattrapage échoué, réessayez plus tard', timesUnit: 'fois', daysUnit: 'jours', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Langue', @@ -1388,6 +1429,13 @@ const fr = T( contactServiceInfoDescription: 'Description du problème (date d\'inscription, utilisation, etc.)', contactServiceMethod: 'Méthode de contact', contactServiceMethodDetail: 'Veuillez contacter le support via la page "À propos" dans l\'application ou par e-mail officiel, en fournissant les informations ci-dessus pour vérification d\'identité et assistance à la réinitialisation.', + experimentalFeature: 'Fonctionnalité expérimentale', + experimentalFeatureDesc: 'Le compte Xianyan est une fonctionnalité expérimentale. L\'inscription et la connexion ont peu d\'impact sur l\'utilisation et ne sont pas recommandées. La plupart des fonctionnalités sont accessibles sans connexion.', + dontShowAgain: 'Ne plus afficher', + viewExperimentalFeatures: 'Voir les fonctionnalités expérimentales', + userBatchFlag: 'Lot : {flag}', + openPeriod: 'Ouvert : {period}', + expireNotice: 'Ferme après {year}', ), progress: TProgress( title: 'Progrès', diff --git a/lib/l10n/languages/hi.dart b/lib/l10n/languages/hi.dart index cdc8d8c1..ac5beaf2 100644 --- a/lib/l10n/languages/hi.dart +++ b/lib/l10n/languages/hi.dart @@ -620,6 +620,47 @@ const hi = T( makeupFailedRetry: 'पूरक विफल, बाद में पुनः प्रयास करें', timesUnit: 'बार', daysUnit: 'दिन', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'भाषा', @@ -1351,6 +1392,13 @@ const hi = T( contactServiceInfoDescription: 'समस्या विवरण (पंजीकरण तिथि, उपयोग आदि)', contactServiceMethod: 'संपर्क विधि', contactServiceMethodDetail: 'कृपया ऐप में "परिचय" पेज या आधिकारिक ईमेल के माध्यम से सहायता से संपर्क करें, पहचान सत्यापन और सहायता के लिए उपरोक्त जानकारी प्रदान करें।', + experimentalFeature: 'प्रायोगिक सुविधा', + experimentalFeatureDesc: 'Xianyan खाता एक प्रायोगिक सुविधा है। पंजीकरण और लॉगिन का उपयोग पर बहुत कम प्रभाव है और अनुशंसित नहीं है। अधिकांश सुविधाएँ लॉगिन के बिना काम करती हैं।', + dontShowAgain: 'फिर से न दिखाएँ', + viewExperimentalFeatures: 'प्रायोगिक सुविधाएँ देखें', + userBatchFlag: 'बैच: {flag}', + openPeriod: 'खुला: {period}', + expireNotice: '{year} के बाद बंद', ), progress: TProgress( title: 'प्रगति', diff --git a/lib/l10n/languages/it.dart b/lib/l10n/languages/it.dart index 75e14363..ba4eae1e 100644 --- a/lib/l10n/languages/it.dart +++ b/lib/l10n/languages/it.dart @@ -630,6 +630,47 @@ const it = T( makeupFailedRetry: 'Recupero fallito, riprova più tardi', timesUnit: 'volte', daysUnit: 'giorni', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Lingua', @@ -1378,6 +1419,13 @@ const it = T( contactServiceInfoDescription: 'Descrizione del problema (data di registrazione, utilizzo, ecc.)', contactServiceMethod: 'Metodo di contatto', contactServiceMethodDetail: 'Contatta il supporto tramite la pagina "Informazioni" nell\'app o via email ufficiale, fornendo le informazioni sopra per la verifica dell\'identità e l\'assistenza.', + experimentalFeature: 'Funzione sperimentale', + experimentalFeatureDesc: 'L\'account Xianyan è una funzione sperimentale. La registrazione e l\'accesso hanno poco impatto sull\'uso e non sono raccomandati. La maggior parte delle funzioni è disponibile senza accesso.', + dontShowAgain: 'Non mostrare più', + viewExperimentalFeatures: 'Visualizza funzioni sperimentali', + userBatchFlag: 'Batch: {flag}', + openPeriod: 'Aperto: {period}', + expireNotice: 'Chiude dopo {year}', ), progress: TProgress( title: 'Progresso', diff --git a/lib/l10n/languages/ja.dart b/lib/l10n/languages/ja.dart index 51be9999..fe99fc37 100644 --- a/lib/l10n/languages/ja.dart +++ b/lib/l10n/languages/ja.dart @@ -612,13 +612,55 @@ const ja = T( makeupLimitInfo: '1日1回のみ振替可能。ポイント不足の場合は振替できません({0}ポイント必要)', confirmMakeup: '振替を確認', insufficientPoints: 'ポイント不足', - insufficientPointsDesc: '振替には{0}ポイントが必要です。現在: {1}\n\n💡 毎日のチェックインや記事の投稿でポイントを獲得できます', + insufficientPointsDesc: + '振替には{0}ポイントが必要です。現在: {1}\n\n💡 毎日のチェックインや記事の投稿でポイントを獲得できます', makeupSuccess: '振替成功', makeupSuccessDesc: '{0} 振替成功、{1}ポイント消費', makeupFailed: '振替失敗', makeupFailedRetry: '振替に失敗しました。後でもう一度お試しください', timesUnit: '回', daysUnit: '日', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: '言語', @@ -896,17 +938,23 @@ const ja = T( confirmCleanChatTrashTitle: 'チャットゴミ箱をクリア', confirmCleanChatTrashContent: '30日以上前のメッセージとファイルを永久に削除します。この操作は取り消せません。', confirmCleanChatThumbnailsTitle: 'チャットサムネイルをクリア', - confirmCleanChatThumbnailsContent: 'すべてのチャット画像のサムネイルキャッシュをクリアします。元の画像は削除されません。', + confirmCleanChatThumbnailsContent: + 'すべてのチャット画像のサムネイルキャッシュをクリアします。元の画像は削除されません。', confirmClearAllCacheTitle: '全キャッシュをクリア', - confirmClearAllCacheContent: 'すべてのキャッシュデータをクリアしますか?オフラインコンテンツが削除されます。この操作は取り消せません。', + confirmClearAllCacheContent: + 'すべてのキャッシュデータをクリアしますか?オフラインコンテンツが削除されます。この操作は取り消せません。', confirmCleanTransferCacheTitle: '転送キャッシュをクリア', - confirmCleanTransferCacheContent: '転送サムネイル、一時ファイル、30日以上前の転送記録をクリアします。受信したファイルは削除されません。', + confirmCleanTransferCacheContent: + '転送サムネイル、一時ファイル、30日以上前の転送記録をクリアします。受信したファイルは削除されません。', confirmClearAllChatDataTitle: '全チャットデータをクリア', - confirmClearAllChatDataContent: 'すべてのチャットセッション、メッセージ、添付ファイル、ゴミ箱データを削除します。この操作は取り消せません!', + confirmClearAllChatDataContent: + 'すべてのチャットセッション、メッセージ、添付ファイル、ゴミ箱データを削除します。この操作は取り消せません!', confirmCleanReadlaterCacheTitle: '後で読むキャッシュをクリア', - confirmCleanReadlaterCacheContent: '後で読むサムネイル、添付ファイル、同期一時ファイルをクリアします。メッセージ記録は削除されません。', + confirmCleanReadlaterCacheContent: + '後で読むサムネイル、添付ファイル、同期一時ファイルをクリアします。メッセージ記録は削除されません。', confirmClearReadlaterDataTitle: '全後で読むデータをクリア', - confirmClearReadlaterDataContent: 'すべての後で読むメッセージ、添付ファイル、サムネイルを削除します。この操作は取り消せません!', + confirmClearReadlaterDataContent: + 'すべての後で読むメッセージ、添付ファイル、サムネイルを削除します。この操作は取り消せません!', clearAll: 'すべてクリア', clean2: 'クリア', enabled2: '有効', @@ -1293,7 +1341,8 @@ const ja = T( browser: 'ブラウザ', unableOpenBrowser: 'ブラウザを開けません', cannotVerifyEmail: 'メールを認証できませんか?', - skipEmailVerifyTip: 'メール認証をスキップし、次のステップで秘密の質問を設定して登録を完了できます。秘密の質問はパスワードリカバリやアカウントセキュリティに使用できます。', + skipEmailVerifyTip: + 'メール認証をスキップし、次のステップで秘密の質問を設定して登録を完了できます。秘密の質問はパスワードリカバリやアカウントセキュリティに使用できます。', setSecQuestionToContinue: '秘密の質問を設定して続行', warmTips: 'ヒント', tipOpenWithoutLogin: '閑言はオープンであり、ログインしなくてもほとんどの機能を利用できます', @@ -1316,7 +1365,16 @@ const ja = T( contactServiceInfoDevice: 'デバイス情報(モデル/OSバージョン)', contactServiceInfoDescription: '問題の説明(登録時期、使用状況など)', contactServiceMethod: '連絡方法', - contactServiceMethodDetail: 'アプリ内の「について」ページまたは公式メールからサポートにご連絡ください。本人確認後、パスワードリセットをサポートいたします。', + contactServiceMethodDetail: + 'アプリ内の「について」ページまたは公式メールからサポートにご連絡ください。本人確認後、パスワードリセットをサポートいたします。', + experimentalFeature: '実験的な機能', + experimentalFeatureDesc: + '閑言アカウントは実験的な機能です。登録・ログインは利用にほとんど影響せず、推奨されません。ほとんどの機能はログインなしで利用できます。', + dontShowAgain: '今後表示しない', + viewExperimentalFeatures: '実験的な機能を見る', + userBatchFlag: 'バッチ: {flag}', + openPeriod: '公開期間: {period}', + expireNotice: '{year}以降に閉鎖', ), progress: TProgress( title: '進捗', @@ -1899,7 +1957,8 @@ const ja = T( title: 'アカウントインサイト', markAllRead: 'すべて既読にする', close: '閉じる', - testAccountWarning: '⚠️ このアカウントは閑言公式が提供するテストアカウントの可能性があります。複数人が使用しています。パスワードを変更しないでください、このアカウントでノートを作成しないでください。必要に応じてご自身でアカウント登録してください。', + testAccountWarning: + '⚠️ このアカウントは閑言公式が提供するテストアカウントの可能性があります。複数人が使用しています。パスワードを変更しないでください、このアカウントでノートを作成しないでください。必要に応じてご自身でアカウント登録してください。', ohosDeviceWarning: '⚠️ 一部のデバイスでは識別異常が発生する場合があります(不明と表示やセキュリティ要素エラーなど)', allNormal: 'すべて正常', noSecurityIssues: '注意が必要なセキュリティ項目はありません', diff --git a/lib/l10n/languages/ko.dart b/lib/l10n/languages/ko.dart index e0faf7e2..6b9a9b22 100644 --- a/lib/l10n/languages/ko.dart +++ b/lib/l10n/languages/ko.dart @@ -612,13 +612,55 @@ const ko = T( makeupLimitInfo: '하루 1회만 보충 가능. 포인트 부족 시 불가 ({0}포인트 필요)', confirmMakeup: '보충 확인', insufficientPoints: '포인트 부족', - insufficientPointsDesc: '보충에 {0}포인트 필요, 현재: {1}\n\n💡 매일 출석, 글 작성 등으로 포인트 획득 가능', + insufficientPointsDesc: + '보충에 {0}포인트 필요, 현재: {1}\n\n💡 매일 출석, 글 작성 등으로 포인트 획득 가능', makeupSuccess: '보충 성공', makeupSuccessDesc: '{0} 보충 성공, {1}포인트 차감', makeupFailed: '보충 실패', makeupFailedRetry: '보충 실패, 나중에 다시 시도해 주세요', timesUnit: '회', daysUnit: '일', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: '언어', @@ -894,19 +936,26 @@ const ko = T( allCacheCleared: '전체 캐시가 삭제되었습니다', cleanFailed2: '정리 실패: {0}', confirmCleanChatTrashTitle: '채팅 휴지통 정리', - confirmCleanChatTrashContent: '30일 이상 된 메시지와 파일을 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다.', + confirmCleanChatTrashContent: + '30일 이상 된 메시지와 파일을 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다.', confirmCleanChatThumbnailsTitle: '썸네일 정리', - confirmCleanChatThumbnailsContent: '모든 채팅 이미지의 썸네일 캐시를 정리합니다. 원본 이미지는 삭제되지 않습니다.', + confirmCleanChatThumbnailsContent: + '모든 채팅 이미지의 썸네일 캐시를 정리합니다. 원본 이미지는 삭제되지 않습니다.', confirmClearAllCacheTitle: '전체 캐시 삭제', - confirmClearAllCacheContent: '모든 캐시 데이터를 삭제하시겠습니까? 오프라인 콘텐츠가 삭제됩니다. 이 작업은 되돌릴 수 없습니다.', + confirmClearAllCacheContent: + '모든 캐시 데이터를 삭제하시겠습니까? 오프라인 콘텐츠가 삭제됩니다. 이 작업은 되돌릴 수 없습니다.', confirmCleanTransferCacheTitle: '전송 캐시 정리', - confirmCleanTransferCacheContent: '전송 썸네일, 임시 파일, 30일 이상 된 전송 기록을 정리합니다. 받은 파일은 삭제되지 않습니다.', + confirmCleanTransferCacheContent: + '전송 썸네일, 임시 파일, 30일 이상 된 전송 기록을 정리합니다. 받은 파일은 삭제되지 않습니다.', confirmClearAllChatDataTitle: '전체 채팅 데이터 삭제', - confirmClearAllChatDataContent: '모든 채팅 세션, 메시지, 첨부파일, 휴지통 데이터를 삭제합니다. 이 작업은 되돌릴 수 없습니다!', + confirmClearAllChatDataContent: + '모든 채팅 세션, 메시지, 첨부파일, 휴지통 데이터를 삭제합니다. 이 작업은 되돌릴 수 없습니다!', confirmCleanReadlaterCacheTitle: '나중에 읽기 캐시 정리', - confirmCleanReadlaterCacheContent: '나중에 읽기 썸네일, 첨부파일, 동기 임시 파일을 정리합니다. 메시지 기록은 삭제되지 않습니다.', + confirmCleanReadlaterCacheContent: + '나중에 읽기 썸네일, 첨부파일, 동기 임시 파일을 정리합니다. 메시지 기록은 삭제되지 않습니다.', confirmClearReadlaterDataTitle: '전체 나중에 읽기 데이터 삭제', - confirmClearReadlaterDataContent: '모든 나중에 읽기 메시지, 첨부파일, 썸네일을 삭제합니다. 이 작업은 되돌릴 수 없습니다!', + confirmClearReadlaterDataContent: + '모든 나중에 읽기 메시지, 첨부파일, 썸네일을 삭제합니다. 이 작업은 되돌릴 수 없습니다!', clearAll: '전체 삭제', clean2: '정리', enabled2: '켬짐', @@ -1294,7 +1343,8 @@ const ko = T( browser: '브라우저', unableOpenBrowser: '브라우저를 열 수 없습니다', cannotVerifyEmail: '이메일을 인증할 수 없나요?', - skipEmailVerifyTip: '이메일 인증을 건너뛰고 다음 단계에서 보안 질문을 설정하여 등록을 완료할 수 있습니다. 보안 질문은 비밀번호 복구 및 계정 보안에 사용됩니다.', + skipEmailVerifyTip: + '이메일 인증을 건너뛰고 다음 단계에서 보안 질문을 설정하여 등록을 완료할 수 있습니다. 보안 질문은 비밀번호 복구 및 계정 보안에 사용됩니다.', setSecQuestionToContinue: '보안 질문 설정 후 계속', warmTips: '팁', tipOpenWithoutLogin: '셴옌은 개방적이며 로그인 없이도 대부분의 기능을 사용할 수 있습니다', @@ -1317,7 +1367,16 @@ const ko = T( contactServiceInfoDevice: '기기 정보(모델/OS 버전)', contactServiceInfoDescription: '문제 설명(가입 시기, 사용 상황 등)', contactServiceMethod: '연락 방법', - contactServiceMethodDetail: '앱 내 "정보" 페이지 또는 공식 이메일을 통해 고객 지원에 문의하세요. 본인 확인 후 비밀번호 재설정을 도와드립니다.', + contactServiceMethodDetail: + '앱 내 "정보" 페이지 또는 공식 이메일을 통해 고객 지원에 문의하세요. 본인 확인 후 비밀번호 재설정을 도와드립니다.', + experimentalFeature: '실험적 기능', + experimentalFeatureDesc: + '한언 계정은 실험적 기능입니다. 가입 및 로그인은 사용에 큰 영향이 없으며 권장하지 않습니다. 대부분의 기능은 로그인 없이 사용할 수 있습니다.', + dontShowAgain: '다시 표시하지 않기', + viewExperimentalFeatures: '실험적 기능 보기', + userBatchFlag: '배치: {flag}', + openPeriod: '공개 기간: {period}', + expireNotice: '{year} 이후 종료', ), progress: TProgress( title: '진행 상황', @@ -1900,8 +1959,10 @@ const ko = T( title: '계정 인사이트', markAllRead: '모두 읽음으로 표시', close: '닫기', - testAccountWarning: '⚠️ 현재 계정은 閑言 공식에서 제공하는 테스트 계정일 수 있습니다. 여러 사람이 사용하고 있습니다. 비밀번호를 변경하지 마시고, 이 계정으로 노트를 만들지 마십시오. 필요한 경우 직접 계정을 등록하십시오.', - ohosDeviceWarning: '⚠️ 일부 기기에서는 식별 오류가 발생할 수 있습니다 (알 수 없음으로 표시되거나 보안 요소 오류 등)', + testAccountWarning: + '⚠️ 현재 계정은 閑言 공식에서 제공하는 테스트 계정일 수 있습니다. 여러 사람이 사용하고 있습니다. 비밀번호를 변경하지 마시고, 이 계정으로 노트를 만들지 마십시오. 필요한 경우 직접 계정을 등록하십시오.', + ohosDeviceWarning: + '⚠️ 일부 기기에서는 식별 오류가 발생할 수 있습니다 (알 수 없음으로 표시되거나 보안 요소 오류 등)', allNormal: '모든 정상', noSecurityIssues: '주의가 필요한 보안 사항이 없습니다', totalCount: '총 {0}건 · {1}건 읽지 않음', diff --git a/lib/l10n/languages/pt.dart b/lib/l10n/languages/pt.dart index 0b07edac..b33cd2d8 100644 --- a/lib/l10n/languages/pt.dart +++ b/lib/l10n/languages/pt.dart @@ -630,6 +630,47 @@ const pt = T( makeupFailedRetry: 'Recuperação falhou, tente novamente mais tarde', timesUnit: 'vezes', daysUnit: 'dias', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Idioma', @@ -1372,6 +1413,13 @@ const pt = T( contactServiceInfoDescription: 'Descrição do problema (data de registro, uso, etc.)', contactServiceMethod: 'Método de contato', contactServiceMethodDetail: 'Contate o suporte pela página "Sobre" no app ou por e-mail oficial, fornecendo as informações acima para verificação de identidade e assistência.', + experimentalFeature: 'Recurso experimental', + experimentalFeatureDesc: 'A conta Xianyan é um recurso experimental. O registro e o login têm pouco impacto no uso e não são recomendados. A maioria dos recursos funciona sem login.', + dontShowAgain: 'Não mostrar novamente', + viewExperimentalFeatures: 'Ver recursos experimentais', + userBatchFlag: 'Lote: {flag}', + openPeriod: 'Aberto: {period}', + expireNotice: 'Fecha após {year}', ), progress: TProgress( title: 'Progresso', diff --git a/lib/l10n/languages/ru.dart b/lib/l10n/languages/ru.dart index 1a1826ac..5b7873af 100644 --- a/lib/l10n/languages/ru.dart +++ b/lib/l10n/languages/ru.dart @@ -627,6 +627,47 @@ const ru = T( makeupFailedRetry: 'Добор не удался, попробуйте позже', timesUnit: 'раз', daysUnit: 'дней', + accountAndData: 'Account & Data', + editProfile: 'Edit Profile', + edit: 'Edit', + editBio: 'Edit Bio', + save: 'Save', + pleaseInput: 'Please enter', + modifySuccess: 'Modified successfully', + modifyFailed: 'Modification failed', + userProfile: 'User Profile', + goBack: 'Back', + userNotExist: 'User not found', + retry: 'Retry', + anonymousUser: 'Anonymous', + articles: 'Articles', + follow: 'Follow', + followed: 'Following', + theUser: 'this user', + privateMessage: 'Message', + gotIt: 'Got it', + shareProfile: 'Share Profile', + blockUser: 'Block User', + personalBio: 'Bio', + titleLevel: 'Title Level', + activeData: 'Activity', + beginner: 'Beginner', + apprentice: 'Apprentice', + skilled: 'Skilled', + expert: 'Expert', + master: 'Master', + signInCount: 'Check-in {count}', + noteCount: 'Notes {count}', + likeCount: 'Likes {count}', + commentCount: 'Comments {count}', + viewCount: 'Views {count}', + readLaterCount: 'Read Later {count}', + modifyField: 'Edit {field}', + pleaseInputField: 'Enter {field}', + fieldModifySuccess: '{field} updated', + fieldModifyFailed: 'Failed: {error}', + debugInfo: 'Debug Info', + defaultBio: 'Light up every moment with words', ), settings: TSettings( language: 'Язык', @@ -1371,6 +1412,13 @@ const ru = T( contactServiceInfoDescription: 'Описание проблемы (дата регистрации, использование и т.д.)', contactServiceMethod: 'Способ связи', contactServiceMethodDetail: 'Обратитесь в поддержку через страницу «О приложении» или по официальной электронной почте, предоставив указанную информацию для проверки личности и помощи в сбросе пароля.', + experimentalFeature: 'Экспериментальная функция', + experimentalFeatureDesc: 'Аккаунт Xianyan — экспериментальная функция. Регистрация и вход мало влияют на использование и не рекомендуются. Большинство функций доступны без входа.', + dontShowAgain: 'Больше не показывать', + viewExperimentalFeatures: 'Посмотреть экспериментальные функции', + userBatchFlag: 'Партия: {flag}', + openPeriod: 'Открыто: {period}', + expireNotice: 'Закроется после {year}', ), progress: TProgress( title: 'Прогресс', diff --git a/lib/l10n/languages/zh_cn.dart b/lib/l10n/languages/zh_cn.dart index 6dab2570..7a6d4991 100644 --- a/lib/l10n/languages/zh_cn.dart +++ b/lib/l10n/languages/zh_cn.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 简体中文翻译数据 /// 创建时间: 2026-05-29 -/// 更新时间: 2026-06-01 +/// 更新时间: 2026-06-06 /// 作用: 简体中文(zh_CN)翻译文本 -/// 上次更新: 新增accountSettings/dataManagement/source/favorites/offline多语言模块 +/// 上次更新: 新增UserCenter子组件国际化翻译字段 /// ============================================================ import '../types/t.dart'; @@ -618,6 +618,47 @@ const zhCN = T( makeupFailedRetry: '补签失败,请稍后重试', timesUnit: '次', daysUnit: '天', + accountAndData: '账户与数据', + editProfile: '编辑资料', + edit: '编辑', + editBio: '修改签名', + save: '保存', + pleaseInput: '请输入', + modifySuccess: '修改成功', + modifyFailed: '修改失败', + userProfile: '用户主页', + goBack: '返回', + userNotExist: '用户不存在', + retry: '重试', + anonymousUser: '匿名用户', + articles: '文章', + follow: '关注', + followed: '已关注', + theUser: '该用户', + privateMessage: '私信', + gotIt: '知道了', + shareProfile: '分享主页', + blockUser: '屏蔽用户', + personalBio: '个人简介', + titleLevel: '头衔等级', + activeData: '活跃数据', + beginner: '新手', + apprentice: '学徒', + skilled: '熟练工', + expert: '专家', + master: '大师', + signInCount: '签到 {count} 次', + noteCount: '笔记 {count} 篇', + likeCount: '点赞 {count} 次', + commentCount: '评论 {count} 条', + viewCount: '浏览 {count} 次', + readLaterCount: '稍后读 {count}', + modifyField: '修改{field}', + pleaseInputField: '请输入{field}', + fieldModifySuccess: '{field}修改成功', + fieldModifyFailed: '修改失败: {error}', + debugInfo: '调试信息', + defaultBio: '用文字点亮生活的每一刻', ), settings: TSettings( language: '语言', @@ -1281,7 +1322,8 @@ const zhCN = T( browser: '浏览器', unableOpenBrowser: '无法打开浏览器', cannotVerifyEmail: '无法验证邮箱?', - skipEmailVerifyTip: '您可以跳过邮箱验证,在下一步设置密保问题作为安全凭证,同样可以完成注册流程。密保问题可用于后续密码找回和账号安全验证。', + skipEmailVerifyTip: + '您可以跳过邮箱验证,在下一步设置密保问题作为安全凭证,同样可以完成注册流程。密保问题可用于后续密码找回和账号安全验证。', setSecQuestionToContinue: '设置密保问题继续注册', warmTips: '温馨提示', tipOpenWithoutLogin: '闲言保持开放性,即使不登录也能体验大部分功能', @@ -1305,6 +1347,14 @@ const zhCN = T( contactServiceInfoDescription: '问题描述(如注册时间、使用情况)', contactServiceMethod: '联系方式', contactServiceMethodDetail: '请通过App内「关于」页面或官方邮箱联系客服,提供以上信息以便核实身份后协助重置密码。', + experimentalFeature: '实验中的功能', + experimentalFeatureDesc: + '闲言账号为实验中的功能,注册登录对用户使用影响不大,且不推荐用户注册登录。大部分功能无需登录即可使用。', + dontShowAgain: '不再提示', + viewExperimentalFeatures: '查看实验中的功能', + userBatchFlag: '用户批次标志:{flag}', + openPeriod: '开放时间:{period}', + expireNotice: '到期后关闭通道({year})', ), progress: TProgress( title: '进度', @@ -1887,7 +1937,8 @@ const zhCN = T( title: '账户洞察', markAllRead: '全部已读', close: '关闭', - testAccountWarning: '⚠️ 当前账号可能是闲言官方提供的测试账号,多人在使用,请勿修改密码,请勿使用当前账号创建笔记,如有需要请自行注册账户', + testAccountWarning: + '⚠️ 当前账号可能是闲言官方提供的测试账号,多人在使用,请勿修改密码,请勿使用当前账号创建笔记,如有需要请自行注册账户', ohosDeviceWarning: '⚠️ 部分设备可能会出现识别异常,如显示未知或安全因素出错', allNormal: '一切正常', noSecurityIssues: '暂无需要关注的账户安全事项', diff --git a/lib/l10n/languages/zh_tw.dart b/lib/l10n/languages/zh_tw.dart index a6f77c1f..7891ad56 100644 --- a/lib/l10n/languages/zh_tw.dart +++ b/lib/l10n/languages/zh_tw.dart @@ -618,6 +618,47 @@ const zhTW = T( makeupFailedRetry: '補簽失敗,請稍後重試', timesUnit: '次', daysUnit: '天', + accountAndData: '帳戶與資料', + editProfile: '編輯資料', + edit: '編輯', + editBio: '修改簽名', + save: '儲存', + pleaseInput: '請輸入', + modifySuccess: '修改成功', + modifyFailed: '修改失敗', + userProfile: '使用者主頁', + goBack: '返回', + userNotExist: '使用者不存在', + retry: '重試', + anonymousUser: '匿名使用者', + articles: '文章', + follow: '關注', + followed: '已關注', + theUser: '該使用者', + privateMessage: '私信', + gotIt: '知道了', + shareProfile: '分享主頁', + blockUser: '封鎖使用者', + personalBio: '個人簡介', + titleLevel: '頭銜等級', + activeData: '活躍數據', + beginner: '新手', + apprentice: '學徒', + skilled: '熟練工', + expert: '專家', + master: '大師', + signInCount: '簽到 {count} 次', + noteCount: '筆記 {count} 篇', + likeCount: '點讚 {count} 次', + commentCount: '評論 {count} 條', + viewCount: '瀏覽 {count} 次', + readLaterCount: '稍後讀 {count}', + modifyField: '修改{field}', + pleaseInputField: '請輸入{field}', + fieldModifySuccess: '{field}修改成功', + fieldModifyFailed: '修改失敗: {error}', + debugInfo: '調試資訊', + defaultBio: '用文字點亮生活的每一刻', ), settings: TSettings( language: '語言', @@ -1280,7 +1321,8 @@ const zhTW = T( browser: '瀏覽器', unableOpenBrowser: '無法開啟瀏覽器', cannotVerifyEmail: '無法驗證郵箱?', - skipEmailVerifyTip: '您可以跳過郵箱驗證,在下一步設置密保問題作為安全憑證,同樣可以完成註冊流程。密保問題可用於後續密碼找回和賬號安全驗證。', + skipEmailVerifyTip: + '您可以跳過郵箱驗證,在下一步設置密保問題作為安全憑證,同樣可以完成註冊流程。密保問題可用於後續密碼找回和賬號安全驗證。', setSecQuestionToContinue: '設置密保問題繼續註冊', warmTips: '溫馨提示', tipOpenWithoutLogin: '閒言保持開放性,即使不登錄也能體驗大部分功能', @@ -1304,6 +1346,14 @@ const zhTW = T( contactServiceInfoDescription: '問題描述(如註冊時間、使用情況)', contactServiceMethod: '聯絡方式', contactServiceMethodDetail: '請透過App內「關於」頁面或官方郵箱聯絡客服,提供以上資訊以便核實身份後協助重設密碼。', + experimentalFeature: '實驗中的功能', + experimentalFeatureDesc: + '閒言帳號為實驗中的功能,註冊登入對使用者使用影響不大,且不推薦使用者註冊登入。大部分功能無需登入即可使用。', + dontShowAgain: '不再提示', + viewExperimentalFeatures: '查看實驗中的功能', + userBatchFlag: '使用者批次標誌:{flag}', + openPeriod: '開放時間:{period}', + expireNotice: '到期後關閉通道({year})', ), progress: TProgress( title: '進度', @@ -1886,7 +1936,8 @@ const zhTW = T( title: '帳號洞察', markAllRead: '全部已讀', close: '關閉', - testAccountWarning: '⚠️ 目前帳號可能是閒言官方提供的測試帳號,多人正在使用,請勿修改密碼,請勿使用目前帳號建立筆記,如有需要請自行註冊帳戶', + testAccountWarning: + '⚠️ 目前帳號可能是閒言官方提供的測試帳號,多人正在使用,請勿修改密碼,請勿使用目前帳號建立筆記,如有需要請自行註冊帳戶', ohosDeviceWarning: '⚠️ 部分裝置可能會出現識別異常,如顯示未知或安全因素出錯', allNormal: '一切正常', noSecurityIssues: '暫無需要關注的帳號安全事項', diff --git a/lib/l10n/translation_io_service.dart b/lib/l10n/translation_io_service.dart index 0a95cca9..1a430bfe 100644 --- a/lib/l10n/translation_io_service.dart +++ b/lib/l10n/translation_io_service.dart @@ -156,6 +156,47 @@ class TranslationIOService { 'makeupFailedRetry': profile.makeupFailedRetry, 'timesUnit': profile.timesUnit, 'daysUnit': profile.daysUnit, + 'accountAndData': profile.accountAndData, + 'editProfile': profile.editProfile, + 'edit': profile.edit, + 'editBio': profile.editBio, + 'save': profile.save, + 'pleaseInput': profile.pleaseInput, + 'modifySuccess': profile.modifySuccess, + 'modifyFailed': profile.modifyFailed, + 'userProfile': profile.userProfile, + 'goBack': profile.goBack, + 'userNotExist': profile.userNotExist, + 'retry': profile.retry, + 'anonymousUser': profile.anonymousUser, + 'articles': profile.articles, + 'follow': profile.follow, + 'followed': profile.followed, + 'theUser': profile.theUser, + 'privateMessage': profile.privateMessage, + 'gotIt': profile.gotIt, + 'shareProfile': profile.shareProfile, + 'blockUser': profile.blockUser, + 'personalBio': profile.personalBio, + 'titleLevel': profile.titleLevel, + 'activeData': profile.activeData, + 'beginner': profile.beginner, + 'apprentice': profile.apprentice, + 'skilled': profile.skilled, + 'expert': profile.expert, + 'master': profile.master, + 'signInCount': profile.signInCount, + 'noteCount': profile.noteCount, + 'likeCount': profile.likeCount, + 'commentCount': profile.commentCount, + 'viewCount': profile.viewCount, + 'readLaterCount': profile.readLaterCount, + 'modifyField': profile.modifyField, + 'pleaseInputField': profile.pleaseInputField, + 'fieldModifySuccess': profile.fieldModifySuccess, + 'fieldModifyFailed': profile.fieldModifyFailed, + 'debugInfo': profile.debugInfo, + 'defaultBio': profile.defaultBio, }; static Map _tSettingsToMap(TSettings settings) => { @@ -447,13 +488,12 @@ class TranslationIOService { map['inputAvatarUrl'] as String? ?? fallback.inputAvatarUrl, selectFromAlbum: map['selectFromAlbum'] as String? ?? fallback.selectFromAlbum, - avatarUrlHint: - map['avatarUrlHint'] as String? ?? fallback.avatarUrlHint, + avatarUrlHint: map['avatarUrlHint'] as String? ?? fallback.avatarUrlHint, pleaseInputUrl: map['pleaseInputUrl'] as String? ?? fallback.pleaseInputUrl, urlMustStartWithHttp: map['urlMustStartWithHttp'] as String? ?? - fallback.urlMustStartWithHttp, + fallback.urlMustStartWithHttp, urlTooLong: map['urlTooLong'] as String? ?? fallback.urlTooLong, invalidUrlFormat: map['invalidUrlFormat'] as String? ?? fallback.invalidUrlFormat, @@ -462,8 +502,7 @@ class TranslationIOService { avatarReviewing: map['avatarReviewing'] as String? ?? fallback.avatarReviewing, avatarChangeSuccess: - map['avatarChangeSuccess'] as String? ?? - fallback.avatarChangeSuccess, + map['avatarChangeSuccess'] as String? ?? fallback.avatarChangeSuccess, avatarChangeFailed: map['avatarChangeFailed'] as String? ?? fallback.avatarChangeFailed, success: map['success'] as String? ?? fallback.success, @@ -473,17 +512,22 @@ class TranslationIOService { loginToViewProfile: map['loginToViewProfile'] as String? ?? fallback.loginToViewProfile, goLogin: map['goLogin'] as String? ?? fallback.goLogin, - consecutiveCheckin: map['consecutiveCheckin'] as String? ?? fallback.consecutiveCheckin, + consecutiveCheckin: + map['consecutiveCheckin'] as String? ?? fallback.consecutiveCheckin, favorites: map['favorites'] as String? ?? fallback.favorites, likes: map['likes'] as String? ?? fallback.likes, dailyCheckin: map['dailyCheckin'] as String? ?? fallback.dailyCheckin, - learningCenter: map['learningCenter'] as String? ?? fallback.learningCenter, - achievementCenter: map['achievementCenter'] as String? ?? fallback.achievementCenter, + learningCenter: + map['learningCenter'] as String? ?? fallback.learningCenter, + achievementCenter: + map['achievementCenter'] as String? ?? fallback.achievementCenter, dailyTask: map['dailyTask'] as String? ?? fallback.dailyTask, leaderboard: map['leaderboard'] as String? ?? fallback.leaderboard, - dataStatistics: map['dataStatistics'] as String? ?? fallback.dataStatistics, + dataStatistics: + map['dataStatistics'] as String? ?? fallback.dataStatistics, myNotes: map['myNotes'] as String? ?? fallback.myNotes, - contentCorrection: map['contentCorrection'] as String? ?? fallback.contentCorrection, + contentCorrection: + map['contentCorrection'] as String? ?? fallback.contentCorrection, myDevices: map['myDevices'] as String? ?? fallback.myDevices, tagCloud: map['tagCloud'] as String? ?? fallback.tagCloud, personalInfo: map['personalInfo'] as String? ?? fallback.personalInfo, @@ -496,53 +540,124 @@ class TranslationIOService { reviewing: map['reviewing'] as String? ?? fallback.reviewing, editUsername: map['editUsername'] as String? ?? fallback.editUsername, editNickname: map['editNickname'] as String? ?? fallback.editNickname, - nearbyDiscovery: map['nearbyDiscovery'] as String? ?? fallback.nearbyDiscovery, - nearbyDiscoveryDesc: map['nearbyDiscoveryDesc'] as String? ?? fallback.nearbyDiscoveryDesc, + nearbyDiscovery: + map['nearbyDiscovery'] as String? ?? fallback.nearbyDiscovery, + nearbyDiscoveryDesc: + map['nearbyDiscoveryDesc'] as String? ?? fallback.nearbyDiscoveryDesc, totalTasks: map['totalTasks'] as String? ?? fallback.totalTasks, taskClaimed: map['taskClaimed'] as String? ?? fallback.taskClaimed, perfectDay: map['perfectDay'] as String? ?? fallback.perfectDay, - perfectDayAllDone: map['perfectDayAllDone'] as String? ?? fallback.perfectDayAllDone, - perfectDayReward: map['perfectDayReward'] as String? ?? fallback.perfectDayReward, - perfectDayRewardDesc: map['perfectDayRewardDesc'] as String? ?? fallback.perfectDayRewardDesc, - claimPerfectDayReward: map['claimPerfectDayReward'] as String? ?? fallback.claimPerfectDayReward, + perfectDayAllDone: + map['perfectDayAllDone'] as String? ?? fallback.perfectDayAllDone, + perfectDayReward: + map['perfectDayReward'] as String? ?? fallback.perfectDayReward, + perfectDayRewardDesc: + map['perfectDayRewardDesc'] as String? ?? + fallback.perfectDayRewardDesc, + claimPerfectDayReward: + map['claimPerfectDayReward'] as String? ?? + fallback.claimPerfectDayReward, rewardSuffix: map['rewardSuffix'] as String? ?? fallback.rewardSuffix, expUnit: map['expUnit'] as String? ?? fallback.expUnit, scoreUnit: map['scoreUnit'] as String? ?? fallback.scoreUnit, noTasks: map['noTasks'] as String? ?? fallback.noTasks, noTasksDesc: map['noTasksDesc'] as String? ?? fallback.noTasksDesc, great: map['great'] as String? ?? fallback.great, - loginToCheckin: map['loginToCheckin'] as String? ?? fallback.loginToCheckin, - loginToCheckinDesc: map['loginToCheckinDesc'] as String? ?? fallback.loginToCheckinDesc, - viewAchievementCenter: map['viewAchievementCenter'] as String? ?? fallback.viewAchievementCenter, + loginToCheckin: + map['loginToCheckin'] as String? ?? fallback.loginToCheckin, + loginToCheckinDesc: + map['loginToCheckinDesc'] as String? ?? fallback.loginToCheckinDesc, + viewAchievementCenter: + map['viewAchievementCenter'] as String? ?? + fallback.viewAchievementCenter, todaySigned: map['todaySigned'] as String? ?? fallback.todaySigned, tapToCheckin: map['tapToCheckin'] as String? ?? fallback.tapToCheckin, signed: map['signed'] as String? ?? fallback.signed, weeklyCheckin: map['weeklyCheckin'] as String? ?? fallback.weeklyCheckin, - totalCheckinDays: map['totalCheckinDays'] as String? ?? fallback.totalCheckinDays, - checkinHistory: map['checkinHistory'] as String? ?? fallback.checkinHistory, - noCheckinRecord: map['noCheckinRecord'] as String? ?? fallback.noCheckinRecord, + totalCheckinDays: + map['totalCheckinDays'] as String? ?? fallback.totalCheckinDays, + checkinHistory: + map['checkinHistory'] as String? ?? fallback.checkinHistory, + noCheckinRecord: + map['noCheckinRecord'] as String? ?? fallback.noCheckinRecord, todayLabel: map['todayLabel'] as String? ?? fallback.todayLabel, checkinDate: map['checkinDate'] as String? ?? fallback.checkinDate, status: map['status'] as String? ?? fallback.status, signedStatus: map['signedStatus'] as String? ?? fallback.signedStatus, remark: map['remark'] as String? ?? fallback.remark, - dailyCheckinTaskDone: map['dailyCheckinTaskDone'] as String? ?? fallback.dailyCheckinTaskDone, + dailyCheckinTaskDone: + map['dailyCheckinTaskDone'] as String? ?? + fallback.dailyCheckinTaskDone, makeupCheckin: map['makeupCheckin'] as String? ?? fallback.makeupCheckin, - makeupCostInfo: map['makeupCostInfo'] as String? ?? fallback.makeupCostInfo, + makeupCostInfo: + map['makeupCostInfo'] as String? ?? fallback.makeupCostInfo, makeupDate: map['makeupDate'] as String? ?? fallback.makeupDate, costPoints: map['costPoints'] as String? ?? fallback.costPoints, currentPoints: map['currentPoints'] as String? ?? fallback.currentPoints, makeupInfo: map['makeupInfo'] as String? ?? fallback.makeupInfo, - makeupLimitInfo: map['makeupLimitInfo'] as String? ?? fallback.makeupLimitInfo, + makeupLimitInfo: + map['makeupLimitInfo'] as String? ?? fallback.makeupLimitInfo, confirmMakeup: map['confirmMakeup'] as String? ?? fallback.confirmMakeup, - insufficientPoints: map['insufficientPoints'] as String? ?? fallback.insufficientPoints, - insufficientPointsDesc: map['insufficientPointsDesc'] as String? ?? fallback.insufficientPointsDesc, + insufficientPoints: + map['insufficientPoints'] as String? ?? fallback.insufficientPoints, + insufficientPointsDesc: + map['insufficientPointsDesc'] as String? ?? + fallback.insufficientPointsDesc, makeupSuccess: map['makeupSuccess'] as String? ?? fallback.makeupSuccess, - makeupSuccessDesc: map['makeupSuccessDesc'] as String? ?? fallback.makeupSuccessDesc, + makeupSuccessDesc: + map['makeupSuccessDesc'] as String? ?? fallback.makeupSuccessDesc, makeupFailed: map['makeupFailed'] as String? ?? fallback.makeupFailed, - makeupFailedRetry: map['makeupFailedRetry'] as String? ?? fallback.makeupFailedRetry, + makeupFailedRetry: + map['makeupFailedRetry'] as String? ?? fallback.makeupFailedRetry, timesUnit: map['timesUnit'] as String? ?? fallback.timesUnit, daysUnit: map['daysUnit'] as String? ?? fallback.daysUnit, + accountAndData: + map['accountAndData'] as String? ?? fallback.accountAndData, + editProfile: map['editProfile'] as String? ?? fallback.editProfile, + edit: map['edit'] as String? ?? fallback.edit, + editBio: map['editBio'] as String? ?? fallback.editBio, + save: map['save'] as String? ?? fallback.save, + pleaseInput: map['pleaseInput'] as String? ?? fallback.pleaseInput, + modifySuccess: map['modifySuccess'] as String? ?? fallback.modifySuccess, + modifyFailed: map['modifyFailed'] as String? ?? fallback.modifyFailed, + userProfile: map['userProfile'] as String? ?? fallback.userProfile, + goBack: map['goBack'] as String? ?? fallback.goBack, + userNotExist: map['userNotExist'] as String? ?? fallback.userNotExist, + retry: map['retry'] as String? ?? fallback.retry, + anonymousUser: map['anonymousUser'] as String? ?? fallback.anonymousUser, + articles: map['articles'] as String? ?? fallback.articles, + follow: map['follow'] as String? ?? fallback.follow, + followed: map['followed'] as String? ?? fallback.followed, + theUser: map['theUser'] as String? ?? fallback.theUser, + privateMessage: + map['privateMessage'] as String? ?? fallback.privateMessage, + gotIt: map['gotIt'] as String? ?? fallback.gotIt, + shareProfile: map['shareProfile'] as String? ?? fallback.shareProfile, + blockUser: map['blockUser'] as String? ?? fallback.blockUser, + personalBio: map['personalBio'] as String? ?? fallback.personalBio, + titleLevel: map['titleLevel'] as String? ?? fallback.titleLevel, + activeData: map['activeData'] as String? ?? fallback.activeData, + beginner: map['beginner'] as String? ?? fallback.beginner, + apprentice: map['apprentice'] as String? ?? fallback.apprentice, + skilled: map['skilled'] as String? ?? fallback.skilled, + expert: map['expert'] as String? ?? fallback.expert, + master: map['master'] as String? ?? fallback.master, + signInCount: map['signInCount'] as String? ?? fallback.signInCount, + noteCount: map['noteCount'] as String? ?? fallback.noteCount, + likeCount: map['likeCount'] as String? ?? fallback.likeCount, + commentCount: map['commentCount'] as String? ?? fallback.commentCount, + viewCount: map['viewCount'] as String? ?? fallback.viewCount, + readLaterCount: + map['readLaterCount'] as String? ?? fallback.readLaterCount, + modifyField: map['modifyField'] as String? ?? fallback.modifyField, + pleaseInputField: + map['pleaseInputField'] as String? ?? fallback.pleaseInputField, + fieldModifySuccess: + map['fieldModifySuccess'] as String? ?? fallback.fieldModifySuccess, + fieldModifyFailed: + map['fieldModifyFailed'] as String? ?? fallback.fieldModifyFailed, + debugInfo: map['debugInfo'] as String? ?? fallback.debugInfo, + defaultBio: map['defaultBio'] as String? ?? fallback.defaultBio, ); } @@ -574,45 +689,140 @@ class TranslationIOService { ) { assert(() { const expectedKeys = { - 'title', 'myFavorites', 'readingHistory', 'darkMode', 'accountSettings', - 'dataManagement', 'offlineMode', 'cacheManagement', 'themeCustomization', - 'desktopWidgets', 'sentenceSource', 'aboutApp', 'rateApp', 'debugMode', - 'tapToLogin', 'defaultUserName', 'appSlogan', 'freeTier', 'points', - 'checkin', 'notes', 'quickActions', 'scanQr', 'nearbyTransfer', 'payment', - 'selectScanMethod', 'scanQrLogin', 'scanQrCode', 'appStoreNotFound', - 'experimentalFeature', 'underReview', 'changeAvatar', 'inputAvatarUrl', - 'selectFromAlbum', 'avatarUrlHint', 'pleaseInputUrl', 'urlMustStartWithHttp', - 'urlTooLong', 'invalidUrlFormat', 'avatarUnderReview', 'avatarReviewing', - 'avatarChangeSuccess', 'avatarChangeFailed', 'success', 'failed', 'ok', - 'loading', 'loginToViewProfile', 'goLogin', 'consecutiveCheckin', - 'favorites', 'likes', 'dailyCheckin', 'learningCenter', 'achievementCenter', - 'dailyTask', 'leaderboard', 'dataStatistics', 'myNotes', 'contentCorrection', - 'myDevices', 'tagCloud', 'personalInfo', 'username', 'nickname', 'bio', - 'notSet', 'notFilled', 'set', 'reviewing', 'editUsername', 'editNickname', - 'nearbyDiscovery', 'nearbyDiscoveryDesc', 'totalTasks', 'taskClaimed', - 'perfectDay', 'perfectDayAllDone', 'perfectDayReward', 'perfectDayRewardDesc', - 'claimPerfectDayReward', 'rewardSuffix', 'expUnit', 'scoreUnit', - 'noTasks', 'noTasksDesc', 'great', - 'loginToCheckin', 'loginToCheckinDesc', 'viewAchievementCenter', - 'todaySigned', 'tapToCheckin', 'signed', 'weeklyCheckin', - 'totalCheckinDays', 'checkinHistory', 'noCheckinRecord', 'todayLabel', - 'checkinDate', 'status', 'signedStatus', 'remark', - 'dailyCheckinTaskDone', 'makeupCheckin', 'makeupCostInfo', 'makeupDate', - 'costPoints', 'currentPoints', 'makeupInfo', 'makeupLimitInfo', - 'confirmMakeup', 'insufficientPoints', 'insufficientPointsDesc', - 'makeupSuccess', 'makeupSuccessDesc', 'makeupFailed', 'makeupFailedRetry', - 'timesUnit', 'daysUnit', + 'title', + 'myFavorites', + 'readingHistory', + 'darkMode', + 'accountSettings', + 'dataManagement', + 'offlineMode', + 'cacheManagement', + 'themeCustomization', + 'desktopWidgets', + 'sentenceSource', + 'aboutApp', + 'rateApp', + 'debugMode', + 'tapToLogin', + 'defaultUserName', + 'appSlogan', + 'freeTier', + 'points', + 'checkin', + 'notes', + 'quickActions', + 'scanQr', + 'nearbyTransfer', + 'payment', + 'selectScanMethod', + 'scanQrLogin', + 'scanQrCode', + 'appStoreNotFound', + 'experimentalFeature', + 'underReview', + 'changeAvatar', + 'inputAvatarUrl', + 'selectFromAlbum', + 'avatarUrlHint', + 'pleaseInputUrl', + 'urlMustStartWithHttp', + 'urlTooLong', + 'invalidUrlFormat', + 'avatarUnderReview', + 'avatarReviewing', + 'avatarChangeSuccess', + 'avatarChangeFailed', + 'success', + 'failed', + 'ok', + 'loading', + 'loginToViewProfile', + 'goLogin', + 'consecutiveCheckin', + 'favorites', + 'likes', + 'dailyCheckin', + 'learningCenter', + 'achievementCenter', + 'dailyTask', + 'leaderboard', + 'dataStatistics', + 'myNotes', + 'contentCorrection', + 'myDevices', + 'tagCloud', + 'personalInfo', + 'username', + 'nickname', + 'bio', + 'notSet', + 'notFilled', + 'set', + 'reviewing', + 'editUsername', + 'editNickname', + 'nearbyDiscovery', + 'nearbyDiscoveryDesc', + 'totalTasks', + 'taskClaimed', + 'perfectDay', + 'perfectDayAllDone', + 'perfectDayReward', + 'perfectDayRewardDesc', + 'claimPerfectDayReward', + 'rewardSuffix', + 'expUnit', + 'scoreUnit', + 'noTasks', + 'noTasksDesc', + 'great', + 'loginToCheckin', + 'loginToCheckinDesc', + 'viewAchievementCenter', + 'todaySigned', + 'tapToCheckin', + 'signed', + 'weeklyCheckin', + 'totalCheckinDays', + 'checkinHistory', + 'noCheckinRecord', + 'todayLabel', + 'checkinDate', + 'status', + 'signedStatus', + 'remark', + 'dailyCheckinTaskDone', + 'makeupCheckin', + 'makeupCostInfo', + 'makeupDate', + 'costPoints', + 'currentPoints', + 'makeupInfo', + 'makeupLimitInfo', + 'confirmMakeup', + 'insufficientPoints', + 'insufficientPointsDesc', + 'makeupSuccess', + 'makeupSuccessDesc', + 'makeupFailed', + 'makeupFailedRetry', + 'timesUnit', + 'daysUnit', }; final mapKeys = map.keys.toSet(); final missingInImport = expectedKeys.difference(mapKeys); if (missingInImport.isNotEmpty) { // ignore: avoid_print - print('⚠️ [TranslationIO] TProfile _importProfile 缺失字段: $missingInImport'); + print( + '⚠️ [TranslationIO] TProfile _importProfile 缺失字段: $missingInImport', + ); } final extraInImport = mapKeys.difference(expectedKeys); if (extraInImport.isNotEmpty) { // ignore: avoid_print - print('⚠️ [TranslationIO] TProfile _importProfile 多余字段: $extraInImport'); + print( + '⚠️ [TranslationIO] TProfile _importProfile 多余字段: $extraInImport', + ); } return true; // assert 总是通过,仅打印警告 }()); diff --git a/lib/l10n/types/t_auth.dart b/lib/l10n/types/t_auth.dart index fa408253..2bc42a06 100644 --- a/lib/l10n/types/t_auth.dart +++ b/lib/l10n/types/t_auth.dart @@ -128,6 +128,13 @@ class TAuth { required this.contactServiceInfoDescription, required this.contactServiceMethod, required this.contactServiceMethodDetail, + required this.experimentalFeature, + required this.experimentalFeatureDesc, + required this.dontShowAgain, + required this.viewExperimentalFeatures, + required this.userBatchFlag, + required this.openPeriod, + required this.expireNotice, }); final String welcomeBack; @@ -268,6 +275,27 @@ class TAuth { final String contactServiceMethod; final String contactServiceMethodDetail; + /// 实验中的功能 + final String experimentalFeature; + + /// 闲言账号为实验中的功能,注册登录对用户使用影响不大... + final String experimentalFeatureDesc; + + /// 不再提示 + final String dontShowAgain; + + /// 查看实验中的功能 + final String viewExperimentalFeatures; + + /// 用户批次标志:{flag} + final String userBatchFlag; + + /// 开放时间:{period} + final String openPeriod; + + /// 到期后关闭通道({year}) + final String expireNotice; + Map toMap() => { 'welcomeBack': welcomeBack, 'loginToAccount': loginToAccount, @@ -388,10 +416,16 @@ class TAuth { 'contactServiceInfoDescription': contactServiceInfoDescription, 'contactServiceMethod': contactServiceMethod, 'contactServiceMethodDetail': contactServiceMethodDetail, + 'experimentalFeature': experimentalFeature, + 'experimentalFeatureDesc': experimentalFeatureDesc, + 'dontShowAgain': dontShowAgain, + 'viewExperimentalFeatures': viewExperimentalFeatures, + 'userBatchFlag': userBatchFlag, + 'openPeriod': openPeriod, + 'expireNotice': expireNotice, }; - static TAuth fromMap(Map map, {TAuth? fallback}) => - TAuth( + static TAuth fromMap(Map map, {TAuth? fallback}) => TAuth( welcomeBack: map['welcomeBack']?.isNotEmpty == true ? map['welcomeBack']! : (fallback?.welcomeBack ?? ''), @@ -452,9 +486,7 @@ class TAuth { userAgreement: map['userAgreement']?.isNotEmpty == true ? map['userAgreement']! : (fallback?.userAgreement ?? ''), - and: map['and']?.isNotEmpty == true - ? map['and']! - : (fallback?.and ?? ''), + and: map['and']?.isNotEmpty == true ? map['and']! : (fallback?.and ?? ''), privacyPolicy: map['privacyPolicy']?.isNotEmpty == true ? map['privacyPolicy']! : (fallback?.privacyPolicy ?? ''), @@ -680,7 +712,8 @@ class TAuth { skipEmailVerifyTip: map['skipEmailVerifyTip']?.isNotEmpty == true ? map['skipEmailVerifyTip']! : (fallback?.skipEmailVerifyTip ?? ''), - setSecQuestionToContinue: map['setSecQuestionToContinue']?.isNotEmpty == true + setSecQuestionToContinue: + map['setSecQuestionToContinue']?.isNotEmpty == true ? map['setSecQuestionToContinue']! : (fallback?.setSecQuestionToContinue ?? ''), warmTips: map['warmTips']?.isNotEmpty == true @@ -731,23 +764,49 @@ class TAuth { contactServiceSubtitle: map['contactServiceSubtitle']?.isNotEmpty == true ? map['contactServiceSubtitle']! : (fallback?.contactServiceSubtitle ?? ''), - contactServiceInfoAccount: map['contactServiceInfoAccount']?.isNotEmpty == true + contactServiceInfoAccount: + map['contactServiceInfoAccount']?.isNotEmpty == true ? map['contactServiceInfoAccount']! : (fallback?.contactServiceInfoAccount ?? ''), contactServiceInfoEmail: map['contactServiceInfoEmail']?.isNotEmpty == true ? map['contactServiceInfoEmail']! : (fallback?.contactServiceInfoEmail ?? ''), - contactServiceInfoDevice: map['contactServiceInfoDevice']?.isNotEmpty == true + contactServiceInfoDevice: + map['contactServiceInfoDevice']?.isNotEmpty == true ? map['contactServiceInfoDevice']! : (fallback?.contactServiceInfoDevice ?? ''), - contactServiceInfoDescription: map['contactServiceInfoDescription']?.isNotEmpty == true + contactServiceInfoDescription: + map['contactServiceInfoDescription']?.isNotEmpty == true ? map['contactServiceInfoDescription']! : (fallback?.contactServiceInfoDescription ?? ''), contactServiceMethod: map['contactServiceMethod']?.isNotEmpty == true ? map['contactServiceMethod']! : (fallback?.contactServiceMethod ?? ''), - contactServiceMethodDetail: map['contactServiceMethodDetail']?.isNotEmpty == true + contactServiceMethodDetail: + map['contactServiceMethodDetail']?.isNotEmpty == true ? map['contactServiceMethodDetail']! : (fallback?.contactServiceMethodDetail ?? ''), + experimentalFeature: map['experimentalFeature']?.isNotEmpty == true + ? map['experimentalFeature']! + : (fallback?.experimentalFeature ?? ''), + experimentalFeatureDesc: map['experimentalFeatureDesc']?.isNotEmpty == true + ? map['experimentalFeatureDesc']! + : (fallback?.experimentalFeatureDesc ?? ''), + dontShowAgain: map['dontShowAgain']?.isNotEmpty == true + ? map['dontShowAgain']! + : (fallback?.dontShowAgain ?? ''), + viewExperimentalFeatures: + map['viewExperimentalFeatures']?.isNotEmpty == true + ? map['viewExperimentalFeatures']! + : (fallback?.viewExperimentalFeatures ?? ''), + userBatchFlag: map['userBatchFlag']?.isNotEmpty == true + ? map['userBatchFlag']! + : (fallback?.userBatchFlag ?? ''), + openPeriod: map['openPeriod']?.isNotEmpty == true + ? map['openPeriod']! + : (fallback?.openPeriod ?? ''), + expireNotice: map['expireNotice']?.isNotEmpty == true + ? map['expireNotice']! + : (fallback?.expireNotice ?? ''), ); } diff --git a/lib/l10n/types/t_profile.dart b/lib/l10n/types/t_profile.dart index 02f2681a..bbb1a4fc 100644 --- a/lib/l10n/types/t_profile.dart +++ b/lib/l10n/types/t_profile.dart @@ -1,9 +1,9 @@ /// ============================================================ /// 闲言APP — 个人页翻译类型 /// 创建时间: 2026-05-29 -/// 更新时间: 2026-06-02 +/// 更新时间: 2026-06-06 /// 作用: 个人中心页面翻译键定义 -/// 上次更新: 新增个人中心功能模块相关翻译字段 +/// 上次更新: 新增UserCenter子组件国际化翻译字段 /// ============================================================ class TProfile { @@ -127,56 +127,121 @@ class TProfile { required this.makeupFailedRetry, required this.timesUnit, required this.daysUnit, + required this.accountAndData, + required this.editProfile, + required this.edit, + required this.editBio, + required this.save, + required this.pleaseInput, + required this.modifySuccess, + required this.modifyFailed, + required this.userProfile, + required this.goBack, + required this.userNotExist, + required this.retry, + required this.anonymousUser, + required this.articles, + required this.follow, + required this.followed, + required this.theUser, + required this.privateMessage, + required this.gotIt, + required this.shareProfile, + required this.blockUser, + required this.personalBio, + required this.titleLevel, + required this.activeData, + required this.beginner, + required this.apprentice, + required this.skilled, + required this.expert, + required this.master, + required this.signInCount, + required this.noteCount, + required this.likeCount, + required this.commentCount, + required this.viewCount, + required this.readLaterCount, + required this.modifyField, + required this.pleaseInputField, + required this.fieldModifySuccess, + required this.fieldModifyFailed, + required this.debugInfo, + required this.defaultBio, }); /// 页面标题 final String title; + /// 我的收藏 final String myFavorites; + /// 阅读历史 final String readingHistory; + /// 深色模式 final String darkMode; + /// 账户设置 final String accountSettings; + /// 数据管理 final String dataManagement; + /// 离线模式 final String offlineMode; + /// 缓存管理 final String cacheManagement; + /// 主题个性化 final String themeCustomization; + /// 桌面小部件 final String desktopWidgets; + /// 句子来源 final String sentenceSource; + /// 关于闲言 final String aboutApp; + /// 给个好评 final String rateApp; + /// 调试模式 final String debugMode; + /// 点击登录 final String tapToLogin; + /// 默认用户名 final String defaultUserName; + /// 应用标语 final String appSlogan; + /// 免费版标签 final String freeTier; + /// 积分标签 final String points; + /// 签到标签 final String checkin; + /// 笔记标签 final String notes; + /// 快捷操作 final String quickActions; + /// 扫一扫 final String scanQr; + /// 面对面快传 final String nearbyTransfer; + /// 收付款 final String payment; @@ -200,115 +265,169 @@ class TProfile { /// 修改头像 final String changeAvatar; + /// 输入头像URL final String inputAvatarUrl; + /// 从相册选择 final String selectFromAlbum; + /// URL长度提示 final String avatarUrlHint; + /// 请输入URL地址 final String pleaseInputUrl; + /// URL必须以http开头 final String urlMustStartWithHttp; + /// URL过长 final String urlTooLong; + /// URL格式不正确 final String invalidUrlFormat; + /// 图像审核中 final String avatarUnderReview; + /// 正在审核头像图片 final String avatarReviewing; + /// 头像修改成功 final String avatarChangeSuccess; + /// 头像修改失败 final String avatarChangeFailed; + /// 成功 final String success; + /// 失败 final String failed; + /// 好的 final String ok; + /// 加载中 final String loading; + /// 登录后查看个人中心 final String loginToViewProfile; + /// 去登录 final String goLogin; /// 连续签到 final String consecutiveCheckin; + /// 收藏 final String favorites; + /// 点赞 final String likes; + /// 每日签到 final String dailyCheckin; + /// 学习中心 final String learningCenter; + /// 成就中心 final String achievementCenter; + /// 每日任务 final String dailyTask; + /// 排行榜 final String leaderboard; + /// 数据统计 final String dataStatistics; + /// 我的笔记 final String myNotes; + /// 内容纠错 final String contentCorrection; + /// 我的设备 final String myDevices; + /// 标签云 final String tagCloud; + /// 个人信息 final String personalInfo; + /// 用户名 final String username; + /// 昵称 final String nickname; + /// 个性签名 final String bio; + /// 未设置 final String notSet; + /// 未填写 final String notFilled; + /// 已设置 final String set; + /// 审核中 final String reviewing; + /// 修改用户名 final String editUsername; + /// 修改昵称 final String editNickname; + /// 附近设备发现 final String nearbyDiscovery; + /// 附近设备发现描述 final String nearbyDiscoveryDesc; + /// 总任务 final String totalTasks; + /// 已领取(任务) final String taskClaimed; + /// 完美日 final String perfectDay; + /// 所有任务已完成 final String perfectDayAllDone; + /// 完美日奖励 final String perfectDayReward; + /// 所有任务已完成!领取额外奖励 final String perfectDayRewardDesc; + /// 领取完美日奖励 final String claimPerfectDayReward; + /// 奖励(后缀) final String rewardSuffix; + /// 经验(单位) final String expUnit; + /// 积分(单位) final String scoreUnit; + /// 暂无任务 final String noTasks; + /// 暂无任务描述 final String noTasksDesc; + /// 太棒了! final String great; @@ -316,69 +435,225 @@ class TProfile { /// 需要登录后才能签到 final String loginToCheckin; + /// 登录后即可参与每日签到,获取积分奖励 final String loginToCheckinDesc; + /// 查看成就中心 final String viewAchievementCenter; + /// 今日已签到 final String todaySigned; + /// 点击签到 final String tapToCheckin; + /// 已签到 final String signed; + /// 本周签到 final String weeklyCheckin; + /// 累计{0}天 final String totalCheckinDays; + /// 签到历史记录 final String checkinHistory; + /// 暂无签到记录 final String noCheckinRecord; + /// 今日 final String todayLabel; + /// 签到日期 final String checkinDate; + /// 状态 final String status; + /// 已签到 ✓ final String signedStatus; + /// 备注 final String remark; + /// 完成每日签到任务 final String dailyCheckinTaskDone; + /// 补签 final String makeupCheckin; + /// 补签将消耗积分,请确认以下信息: final String makeupCostInfo; + /// 补签日期 final String makeupDate; + /// 消耗积分 final String costPoints; + /// 当前积分 final String currentPoints; + /// 补签说明 final String makeupInfo; + /// 每日仅可补签1次,积分不足将无法补签(需{0}积分) final String makeupLimitInfo; + /// 确认补签 final String confirmMakeup; + /// 积分不足 final String insufficientPoints; + /// 补签需要{0}积分,当前积分: {1}\n\n💡 可通过每日签到、发布文章等方式获取积分 final String insufficientPointsDesc; + /// 补签成功 final String makeupSuccess; + /// {0} 补签成功,已扣除{1}积分 final String makeupSuccessDesc; + /// 补签失败 final String makeupFailed; + /// 补签失败,请稍后重试 final String makeupFailedRetry; + /// 次 final String timesUnit; + /// 天 final String daysUnit; + /// 账户与数据 + final String accountAndData; + + /// 编辑资料 + final String editProfile; + + /// 编辑 + final String edit; + + /// 修改签名 + final String editBio; + + /// 保存 + final String save; + + /// 请输入 + final String pleaseInput; + + /// 修改成功 + final String modifySuccess; + + /// 修改失败 + final String modifyFailed; + + /// 用户主页 + final String userProfile; + + /// 返回 + final String goBack; + + /// 用户不存在 + final String userNotExist; + + /// 重试 + final String retry; + + /// 匿名用户 + final String anonymousUser; + + /// 文章 + final String articles; + + /// 关注 + final String follow; + + /// 已关注 + final String followed; + + /// 该用户 + final String theUser; + + /// 私信 + final String privateMessage; + + /// 知道了 + final String gotIt; + + /// 分享主页 + final String shareProfile; + + /// 屏蔽用户 + final String blockUser; + + /// 个人简介 + final String personalBio; + + /// 头衔等级 + final String titleLevel; + + /// 当前积分 + // final String currentPoints; // 已在上面(remote)定义 + /// 活跃数据 + final String activeData; + + /// 新手 + final String beginner; + + /// 学徒 + final String apprentice; + + /// 熟练工 + final String skilled; + + /// 专家 + final String expert; + + /// 大师 + final String master; + + /// 签到 {count} 次 + final String signInCount; + + /// 笔记 {count} 篇 + final String noteCount; + + /// 点赞 {count} 次 + final String likeCount; + + /// 评论 {count} 条 + final String commentCount; + + /// 浏览 {count} 次 + final String viewCount; + + /// 稍后读 {count} + final String readLaterCount; + + /// 修改{field} + final String modifyField; + + /// 请输入{field} + final String pleaseInputField; + + /// {field}修改成功 + final String fieldModifySuccess; + + /// 修改失败: {error} + final String fieldModifyFailed; + + /// 调试信息 + final String debugInfo; + + /// 用文字点亮生活的每一刻 + final String defaultBio; + Map toMap() => { 'title': title, 'myFavorites': myFavorites, @@ -499,10 +774,54 @@ class TProfile { 'makeupFailedRetry': makeupFailedRetry, 'timesUnit': timesUnit, 'daysUnit': daysUnit, + 'accountAndData': accountAndData, + 'editProfile': editProfile, + 'edit': edit, + 'editBio': editBio, + 'save': save, + 'pleaseInput': pleaseInput, + 'modifySuccess': modifySuccess, + 'modifyFailed': modifyFailed, + 'userProfile': userProfile, + 'goBack': goBack, + 'userNotExist': userNotExist, + 'retry': retry, + 'anonymousUser': anonymousUser, + 'articles': articles, + 'follow': follow, + 'followed': followed, + 'theUser': theUser, + 'privateMessage': privateMessage, + 'gotIt': gotIt, + 'shareProfile': shareProfile, + 'blockUser': blockUser, + 'personalBio': personalBio, + 'titleLevel': titleLevel, + // 'currentPoints': currentPoints, // 已在上面(remote)定义 + 'activeData': activeData, + 'beginner': beginner, + 'apprentice': apprentice, + 'skilled': skilled, + 'expert': expert, + 'master': master, + 'signInCount': signInCount, + 'noteCount': noteCount, + 'likeCount': likeCount, + 'commentCount': commentCount, + 'viewCount': viewCount, + 'readLaterCount': readLaterCount, + 'modifyField': modifyField, + 'pleaseInputField': pleaseInputField, + 'fieldModifySuccess': fieldModifySuccess, + 'fieldModifyFailed': fieldModifyFailed, + 'debugInfo': debugInfo, + 'defaultBio': defaultBio, }; - static TProfile fromMap(Map map, {TProfile? fallback}) => - TProfile( + static TProfile fromMap( + Map map, { + TProfile? fallback, + }) => TProfile( title: map['title']?.isNotEmpty == true ? map['title']! : (fallback?.title ?? ''), @@ -638,9 +957,7 @@ class TProfile { failed: map['failed']?.isNotEmpty == true ? map['failed']! : (fallback?.failed ?? ''), - ok: map['ok']?.isNotEmpty == true - ? map['ok']! - : (fallback?.ok ?? ''), + ok: map['ok']?.isNotEmpty == true ? map['ok']! : (fallback?.ok ?? ''), loading: map['loading']?.isNotEmpty == true ? map['loading']! : (fallback?.loading ?? ''), @@ -698,18 +1015,14 @@ class TProfile { nickname: map['nickname']?.isNotEmpty == true ? map['nickname']! : (fallback?.nickname ?? ''), - bio: map['bio']?.isNotEmpty == true - ? map['bio']! - : (fallback?.bio ?? ''), + bio: map['bio']?.isNotEmpty == true ? map['bio']! : (fallback?.bio ?? ''), notSet: map['notSet']?.isNotEmpty == true ? map['notSet']! : (fallback?.notSet ?? ''), notFilled: map['notFilled']?.isNotEmpty == true ? map['notFilled']! : (fallback?.notFilled ?? ''), - set: map['set']?.isNotEmpty == true - ? map['set']! - : (fallback?.set ?? ''), + set: map['set']?.isNotEmpty == true ? map['set']! : (fallback?.set ?? ''), reviewing: map['reviewing']?.isNotEmpty == true ? map['reviewing']! : (fallback?.reviewing ?? ''), @@ -860,5 +1173,129 @@ class TProfile { daysUnit: map['daysUnit']?.isNotEmpty == true ? map['daysUnit']! : (fallback?.daysUnit ?? ''), + accountAndData: map['accountAndData']?.isNotEmpty == true + ? map['accountAndData']! + : (fallback?.accountAndData ?? ''), + editProfile: map['editProfile']?.isNotEmpty == true + ? map['editProfile']! + : (fallback?.editProfile ?? ''), + edit: map['edit']?.isNotEmpty == true + ? map['edit']! + : (fallback?.edit ?? ''), + editBio: map['editBio']?.isNotEmpty == true + ? map['editBio']! + : (fallback?.editBio ?? ''), + save: map['save']?.isNotEmpty == true + ? map['save']! + : (fallback?.save ?? ''), + pleaseInput: map['pleaseInput']?.isNotEmpty == true + ? map['pleaseInput']! + : (fallback?.pleaseInput ?? ''), + modifySuccess: map['modifySuccess']?.isNotEmpty == true + ? map['modifySuccess']! + : (fallback?.modifySuccess ?? ''), + modifyFailed: map['modifyFailed']?.isNotEmpty == true + ? map['modifyFailed']! + : (fallback?.modifyFailed ?? ''), + userProfile: map['userProfile']?.isNotEmpty == true + ? map['userProfile']! + : (fallback?.userProfile ?? ''), + goBack: map['goBack']?.isNotEmpty == true + ? map['goBack']! + : (fallback?.goBack ?? ''), + userNotExist: map['userNotExist']?.isNotEmpty == true + ? map['userNotExist']! + : (fallback?.userNotExist ?? ''), + retry: map['retry']?.isNotEmpty == true + ? map['retry']! + : (fallback?.retry ?? ''), + anonymousUser: map['anonymousUser']?.isNotEmpty == true + ? map['anonymousUser']! + : (fallback?.anonymousUser ?? ''), + articles: map['articles']?.isNotEmpty == true + ? map['articles']! + : (fallback?.articles ?? ''), + follow: map['follow']?.isNotEmpty == true + ? map['follow']! + : (fallback?.follow ?? ''), + followed: map['followed']?.isNotEmpty == true + ? map['followed']! + : (fallback?.followed ?? ''), + theUser: map['theUser']?.isNotEmpty == true + ? map['theUser']! + : (fallback?.theUser ?? ''), + privateMessage: map['privateMessage']?.isNotEmpty == true + ? map['privateMessage']! + : (fallback?.privateMessage ?? ''), + gotIt: map['gotIt']?.isNotEmpty == true + ? map['gotIt']! + : (fallback?.gotIt ?? ''), + shareProfile: map['shareProfile']?.isNotEmpty == true + ? map['shareProfile']! + : (fallback?.shareProfile ?? ''), + blockUser: map['blockUser']?.isNotEmpty == true + ? map['blockUser']! + : (fallback?.blockUser ?? ''), + personalBio: map['personalBio']?.isNotEmpty == true + ? map['personalBio']! + : (fallback?.personalBio ?? ''), + titleLevel: map['titleLevel']?.isNotEmpty == true + ? map['titleLevel']! + : (fallback?.titleLevel ?? ''), + // currentPoints: already assigned above from remote + activeData: map['activeData']?.isNotEmpty == true + ? map['activeData']! + : (fallback?.activeData ?? ''), + beginner: map['beginner']?.isNotEmpty == true + ? map['beginner']! + : (fallback?.beginner ?? ''), + apprentice: map['apprentice']?.isNotEmpty == true + ? map['apprentice']! + : (fallback?.apprentice ?? ''), + skilled: map['skilled']?.isNotEmpty == true + ? map['skilled']! + : (fallback?.skilled ?? ''), + expert: map['expert']?.isNotEmpty == true + ? map['expert']! + : (fallback?.expert ?? ''), + master: map['master']?.isNotEmpty == true + ? map['master']! + : (fallback?.master ?? ''), + signInCount: map['signInCount']?.isNotEmpty == true + ? map['signInCount']! + : (fallback?.signInCount ?? ''), + noteCount: map['noteCount']?.isNotEmpty == true + ? map['noteCount']! + : (fallback?.noteCount ?? ''), + likeCount: map['likeCount']?.isNotEmpty == true + ? map['likeCount']! + : (fallback?.likeCount ?? ''), + commentCount: map['commentCount']?.isNotEmpty == true + ? map['commentCount']! + : (fallback?.commentCount ?? ''), + viewCount: map['viewCount']?.isNotEmpty == true + ? map['viewCount']! + : (fallback?.viewCount ?? ''), + readLaterCount: map['readLaterCount']?.isNotEmpty == true + ? map['readLaterCount']! + : (fallback?.readLaterCount ?? ''), + modifyField: map['modifyField']?.isNotEmpty == true + ? map['modifyField']! + : (fallback?.modifyField ?? ''), + pleaseInputField: map['pleaseInputField']?.isNotEmpty == true + ? map['pleaseInputField']! + : (fallback?.pleaseInputField ?? ''), + fieldModifySuccess: map['fieldModifySuccess']?.isNotEmpty == true + ? map['fieldModifySuccess']! + : (fallback?.fieldModifySuccess ?? ''), + fieldModifyFailed: map['fieldModifyFailed']?.isNotEmpty == true + ? map['fieldModifyFailed']! + : (fallback?.fieldModifyFailed ?? ''), + debugInfo: map['debugInfo']?.isNotEmpty == true + ? map['debugInfo']! + : (fallback?.debugInfo ?? ''), + defaultBio: map['defaultBio']?.isNotEmpty == true + ? map['defaultBio']! + : (fallback?.defaultBio ?? ''), ); } diff --git a/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/ohos/entry/src/main/ets/entryability/EntryAbility.ets index 6a18c0a5..88dc2cc3 100644 --- a/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -36,6 +36,12 @@ export default class EntryAbility extends FlutterAbility { } } }); + + // 冷启动时检查待处理的快捷方式 + if (this.pendingShortcutType && this.pendingShortcutType.length > 0) { + this.notifyShortcutAction(this.pendingShortcutType); + this.pendingShortcutType = null; + } } private getClipboardData(result: MethodResult): void { diff --git a/ohos/entry/src/main/module.json5 b/ohos/entry/src/main/module.json5 index d555f8cc..5aea396c 100644 --- a/ohos/entry/src/main/module.json5 +++ b/ohos/entry/src/main/module.json5 @@ -23,7 +23,7 @@ "exported": true, "metadata": [ { - "name": "ohos.shortcut.config", + "name": "ohos.ability.shortcuts", "resource": "$profile:shortcuts" } ], @@ -52,37 +52,6 @@ "ohos.want.action.sendMultipleData" ] } - ], - // 长按桌面图标快捷操作 - "shortcuts": [ - { - "shortcutId": "action_theme", - "label": "$string:shortcut_theme_label", - "icon": "$media:icon", - "wants": [ - { - "bundleName": "apps.xy.xianyan", - "abilityName": "EntryAbility", - "parameters": { - "shortcutType": "action_theme" - } - } - ] - }, - { - "shortcutId": "action_general_settings", - "label": "$string:shortcut_general_settings_label", - "icon": "$media:icon", - "wants": [ - { - "bundleName": "apps.xy.xianyan", - "abilityName": "EntryAbility", - "parameters": { - "shortcutType": "action_general_settings" - } - } - ] - } ] } ], @@ -249,7 +218,6 @@ "when": "inuse" } }, - { "name": "ohos.permission.READ_MEDIA", "reason": "$string:permission_read_media_reason", @@ -273,22 +241,6 @@ "abilities": ["EntryAbility"], "when": "inuse" } - }, - { - "name": "ohos.permission.NOTIFICATION", - "reason": "$string:permission_notification_reason", - "usedScene": { - "abilities": ["EntryAbility"], - "when": "inuse" - } - }, - { - "name": "ohos.permission.PASTEBOARD", - "reason": "$string:permission_pasteboard_reason", - "usedScene": { - "abilities": ["EntryAbility"], - "when": "inuse" - } } ] } diff --git a/ohos/entry/src/main/resources/base/profile/shortcuts.json b/ohos/entry/src/main/resources/base/profile/shortcuts.json index 0ceb0237..99db3454 100644 --- a/ohos/entry/src/main/resources/base/profile/shortcuts.json +++ b/ohos/entry/src/main/resources/base/profile/shortcuts.json @@ -7,6 +7,7 @@ "wants": [ { "bundleName": "apps.xy.xianyan", + "moduleName": "entry", "abilityName": "EntryAbility", "parameters": { "shortcutType": "action_theme" @@ -21,6 +22,7 @@ "wants": [ { "bundleName": "apps.xy.xianyan", + "moduleName": "entry", "abilityName": "EntryAbility", "parameters": { "shortcutType": "action_general_settings" diff --git a/pubspec.macos.yaml b/pubspec.macos.yaml index 9eae5e57..5bdb0b05 100644 --- a/pubspec.macos.yaml +++ b/pubspec.macos.yaml @@ -249,7 +249,6 @@ dependencies: basic_utils: ^5.7.0 # 通用工具集(Base64/ASN1) wifi_iot: ^0.3.19 # WiFi IoT设备连接 nearby_service: ^0.2.1 # 近场设备发现+通信 - nearby_connections: ^4.1.1 # Google Nearby Connections(蓝牙发现+Wi-Fi Direct传输,仅Android/iOS) flutter_localizations: sdk: flutter # Flutter国际化支持 diff --git a/pubspec.ohos.yaml b/pubspec.ohos.yaml index 64cf43e6..456f6c04 100644 --- a/pubspec.ohos.yaml +++ b/pubspec.ohos.yaml @@ -281,7 +281,6 @@ dependencies: flutter_webrtc: # v1.4.0-ohos.1 | WebRTC音视频通信(本地化-鸿蒙适配) path: packages/flutter_webrtc web_socket_channel: ^3.0.3 # WebSocket客户端 - mime: ^2.0.0 # MIME类型识别 mobile_scanner: # v7.1.4-ohos.1 | 二维码/条形码扫描(本地化-鸿蒙适配) path: packages/mobile_scanner @@ -290,8 +289,6 @@ dependencies: path: packages/wifi_iot nearby_service: # v0.2.1 | 近场设备发现+通信(本地化-鸿蒙适配) path: packages/nearby_service - nearby_connections: # v4.1.1 | Google Nearby Connections(本地化-鸿蒙适配,仅Android/iOS) - path: packages/nearby_connections flutter_localizations: sdk: flutter # Flutter国际化支持 @@ -425,8 +422,6 @@ dependency_overrides: path: packages/home_widget nearby_service: path: packages/nearby_service - nearby_connections: - path: packages/nearby_connections # ============================================================ # Flutter 配置