Commit Graph
52 Commits
Author SHA1 Message Date
laoyang b623a876dd fix(待办): 修正行高异常(我误读了测量单位,绕了三轮)
## 现象
待办列表每一条都非常高,几乎占满半屏。

## 根因
做 #42(编辑入口扩大热区)时,我给内容区 Column 加了 `.height('100%')`。
在 ListItem 里,百分比的参照系不是"行内容高度",而是被拉伸的可用高度 ⇒
单条 ListItem 被撑到 **1790px(573vp)**。

## 修正
删掉 `.height('100%')`,热区改由外层 Row 的 `padding(10)` 承担 ——
行高恢复由内容自然决定。

同时把待办列表从 `ListView + ListItemGroup + ForEach(Column)`
重构为**扁平 List**(大标题/分组头/条目摊平成一维数组),
因为原先改成 List 后分组渲染需要 ListItemGroup;扁平化后
分组视觉完全保留(分组头就是一行普通 ListItem),结构更可控。

## ⚠️ 我这轮绕了远路,记录下来
**误把 px 当 vp。** 屏幕 1080px ÷ 3.125 密度 ⇒ 1vp = 3.125px。

| 测量 | 换算 | 当时我的判断 | 实际 |
|---|---|---|---|
| 1790px | 573vp | 异常 ❌ | 对,确实异常 |
| 176px | **56vp** | 以为异常,继续改 | **正常**(两行文字+padding)|
| 117px | 37vp | 以为异常 | 正常(探针单行)|
| 275px | 88vp | 以为"修好了" | 我强行加 height(88) 造成 |

于是白白做了三轮"优化"(加 constraintSize、改删除按钮固定高、
给 ListItem 加 height(88)),每次都让行高更高,最后全部回退。

教训:**dump 出来的 bounds 是 px,改代码用的是 vp,必须先换算再判断**。

## 编译
`BUILD SUCCESSFUL`

