Browse Source

修拾忆页看不到助手闹钟:换源时把本地那份的展示出口弄丢了

真机实测暴露的回归。设备上取到的证据:

  assistant_reminders: 2 条("南山会议" 16:00、"晚餐时间" 17:00),synced=false
  assistant_directives: 2 条 SET_clock,status=done
  memory_items_cache: 0 条

也就是说助手完全正常:听懂了、下发了指令、指令也落地了。但用户在拾忆页
什么都看不到。

## 原因

_rebuildEvents 换成只读 MemoryService.items 之后,AssistantDirectiveService
的 reminders **全仓再没有任何消费者**(grep 可证)。后端还没部署 memory 模块,
memory_add 必然失败,于是本地那两条既进不了 MemoryService、也没有展示出口。

我上一轮的提交说明写着「迁移期双写……即使上传失败,用户至少还能在本机看到
自己刚定的闹钟」——**那句话是错的**,双写的「本地」那一半当时已经没有出口了。

## 改动

- CalendarController._rebuildEvents 补上 _localReminderEvents():把
  **还没同步**的助手闹钟也投影进日历。已同步的跳过,否则 MemoryService 与
  本地两边都投影会出现两条。
- 重新订阅 AssistantDirectiveService.reminders(_reminderWorker):
  不订阅的话助手刚定完闹钟页面不会刷新,要退出重进才看得到。
- 「提醒 → 记忆项」的换算收敛成 AssistantReminderToMemory 扩展,
  上传路径与日历投影共用同一份,删掉 service 里那份重复实现。
- 本地未同步项的 memoryId 是 0:点开编辑或勾完成时给明确提示
  (memLocalOnlyHint,41 语言已补),不再静默无反应——它没有服务端 id,
  改了也没处提交。

## 另一个症状:EMAI 回复没声音(未修,不在本次改动范围)

同一份日志里:

  [ERROR][PcmStreamPlayer] 播放错误: PlatformException(-11850, Operation Stopped)
  [ERROR][PcmStreamPlayer] 启动播放失败: (-11850) Operation Stopped

反复几十次。EMAI 的回复音频走 BailianMultimodalService.audioOut →
PcmStreamPlayer.feed,**整条链路不经过 TtsService**,也不在本次改动的文件里
(改动清单见 git diff --name-only)。-11850 是 AVFoundation 层的错误,
指向音频会话状态,需要单独排查。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main
Rodger-Wang 1 month ago
parent
commit
5eeedf1013
  1. 1
      apps/client/lib/core/translations/language/ar_sa.dart
  2. 1
      apps/client/lib/core/translations/language/bg_bg.dart
  3. 1
      apps/client/lib/core/translations/language/cs_cz.dart
  4. 1
      apps/client/lib/core/translations/language/da_dk.dart
  5. 1
      apps/client/lib/core/translations/language/de_de.dart
  6. 1
      apps/client/lib/core/translations/language/el_gr.dart
  7. 1
      apps/client/lib/core/translations/language/en_us.dart
  8. 1
      apps/client/lib/core/translations/language/es_es.dart
  9. 1
      apps/client/lib/core/translations/language/et_ee.dart
  10. 1
      apps/client/lib/core/translations/language/fa_ir.dart
  11. 1
      apps/client/lib/core/translations/language/fi_fi.dart
  12. 1
      apps/client/lib/core/translations/language/fr_fr.dart
  13. 1
      apps/client/lib/core/translations/language/hi_in.dart
  14. 1
      apps/client/lib/core/translations/language/hr_hr.dart
  15. 1
      apps/client/lib/core/translations/language/hu_hu.dart
  16. 1
      apps/client/lib/core/translations/language/id_id.dart
  17. 1
      apps/client/lib/core/translations/language/is_is.dart
  18. 1
      apps/client/lib/core/translations/language/it_it.dart
  19. 1
      apps/client/lib/core/translations/language/ja_jp.dart
  20. 1
      apps/client/lib/core/translations/language/ko_kr.dart
  21. 1
      apps/client/lib/core/translations/language/lt_lt.dart
  22. 1
      apps/client/lib/core/translations/language/lv_lv.dart
  23. 1
      apps/client/lib/core/translations/language/ms_my.dart
  24. 1
      apps/client/lib/core/translations/language/nb_no.dart
  25. 1
      apps/client/lib/core/translations/language/nl_nl.dart
  26. 1
      apps/client/lib/core/translations/language/pl_pl.dart
  27. 1
      apps/client/lib/core/translations/language/pt_pt.dart
  28. 1
      apps/client/lib/core/translations/language/ro_ro.dart
  29. 1
      apps/client/lib/core/translations/language/ru_ru.dart
  30. 1
      apps/client/lib/core/translations/language/sk_sk.dart
  31. 1
      apps/client/lib/core/translations/language/sl_si.dart
  32. 1
      apps/client/lib/core/translations/language/sr_rs.dart
  33. 1
      apps/client/lib/core/translations/language/sv_se.dart
  34. 1
      apps/client/lib/core/translations/language/th_th.dart
  35. 1
      apps/client/lib/core/translations/language/tl_ph.dart
  36. 1
      apps/client/lib/core/translations/language/tr_tr.dart
  37. 1
      apps/client/lib/core/translations/language/uk_ua.dart
  38. 1
      apps/client/lib/core/translations/language/vi_vn.dart
  39. 1
      apps/client/lib/core/translations/language/zh_cn.dart
  40. 1
      apps/client/lib/core/translations/language/zh_hk.dart
  41. 1
      apps/client/lib/core/translations/language/zh_tw.dart
  42. 52
      apps/client/lib/data/models/assistant_directive.dart
  43. 41
      apps/client/lib/data/services/assistant_directive_service.dart
  44. 37
      apps/client/lib/modules/home/controllers/calendar_controller.dart
  45. 18
      apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart

1
apps/client/lib/core/translations/language/ar_sa.dart

@ -1,5 +1,6 @@
//阿拉伯-沙特阿拉伯语 //阿拉伯-沙特阿拉伯语
const Map<String, String> arSA = { const Map<String, String> arSA = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/bg_bg.dart

@ -1,5 +1,6 @@
//保加利亚-保加利亚语 //保加利亚-保加利亚语
const Map<String, String> bgBG = { const Map<String, String> bgBG = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/cs_cz.dart

@ -1,5 +1,6 @@
//捷克-捷克语 //捷克-捷克语
const Map<String, String> csCZ = { const Map<String, String> csCZ = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/da_dk.dart

@ -1,5 +1,6 @@
//丹麦-丹麦语 //丹麦-丹麦语
const Map<String, String> daDK = { const Map<String, String> daDK = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/de_de.dart

@ -1,5 +1,6 @@
//德语-德语 //德语-德语
const Map<String, String> deDE = { const Map<String, String> deDE = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/el_gr.dart

@ -1,5 +1,6 @@
//希腊-希腊语 //希腊-希腊语
const Map<String, String> elGR = { const Map<String, String> elGR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/en_us.dart

@ -1,5 +1,6 @@
//美国-英语 //美国-英语
const Map<String, String> enUS = { const Map<String, String> enUS = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/es_es.dart

@ -1,5 +1,6 @@
//西班牙-西班牙语 //西班牙-西班牙语
const Map<String, String> esES = { const Map<String, String> esES = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/et_ee.dart

@ -1,5 +1,6 @@
//爱沙尼亚-爱沙尼亚语 //爱沙尼亚-爱沙尼亚语
const Map<String, String> etEE = { const Map<String, String> etEE = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/fa_ir.dart

@ -1,5 +1,6 @@
//伊朗-波斯语 //伊朗-波斯语
const Map<String, String> faIR = { const Map<String, String> faIR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/fi_fi.dart

@ -1,5 +1,6 @@
//芬兰-芬兰语 //芬兰-芬兰语
const Map<String, String> fiFI = { const Map<String, String> fiFI = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/fr_fr.dart

@ -1,5 +1,6 @@
// 法国-法语 // 法国-法语
const Map<String, String> frFR = { const Map<String, String> frFR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/hi_in.dart

@ -1,5 +1,6 @@
// 印地-印地语 // 印地-印地语
const Map<String, String> hiIN = { const Map<String, String> hiIN = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/hr_hr.dart

@ -1,5 +1,6 @@
// 克罗地亚-克罗地亚语 // 克罗地亚-克罗地亚语
const Map<String, String> hrHR = { const Map<String, String> hrHR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/hu_hu.dart

@ -1,5 +1,6 @@
//匈牙利-匈牙利语 //匈牙利-匈牙利语
const Map<String, String> huHU = { const Map<String, String> huHU = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/id_id.dart

@ -1,5 +1,6 @@
//印尼-印尼语 //印尼-印尼语
const Map<String, String> idID = { const Map<String, String> idID = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/is_is.dart

@ -1,5 +1,6 @@
// 冰岛-冰岛语 // 冰岛-冰岛语
const Map<String, String> isIS = { const Map<String, String> isIS = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/it_it.dart

@ -1,5 +1,6 @@
// 意大利-意大利语 // 意大利-意大利语
const Map<String, String> itIT = { const Map<String, String> itIT = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/ja_jp.dart

@ -1,5 +1,6 @@
// 日本-日本語 // 日本-日本語
const Map<String, String> jaJP = { const Map<String, String> jaJP = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/ko_kr.dart

@ -1,5 +1,6 @@
// 韩国-韩语 // 韩国-韩语
const Map<String, String> koKR = { const Map<String, String> koKR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/lt_lt.dart

@ -1,4 +1,5 @@
const Map<String, String> ltLT = { const Map<String, String> ltLT = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/lv_lv.dart

@ -1,4 +1,5 @@
const Map<String, String> lvLV = { const Map<String, String> lvLV = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/ms_my.dart

@ -1,5 +1,6 @@
// 马来西亚-马来语 // 马来西亚-马来语
const Map<String, String> msMY = { const Map<String, String> msMY = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/nb_no.dart

@ -1,5 +1,6 @@
// 挪威-挪威语 // 挪威-挪威语
const Map<String, String> nbNO = { const Map<String, String> nbNO = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/nl_nl.dart

@ -1,5 +1,6 @@
// 荷兰-荷兰语 // 荷兰-荷兰语
const Map<String, String> nlNL = { const Map<String, String> nlNL = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/pl_pl.dart

@ -1,5 +1,6 @@
// 波兰-波兰语 // 波兰-波兰语
const Map<String, String> plPL = { const Map<String, String> plPL = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/pt_pt.dart

@ -1,5 +1,6 @@
// 葡萄牙-葡萄牙语 // 葡萄牙-葡萄牙语
const Map<String, String> ptPT = { const Map<String, String> ptPT = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/ro_ro.dart

@ -1,5 +1,6 @@
// 罗马尼亚-罗马尼亚语 // 罗马尼亚-罗马尼亚语
const Map<String, String> roRO = { const Map<String, String> roRO = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/ru_ru.dart

@ -1,5 +1,6 @@
// 俄罗斯-俄语 // 俄罗斯-俄语
const Map<String, String> ruRU = { const Map<String, String> ruRU = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/sk_sk.dart

@ -1,5 +1,6 @@
// 斯洛伐克-斯洛伐克语 // 斯洛伐克-斯洛伐克语
const Map<String, String> skSK = { const Map<String, String> skSK = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/sl_si.dart

@ -1,5 +1,6 @@
// 斯洛文尼亚-斯洛文尼亚语 // 斯洛文尼亚-斯洛文尼亚语
const Map<String, String> slSI = { const Map<String, String> slSI = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/sr_rs.dart

@ -1,5 +1,6 @@
// 塞尔维亚-塞尔维亚语 // 塞尔维亚-塞尔维亚语
const Map<String, String> srRS = { const Map<String, String> srRS = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/sv_se.dart

@ -1,5 +1,6 @@
//瑞典-瑞典语 //瑞典-瑞典语
const Map<String, String> svSE = { const Map<String, String> svSE = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/th_th.dart

@ -1,5 +1,6 @@
//泰国-泰语 //泰国-泰语
const Map<String, String> thTH = { const Map<String, String> thTH = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/tl_ph.dart

@ -1,5 +1,6 @@
//菲律宾-塔加路语 //菲律宾-塔加路语
const Map<String, String> tlPH = { const Map<String, String> tlPH = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/tr_tr.dart

@ -1,5 +1,6 @@
//土耳其-土耳其语 //土耳其-土耳其语
const Map<String, String> trTR = { const Map<String, String> trTR = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/uk_ua.dart

@ -1,5 +1,6 @@
// 乌克兰-乌克兰语 // 乌克兰-乌克兰语
const Map<String, String> ukUA = { const Map<String, String> ukUA = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/vi_vn.dart

@ -1,5 +1,6 @@
//越南-越南语 //越南-越南语
const Map<String, String> viVN = { const Map<String, String> viVN = {
'memLocalOnlyHint': 'Not synced to the server yet, cannot edit',
'memDailyCount': 'You have @n item(s) today.', 'memDailyCount': 'You have @n item(s) today.',
'memCatAll': 'All', 'memCatAll': 'All',
'memCatTodo': 'To-do', 'memCatTodo': 'To-do',

1
apps/client/lib/core/translations/language/zh_cn.dart

@ -1,5 +1,6 @@
//中国-简体中文 //中国-简体中文
const Map<String, String> zhCN = { const Map<String, String> zhCN = {
'memLocalOnlyHint': '这条还没同步到服务端,暂时不能编辑',
'memDailyCount': '今天有 @n 件事。', 'memDailyCount': '今天有 @n 件事。',
'memCatAll': '全部', 'memCatAll': '全部',
'memCatTodo': '待办', 'memCatTodo': '待办',

1
apps/client/lib/core/translations/language/zh_hk.dart

@ -1,5 +1,6 @@
//香港-繁体中文 //香港-繁体中文
const Map<String, String> zhHK = { const Map<String, String> zhHK = {
'memLocalOnlyHint': '這條還沒同步到伺服器,暫時不能編輯',
'memDailyCount': '今天有 @n 件事。', 'memDailyCount': '今天有 @n 件事。',
'memCatAll': '全部', 'memCatAll': '全部',
'memCatTodo': '待辦', 'memCatTodo': '待辦',

1
apps/client/lib/core/translations/language/zh_tw.dart

@ -1,5 +1,6 @@
//台湾-繁体中文 //台湾-繁体中文
const Map<String, String> zhTW = { const Map<String, String> zhTW = {
'memLocalOnlyHint': '這條還沒同步到伺服器,暫時不能編輯',
'memDailyCount': '今天有 @n 件事。', 'memDailyCount': '今天有 @n 件事。',
'memCatAll': '全部', 'memCatAll': '全部',
'memCatTodo': '待辦', 'memCatTodo': '待辦',

52
apps/client/lib/data/models/assistant_directive.dart

@ -1,5 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart';
import 'memory_item.dart';
/// 助手指令:百炼多模态在 `RespondingContent.extra_info.tool_calls` 里下发的 /// 助手指令:百炼多模态在 `RespondingContent.extra_info.tool_calls` 里下发的
/// 端侧函数调用。 /// 端侧函数调用。
/// ///
@ -389,3 +393,51 @@ class AssistantReminder {
synced: j['synced'] == true, synced: j['synced'] == true,
); );
} }
/// 提醒 → 记忆项的换算。
///
/// 收敛在这里而不是各写一份:上传服务端(AssistantDirectiveService._uploadReminder)
/// 与拾忆页的本地投影(CalendarController)都要用,两处各写一遍必然漂移。
extension AssistantReminderToMemory on AssistantReminder {
MemoryItem toMemoryItem() {
final now = DateTime.now();
final today = '${now.year}-'
'${now.month.toString().padLeft(2, '0')}-'
'${now.day.toString().padLeft(2, '0')}';
return MemoryItem(
category: MemoryCategory.alarm,
source: MemorySource.assistant,
sourceId: directiveId,
clientKey: directiveId.isNotEmpty ? directiveId : id,
title: content.isNotEmpty ? content : 'memCatAlarm'.tr,
happenDate: date ?? today,
happenTime: time,
repeatRule: _repeatRuleToRaw(repeat),
repeatRaw: repeatRaw,
weekday: weekday ?? 0,
remindAhead: 5,
state: done ? MemoryState.done : MemoryState.pending,
);
}
}
/// 本地 RepeatRule 枚举 → 服务端的字符串口径。
///
/// unknown 按单次处理:认不出的重复规则宁可只响一次,
/// 也不要按猜的规则天天响。
String _repeatRuleToRaw(RepeatRule r) {
switch (r) {
case RepeatRule.daily:
return MemoryRepeat.daily;
case RepeatRule.weekly:
return MemoryRepeat.weekly;
case RepeatRule.weekdays:
return MemoryRepeat.weekdays;
case RepeatRule.weekend:
return MemoryRepeat.weekend;
case RepeatRule.once:
case RepeatRule.unknown:
return MemoryRepeat.once;
}
}

41
apps/client/lib/data/services/assistant_directive_service.dart

@ -5,7 +5,6 @@ import 'package:get_storage/get_storage.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import '../models/assistant_directive.dart'; import '../models/assistant_directive.dart';
import '../models/memory_item.dart';
import '../models/user_Info.dart'; import '../models/user_Info.dart';
import 'memory_service.dart'; import 'memory_service.dart';
@ -139,19 +138,8 @@ class AssistantDirectiveService extends GetxService {
if (!Get.isRegistered<MemoryService>()) return; if (!Get.isRegistered<MemoryService>()) return;
if (!User.isLoggedIn()) return; if (!User.isLoggedIn()) return;
try { try {
final item = MemoryItem( // 换算收敛在 AssistantReminderToMemory 扩展里,与拾忆页的本地投影共用同一份
category: MemoryCategory.alarm, final item = r.toMemoryItem();
source: MemorySource.assistant,
sourceId: r.directiveId,
clientKey: r.directiveId.isNotEmpty ? r.directiveId : r.id,
title: r.content.isNotEmpty ? r.content : 'memCatAlarm'.tr,
happenDate: r.date ?? _todayString(),
happenTime: r.time,
repeatRule: _repeatToRaw(r.repeat),
repeatRaw: r.repeatRaw,
weekday: r.weekday ?? 0,
remindAhead: 5,
);
final saved = await MemoryService.to.add(item); final saved = await MemoryService.to.add(item);
if (saved != null) { if (saved != null) {
r.synced = true; r.synced = true;
@ -163,31 +151,6 @@ class AssistantDirectiveService extends GetxService {
} }
} }
static String _todayString() {
final d = DateTime.now();
return '${d.year}-${d.month.toString().padLeft(2, '0')}-'
'${d.day.toString().padLeft(2, '0')}';
}
/// 本地 RepeatRule 枚举 → 服务端的字符串口径
static String _repeatToRaw(RepeatRule r) {
switch (r) {
case RepeatRule.daily:
return MemoryRepeat.daily;
case RepeatRule.weekly:
return MemoryRepeat.weekly;
case RepeatRule.weekdays:
return MemoryRepeat.weekdays;
case RepeatRule.weekend:
return MemoryRepeat.weekend;
// unknown 按单次处理——与上面 d.note 的口径一致,
// 认不出的重复规则宁可只响一次,也不要按猜的规则天天响。
case RepeatRule.once:
case RepeatRule.unknown:
return MemoryRepeat.once;
}
}
/// 用户在代办页勾选完成 /// 用户在代办页勾选完成
void toggleReminderDone(String id, bool done) { void toggleReminderDone(String id, bool done) {
final r = reminders.firstWhereOrNull((e) => e.id == id); final r = reminders.firstWhereOrNull((e) => e.id == id);

37
apps/client/lib/modules/home/controllers/calendar_controller.dart

@ -4,7 +4,9 @@ import 'package:flutter/material.dart';
import 'package:lunar/lunar.dart'; import 'package:lunar/lunar.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/models/assistant_directive.dart';
import '../../../data/models/memory_item.dart'; import '../../../data/models/memory_item.dart';
import '../../../data/services/assistant_directive_service.dart';
import '../../../data/services/memory_service.dart'; import '../../../data/services/memory_service.dart';
import '../views/widgets/calendar/calendar_event.dart'; import '../views/widgets/calendar/calendar_event.dart';
@ -51,6 +53,7 @@ class CalendarController extends GetxController
/// 监听助手提醒变化的 worker,onClose 要停掉 /// 监听助手提醒变化的 worker,onClose 要停掉
Worker? _memoryWorker; Worker? _memoryWorker;
Worker? _reminderWorker;
// 折叠态横向条的代办/花销筛选:null=全部,非空=只看该类别。 // 折叠态横向条的代办/花销筛选:null=全部,非空=只看该类别。
// 点击"代办"/"花销"标签设置;点击日期(onDaySelected)重置为全部。 // 点击"代办"/"花销"标签设置;点击日期(onDaySelected)重置为全部。
@ -120,6 +123,14 @@ class CalendarController extends GetxController
(_) => _rebuildEvents(), (_) => _rebuildEvents(),
); );
} }
// 迁移期还要盯着本地那份:服务端没收下时(后端未部署 / 断网),
// 助手刚定的闹钟只存在于本地 reminders 里,不订阅它页面就不会刷新。
if (Get.isRegistered<AssistantDirectiveService>()) {
_reminderWorker = ever<List<AssistantReminder>>(
AssistantDirectiveService.to.reminders,
(_) => _rebuildEvents(),
);
}
} }
/// 从 [MemoryService] 单一来源投影成日历事件。 /// 从 [MemoryService] 单一来源投影成日历事件。
@ -136,20 +147,41 @@ class CalendarController extends GetxController
for (final item in MemoryService.to.items) { for (final item in MemoryService.to.items) {
list.addAll(_expandItem(item)); list.addAll(_expandItem(item));
} }
list.addAll(_localReminderEvents());
events.assignAll(list); events.assignAll(list);
} }
/// 迁移期的本地兜底:把**还没同步到服务端**的助手闹钟也投影出来。
///
/// ⚠️ 不做这个,「双写」就是假的。SET_clock 会把闹钟写进本地 reminders,
/// 但换源之后页面只读 MemoryService —— 后端没部署或断网时 memory_add 失败,
/// 本地那份没有任何展示出口,用户对助手说完「定个会议提醒」,
/// 助手答应了、指令也落地了,拾忆页却空空如也(真机实测踩到)。
///
/// 已同步的跳过:那时 MemoryService 里的才是权威,两边都投影会出现两条。
List<CalendarEvent> _localReminderEvents() {
if (!Get.isRegistered<AssistantDirectiveService>()) return const [];
final out = <CalendarEvent>[];
for (final r in AssistantDirectiveService.to.reminders) {
if (r.synced) continue;
// id 前缀区分来源:本地项的 memoryId 是 0,点开编辑时要能识别出
// 「这条还没上服务端」而不是去 MemoryService 里查一个不存在的 id。
out.addAll(_expandItem(r.toMemoryItem(), idPrefix: 'local:${r.id}:'));
}
return out;
}
/// 把一条记忆项摊成日历事件。 /// 把一条记忆项摊成日历事件。
/// ///
/// 窗口取「今天前 7 天 ~ 后 60 天」:往前留几天是为了让用户还能看到刚过去的 /// 窗口取「今天前 7 天 ~ 后 60 天」:往前留几天是为了让用户还能看到刚过去的
/// 事项,往后 60 天覆盖月视图翻两页。 /// 事项,往后 60 天覆盖月视图翻两页。
List<CalendarEvent> _expandItem(MemoryItem item) { List<CalendarEvent> _expandItem(MemoryItem item, {String idPrefix = ''}) {
final anchor = item.happenDateTime; final anchor = item.happenDateTime;
final cat = CalendarEventCategoryX.fromRaw(item.category); final cat = CalendarEventCategoryX.fromRaw(item.category);
final color = MemoryCategory.colorOf(item.category); final color = MemoryCategory.colorOf(item.category);
CalendarEvent make(DateTime day, String suffix) => CalendarEvent( CalendarEvent make(DateTime day, String suffix) => CalendarEvent(
id: '${item.id}$suffix', id: '$idPrefix${item.id}$suffix',
memoryId: item.id, memoryId: item.id,
title: item.title, title: item.title,
// 负责人与截止原文合成一行副标题;两者都空时留空而不是塞占位符 // 负责人与截止原文合成一行副标题;两者都空时留空而不是塞占位符
@ -213,6 +245,7 @@ class CalendarController extends GetxController
@override @override
void onClose() { void onClose() {
_memoryWorker?.dispose(); _memoryWorker?.dispose();
_reminderWorker?.dispose();
_snapAnimation.dispose(); _snapAnimation.dispose();
monthPageController.dispose(); monthPageController.dispose();
weekPageController.dispose(); weekPageController.dispose();

18
apps/client/lib/modules/main_tab/views/widgets/todo_scope_list.dart

@ -325,12 +325,28 @@ class TodoScopeList extends GetView<CalendarController> {
/// 先按 memoryId 回查真实的 MemoryItem——日历事件是投影出来的(重复项会摊成 /// 先按 memoryId 回查真实的 MemoryItem——日历事件是投影出来的(重复项会摊成
/// 多天,id 带了日期后缀),不能拿它直接当记忆项用。 /// 多天,id 带了日期后缀),不能拿它直接当记忆项用。
Future<void> _openEdit(CalendarEvent event) async { Future<void> _openEdit(CalendarEvent event) async {
// memoryId == 0 表示这条还没同步到服务端(迁移期的本地助手闹钟)。
// 它没有服务端 id,改了也没处提交,先明确告诉用户而不是静默无反应。
if (event.memoryId == 0) {
Get.snackbar('memCatAlarm'.tr, 'memLocalOnlyHint'.tr,
snackPosition: SnackPosition.TOP);
return;
}
final item = MemoryService.to.items final item = MemoryService.to.items
.firstWhereOrNull((e) => e.id == event.memoryId); .firstWhereOrNull((e) => e.id == event.memoryId);
if (item == null) return; if (item == null) return;
await MemoryEditSheet.show(item: item); await MemoryEditSheet.show(item: item);
} }
Future<void> _toggleDoneGuarded(CalendarEvent event) async {
if (event.memoryId == 0) {
Get.snackbar('memCatAlarm'.tr, 'memLocalOnlyHint'.tr,
snackPosition: SnackPosition.TOP);
return;
}
await _toggleDone(event);
}
Future<void> _toggleDone(CalendarEvent event) async { Future<void> _toggleDone(CalendarEvent event) async {
final item = MemoryService.to.items final item = MemoryService.to.items
.firstWhereOrNull((e) => e.id == event.memoryId); .firstWhereOrNull((e) => e.id == event.memoryId);
@ -371,7 +387,7 @@ class TodoScopeList extends GetView<CalendarController> {
GestureDetector( GestureDetector(
// 勾选框自己吃掉点击,不要冒泡到卡片的「打开编辑」 // 勾选框自己吃掉点击,不要冒泡到卡片的「打开编辑」
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => _toggleDone(event), onTap: () => _toggleDoneGuarded(event),
child: Padding( child: Padding(
padding: EdgeInsets.only(right: AppKidsDimens.space8, top: 2.h), padding: EdgeInsets.only(right: AppKidsDimens.space8, top: 2.h),
child: Icon( child: Icon(

Loading…
Cancel
Save