## 真机验证
全部待办行统一 **56.3vp**(176px):未完成 1 条、已完成 6 条一致;
分组头 21.8vp、大标题 15.4vp,层级清晰。
2026-11-04 08:44:27 +08:00
laoyang 413900bbce feat(待办): 提醒 / 左滑删除 / 优先级标签地点 / 编辑热区 / 卡片刷新 (#38 #39 #40 #41 #42)
一次性完成待办模块的 5 项缺口。

## #38 待办提醒
`ReminderService` 原本只服务 `kind='event'`,待办完全不提醒。

- `EventDb.queryRemindableTodos`:查未完成待办,**额外要求 `completed=0`**
  (否则做完的待办到期还会弹)
- `refreshReminders` 合并日程 + 待办两路查询,文案按 kind 分支:
  待办是「将在 X 分钟后到期」/`该待办已到期`,日程仍为「开始」
- `IcsUtil.buildTodo` 写 VALARM;`rewriteTodoFields` 编辑时整段重建 VALARM
  (逐行改 TRIGGER 容易漏 ACTION/DESCRIPTION)
- 待办弹层加提醒芯片(不提醒 / 5 / 15 / 30 分钟 / 1 小时 / 1 天,多选)
- 三个写操作后调 `refreshReminders`,保证改完立刻重排

协议要点:VTODO 的 VALARM `TRIGGER` 相对 **DUE**(RFC 5545 §3.8.6.3),
所以 `TRIGGER:-PT5M` 就是"到期前 5 分钟",无需额外写 RELATED=END。

## #39 待办列表左滑删除
`swipeAction` 只能挂 ListItem,而原结构是 `Scroll + Column + ForEach`。

- 外层 Scroll → List,分组改用 `ListItemGroup`(保留"色点+本名+条数"分组头)
- 每个 ListItem 挂 `swipeAction({ end: ... })`,滑出红底「删除」
- 点击复用现有 `askDeleteTodo`(二次确认)→ `doDeleteTodo`(软删 + 服务器 DELETE)
- 未完成 / 已完成两个清单都支持

## #40 优先级 / 标签 / 地点
- 表加 `priority`(INTEGER) / `categories`(TEXT);`location` 本来就有
- PRIORITY 存 **iCal 原生值**(1 最高 … 9 最低,0 未设置),写回零转换
- ICS:`parseTodos` 读 PRIORITY/CATEGORIES,`buildTodo` 与 `rewriteTodoFields` 写
- 新增 `todoExtras()` 供新建与改写共用,避免两处生成结构不一致
- `rewriteTodoFields` 用 `priority=-1` 做"不改"哨兵(0 是合法语义:清空)
- 弹层加优先级芯片(高/中/低)、标签输入、地点输入

## #41 重复待办打勾推进
核查结论:**已实现且完整**。`toggleTodo` 已按 `recurRule !== ''` 分流到
`completeRecurringRound`,且该方法会保留"基准→DUE 偏移"、复位 STATUS 后推进。
本次未改逻辑,仅补了完成后刷新卡片与提醒。

## #42 编辑入口热区
内容区 `Column` 原来高度只到文字高度,行内上下留白点不到。
加 `.height('100%')` + `justifyContent(Center)` 撑满行高。
勾选圈仍是独立 onClick(ArkUI 不冒泡),点圈只切完成态、不进编辑。

## 顺带修复(共性遗漏)
`toggleTodo` / `saveTodoEdit` / `saveNewTodo` / `doDeleteTodo` 四处
改完库只刷 `this.todos`,**都没推送桌面卡片** —— 卡片停在旧数据。
四处统一补 `CardDataService.pushToAllForms`。

## 验证
- 编译 `BUILD SUCCESSFUL`
- 真机 dump 确认弹层新 UI 全部渲染:优先级(高/中/低)、
  提醒(不提醒/提前5分钟/15/30/1小时/1天)、标签、地点
- ICS 生成逻辑用等价实现校验:PRIORITY、CATEGORIES(逗号转义 \,
  LOCATION、VALARM(TRIGGER:-PT5M 位于 VTODO 内、结构闭合)全部通过

## 未验证
端到端「填字段→创建→查服务器 ICS」未跑通:`uitest inputText` 按坐标输入时
多个输入框会累积到同一个(把标签/地点内容追加进了标题框),是自动化限制而非代码问题。
需人工建一条带各字段的待办,确认服务器端 ICS 与上述结构一致。
提醒能否实际弹出依赖代理提醒权限(此前有配额为 0 的记录)。
2026-11-04 08:23:42 +08:00
laoyang b8d5aa630c fix(卡片/粘贴): 5 项反馈修复 —— 粘贴按钮、卡片数据、卡片加号、卡片滚动、列表统一
## 1. 粘贴按钮文字看不清(EventEditPage)

根因不是"忘了设文字"——空参时系统本就给默认样式(LINES 图标 + PASTE 文案)。
真正原因是我**给了一个和系统文字颜色太接近的背景色**(card_bg 近乎白)。

查 SDK 类型定义 `paste_button.d.ts`,错误码 2 明确列出:
  「字体或图标颜色与组件背景颜色相近」「文字超出组件背景区域」等
安全组件创建时会做样式校验,对比度不足会让文字灰淡难辨、甚至授权失败。

同时发现 icon/text/buttonType **只能走构造参数**,不是链式属性
(`.text()` 会编译报 "Property 'text' does not exist")。

改为:
```
PasteButton({
  icon: PasteIconStyle.LINES,      // 该枚举只有 LINES
  text: PasteDescription.PASTE,    // 该枚举只有 PASTE
  buttonType: ButtonType.Capsule
})
```
并**去掉自定义背景色**,交给系统默认样式,只保留尺寸与圆角。

## 2. 待办卡片显示 0 条(CardDataService)

`pushToAllForms()` 是同步完成后刷新所有卡片的主路径,
但它只调 `buildCardData()`(日程),**从未调 `buildTodoCardData()`** ⇒
推给卡片的 binding 里 todoCount / todoListJson 全是默认值。

而 EntryFormAbility.pushData() 里合并了待办 —— 两条路径不一致,
所以表现成"有时对、有时 0"。

修:抽出 `mergeTodoInto()` 供两处共用;待办数据与 dayOffset 无关,
整个 pushToAllForms 只构建一次。

真机日志:`待办卡片数据刷新:未完成 5 条…列表输出 5 条`(原为 0)。

## 3. 待办卡片右上角 + 点了没反应(EntryAbility + Index)

待办卡片发的是 `widgetAction: 'addTodo'` / `'openTodo'`,
但 EntryAbility 只判断 `raw === 'add'`,Index 也只处理 `'add'`
⇒ 参数被静默丢弃。

修:两端都补上 addTodo / openTodo。
  addTodo  → 切待办 Tab + 打开新建待办
  openTodo → 切待办 Tab

## 4. 日程 2×4 卡片右侧可滚动,15 条(CardDataService + Widget4x2)

数据侧 `slice(0, 2)` → `slice(0, 15)`
视图侧 `Column + ForEach` → `List + ListItem`(虚拟化 + 滚动惯性 + 回弹)

真机日志:`进行中 15 条`,确认上限生效。

## 5. 待办 4×4 / 6×4 列表样式统一,全部可滚动

按用户选择,**统一成 4×4 样式**(勾选框 + 色条 + 标题 + 副行「截止时间 · 日历本」)。

6×4 原来是「标题 + 独立截止时间列 + 独立日历本列」,窄卡片里会把标题挤没,
改为与 4×4 完全相同的结构。已用逐行 diff 校验:两卡 todoRow **36 行完全一致**。

`MAX_TODO_ITEMS` 20 → 15,与日程卡片口径一致(所有待办卡片都已可滚动,
不必再按卡片尺寸给不同上限)。

## 编译
`BUILD SUCCESSFUL`

## 未验证
真机仅验证了「待办卡片数据不再为 0」与「日程 15 条上限」两项。
粘贴按钮视觉、卡片内滚动、卡片 + 跳转、两卡样式一致性需上机目视确认。
2026-11-03 23:14:43 +08:00
laoyang 2bb620a6ed fix(日历本): 按 COMP_SET 类型分别列出 —— 待办只给待办本,日程只给日历本
上一个提交把"全部可写本"铺开,导致新建待办时连纯日历本(娱乐/生活/工作/家庭)
也列出来,新建日程时又把纯待办本列出来。用户选错类型时服务器会拒绝写入,
或写进去但永远不在对应列表显示。

## 依据
`DavAccount.calendarComps[i]` 早就在库里存着服务器 COMP_SET 声明:
  'event'   只有 VEVENT
  'todo'    只有 VTODO
  'both'    两者都支持
  'unknown' 未声明(旧数据)

但 `CalSource` 没透传它,界面层拿不到类型,只能按"可写"一刀切。

## 改动
- `CalSource` 增加 `comps` 字段
- `CalendarDataService.loadSources` 从 `acc.calendarComps[i]` 透传(空串归 'unknown')
- 待办侧(`Index.openTodoCreator`):排除 `comps === 'event'`
- 日程侧(`EventEditPage.CalendarDataBridge.loadWritableDavSources`):排除 `comps === 'todo'`

**'unknown' 两端都保留**:旧数据没有 COMP_SET 信息,武断排除会让老用户
升级后发现日历本"消失"。

## 真机验证(5NC0225B27004718)

服务器实际类型(PROPFIND 实测):

| 本 | COMP_SET | 待办列表 | 日程列表 |
|---|---|---|---|
| 娱乐 / 生活 / 工作 / 家庭 | VEVENT | 不显示 | 显示 |
| Inbox / 家庭计划 / 工作任务 / 个人成长 | VTODO | 显示 | 不显示 |

改后实测:
- 新建待办 → 只列「群晖:Inbox / 家庭计划 / 工作任务 / 个人成长」4 个 ✅
- 新建日程 → 只列「群晖 · 娱乐 / 生活 / 工作 / 家庭」4 个 ✅

两端的本数量都从 8 降到 4,与服务器声明完全一致。

## 编译
`BUILD SUCCESSFUL`
2026-11-03 23:04:00 +08:00
laoyang ca97ec7fcd feat(待办): +号职责感知 + 新建待办(所有可写日历本铺开选择)
在「待办」视图下点 + 新建待办,其余视图仍新建日程。
新建时把全部可写 CalDAV 日历本铺开供选择,交互与新建日程一致。

## 原状
`TodoService` 只有 改/删/设重复/打勾,**没有新建方法**;
`saveTodoEdit` 开头 `if (... || this.todoEditId <= 0) return;` 直接挡掉新建。
+号固定 `addEvent()`,在待办页点它却跳到日程编辑页,与当前语境不符。

## 改动

### IcsUtil.buildTodo(新增)
从零构造 VTODO 资源。不能复用 `rewriteTodoCompletion`——后者是在服务器原文上
打补丁(保留未知字段),前提是原文已存在;新建时没有原文。

字段取舍:
- UID / DTSTAMP / SUMMARY 必写
- DESCRIPTION 为空则不写
- DUE 可选;只有日期时写 `DUE;VALUE=DATE`
- STATUS 固定 NEEDS-ACTION(完成态由 setCompleted 切换)
- 不写 PRIORITY,避免凭空替用户设优先级

⚠️ 时间格式必须与现有待办写入保持一致:本工程 `rewriteTodoSchedule` 用的是
`DUE:` + `fmtIcsTime(dueMs, false)`,即**浮动本地时间、不带 Z**。
若这里写成 UTC,同一字段出现两种格式,服务器上会显示成早 8 小时的待办。

### TodoService.createTodo(新增)
**先 PUT 服务器、成功后才落库**——与 updateTodo 的顺序相反。
更新时本地已有行可回滚;新建若先落库再 PUT 失败,会留下"本地有、服务器没有"
的孤儿待办,用户看着它存在却永远同步不出去。

同时校验:标题非空、href 非空、账号可定位、日历本可写(只读本提前拦下,避免 403)。

### Index.ets
- `todoCreating` 状态区分新建/编辑
- `openTodoCreator()`:收集全部可写 dav 本;默认选中 Inbox(名字含
  inbox/收件箱 者优先),找不到用第一个 —— 少点一步但仍可改
- `saveNewTodo()`:走 createTodo
- +号分流:`mode === 'todo'` → 新建待办,否则新建日程
- 弹层标题「新建待办」/按钮「创建」,新建时隐藏"删除"入口
- 日历本区:新建时 Flex 铺开色块胶囊(48% 宽,选中染本色调),
  编辑时保持原来只读显示

`CalSource` 没有 href 字段,`createTodo` 需要的 URL 由 calKey(`<accountId>_<index>`)
反解出账号与索引后从 `account.calendarHrefs[index]` 取,映射存在 `todoBookHrefs`。

## 真机验证(5NC0225B27004718)

待办视图点 + → 弹层标题「新建待办」、8 个日历本全部铺开、
按钮显示「创建」。填标题后点创建:

```
PUT .../synccalendartest/home_todo/todo-1793717941310-bl6ffh.ics → 201
待办新建成功 id=749 → todo-1793717941310-bl6ffh.ics
```

- 默认落到 `home_todo`(Inbox)✅ Inbox 智能匹配生效
- 服务器端复查该资源返回 **200** ✅ 真的创建了
- App 列表即时刷新:未完成 5→6 项,出现「群晖:Inbox(1)」✅

测试数据已删除(DELETE → 204),未在服务器留残留。

## 编译
`BUILD SUCCESSFUL`,本次改动无新增警告。
2026-11-03 23:00:07 +08:00
laoyang c39a48e95f fix(待办): 标签改为只看数据事实,不依赖可能缺失的 comps 元数据 #6
## 现象
上一版把 VTODO 同步接进同步循环后,待办数据**确实拉回来了**
(日志:家庭计划3条 / 工作任务2条 / 个人成长2条),
但待办标签仍然不显示。

## 根因:判定挂在元数据上,而元数据拿不到
`shouldShowTodoTab()` 是 `hasTodoBook() && todos.length > 0`。
`hasTodoBook()` 读 `acc.calendarComps`,而这个字段来自服务器的
`supported-calendar-component-set`。

实测:日志里**完全没有**"日历本组件类型 ..."这条记录,
说明该解析路径根本没执行 / 服务器不返回这个属性 ⇒ `calendarComps` 恒为空
⇒ `hasTodoBook()` 恒 false ⇒ 明明有待办也不显示标签。

## 修法:改看事实
直接 `return this.todos.length > 0;`

因为 `CalendarDataService.loadTodos` 内部**已经按 visibleKeys(用户勾选的
日历本)过滤**过了,所以 `todos.length > 0` 本身就等价于
"用户勾选范围内确实有待办"。用户一个待办本都没勾时,这个值必然是 0 ——
`hasTodoBook()` 原本想防的场景天然被覆盖,它是多余的。

## 原需求仍然满足
用户要求"没勾选任何待办本 → 标签不显示":
没勾选 ⇒ loadTodos 按 visibleKeys 过滤后为空 ⇒ 标签不显示。✓
"勾选了但没待办数据 → 也不显示":0 条 ⇒ 不显示。✓

## comps 还留着,但只给图标用
账号页用 comps 区分日历/待办图标 —— 那里判断错了只是图标不准,
不影响功能可用性;功能判定不能挂在这种"可能缺失"的元数据上。

## 验证(真机)
日志:
  日历本[5]「家庭计划」REPORT 返回 0 个资源 → 待办同步:3 条
  日历本[6]「工作任务」REPORT 返回 0 个资源 → 待办同步:2 条
  日历本[7]「个人成长」REPORT 返回 0 个资源 → 待办同步:2 条
界面 dump:顶部标签 同步日历|今天|月|周|列表|**待办**|2026年11月
待办标签存在 = True ✓

(另注意:这几个本 VEVENT 为 0、只有 VTODO,正是纯待办本。)

## 教训
功能可见性判定必须挂在**数据事实**上,不能挂在"可能缺失的元数据"上。
服务器不保证返回 supported-calendar-component-set,用它做开关会制造假阴性。
2026-11-02 22:43:10 +08:00
laoyang 09cb0f9664 feat(账号/待办): 日历本区分日历与待办类型,待办标签两级判定 #6
用户要求两点:
1. 添加/修改账号时,在颜色前面显示图标区分「日历本 / 待办本」
2. 没勾选任何待办本 → 待办标签不显示;选了待办本但没数据 → 也不显示

## 协议层:supported-calendar-component-set
CalDAV 用这个属性声明集合支持哪些组件:
    <C:supported-calendar-component-set>
      <C:comp name="VEVENT"/>   ← 纯日历本
      <C:comp name="VTODO"/>    ← 纯待办本
    </C:supported-calendar-component-set>

DavClient 改动:
- PROPFIND 请求加该属性(两处:propfindColors / propfindCalendars)
- 新增 `parseComps()` 归一成四类:event / todo / both / **unknown**
- `DavColorEntry` 与 `DavCalendarEntry` 都加 comps 字段

⭐ 为什么保留 unknown:
   很多服务器(含自研实现)压根不返回这个属性。若武断判成"不是待办本",
   用户原本能用的日历会被错误分类。unknown 一律按**日历**对待,保持兼容。

## 存储
DavAccount 增加 `calendarComps: string[]`,与 calendarHrefs 一一对应。
JSON 存储天然支持;旧数据读出来是空数组 → 全部 unknown → 视为日历。

## 界面
EditCalendarRow 在颜色圆点**前面**加类型图标,用户先看类型再看颜色:
  · 待办本 → ✓ 配成功色
  · 混合本 → ▦✓
  · 日历/unknown → ▦

## 待办标签两级判定
1. `hasTodoBook()`:被选中的日历本里存在 comps 为 todo/both 的
   —— 一个都没选 ⇒ 必然没待办,直接不显示
2. `todos.length > 0`
   —— 选了待办本但没数据,也不显示

比只看条数更准:没勾待办本时,同步可能拉到残留数据,
只看条数会误显示;第一级在"还没同步"时就能给出确定答案,不会闪一下。

## 待确认(需真机日志)
服务器是否真的返回 supported-calendar-component-set。
诊断日志已加:`日历本组件类型 <名称> → <类型>`。
若全是 unknown,说明服务器没声明 ⇒ 图标会退化成日历图标,
此时需要改用其它判据(如按本内是否含 VTODO 探测)。
2026-11-02 22:32:51 +08:00
laoyang 24e33ed5c9 fix(月视图): 翻月闪跳 —— 改为显式传参重建,不依赖 @State 即时可读 #6
用户反馈:11 月滑到 12 月时,先闪回 11 月,然后才跳到 12 月。

## 根因(两层)

### 第一层:异步时序
`reloadEvents()` 是 async(内部 await 读库),调用只是**启动**。
原代码:
    this.reloadEvents();                    // 只启动
    this.swiperController.changeIndex(1);   // 立刻回弹
回弹那一刻 monthPages 还是旧数据,中间页仍是上个月 ⇒ 闪回。

修:把 `rebuildPages()` 提到 changeIndex **之前**同步执行。

### 第二层:@State 赋值在当前 tick 内不可读
改完第一层后仍会闪。日志证据:
    onChange index=2 当前=2026-11
    同步重建完成 中间页=2026-11     ← 明明先赋了 displayMonth=12

说明 `this.displayMonth = v` 之后、同一个同步调用栈内读取它,
**不一定拿到新值**(@State 走可观察代理,写入与读取之间有调度)。

修:新增 `rebuildPagesFor(year, month)`,把月份**作为参数显式传入**,
内部不再读 `this.displayXxx`。这样无论 @State 何时生效都算得对。

## 附带修正:诊断日志本身有 bug
之前用 `monthPages[1][0].dateMs` 当作"中间页月份",
但月历第一格常属于**上个月**(12月的第一格是 11/30),
日志就写成"中间页=11月",让我误判成"月份没变",白查了一轮。
改用记录**传入参数**后真相立刻清楚:入参全程都是 2026-12,逻辑一直是对的。

## 真机验证
  11月 → 左滑 → 2026年12月,选中圆存在,无闪回。
日志确认:
  TRACE_PARAM 重建入参=2026-12(同步重建)
  onChange index=1 guard=true → 被 guard 正确拦下
  TRACE_PARAM 重建入参=2026-12(数据重建)
诊断日志已在提交前移除(grep 0 处)。

## 教训
1. @State 赋值后**不要在同一同步栈内立刻读它**做计算,要用局部变量/参数传递。
2. 写诊断日志时,验证"我关心的那个值"必须用可靠来源。
   用 `数组[0]` 代表整体属性是常见陷阱 —— 这次就因此浪费了一轮排查。
2026-11-02 22:25:00 +08:00
laoyang 6305b4a7f0 fix(月视图): 翻月后选中态丢失,整月没有一天被选中 #6
用户反馈:月视图滑动切换月份后会"跳一下",整月没有任何一天被选中。
期望:切到下个月后,**同样的日号**保持选中(如 11/2 → 12/2)。

## 根因
handleSwiperChange 只改了 displayYear/displayMonth,**完全没动 selectedDate**。
于是 selectedDate 还停在上个月,而新月份所有格子的 dateMs 都变了,
`cell.dateMs === this.selectedDate` 永远为 false ⇒ 整月无选中。

## 修法
翻月后把 selectedDate 迁到新月份的同一天;该月没有这天时钳到月底
(1/31 → 2/28,与 incForFreq 的钳制策略一致)。

## 顺带修掉日号漂移
单纯的"钳制"有个副作用:1/31 → 2/28 → 3/28 → 4/28 …
日号被永久改成 28,翻到有 31 天的月份也回不去了。

加 `preferredDay` 记录**用户主动选中的日号**(点日期时更新,翻月时不更新):
  偏好31号:1/31 → 2/28(钳制显示) → 3/31(回到偏好) → 4/30 → 5/31
"月末"的语义得以保留。

## 验证
脚本 18 项全通过(跨年、闰年 2/29、大月小月、连续翻月、往前往后)。
真机实测:
  11月2日 → 左滑 → 12月,选中圆存在(144×144)
  → 再左滑 → 2027年1月,圆内日期仍是"2"

## 教训
"只改显示状态、不改选中状态"是日历类视图的高频 bug。
翻页/翻月时,凡是与"当前选中项"绑定的视觉(高亮、背景),
都要检查选中项本身有没有跟着迁移 —— 否则 diff 时匹配不上就全空白。
2026-11-02 22:16:08 +08:00
laoyang d8ee2aa2be fix(周视图): 星期标签移到圆外,圆径回到 46vp 与月视图一致 #6
用户反馈:上一版圆太大,把"一"(星期标签)也圈进去了,不应包进来。

## 结构调整
原来(错):
    Stack{ 圆(54vp), Column{ 星期+日期+农历+点 } }
    —— 为了让圆包住"一"把圆径撑到 54vp,结果星期也被圈进去

现在(对):
    Column
      ├ Text(星期)        ← 在圆外
      └ Stack{ 圆(46vp), Column{ 日期+农历+点 } }

圆只包 日期(72)+农历(27)+点(11)+间隙 ≈ 116px,46vp(138px) 够用,
圆径回到与月视图完全一致。

## 过程说明(走了弯路)
这轮我先用脚本做局部替换,结果括号层级错乱,改了三版都没编译过:
  · 版1:找不到锚点(注释文字叠加,锚点不匹配)
  · 版2:补闭合时多写了一个 `}`,报 "Declaration or statement expected"
  · 版3:`Stack` 不支持 `justifyContent`,报 "Property 'justifyContent' does not
         exist on type 'StackAttribute'"

最后**整体重写整个 dayCellCircle 函数**(按行号范围一次性替换)才干净。

## 结论
多层嵌套(Column > Stack > Column)的 ArkUI 结构,靠脚本做局部增删很容易
括号错位。**这类改动应当整块替换而不是局部 patch**,一次写对更省时间。
2026-11-02 22:11:21 +08:00
laoyang ddaf41fee8 fix(周视图): 圆径按内容高度适配,修"上露星期、下漏圆点" #6
用户反馈:周视图圆的上沿包进了"一"(星期标签),下沿却把事件点漏在外面。

## 根因
周条加了星期标签后,内容实际高度约 147px:
  星期 10px字号 ≈ 28
  日期框 24vp    ≈ 72
  农历 9px字号   ≈ 27
  事件点 3.5vp   ≈ 11
  3 个间隙       ≈ 9
  合计           ≈ 147px

而圆固定 46vp = 138px ⇒ **内容比圆高 9px**。
Stack 的 alignContent 默认居中,导致内容上下对称溢出:
上面露出星期标签,下面漏掉事件点。

## 修法
圆径按内容取,不搞一刀切:
  · 周条(showWeekLabel=true,内容≈147px)→ 54vp(≈162px)
  · 月视图(无星期标签,内容≈118px)      → 46vp(≈138px)
同时 Stack 显式 alignContent(Alignment.Center),避免内容高于圆时对齐跑偏。

## 验证(真机 dump 实测)
  圆范围 y 495~664(169px)
  星期"一"  y 497~534  圆内 ✓
  日期"2"   y 537~612  圆内 ✓
  农历"廿四" y 615~648  圆内 ✓
  事件点×7  y 651~662  圆内 ✓

## 教训
Stack 里"固定尺寸的装饰元素"(圆)+ "不定高的内容"(文字块)组合时,
圆径必须 >= 内容高度,否则内容一定溢出。
这类问题看截图不容易发现(因为溢出部分常被裁掉或重叠),
**必须 dump 出两者的 bounds 做数值比对**才准。
2026-11-02 22:08:29 +08:00
laoyang 2cc3b1ba42 fix(周视图): 修复复用后颜色过浅、星期标签丢失 #6
用户反馈:周视图复用月视图后,日期/文字颜色太浅(灰),选中也不变白。

## 根因:weekStripDays 从未设置 inMonth
dayCellCircle 的颜色分支是:
    .fontColor(!cell.inMonth ? text_hint(灰) : ...)
但 weekStripDays() 只设了 dateMs/day/isToday/lunar/hasEvent,
**inMonth 一直是默认值 false** ⇒ 周条每天都被当成"非本月",全部走灰色分支。

修法:weekStripDays 里显式 `cell.inMonth = true`
(周条 7 天都是有效日期,没有"月外"概念)。

## 顺带补回丢失的星期标签
复用 dayCellCircle 时,原来周条自己那行 WEEK_LABELS(一/二/三…)被一并删掉了,
现在周条没有星期。
做法:dayCellCircle 加参数 `showWeekLabel`(默认 false),
周条传 true。月视图的星期在表头单独一行、不在格子里,所以默认不显示。

## 农历提亮
实测采样(JPEG,最暗 25%):
  周条农历 rgb(175~183)  ← text_hint(#4D000000)
  月历日期 rgb(27~54)    ← text_primary(#182431)
周条里农历紧挨日期数字,对比过强会显"糊"。
周条农历改用 text_secondary(约 rgb(120~150))、字号 8→9;
月视图农历保持 text_hint,不破坏已有观感。

## 教训
复用组件时必须核对**数据契约**:dayCellCircle 依赖 inMonth 决定颜色,
而 weekStripDays 从没提供这个字段。
"能编译、能显示"不等于"语义正确" —— 默认值 false 这种静默错误最难发现。
2026-11-02 22:06:15 +08:00
laoyang 22db069bee style(周视图): 选中/今天的圆放大到 42vp #6
用户反馈:周视图的圆不够大。

## 实测数据(设备 dump)
  周条每列 Stack = 133 × 192 px
  原来圆       = 113 × 113 px(36vp)

周条每列有 192px 高,圆只占 113px,显得空;而且比月视图的 138px 还小,
两个视图的"选中"视觉分量不一致。

## 关键约束:宽度是硬上限
列宽 133px ≈ 44vp。给 52vp(≈156px) 会**横向溢出被裁切**。
所以取 42vp(≈126px),留 7px 余量。

## 字号同步放大
大圆里字太小会失衡,一起调:
  日期 14 → 16、日期框 30 → 34
  农历 8 → 9、事件点 3.5 → 4

⚠️ 教训:改尺寸前先 dump 量格子实际宽高,别拍脑袋。
   这次如果直接按"感觉不够大"给 52,会被裁成方角,又得返工。
2026-11-02 21:59:30 +08:00
laoyang 4f19c9482d style(月/周视图): 今天也变大圆、周视图统一正圆、待办标签按需显示、今天按钮状态化 #6
用户提的四个细节,一次改完。

## 1. 今天的淡蓝底也要变大圆
原来 `isToday` 的淡蓝底贴在日期数字上(24×24 小圈),而选中态是 46vp 大圆。
同一天"选不选中"看起来像两种不同的东西。
现在两者尺寸统一:选中=实蓝圆,今天=淡蓝圆,都是 46vp。
日期数字自身的背景一律透明,避免"大圆里套小圈"。

## 2. 周视图选中态也变大圆
周条原来只有 30×30 的小圈,跟月视图 46vp 的大圆不一致。
同一个"选中"在两个视图里长得不一样,会让人以为是不同状态。
改用 Stack + 36vp 正方形 + 50% 圆角(周条比月历窄,36 够用且不压邻居)。

## 3. 没有待办就不显示「待办」标签
判定用 `this.todos.length > 0`。
todos 已按"可见日历本"过滤,所以这个判断等于"用户实际能看到几条待办"。
否则用户点进去一片空白,会以为功能坏了。

## 4. 「今天」按钮要能看出"在不在今天"
· 已在今天 → 透明底 + 蓝框蓝字(无需跳转)
· 不在今天 → 蓝底白字(可点,点了回今天)
新增 isOnToday() 判定:selectedDate === startOfDay(now)。

## 为什么圆一定要用 Stack
Column 是 layoutWeight(1) 撑满的**矩形**,圆角给再大也只是"圆角方块/胶囊"。
只有独立的正方形容器取 50% 圆角才是真圆。
这一点这轮改了两遍才对(第一版铺在 Column 上,得到 149×133 的方块)。
2026-11-02 21:56:25 +08:00
laoyang b41eaabc41 style(月视图): 选中态改为正圆蓝底 #6
用户要求:选中时的蓝色背景做成正圆,把日期、农历、事件点都包进去。

## 为什么第一版不圆
上一版把蓝底铺在最外层 Column 上。Column 是 layoutWeight(1) 撑满格子的
**矩形**(149×133),无论圆角给多大,只能是"圆角方块"或"胶囊",
变不成正圆。

## 这一版
新增 dayCellCircle,结构改为 Stack 叠放:
  ├─ 底层:选中时一个 46vp × 46vp 的正方形容器,borderRadius 50% → 真正的圆
  └─ 上层:原有内容(日期数字 / 农历 / 事件点),背景透明

尺寸 46vp ≈ 138px 的依据:
  · 内容实际高约 121px(日期框 75 + 农历 29 + 点 11 + 间隙)
  · 格子宽 149px
  ⇒ 138 能包住全部内容,又不超出格子宽度

选中时日期数字、农历、事件点统一转白(与日期同色),
因为浅红压在深蓝底上会发灰。

⚠️ 踩坑记录:改这个之前最好先量一下尺寸。我是从 dump 里读到
   "日期 599~674、农历 677~706、点 709~720" 才确定 46vp 这个值的,
   拍脑袋给尺寸很容易包不住或溢出格子。
2026-11-02 21:51:34 +08:00
laoyang e92daf5297 style(月视图): 农历恢复原色,下方加浅红小圆点 #6
用户反馈:农历整体染红不好看(数据本身是对的,确实每天都有日程),
要求改成"农历下面一个浅红色的点,不要太红、不要太刺眼"。

## 改动
- 农历文字恢复原色(灰 / 选中日仍是提示灰),不再染红
- 农历**下方**单独一行放一个 3.5vp 小圆点
- 颜色新增 `soft_red`,不用之前的 event_mark(#D93025)
  · 浅色 #F2938A —— 带粉的浅红,是"柔和提示"而非"警报"
  · 深色 #C97A72 —— 黑底上浅粉会发灰,用稍沉的暖红
- 无事件时圆点透明但仍占位,保证有/无事件行高一致,整月不跳

## ⚠️ 两个必须处理的布局约束
1. **圆点必须独立成行**,不能塞进农历那一行的 Row 里。
   上一版就错在这:农历带 layoutWeight(1) 撑满整行,圆点被挤到行尾,
   视觉上贴着下一个日期。独立成行后天然居中在格子下方。

2. **格子高度已被占满**(实测 175px 全用尽),直接加一行会被顶出格子。
   所以把 Column space 从 2 收到 1、日期框 26→24,换出约 4px 给圆点。

周视图同步改成同一套标记,避免"月历一个样、周条另一个样"。
2026-11-02 21:31:52 +08:00
laoyang 8ca1eb76e0 fix(月视图): 首屏不变色 —— 真正根因是外层 ForEach key 不变 #6
上一版只改内层 key 没修好,因为问题不在内层。

## 真正根因
外层 ForEach 的 key 是:
    (cells) => String(cells[0].dateMs)     // "这页第一天"
重建前后这个值**完全相同**(还是那个月的 1 号)。
⇒ ArkUI 判定"这一项没变" ⇒ **不重新执行 itemGenerator**
⇒ 里面的 chunkCells(cells) 不重算
⇒ 内层 ForEach 收到的还是**旧的 cells 数组**,旧数组里 hasEvent 仍是 false

内层 key 再怎么改都没用 —— 内层压根没被重新调用。

## 修法
新增 `@State monthDataVersion`,每次 rebuildPages 后自增,拼进外层 key:

    (cells) => `${cells[0].dateMs}_v${this.monthDataVersion}`

key 一变 → 外层重建 → 内层重新执行 → 颜色正确。

为什么不用"把整页 hasEvent 拼成 key":一页 42 格,key 会长很多,
且会参与 diff 比较,有性能代价。版本号是 O(1),语义也更清楚。

## 同时补了诊断日志
buildMonthCells 里输出:事件条数 / 有事件日期数 / 本页命中格数 / 当前版本号。
用来判断问题卡在哪一层:
  · "有事件的日期 0 天" → 数据层没算对
  · "命中 X 格" 但界面没变色 → 渲染/key 问题

## ⚠️ 教训(两层 ForEach 嵌套时)
这类 bug 我第一轮判断错了:只看到最内层 key 缺 hasEvent 就改了,
没意识到**外层 key 不变会让整棵子树被复用**,内层改什么都没用。
排查嵌套 ForEach 的刷新问题时,要从**最外层**往里查,
每一层的 key 都必须随数据变化。
2026-11-02 17:12:55 +08:00
laoyang d49b041a2c fix(月视图): 首屏农历不变红,切视图才生效 #6
用户反馈:刚打开应用时,月视图里有事件的日期农历没变红;
切到周视图再切回月视图,才变红。

## 根因:ForEach 的 key 不完整
月视图的 key 是:
    (cell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`
**没有包含 hasEvent。**

首屏时序:
  1. aboutToAppear → rebuildPages(),此时 events 为空,hasEvent 全 false
  2. reloadEvents() 拉回数据 → rebuildPages() 再次重建,hasEvent 变 true
  3. 但每个格子的 key 完全没变(dateMs 和选中态都没变)
  ⇒ ArkUI 判定"这些项没有变化",**复用旧组件、跳过重绘**,颜色停在旧值

切视图能恢复,是因为切回来时整棵子树被重新创建,绕过了 diff。

## 修法
把影响渲染的**全部**状态纳入 key:hasEvent / isToday / 是否选中。
key 变了 → ArkUI 视为新项 → 重建该格子 → 颜色正确。

## 顺带加固
中间层"每周"的 key 原来是 `String(idx)`(0..5),跨月翻页时序号永远重复,
会让 ArkUI 误判为同一周而复用旧节点。改为用该周首日的 dateMs 做 key。

## 为什么不改成 @Observed / @ObjectLink
monthPages 是二维数组(MonthCell[][]),buildMonthCells 返回新数组、
chunkCells 又一次切片,改成可观察对象要动整条构建链路,
牵涉面大且容易引入其它 diff 问题。用 key 是最小、最稳的改法。

⚠️ 通用教训:ArkUI 的 ForEach 用自定义 key 时,**key 必须覆盖所有影响渲染的
   字段**。漏一个字段,该字段更新后界面就不刷新 —— 而且这类 bug 只在
   "同一项的数据变了、key 没变"时出现,首次渲染永远是正常的,很容易漏测。
2026-11-02 17:10:38 +08:00
laoyang fa3dbc6157 style(月视图): 有事的日期标记色改为红色 #6
用户要求先看红色效果,蓝色太不显眼。

## 新增 event_mark 语义色
不直接复用现有的 `error`(#E02020):
  error 是语义色,到处被"失败/危险"复用。把日程标记挂上去,
  以后想调错误提示的红就会连带改掉日历标记,两件事被耦合。
  所以单独加一个 event_mark。

色值(农历只有 8px,小字对对比度要求高):
  · 浅色 #D93025 —— 比 #E02020 更沉,压在白底/浅灰底上更清晰,
    也不像"出错"那么刺眼
  · 深色 #FF6B60 —— 黑底上要亮一点才够,纯红会显脏

## 改动范围(3 处)
  · 月视图(非宽屏)农历文字
  · 月视图(宽屏)补位小圆点
  · 周视图农历文字

选中日仍用 button_text(白):那天日期数字坐在深红底上,
农历必须白色,否则红字压深底看不清。
2026-11-02 17:06:21 +08:00
laoyang ff62cf7f00 fix(月视图): 事件标记改为农历文字变色,修圆点位置错乱 #6
用户反馈:圆点位置很怪,离农历很远、落在两个日期中间。

根因:圆点塞在农历行的 Row 里,而农历用了 layoutWeight(1) 撑满整行,
      圆点被挤到格子最右侧;格子宽 133px 远大于文字宽,
      视觉上就成了"贴着下一个日期"。

采纳用户方案:把标记合并到农历文字本身
  · 有事件 → 农历字用主题色并加粗
  · 无事件 → 保持原来的 hint 灰

好处:不再额外占空间、位置天然跟着文字走、也不会像满屏圆点那样显乱
("点太多显得乱"正是上一版被砍掉的原因)。

对比度:选中日的日期数字坐在深色底上,农历若仍用主题蓝会看不清,
所以选中日改用 button_text(白),与原圆点方案的策略一致。

周视图同步统一为农历变色:两种视图用两套标记会让人以为是不同含义。
宽屏(wideLayout)农历被省略,保留一个小圆点补位(此时格子窄,圆点居中,无位置问题)。
2026-11-02 16:58:56 +08:00
laoyang 788171d1b2 feat(待办): 支持循环任务(标准 CalDAV / VTODO RRULE)#5
只按标准 CalDAV 实现,不依赖家书的自定义机制。
群晖等标准服务器:RRULE 写在 VTODO 上,由客户端负责展开。

## 核心语义决策
循环待办「勾完成」= 这一轮做完了,推进到下一周期,而不是结束。
若照搬非循环逻辑(置 COMPLETED 就完结),用户每勾一次循环任务它就永久结束,
循环规则等于白设。所以:
  · 非循环 → 置 COMPLETED,永久结束
  · 循环   → DUE 推到下一周期 + 状态复位 NEEDS-ACTION

## 关键修复:DTSTART 与 DUE 必须分开
原 parseTodos 把 DUE 和 DTSTART 都写进 startTime,后出现的覆盖先出现的。
但循环下两者语义完全不同:
  · DTSTART = RRULE 的重复基准
  · DUE     = 这一条的截止时间
混在一起会导致重复基准被截止时间覆盖,算出来的发生日全错。
现在拆成独立字段,数据库补 dtstart 列(rrule 列已有)。

## 顺带修掉两个既有 bug(测试连带查出)
rewriteTodoCompletion 与 rewriteTodoFields 里,插入阶段的块序号
vtodoIdx 在遇到 END:VTODO 时被重置成 -1 ⇒ 每块都从 0 重新数,
多 VTODO 文件里永远匹配不到目标块,字段加不上(静默失败,
表现为「点了没反应」)。三处一并修正,vtodoIdx 改为全局块序号。

## 实现
- IcsUtil.incForFreq:按 FREQ 推周期,处理月末溢出
  (1/31 + 1月 → 2/28 钳制,不跳到 3 月;2/29 + 1年 → 2/28)
- IcsUtil.freqOfRrule / rewriteTodoRecurrence
- TodoService.setRecurrence / completeRecurringRound
- 写回时同时给 DTSTART + DUE:RFC 5545 里 VTODO 的 DTSTART 是 OPTIONAL,
  但缺它时各客户端对 RRULE 基准理解不一致,显式给能消除歧义
- 待办行显示「↻ 每周」标记(否则循环任务和普通待办长得一样,
  用户勾完发现「又冒出新的」会困惑)
- 编辑弹框加 每天/每周/每月/每年 选择,再点一次取消

## 验证
scripts/test-ics-todo-recurrence.js:24 项全通过
(月末钳制、闰年、字段保留、取消循环、多 VTODO 定位、结构完整性)。
编译 BUILD SUCCESSFUL。

## 说明
服务器若不支持 RRULE(如群晖 Calendar 规格里未提及循环任务),
则循环只在客户端可见,同步到其它客户端会退化为普通任务。
2026-11-02 16:53:30 +08:00
laoyang 9066a12bbc feat(待办): 按日历本分组 + 编辑/删除 + 修整行点击误完成 + 周视图圆点 #5 #6
## 1. 修 bug:整行点击都会勾选完成
上一版把 onClick 挂在最外层 Row 上,点标题/点截止时间/点空白都会改状态。
改为:
  · 只有左侧圆圈 → 勾选/取消完成
  · 其余内容区 → 打开编辑弹框(不改变完成状态)

## 2. 待办按日历本分组
用户:待办应按日历本分类,未完成里也能按家庭/生活分开,要有区分度。
未完成/已完成 两大段内部,再按日历本聚合:
  · 分组标题前带该本颜色圆点(这是区分度的来源)
  · 组标题显示 本名(数量)
  · 组内按截止时间升序,无截止时间排最后
  · 组之间按组内最早截止时间排序(最紧急的本在前)
CalendarDataService.loadTodos 补 d.calName(原来只带 calKey,分组标题没名字)。

## 3. 待办编辑弹框
点内容区弹出,可改:标题 / 备注 / 截止时间(含日期+时间选择器,可清除),
另有删除(二次确认)。只读本禁用编辑并提示。

⚠️ 用**整页覆盖层**而不是 bindSheet:ArkUI 同一组件只能挂一个 bindSheet,
Index 已用 detailShow 挂了日程详情,再挂会被覆盖(EventEditPage 顶部记过此坑)。

写入仍走「GET 原文 → 改字段 → PUT」,只动 SUMMARY/DESCRIPTION/DUE,
**不动 STATUS/COMPLETED**(改内容不该重置完成状态),
并保留 PRIORITY/CATEGORIES/X-* 等本地没存的字段。

## 4. 周视图圆点
weekStripDays 原来没算 hasEvent;补上与月视图**共用同一份**事件日期集合
(buildEventDaySet),口径完全一致。
颜色:用主题色 brand(选中日换白),**不取日历本的颜色** ——
圆点只表示这天有安排,若取本色会误导成只有那个本有日程。

## 验证
scripts/test-ics-todo-fields.js:19 项全通过
(保留 PRIORITY/CATEGORIES/X-扩展、特殊字符转义、多 VTODO 只改目标、
缺字段补齐位置、结构完整性)。编译 BUILD SUCCESSFUL。

## 未做
子任务(父子层级 / RELATED-TO):需先确认服务器是否支持,另开工单。
2026-11-02 15:47:03 +08:00
laoyang 98c4b6a96f feat: 待办可勾选完成 + 月视图事件圆点 #5 #6
== #6 月视图事件标记(每日期最多一个圆点)==
应用市场反馈希望有事件的日期带标记。此前该功能存在但被移除,
原因是**按日历本逐个渲染**:3 个本 3 个点,15 个本满屏都是点。

- MonthCell 只加**一个布尔** hasEvent,不存每本一个的集合
- 用 buildEventDaySet() 一次遍历把有事件的日期收进 Set,
  渲染时 O(1) 查(避免 42 格 × N 事件的 O(n²),本机近 3000 条数据会卡)
- 判定口径与 eventsOfDate 一致(区间相交)→ 跨天事件覆盖的每一天都有点
- 待办不计入(无时间点语义,混进来点会失去意义)
- 视觉用主题色 5vp 小点而非纯红:42 格里 30 个红点会吵,
  正是用户嫌乱的同类问题;选中日深底下换白色保证对比度
- 无事件时用等高透明占位,避免行高跳动

== #5 待办勾选完成 ==
先回答关键问题:**系统未开放待办 API**。
本机 SDK 检索 @ohos.calendarManager 全文,todo/VTODO/task 零命中,
Calendar/Event 只处理 VEVENT ⇒ 第三方应用无法读写系统「待办」页签。
故只能在本应用内实现,并已在待办页注明不会出现在系统待办中。

现状是**只读**(点击提示请在源端修改),本次补上写回:

- TodoService.setCompleted():GET 原文 → 改状态字段 → PUT 回写
- EventDb: queryById / setTodoCompleted
- DavClient.getRawStrict():失败**抛错**(原 getRaw 失败返回 HTTP 4xx
  字符串,若当 ICS 内容 PUT 回去会把待办覆盖成一句错误信息)
- IcsUtil.rewriteTodoCompletion()

⚠️ 两个关键设计:
1. **不重建 VTODO,只在原文上改字段**
   VTODO 有 SUMMARY/DESCRIPTION/DUE/PRIORITY/CATEGORIES/X-* 等字段,
   本地库只存了少数几个。重建后 PUT 会把没存的字段全部抹掉(丢数据)。
2. **两遍处理,与字段顺序无关**
   RFC 5545 不规定字段顺序。单遍实现若 STATUS 出现在 UID 之前,
   matched 仍为 false → 状态改不到 → 用户点完成没反应。
   改为第 1 遍定位目标 VTODO 块、第 2 遍在块内改写。
   插入补字段时按**块序号**定位而非行号(改动会删掉旧 COMPLETED 行,
   行号不再对齐,会让字段落到 END:VCALENDAR 之后,部分服务端直接 400)。

同步维护三个字段(按 RFC 5545,缺一个各客户端显示就不一致):
STATUS / COMPLETED / PERCENT-COMPLETE。

UI:顶部新增「待办」标签(月/周/列表/待办),未完成在前、已完成在后,
点击勾选即写回;写回失败本地状态不变(避免界面与服务器不一致)。

验证:scripts/test-ics-todo.js 覆盖 26 项(状态在 UID 前/后、取消完成、
多 VTODO 只改目标、缺 STATUS 补字段、折行、结构完整性),全部通过。
编译 BUILD SUCCESSFUL。
2026-11-02 14:48:43 +08:00
laoyang d993dd3d4b 移除定位权限:地理编码不需要 LOCATION(AGC 隐私标签审核)
AGC 以「未如实填写隐私标签」退回本次提审。核查后发现根因是
申请了两个用不到的敏感权限,与隐私标签无法自洽。

依据(本机 SDK API 26,targetSdk 26.0.0):
- @ohos.geoLocationManager.d.ts 中 getCurrentLocation / getLastLocation /
  on('locationChange') 均有 @permission APPROXIMATELY_LOCATION;
- 而 getAddressesFromLocationName(地址→坐标)与 getAddressesFromLocation
  (坐标→地址)**没有 @permission**,只有 @syscap ...Location.Geocoder。
- 本应用从不调用 getCurrentLocation,本就不读设备实时位置,只在「一键导航」
  时把用户填写的地址换算为坐标。

改动:
- module.json5:删除 LOCATION、APPROXIMATELY_LOCATION(现仅 5 个权限)
- Index.ets:删除 ensureLocationPermission(),openInAmap() 直接调
  geocodeAddress();同步清掉随之无用的 abilityAccessCtrl / bundleManager 导入
- string.json:删除 perm_location、perm_location_approx
- AppSettings.ets:更新权限一致性注释,警示后续勿再照旧文档写错政策

验证:assembleHap BUILD SUCCESSFUL;解包 hap 与 .app 内 module.json 均只剩
5 个权限、LOCATION 零命中;真机 hdc install -r 成功,aa start 无崩溃。

注意:华为旧文档 @ohos.geolocation(API 9 已废弃)仍写"需要权限 LOCATION",
那是老接口,勿据此判断——应以本机 .d.ts 为准。
2026-10-28 23:14:08 +08:00
laoyang 942f71bfdf 0.0.8 上架收尾:移除同步记录落盘 + 镜像空清单防护 + 前台默认间隔 15 分钟
三处改动(均来自 2026-10-16 真机日志实测):

1) 移除「同步记录」落盘
   · LogUtil 顶部新增 ENABLE_FILE_LOG=false(发行版只输出 console),
     真机排查时改 true 即可恢复落盘,所有调用点无需改动;
   · init() 里顺手 unlink 历史版本遗留的 sync.log / mirror_diag.txt,
     保证升级上来的设备沙箱也干净;
   · SystemCalendarMirror 删掉 dumpDiagnosis 整个方法(不再导出 mirror_diag.txt),
     连带去掉已无引用的 fs 依赖;
   · SettingsPage「清理残留镜像账户」提示语由"见同步日志"改为"见下方"(清单本就在页面上)。

2) 镜像防护:系统日历清单为 0 时跳过创建
   · 依据:2026-10-16 07:55:00 日志 `系统日历账户清单(共 0)` →
     `镜像完成:本=0 … 失败=12`,12 个 bookId 连续 `201 CreateCalendar failed!`。
   · 真因:设备重启后尚未解锁(direct boot),日历数据在加密存储里读不到,
     getAllCalendars() 返回空列表 ⇒ createCalendar 必然失败,还可能留下半创建账户。
   · 正常设备至少有一个本机日历,故"清单为 0"只可能是服务未就绪 ⇒ syncNow()
     在 plan/syncBook 之前直接跳过本轮,等解锁后的下一次同步自然恢复。

3) 前台自动同步默认间隔 1 分钟 → 15 分钟
   · 依据:2026-10-16 17:16–17:20 日志,5 分钟内连跑 5 轮
     "12 本全量比对 + 出站镜像 + 提醒刷新",随后
     `17:20:43 短时任务超时(配额用尽),后台同步将被挂起`
     —— 前台把短时任务配额烧光,反而拖垮了真正的后台同步通道。
   · 设备 preferences 里并无 sync_interval_minutes 键 ⇒ 走的就是这个默认值。
   · AppSettings.getSyncIntervalMinutes 默认值 + Index.startAutoSync 的
     setOrCreate 与 ?? 兜底 + SettingsPage 的 @State 初值,四处一并改为 15。
   · 用户仍可在设置页选 1/5/15/30 分钟 或 1 小时。

验证:assembleHap 与 assembleApp 均 BUILD SUCCESSFUL。
2026-10-16 19:38:21 +08:00
laoyang 17d7602d7d 重复日程:新增结束条件(到某天/共几次);修复仅这一次失效与标题被清空
新增能力:
- 新建重复日程时可设"结束条件":永不结束 / 重复到某天 / 共重复几次
  (之前只能选重复频率,无法指定次数或截止日期)
- 编辑既有重复日程会**回显**服务器上已有的 COUNT / UNTIL 并可修改

修复三个真 bug:
1. ⭐ "仅这一次"失效(实际改了全部):重复日程在列表里展开后,所有次共用
   主事件的 id,编辑页只拿到 id、拿不到"用户点的是哪一次" → occStartMs 被当成
   主事件的第一次发生 → EXDATE 排除的是第一次、覆盖行写在第一次上,
   用户当次既没排掉、系列属性又被动过,看起来就是"全改了"。
   修法:Index 打开编辑页时用 pendingOccurrenceTs 传入被点那次的 startTime;
   seriesBoundaries 再把该时刻"吸附"到真实发生(容差 12h),确保 EXDATE 能匹配。
2. ⭐ 已有标题被删空:original 快照漏拷贝 title/description/location/reminders,
   restoreRowById 于是把主事件行还原成空标题。
   修法:快照补全全部字段。
3. ⭐ setExdate/setRrule 被 restoreRowById 覆盖(顺序错误):
   先写 EXDATE 再还原 → 快照里的旧 exdate 又把排除覆盖掉。
   修法:一律"先还原主事件行、再写 EXDATE/截断 RRULE"。

其它:
- 编辑页现在显示"被点那一次"的实际时间(此前显示的是主事件第一次的时间)
- 'all' 分支改为把改动写到主事件行,整系列一起改
- 复用唯一底部弹层(新增 sheetMode='repeatEnd' 选截止日期),仍保持只挂 1 个 bindSheet
2026-10-10 07:05:32 +08:00
laoyang 5e6f347fc6 修复:编辑重复日程缺范围选择;同步并发不再误报失败
问题1(用户反馈):删除重复日程有"仅这一次/此次及以后/全部"三选项,编辑却没有。
根因:save() 把 validate() 与 chosenBook() 检查放在 isSeriesOccurrence() **之前**,
      任一不满足即提前 return,永远到不了范围选择弹层;delete 路径则是先判范围。
修法:
- save() 把"重复日程先选范围"提到最前(与删除路径一致),校验推迟到 doSave() 内
- chosenBook() 增加兜底:展开编辑时若该日程所属日历本不在"可写清单"里
  (被标记只读/写权限探测失败),用该日程自己的 calKey/href 构造目标;
  initPage 里还会用 CalendarDataBridge.lookupBook 把它补进选择列表(带真实名称/颜色)
- 新增 original 原始行快照:doSave 会就地改写 this.event 的 calKey/startTime,
  而 EXDATE/UNTIL/拆系列必须依据原值 —— 否则范围计算用错系列
- applySeriesEdit 的 one/future 分支在改写当前行后,用 EventDb.restoreRowById 还原原行,
  避免原系列残留幽灵行
- 加保存判定日志(脱敏)便于诊断

问题2(用户反馈):同步进行中再点手动同步/账号管理确定,提示"同步失败"造成误解。
根因:互斥锁抛的是普通 Error,三个调用点统一按"失败"提示。
修法:
- SyncEngine 新增 SyncBusyError 专用类型 + isSyncing/isAnySyncing 查询;
  锁命中时抛 SyncBusyError,且 catch 不再把它包成普通 Error
- Index.syncSingle / AccountsPage.syncSingleAccount:命中→提示
  "有同步任务正在进行中,请耐心等待",不显示失败
- Index.syncAll:命中记为 busySkipped(不算失败),全部被跳过时也只提示等待
2026-10-09 22:19:31 +08:00
laoyang 32162b8f04 修复折叠屏合盖旋转不再切换布局的回归
上一版为满足"接近1:1屏幕横竖屏布局一致",把宽屏判据改成纯短边≥840vp,
导致长条形屏幕(普通手机、折叠屏合上后的外屏)旋转横屏也不出双栏。
现在两条判据满足其一即双栏:
① 短边≥840vp(展开态/平板)——旋转不变,维持商店合规要求;
② 横屏且宽高比≥1.5(长条形屏幕)——旋转正常切换,恢复 0.0.2 行为。
判据实现由 mediaquery 改为 windowSizeChange(px→vp),旋转/折叠展开均实时生效,
并记录窗口尺寸日志便于真机排查。
2026-09-29 09:09:06 +08:00
laoyang 2f4657ced3 上架自检两项整改:横竖屏布局一致性 + 页面转场改为淡入淡出(弹簧曲线)
一、横竖屏(意见 1:折叠屏展开态 / 平板需支持横竖屏切换;接近 1:1 的设备横竖屏布局需保持一致)
- 主界面布局判据由"是否横屏 (orientation: landscape)"改为"窗口短边是否达到大屏断点 840vp":
  min(宽,高) >= 840vp 等价于 (width >= 840vp) && (height >= 840vp),用两条 mediaquery 合成。
- 短边在旋转时不变(宽高互换),因此横竖屏布局天然保持一致 —— 屏幕比例接近 1:1 的设备
  (折叠屏展开态、平板)在旋转时不会再出现"单栏 ⇄ 双栏"的布局跳变。
- 不锁死屏幕方向(module.json5 仍为 auto_rotation_restricted),折叠屏展开态与平板
  依旧可以自由横竖屏切换显示。

二、转场动效(意见 2:使用系统转场,淡入淡出,曲线优先弹簧曲线)
- 全部 7 个 @Entry 页面新增 pageTransition():只做 opacity 淡入淡出,
  不调用 .slide() / .translate(),即不做左右平移 / 上下位移 / 单帧直接切换。
- 转场曲线使用 curves.springMotion() 弹簧曲线。
2026-09-28 14:20:14 +08:00
laoyang 736a0a6587 新增系统日历镜像功能,镜像与备份目标统一改用 bookId;修复重复日程覆盖行、备份目标随勾选漂移等一批问题
- 新增 SystemCalendarMirror / MirrorSnapshot / MirrorInbound:把选定的 CalDAV 日历本镜像写入系统日历(供小艺、桌面日历卡片、手表可见),并做入站对账,区分"自己写入的 / 用户改的 / 用户删的 / 用户新建的",镜像标识一律用稳定的 bookId(accId-href哈希),不再用会随勾选顺序漂移的 calKey
- 后台同步接入镜像:同步前先做入站对账、同步后自动出站镜像,无需用户手动触发
- 系统日历备份目标改用 bookId:当用户取消同步某个日历本导致目标失效时,自动清空目标并把模式回退为"仅显示";开启备份必须先选定目标本,不再自动挑第一个本
- EventDb:覆盖行保留 RECURRENCE-ID,排除主事件对应发生改用 overrideKey(calKey, uid, recurrenceId, startTime),修复重复日程出现"幽灵条目"的问题
- 新增 WRITE_CALENDAR 权限声明及其用途说明;添加日历不再出现"本机",日历本对齐显示;版本升至 0.0.3(300)
2026-09-19 23:07:03 +08:00
laoyang 69a4a86a04 更改了周视图颜色的对比度,现在看起来更舒服了。
Signed-off-by: laoyang <laoyang@example.com>
2026-09-16 22:05:27 +08:00
laoyang d7c49db304 增加修复了深色模式,完全沉浸的深色模式完成了。
Signed-off-by: laoyang <laoyang@example.com>
2026-09-16 21:40:11 +08:00
laoyang 353473127a 应用在首次打开或运行中,在用户未主动点击权限对应的相关功能或服务时,提前向用户弹窗申请开启【读取全部日程】权限,不符合相关法律法规要求。该问题已被修复。
Signed-off-by: laoyang <laoyang@example.com>
2026-09-16 20:54:41 +08:00
laoyang 76d82886e4 修复了进入日历没有日程卡片的bug。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-16 12:34:39 +08:00
laoyang cc7523a8ec 修复了从卡片进入日历无法定位当前日期的bug。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-16 11:38:01 +08:00
laoyang 2173b06b43 新增了列表显示模式,用户可以在设置中进行选择。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-16 10:07:57 +08:00
laoyang 7cf9203ec3 修改了用户协议等打开方式。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-15 15:07:42 +08:00
laoyang 5eaeeb0f4c 增强了安全配置。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-15 12:51:58 +08:00
laoyang 088f7b3773 增加了用户隐私协议等,增加了引导卡片。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-15 09:06:15 +08:00
laoyang 64a58170ac 更改了几处交互逻辑。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-14 21:53:15 +08:00
laoyang c04a9a28cf 更改了日程的样式显示,现在按照时间轴显示。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-14 21:28:51 +08:00
laoyang fa152415cd 为日程列表增加了一条当前时间的横线,用来表示当前进度。
Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-14 10:18:31 +08:00
laoyang 3c9b4dcfdd 1.修改了2x2的卡片样式。
2.为卡片增加了添加按钮,可以通过卡片直接添加日程。

Signed-off-by: Yang Yongquan <i@yangyq.net>
2026-09-14 09:07:47 +08:00
laoyang d1252ed2b7 1.修改了设置页面的展示顺序。
2.在设置页面新增了软件特性和使用帮助。
3.设置页面中,静音和只读合并。
2026-09-14 08:24:36 +08:00
laoyang 5b6be08d9a 完成了用户账户加密,更改了只读日程的显示样式;日程中的地址,可以通过高德地图导航了。 2026-09-14 07:55:52 +08:00
laoyang 6899a5074b 修改了通知相关的功能,能够正常通知了。 2026-09-13 23:35:52 +08:00
laoyang 0fef40c7f3 修复了部分日程没有提醒时间的问题。 2026-09-13 21:50:28 +08:00
laoyang 04fdf3d386 1.增加了重力感应,也就是横屏响应式布局。
2.修改了沉浸式布局。
3.修改了添加日程和编辑日程页面,增加了重复、提醒等功能。
4.修改了权限问题,如果日历本是只读,则有删除线做标识。同时,对于只读的日程,点击后将不再进入编辑页面,而是展示详情。
5.系统日历的处理。给用户两个选择,第一个选择就是只显示系统日历。第二种选择,用户可以选择一个caldav账户中的某一个日历本,把系统日历中的日程,包括日历日程和应用创建的日程,都读取出来,然后加入到这个日历本下,最后同步到caldav的服务器上,这样的好处是,手机丢失了,或者换了手机品牌型号,手机上的日程仍然在自己的caldav服务器上有一个备份。当然,系统日历中的caldav日历,就不会再读取了。
2026-09-13 20:25:18 +08:00
laoyang 61bd6fc75f 修复了重复日历第一次不显示的问题。 2026-09-13 17:48:39 +08:00
laoyang e0c6aa46d0 删除了日视图,感觉跟列表视图差不多,有点鸡肋。周视图改为和月视图一样,左右滑动进行切换。 2026-09-13 16:44:33 +08:00