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
|
1cc35f147d
|
fix(新建日程): 非今天默认 09:00,今天仍用当前时刻 #4
应用市场反馈:「新建非今天的日程时,默认时间固定在当天 9 点更好」。
根因:原来日期取所选日,但**时分固定取 now.getHours()**,
与所选日期无关。所以下午三点建明天的日程会得到「明天 15:00」。
改为按日期分支:
· 今天 → 当前时刻(今天 09:00 往往已过,「现在就记一笔」才是常态)
· 非今天 → 09:00(现在几点对别的日子没有参考价值)
顺带统一:toggleAllDay 关闭全天时原来硬编码 9 点,改为引用
DEFAULT_START_HOUR 常量,两处口径一致。
|
2026-11-02 14:48:28 +08:00 |
|
laoyang
|
199b6c177f
|
feat(清理): 卸载残留治理 —— 显式清除入口 + 孤儿兜底加强 #3
鸿蒙没有「应用即将被卸载」的回调(网传的 preUninstallHook /
uninstallDataPolicy 在本机 SDK 中均不存在),而系统日历数据属于
沙箱外,卸载时不会被系统清理 ⇒ 用户卸载后会永久残留镜像账户
(还会继续提醒)。本次提供显式入口 + 状态兜底。
1. AccountStore.isEmptyVaultConfirmed()
区分「账号库真的空」与「loadAll 吞异常返回空」。
依据:loadAll 内部把所有异常 catch 后返回 [],调用方无法从返回值
辨别,而孤儿清理一旦误判就会把用户真实在用的镜像全删掉。
2. SystemCalendarMirror.purgeOrphanAccountMirrors()
原来 accounts.length === 0 一律跳过(安全阀),导致卸载重装后
旧镜像永远清不掉。改为三态:
① 有账号 → 只清 accId 不匹配的(原逻辑)
② 空 + 确认空白 → 全清(卸载/重置后的残留)
③ 空 + 拿不准 → 仍跳过(读异常,不能误删)
全清模式下不再因「解析不出 accId」跳过脏账户,否则清不干净。
3. SettingsPage 新增独立卡片「清除系统日历数据」
- 「彻底清除系统日历镜像数据」:二次确认后 removeAll,
并关闭镜像开关(否则下一轮同步立刻重建,用户以为没清掉),
镜像本消失后把提醒交回 App(避免漏提醒)
- 零结果提示区分「没权限」与「本来就没有」,用户要的是确认清干净
- 卡片放在镜像开关之外:关着镜像时也能清残留(正是典型场景)
- 原「清理残留镜像账户(诊断)」一并从镜像卡片内移入本卡
只删系统日历镜像;CalDAV 账号与服务器日程完全不动。
|
2026-10-30 13:34:09 +08:00 |
|
laoyang
|
63b1a75b03
|
docs: README 更新至 0.1.0,补齐 0.0.7~0.1.0 缺失内容
README 的版本信息此前停在 0.0.6(versionCode 600),已滞后 4 个版本。
- 版本行:0.0.6/600 -> 0.1.0/10000
- 补「地点搜索补全」「一键导航」「必填项校验强提醒」(0.1.0)
- 新增「CalDAV 地址自动发现」小节(0.0.7:.well-known 发现 + 人话错误提示)
- 隐私段落:明确「不申请定位权限」,补充华为地图服务说明与
AES-256-GCM / HUKS 凭据加密细节
|
2026-10-28 23:18:48 +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
|
b7bc4b5403
|
chore: 版本号 0.0.9 -> 0.1.0(versionCode 10000)
0.0.9 已上架,开启 0.1.0 开发周期。
versionCode 按 major*1e6 + minor*1e4 + patch*100 计 = 10000。
同步更新官网 deploy/site:index.html / sf.html / uh.html 的版本标识,
_404.tpl.html 改后重跑 _gen404.py 重新生成 404.html。
|
2026-10-21 14:12:52 +08:00 |
|
laoyang
|
96335728f5
|
feat(编辑页): 必填项校验失败改为强提醒(Toast + 字段标红 + 滚回定位)
原来校验失败只写 statusMsg,而它渲染在表单最底部("删除日程"按钮上方)。
用户在顶部点"保存"时那行红字远在屏幕之外,观感就是"点了没反应"。
新增 showFormError(msg, field) 作为唯一错误出口,同时做三件事:
- Toast:系统级浮层,用 bottom:'70%' 抬到屏幕偏上方,不受滚动位置影响,必定可见;
- 字段标红:标题输入框 / 时间卡片 / 日历本区域加 error 色边框,并在紧邻位置给出红字说明;
- 滚回出错位置:scrollEdge(Top) + 对标题 requestFocus('titleInput')。
配套:
- 标题 placeholder 改为「标题(必填)」,静态说明必填属性;
- 用户开始输入标题 / 选中日历本后自动撤掉红框与状态行,不会"改了还一直红着";
- 日历本未选也走 showFormError(此前只写状态行);
- 底部状态行仅在 errorField==='' 时显示,避免与就地提示重复;
- 保存异常分支补 Toast:该分支紧接着 router.back(),状态行来不及被看到。
|
2026-10-21 10:17:46 +08:00 |
|
laoyang
|
a32c049199
|
fix(地点): 候选改为 bindPopup 浮层,根治输入框跳动与选完复现
旧方案把候选做成内联列表,虽放在输入框上方,但:
1. 候选高度变化(搜索中 / 出结果 / 条数变化)会改变表单高度,输入框随之反复移动,
再靠 scrollBy 事后纠正,表现为输入框来回跳好几次;
2. 选中候选后 applyPlace 程序回写 text 会触发 TextInput.onChange,
把 placePicked 重置为 false 并重新发起搜索,导致候选框消失后又冒出来一次。
改为:
- 候选用 .bindPopup($$placeOpen, { placement: Top }) 挂在输入框上,
不参与布局,出现与消失完全不移动输入框;
- 移除全部 scrollBy 对齐逻辑(keyboardPadVp / ensurePlaceVisible / 键盘监听),
键盘避让交由 KeyboardAvoidMode.RESIZE 的默认光标可见行为;
- 用 lastAppliedText 精确识别「程序回写 text 造成的 onChange」,不再发搜索;
- runSuggest 期间浮层持续展开(搜索中 → 结果),避免收起再弹造成闪烁;
- 抽出 closePlacePopup() 作为统一收起入口。
|
2026-10-21 09:03:31 +08:00 |
|
laoyang
|
6ab7bcfb76
|
fix(地点): 修掉 px/vp 单位混用的对齐 bug,输入框稳定钉在键盘上方,候选向上生长
- ensurePlaceVisible: getRectangleById 返回 px,scrollBy 收 vp,必须换算(上一版输入框被推飞的真因)
- 去掉 keyboardPadVp 动态底部留白:数学上不需要,只会在手动滚到底时露出大块空白
- 聚焦期间候选条数变化(加载中/有结果/无结果)每次都重新钉一次,避免输入框上下跳
- 提交请求前先收起旧候选,统一由一次对齐收口
- 新增 onBlur:失焦即收起候选,不留残影
|
2026-10-21 08:52:13 +08:00 |
|
laoyang
|
8e0c08ccfb
|
修复:地点候选列表被软键盘挡住
现象:地点输入框在表单第 7 位(标题/全天/时间/重复/提醒/日历本之后),
键盘弹起后候选列表正好落在键盘后面,看不到也点不到。
根因:ArkUI 默认 KeyboardAvoidMode.OFFSET 只保证"光标可见",
它不会为输入框下方新插入的候选列表预留空间;页面也不主动滚动。
修法(三层):
1) setKeyboardAvoidMode(RESIZE):把页面高度压到键盘上沿,
Scroll 可视区随之变矮(OFFSET 是整页上移,会把表单底部顶出屏幕)。
2) 主动滚动 ensurePlaceVisible():用 getComponentUtils().getRectangleById()
实测「输入框行顶边 + 行高 + 间距 + 候选列表高度」得到整块底边,
与「窗口高度 - 键盘高度」比较,差多少就 scrollBy 多少。
不直接 scrollEdge(Bottom):那会把地点字段顶出屏幕顶部。
3) 兜底:avoidAreaChange 可能还没回调(先弹键盘、后展开候选),
此时按窗口高度 42% 保守估算键盘高度 —— 多滚一点只是页面上移,无害。
顺带:
- 候选列表 Column → List + constraintSize(maxHeight: 216),
6 条候选内部可滚,不再占掉大半个屏幕;24px 行高分隔线改用 List.divider。
- 上限 8 条 → 6 条。
⚠️ 踩到的 API 坑(本 SDK):
- KeyboardAvoidMode 必须从 @kit.ArkUI 导入(不是全局);
- UIAbilityContext **没有** window 属性,要用 window.getLastWindow(ctx);
- getRectangleById 在 ui.getComponentUtils() 上,**不在** UIContext 上;
- avoidAreaChange 回调参数是 AvoidAreaOptions(含 type 字段),
不是老的 AvoidArea,需先判 type === TYPE_KEYBOARD。
验证:assembleHap BUILD SUCCESSFUL;已 install -r 真机。
|
2026-10-21 08:26:16 +08:00 |
|
laoyang
|
8be539d8a3
|
新增:地点输入补全(鸿蒙 Map Kit / Site Kit)
需求:新建/修改日程时,地点输入几个字即可用地图 API 补全详细地址。
选型:鸿蒙系统内置 Map Kit,**不引入高德**。理由(详见
Caldav开发/地点输入补全-地图API选型.md):
1) 配额:高德「输入提示」个人认证仅 100 次/日、企业 1000 次/日(官方 FAQ),
是全 App 共用额度。按 350ms 防抖、单次输入 4~8 请求算,个人额度约等于
15~25 个用户输入一次地点;企业额度一天也烧完。华为侧「POI 搜索/地理编码
当前不收费」,QPS 3000(官方论坛口径,HarmonyOS 5.0+),量级差 6 个数量级。
2) 合规:高德 Web 服务 Key 必须配 IP 白名单、不得出现在客户端;App 直连必然
被反编译提取 → 额度被盗刷、账单算自己、Key 可能被冻结。唯一安全做法是自建
服务端代理,会把「App 本体独立好用」的产品定位破坏掉。
3) 商用:高德个人认证仅限"研发/测试/个人兴趣",上架商店属企业认证场景。
4) Site Kit 全部接口**无需任何权限**(d.ts 无 @permission),完全不需要
申请 LOCATION,契合"权限只在用户主动点该功能时申请"的硬约束。
实现:
- 新增 common/PlaceSuggest.ets(纯服务类):
query()=queryAutoComplete、search()=searchByText(补全失败兜底)、
pickOnMap()=sceneMap.chooseLocation(系统地图选点)、isUnavailable() 供界面降级。
· 请求序号 seq 丢弃过期结果(边打字边搜时后发先至);
· 异常一律返回空数组、绝不抛给调用方 —— 补全坏了日程照样能存;
· 1002600004/1002600003 判定"能力未开通"→ 置 unavailable,只提示一次,
之后不再反复打网络。
- EventEditPage:地点行改为 TextInput + 「地图」按钮 + 内联候选列表。
· 输入停止 350ms 防抖;回车立即搜一次;
· 候选**内联展开**,不新增 bindSheet(硬约束:单页只能一个 bindSheet,
本页已用于时间/重复选择);
· 选中后写入完整地址(formatAddress) 并收起,placePicked 防止再次自动弹出;
· aboutToDisappear 清防抖定时器。
- LOCATION 的 ICS 转义无需改动:IcsUtil.escape() 已处理 , ; \ 换行,
地图地址里的逗号不会破坏格式。
验证:assembleHap BUILD SUCCESSFUL;已 install -r 真机。
⚠️ 功能待 AGC 开通「地图服务」+ 重签 Profile 后才生效(否则抛 1002600004);
未开通时界面显示「地图服务不可用,可直接手动输入地点」,不影响其他功能。
|
2026-10-21 07:59:21 +08:00 |
|
laoyang
|
29898d5dff
|
修复:改了日历本颜色,系统日历镜像颜色不跟变
根因:SyncEngine.refreshCalendarColors 每轮同步都 PROPFIND 服务器把新
calendar-color 写进**内存**里的账号对象,但从不 saveAll(唯一落盘点是
removeDeadBooks 的"服务器删本"分支)。而镜像 listBooks 是重新
AccountStore.loadAll 从磁盘读 → 拿到的永远是旧颜色 → ensureCalendar
setConfig(旧色) → 系统日历颜色永远不变。
用户在「账号设置」页看到的是**当场 PROPFIND** 的新颜色,所以表现为
"设置页颜色对、系统日历颜色错"。
修法:
1) refreshCalendarColors 返回 boolean(颜色/写权限是否有实际变化,
含数组长度补齐);syncAccountInner 在有变化时立刻 loadAll+saveAll
落盘(同 removeDeadBooks 的既有模式),镜像同轮即可读到新色;
2) SystemCalendarMirror.ensureCalendar 的 setConfig 失败由静默改为
写日志(新建/复用两处),"颜色不变"从此可排查。
验证:assembleHap BUILD SUCCESSFUL;已 install -r 到真机。
|
2026-10-21 07:11:23 +08:00 |
|
laoyang
|
c711cdb7d5
|
版本号 0.0.8 -> 0.0.9(versionCode 900)
|
2026-10-20 22:34:12 +08:00 |
|
laoyang
|
2f124eada0
|
镜像到系统日历:新增「全选/全不选」日历本
设置页镜像勾选网格上方加一行「全选(已选 x/N)」:
· 整行可点;勾选框半选时显示为关,点按 = 全选,再次点按 = 全不选;
· 全选:一次性把全部候选本(已排除"系统日历备份目标本"防回灌)写入
mirror_book_ids —— preferences 只落盘一次、提醒缓存只失效一次,
与逐个 toggle 的关键区别;
· 全不选:逐本 removeById 删对应系统日历账户(与取消单个勾选同语义),
逐个独立 try,失败不影响其余;
· onChange 加同值守卫,过滤系统重复回调导致的二次翻转。
验证:assembleHap BUILD SUCCESSFUL;已 install -r 到真机。
|
2026-10-20 22:31:29 +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
|
24d8117ce7
|
v51c 卡片后台同步:修复"每轮全量重拉",单账号 236 秒 → 10 秒
真机实测根因:服务端 CalDav.php:253 用 preg_replace('/[^A-Za-z0-9._-]/','_',$uid)
派生资源文件名(UID movie-188@familybook → 资源 movie-188_familybook.ics),
而客户端 SyncEngine.syncAccountInner 拿 REPORT 给的**资源名当 uid** 去本地行里查
⇒ byUid 永远查不到 ⇒ 每轮判"变了" ⇒ 全量 GET。ETag 明明一样,比较压根执行不到。
改动:
· SyncEngine —— 加 byEtag 兜底(uid 查不到就用内容哈希认领本地行)+ 复用判定计数日志
(uid 命中 / etag 兜底命中 / 需补拉);抽 groupPush() 去重。
· EventDb.applyRemote —— existing.find() 的 O(n·m) → keyed.get() 的 O(n)。
· BackgroundSyncRunner —— runOnce 每阶段计时(+Xms)+ pid;预算可由 preferences
card_budget_ms 覆盖(诊断开关,默认走内置 9 秒)。
· AppSettings —— getCardBudgetMs()。
· EntryFormAbility —— onUpdateForm 日志带 pid。
· MirrorInbound —— 顺带修一个已死的功能:getEvents(undefined, 字段) 在 7.0.0.109 上
必抛 401 type error!,导致**入站对账 12/12 全失败**;改成"先无参、失败再带字段"
(照抄 SystemCalendarMirror.queryEvents 的绕法)。
真机验证(DEL-AL20,卡片 onUpdateForm 触发,卡片进程 pid=27411):
[21:33:06] 卡片驱动同步:开始(可用窗口 9 秒)
[21:33:09] 「观影」复用判定:uid 命中 0、etag 兜底命中 193、需补拉 0(共 193)
[21:33:09] 「观影」落库完成:不变193,耗时 1 秒 ← 修复前 124 秒
[21:33:17] 同步账号完成:拉取 2182 条,耗时 10 秒 ← 修复前 215~236 秒
[21:33:22] 后台同步[cardTick] 完成:1/1 个账号,总耗时 16 秒
12 个日历本全部跑完 + 出站镜像,9 秒窗口不再"连第一个本都过不去"。
复算(真库快照 + 真服务器 PROPFIND,_tooling/_sc_etag_sim.py):
旧逻辑补拉 1384 个 → 新逻辑 80 个(降 94.2%);观影 193→0、旅行 23→0、工作 362→5。
另证服务端 ETag == md5(正文)、三次 PROPFIND 逐字节一致 ⇒ etag 兜底语义正确、非碰巧。
|
2026-10-15 21:39:54 +08:00 |
|
laoyang
|
8ae4e11507
|
修复同步后日程时间少 8 小时(服务器会删掉 DTSTART/DTEND 的 Z / TZID)
现象:客户端建的 10-16 08:00~17:00 同步到服务器后变成 00:00~09:00。
根因(2026-10-15 连服务器 family.yangyq.net 实测确认,不是 App 的解析 bug):
服务器会**直接删掉** DTSTART/DTEND 上的 `Z` 与 `TZID` 参数,**且不对数值做任何
时移**:
PUT DTSTART:20261020T060000Z -> 存回 DTSTART:20261020T060000
PUT DTSTART;TZID=Asia/Shanghai:20261020T140000 -> 存回 DTSTART:20261020T140000
于是"UTC 00:00"被当成"本地 00:00"。而本地 08:00 的正确 UTC 恰好就是 00:00,
所以症状正好是阅读端少 8 小时。
(DTSTAMP/CREATED/LAST-MODIFIED 是 RFC 规定的 UTC-only 属性,服务器反而保留 Z。)
交叉验证:全账号 2190 条事件里,计时事件(1163 条)的 DTSTART **无一例外都是
浮动本地时间**——带 Z 的 0 条、带 TZID 的 0 条。即该服务器的事实约定就是
"所有时间都是本地墙钟"。第三方客户端写的事件同样是浮动 DTSTART,且
DTSTART 浮动 + `UNTIL=...`(浮动)成对出现(例:研究生例会)。
改法:事件时间一律写**浮动本地时间**,与服务器约定对齐。
- IcsUtil.vevent:DTSTART/DTEND/RECURRENCE-ID/EXDATE 由 fmtUtc 改为 fmtLocal
(本地 getter、不带 Z)
- IcsUtil.fmtIcsTime:改为浮动本地(EXDATE / RECURRENCE-ID / UNTIL 共用)
- IcsUtil.rruleWithUntil:UNTIL 改为浮动本地
(RFC 5545:DTSTART 是本地时间时 UNTIL 必须也是本地时间,二者必须同形态)
- DTSTAMP 仍写 UTC+Z(RFC 规定),fmtUtcNow 不变
- 读取侧无需改动:parseTime / RruleUtil.parseUntil 已把"无 Z"按本地解析,
正好匹配浮动语义,往返一致
端到端验证(真服务器):
新写法 DTSTART:20261016T080000 -> 服务器原样存回 -> 读回仍为本地 08:00 ✅
数据修复:服务器上唯一一条受影响的日程(工作 / 项目评审 /
UID syncal-1792039765773-100047)已由 00:00~09:00 改回 08:00~17:00。
全账号仅此一条是本 App 创建的计时事件。
|
2026-10-15 18:45:51 +08:00 |
|
laoyang
|
c4dc27ef3a
|
0.0.8:卡片整卡可点 —— 点空白处也能进 App 并定位到当天
用户反馈:卡片的空白区域点了没反应,只有落在日程/文字上才跳转。
期望:点卡片**任何位置**都进入 App 并**定位到卡片当前显示的那一天**。
实现(4 张卡片:2x2 / 2x4 / 4x4 / 6x4 一致处理):
1. 根容器加 `.onClick(() => this.openApp())` → **整卡可点**(含留白);
2. `openApp()` 携带 `openDate` 深链参数,值为卡片数据里的 `selectedDate`
(该字段早已在 CardData 中维护:CardDataService.ets:194 = 卡片当前显示日 0 点毫秒);
3. **传参必须转字符串**:`params: { openDate: `${this.selectedDate}` }`。
⚠️ 依据 4x4 原有注释:param 的数值按 **int32** 传递,13 位毫秒时间戳会溢出成
2147483647(曾导致进 App 后定位到 1970)。2x2/4x2 原本传的是 number → 已修正为字符串。
4. **移除内部原有的 openApp 点击**(共 13 处:4x2 四、4x4 四、6x4 五),
避免"子区域 + 根容器"双重触发同一动作。
关于事件冒泡(重要,避免误改):
- ArkUI 的 `onClick` 是**非冒泡**事件,父子组件同绑时**子组件优先**,同一时刻只有一个响应
(华为官方论坛结论)。因此:
· 根容器整卡可点**不会**抢走内部 ‹ › 今 + 按钮的点击;
· **无需** `stopPropagation`(ClickEvent 上也**没有**这个方法——它只在
MouseEvent / TouchEvent 上,实测编译报错已验证)。
- 内部原有的 4x4/6x4 注释写"兄弟节点布局不冒泡"是对的;本次统一补上说明。
验证:
- 编译通过(BUILD SUCCESSFUL)。
- 四卡一致性核验:每卡 openApp 点击点=1(仅根容器)、根容器=1、selectedDate 声明=1、
openDate 字符串传参=1。
- 结构完整性:花括号配平(26/26、53/53、228/228、228/228),编译无语法错误。
⚠️ 未验证(待真机):实际点击留白是否跳转、是否正确定位到所显示的那一天;
以及点 ‹ › 今 + 是否**不会**连带打开 App(依赖上述"子组件优先"结论,需实测确认)。
|
2026-10-14 22:45:51 +08:00 |
|
laoyang
|
bd3afb7bd1
|
0.0.8:卡片同步尝试用短时任务延长窗口(10 秒 → 最多 60 秒)
背景:上一步实现了"卡片 30 分钟刷新驱动同步",但受 FormExtensionAbility
"回调后进程仅存续 10 秒"的硬限制,同步可能被回收掐断。
用户建议:能不能调一个后台进程、或让异步同步在 10 秒后继续跑一会儿?
调研结论(已核实官方文档):
1. ❌ ServiceExtensionAbility(独立后台服务)—— 三方应用**根本不能用**:
官方"仅系统应用可使用",需 Full SDK + AllowAppUsePrivilegeExtension 特权 + 设备白名单。
2. ❌ 从卡片进程 startAbility 拉起主应用 —— 会被拦:官方《组件启动规则》
"位于后台的 UIAbility 应用,启动组件需校验 ohos.permission.START_ABILITIES_FROM_BACKGROUND,
**该权限仅系统应用可申请**"。
(卡片 postCardAction 的 `call` 事件确实能"后台拉起 UIAbility 且不弹界面",
但**只能由用户点击卡片触发**,系统定时刷新的 onUpdateForm 用不上。)
3. ✅ **短时任务 `requestSuspendDelay`** —— 可行路径:让进程在后台多活一会儿,
单次最长 3 分钟(低电量 1 分钟),实际时长以 actualDelayTime 为准。
本次改动(BackgroundSyncRunner):
- 新增 `acquireCardWindow()`:尝试申请短时任务,
· **容错尝试**:官方规定应在"应用在前台"或"onBackground 回调"里调用,
卡片进程是否符合**未知** → 抛错 / 时长为 0 时**优雅退回 9 秒软超时**,不阻塞流程;
· 留 **15 秒余量**提前收手(保证能干净地 cancelSuspendDelay,
否则超时不取消会被系统强制终止进程并浪费配额);
· 加 **60 秒/次总上限**(CARD_MAX_TOTAL_MS)。
- 新增 `releaseCardWindow()`:无论成败都在 finally 里释放,归还配额。
- `runOnceThrottledByCard()` 改为按"实际可用窗口"设软超时,并记录窗口秒数到日志。
⚠️ 配额账(关键取舍):
短时任务**单日总配额仅 10 分钟**,且**只在被挂起后计时**(10 秒内跑完不计费)。
· 正常网络下增量同步几秒完成 → 多落在 10 秒窗口内 → 消耗极少;
· 网络慢才会吃配额;吃满 10 分钟后自动降级为 9 秒软超时,不报错;
· ⚠️ **不能吃光配额**:EntryAbility.onBackground 也申请短时任务(兜"退后台那一次同步"),
故卡片侧设 60 秒/次上限 + 15 秒提前释放,避免把它饿死。
验证:编译通过(BUILD SUCCESSFUL);窗口预算逻辑离线用例通过
(申请被拒/时长为0 → 9 秒;低电量 → 45 秒;正常 3 分钟 → 被 60 秒上限截断;40 秒 → 25 秒)。
|
2026-10-14 16:51:56 +08:00 |
|
laoyang
|
3025f92cb6
|
0.0.8:借服务卡片 30 分钟刷新驱动后台同步
用户反馈"基本不会定期同步,每次都要手动点进去才同步"。
诊断发现主因是「后台自动同步」默认关闭(workScheduler 2 小时兜底根本没注册),
且系统对普通应用后台频率有硬管控(活跃分组下限 2 小时)。
本提交新增一条**免权限、约 30 分钟一次**的定期同步路径:
利用服务卡片已有的定时刷新(form_config.json 的 updateDuration=1,**单位 30 分钟**,
系统每 30 分钟调用一次 onUpdateForm),在该回调里顺带跑一次后台同步。
改动:
1. AppSettings 新增 `card_sync_at`(上次卡片驱动同步时间戳,用于节流)。
2. BackgroundSyncRunner 新增 `runOnceThrottledByCard(context)`:
· 节流 CARD_MIN_GAP_MS = 25 分钟(略小于 30 分钟卡片周期,避免系统刷新漂移被误跳过);
· **先写时间戳再执行** → 防止同步期间卡片再次刷新导致并发重入;
· 复用现有 `runOnce(context,'cardTick')`(完整同步:入站对账→推送→拉取→镜像→刷新卡片与提醒);
· **永不抛错**(后台路径没有 UI 承担异常)。
3. EntryFormAbility.onUpdateForm:
· ① 先用**本地缓存**立刻刷卡片(毫秒级,不依赖网络);
· ② 再跑节流同步,完成后二次推卡片数据,让卡片显示刚同步回来的结果。
⚠️ 关键约束(官方 FormExtensionAbility 生命周期原文):
"FormExtensionAbility 进程不能常驻后台,生命周期回调中无法处理长时间的任务,
生命周期调度完成后会继续存在 **10 秒**……可能需要 10 秒以上才能完成的业务逻辑,
建议拉起主应用进行处理。"
⇒ 同步**可能被进程回收中断**,故:
· 设 **9 秒软超时**主动收手(干净放弃而非被硬杀在半途);
· 语义明确为"**尽力而为地提高后台同步频率**",不承诺 30 分钟必然成功;
· 中断安全:同步幂等(etag 增量比对 + dirty 标记),中断不损坏数据,
下个周期会重试;正常网络下增量同步通常数秒内完成,落在窗口内;
· **不采用"拉起主应用同步"** —— 那会每 30 分钟弹出一次界面,体验不可接受。
配额核算:卡片定时刷新官方上限为**每张卡片每天 50 次**;30 分钟一次 = 48 次/天,
**不超配额**。相比 workScheduler(受活跃分组压制到 ≥2 小时),频率提升约 4 倍。
验证:编译通过(BUILD SUCCESSFUL);节流逻辑离线用例通过
(从未同步/30 分钟/26 分钟/正好 25 分钟 → 执行;24/5/1 分钟 → 跳过)。
|
2026-10-14 16:42:04 +08:00 |
|
laoyang
|
d890a2b053
|
0.0.8 提醒修复(补):已镜像的本由系统日历全权负责,且判据改为"镜像真实存在"
用户要求:如果用户已把自己的日历同步到系统日历里,那么本应用就**完全不再提醒**了
—— 因为那必然是重复提醒。
采纳。同时修正上一版实现的一个**会漏提醒的弱点**:
问题:上一版按"**用户勾选了要镜像的本**"来跳过 App 提醒。但"勾选"只是**意图**,
本可能还没真正写进系统日历(如刚勾选未点「立即执行镜像」、系统日历里还没有对应账户)
→ 按意图跳过会导致**两边都不提醒(漏提醒)**。
改法:判据改为「**镜像账户真实存在**」,新增 `SystemCalendarMirror.mirroredCalKeys(context)`:
1. 镜像开关未开 / 无系统日历权限 → 直接返回**空集**(不跳过任何本);
2. 读系统日历全部账户,取出 name 以 `synccalendar.mirror.` 开头者的 **bookId**;
3. 与 `listBooks()` 的 `bookId → calKey` 对照 → 只有**账户真实存在**的本才计入。
安全阀:任何异常 → 空集 → 不跳过 → **宁可多提醒,绝不漏提醒**。
`ReminderService.mirroredKeys()` 改为委托给它(保留 30 秒缓存与 `invalidateMirrorCache()`);
`refreshReminders` / `tickReminders` 两处仍照旧跳过这些本(跳过即"App 完全不再提醒")。
交互完整性(不变):镜像开关开启/关闭、勾选本变化、权限被回收 四处
均 `invalidateMirrorCache()` + 立即 `refreshReminders()`,
确保**关闭镜像后这些本立刻恢复由 App 提醒**。
验证:编译通过(BUILD SUCCESSFUL);手机未连接 → 未装机。
|
2026-10-14 14:28:42 +08:00 |
|
laoyang
|
19cfe3cf34
|
0.0.8 修复:系统日历重复提醒 / 已过日程仍提醒
用户反馈:① 日程已开始/已过去,系统日历仍提醒;② 同一提前量(如提前 15 分钟)
提醒两次;③ 怀疑"打开 App 后又写一次系统日历"就多提醒一次。
诊断见《系统日历重复提醒-根因诊断.md》。用户怀疑正确,确为两个 bug。
修复一:消除双通道重复提醒(提醒两次)
- 根因:镜像到系统日历后,**系统日历自己会提醒**(条目 reminderTime),
而 ReminderService 为**所有**本地日程排代理提醒、**完全不区分**该本是否已镜像
→ 同一条日程、同一提前量被两个应用各弹一次。
- 改法:新增 `ReminderService.mirroredKeys()` —— 取"已镜像到系统日历"的日历本 calKey
集合(仅在「镜像开关已开 + 有系统日历权限 + 有选中本」时才非空),
`refreshReminders` 与 `tickReminders` **两处都跳过**这些本,改由系统日历提醒。
- ⚠️ 保守设计:判定失败一律返回**空集**(不跳过任何本)→ 宁可多提醒,绝不漏提醒。
- ⚠️ 交互完整性:镜像开关变化时必须**立即失效缓存并重排提醒**(否则会漏提醒):
· 开启镜像 → 交回系统日历负责;· 关闭镜像 → **立刻恢复 App 提醒**;
· 勾选本变化、权限被系统回收 → 同样失效缓存并重排。
新增 `ReminderService.invalidateMirrorCache()`。
修复二:消除"已过去的日程仍提醒"
- 根因:镜像窗口含**过去 12 个月**(windowStart 往前推 12 月),过去的日程也在系统日历里;
而 `updateEvent` 是**整条覆写**(带 reminderTime),系统在覆写时会**重新计算提醒时刻**,
时刻已过就**立刻补发**一条 → 表现为"过去了还提醒"。
- 改法:`toSystemEvent` 写入时只带**提醒时刻尚未到**的提醒(新增 `futureReminders()`)。
已过期提醒已无意义 → 不写,从根上消除覆写补发。
⭐ 全天日程按系统口径以**当天 09:00** 为提醒基准(新增 `reminderBaseMs()`)。
- 刻意**不收缩镜像窗口**:保住历史日程在系统日历中的可见性,只去掉提醒。
修复三:让"改了提醒时间"能同步到系统日历(反向 bug)
- 根因:`diffOf` 只比较 title/isAllDay/start/end/description/location,
**不比较 reminderTime** → 用户改了提醒时间被判"无差异",永不触发 updateEvent
→ 系统日历侧提醒一直不更新。
- 改法:`diffOf` 纳入 `reminderTime` 比较,用规范化 key(排序去重)避免因顺序/重复误判,
且与修复二同口径(只比"会被写入的"提醒),不会导致每轮都重写。
验证:
- 编译通过(BUILD SUCCESSFUL)。
- 离线用例全部通过:
· 修复二:已过去/提醒刚过/昨天全天 → 不写提醒;未来日程/明天全天 → 保留;
· 修复三:提醒改了/新增 → 触发覆写;相同集合(顺序不同、含重复)→ 不覆写。
- 手机未连接 → 未装机(待用户插线测试)。
|
2026-10-14 14:00:58 +08:00 |
|
laoyang
|
a49e7d03b1
|
0.0.8:系统日历账户显示名可切换 + 清理判定改为 bookId 身份校验
背景:应用往系统日历写数据时,账户显示名固定为「同步日历 · <本名>」。多账号用户
无法看出某个日历本属于哪个账号;若把"应用名+账号名"都写上又太长,本名被挤压看不清。
方案见《镜像账户命名方案-去掉应用名前缀.md》,影响评估见《0.0.8升级影响评估.md》。
平台硬约束(已核实 @ohos.calendarManager):
**没有"修改账户"的 API**(只有 create/delete/get/getAll;CalendarConfig 仅
enableReminder+color;CalendarAccount.name 为 readonly)→ displayName 只能在
创建时定,改名必须"删账户 → 重建"。
实现:
1. **三种显示名格式**(分隔符统一半角点 `·` U+00B7、**无空格**):
1 = `同步日历·<本名>`
2 = `同步日历·<账号>·<本名>`
3 = `<账号>·<本名>` ★默认
⚠️ **不提供"仅本名"**:裸本名与用户原有同名日历本无法区分 → 有误删风险;
每个镜像账户名必须至少带一个归属标识(同步日历/账号名/二者)。
displayName 上限 64 字符 → 超长自动截断。
2. ⭐ **一键重命名** `MirrorRenamer.reapplyDisplayNames`,顺序**不可调换**:
① `MirrorInbound.reconcile()` 先回读(把用户在系统日历里手动新建/改动的
收进本地库并置 dirty)—— 因为 deleteCalendar 会删掉账户下**所有**日程,
含用户手动加的,不回读就会丢数据;回读失败则**中止**重命名(宁可不改)。
② `removeAccountsByBookIds` 删旧账户(按 bookId 精确定位,与名字无关;
含"清空日程后重试"降级)
③ `syncNow()` 按新格式重建并全量写回
独立成模块的原因:MirrorInbound 已 import SystemCalendarMirror,反向 import 会成环。
3. ⭐⭐ **清理判定改为「bookId 身份校验」**(`isOurMirrorAccount`)—— 用户指出的关键改进:
**两道闸**:① name 必须以 `synccalendar.mirror.` 开头(挡掉别人家的账户);
② 从 name 切出 `accId`(取最后一个 `-` 之前)并确认在账号库中。
⭐ **附加安全阀**:账号库读不到 → **一律不认为是我们的**(宁可不清,不可误删)。
`displayName` 兜底**降级为仅格式 1/2 可用**(格式 3 = `账号·本名` 与别的 App
账户可能同名,参与兜底会误删)。
⇒ **删不删看 accId 是不是我们的;名字只用来显示。** 判定与名字彻底解耦。
4. `AppSettings` 新增 `mirror_display_format`(1|2|3,默认 3;非法值回落 3)。
5. 设置页新增「系统日历中的显示名称」:三种格式胶囊选择 + **用真实账号名/本名实时预览**
+ 「应用」按钮(未变化时禁用、失败自动回滚选项)+ 检测到旧格式时的弱提示
+ 说明"期间日程会短暂消失几秒"。
6. `ensureCalendar` 按格式创建(已有账户**不在此处改名** —— 改名是破坏性操作,
必须由用户显式点「应用」触发);`syncBook` 增加 format 参数并逐层传递。
7. `describeCal` / `purgeOrphans` 参数语义更新(aggressive → allowDisplayMark)。
**不做自动迁移**(用户决定):升级后老账户名字不变、日程不动,用户想换才在设置页点。
理由:自动迁移期间日程会短暂消失,老用户可能正在开会/开车,看到"日程全没了"会惊慌;
且默认格式是主观偏好,强改所有人等于替用户做决定。
验证:
- 编译通过(BUILD SUCCESSFUL);包内版本 800/0.0.8。
- 离线用例覆盖:三种格式生成(确认半角点、无空格)、64 字符截断、
身份判定五种情形(含"别的 App 同名账户不被删""accId 失效的孤儿仍被清"
"读不到账号时一律不删""格式 3 兜底失效")—— **全部通过**。
- 手机未连接 → 未装机(待用户插线后安装测试)。
|
2026-10-14 10:09:48 +08:00 |
|
laoyang
|
ed26da8dc4
|
0.0.7:CalDAV 地址自动发现 + 人话错误提示 + 只读公开日历
背景:用户反馈(Nextcloud/内网)——DAVx5 用 CalDAV 地址能完整读取,换本应用
读不出日历;用"分享链接"绑定时提示服务器返回 405。
定位:不是协议问题,而是"填入的 URL 不是 CalDAV 端点"。
① 我们的发现=PROPFIND+Depth:1 只认 resourcetype 含 calendar 的集合;填 DAV 根
/remote.php/dav 只能看到 principals/calendars/addressbooks → "未发现日历本"。
② 分享链接(/apps/calendar/p/<token>、/s/<token>)是网页路由 → PROPFIND 405。
③ 全工程此前无 .well-known 自动发现(grep 无命中)→ 必须手填完整路径,
而 DAVx5 内置自动发现,故"DAVx5 能用、我们不能"。
实现(新增 common/DavDiscovery.ets):
1. .well-known/caldav 自动发现:207/200 直接用;301/302/307/308/401 读 Location
2. 智能补全:按 Nextcloud/ownCloud/Radicale/Baikal 等常见路径生成候选并逐个探测,
保留子目录前缀(填 https://h/nextcloud → /nextcloud/remote.php/dav/calendars/<u>/)
3. 分享链接识别:/apps/calendar/p/、/s/、/index.php/s/ 直接拦下并给改法
4. OPTIONS 能力探测:读 DAV/Allow 头,区分"不是 DAV 端点"与"服务端挡了方法"
5. 状态码人话化:405/401/403/404/跳转/网络失败各有可操作指引;
"未发现日历本"区分"地址差一层"(给出补全后的正确地址)与"确实为空"
6. 只读公开日历:识别 public-calendars 链接;只读由既有 calendarWritable 探测自动标记
接入三处:
- CalendarListPage.fetchCalendars / EditAccountPage:先 resolve() 定位端点,
命中则自动修正 serverUrl(并落库),失败则直接给人话原因
- AddAccountPage:连接前拦分享链接;probeServer 遇 405 用 OPTIONS 判定并给指引;
地址输入框提示同步更新
设计要点:发现顺序=原地址优先(保持既有行为、不破坏已能用的用户)→ .well-known
→ 常见候选;单个候选失败不抛错;ArkTS 静态方法内禁用 this → 统一用类名调用。
验证:编译通过;离线用例覆盖分类/完整性判定/候选路径(4 种输入形态)全部通过。
版本:AppScope/app.json5 → 0.0.7 / 700
文档:新增《上架更新说明-0.0.6到0.0.7.md》;官网 sf/uh 补功能说明与使用指引、
版本号与日期更新、404 重建,已部署并线上核验。
打包:assembleApp release → verify-app success、profile type=release、
app-distribution-type=app_gallery、包内版本 700/0.0.7。
|
2026-10-13 12:43:26 +08:00 |
|
laoyang
|
68329c57a0
|
docs: README 补充自行构建的签名配置说明,并修正过时描述
用户诉求:仓库为安全起见把含密钥/口令的构建配置排除在版本库外,
但他人克隆代码后需要自行补齐才能构建,应在说明文件里讲清:
补什么文件、什么格式、在哪个文件里填、这些信息怎么申请。
新增内容:
- README「📦 构建与运行」重写为四节:准备工程 / 首次构建补签名配置 /
编译运行 / 打上架包(.app)
- 「首次构建」一节详述:
· 需要的 4 个文件(.p12 密钥库 / .cer 证书 / .p7b Profile / 两个口令)
· 两条申请途径 —— 途径A DevEco「Automatically generate signature」自动生成
(真机调试用,只需实名认证的华为开发者账号);途径B 在 AGC 后台申请
发布证书与发布 Profile(上架用,含 keytool 生成 CSR 的说明)
· 填写位置与字段说明(signingConfigs[].material.* + products[0].signingConfig)
· 明确「根 build-profile.json5 已 gitignore、勿提交;entry/build-profile.json5
是模块级配置、必须保留」这一易混淆点
· 附完整可抄的 JSON5 示例
- 新增 build-profile.example.json5 模板(占位符形式,无任何真实密钥与个人路径),
用户可直接复制改名使用
- 打 .app 上架包章节补充 verify-app 需带 -inForm zip(否则误报 codesign block)
顺带修正 README 中已过时的描述(与上一轮代码改动保持一致):
- 版本号 0.0.3 → 0.0.6
- 「后台持续同步/长时任务 dataTransfer/常驻通知」→ 分层调度说明
(前台按间隔 + 后台定时唤起(最短约2小时) + 回前台/网络恢复即时补同步)
- 权限列表移除已删除的 KEEP_BACKGROUND_RUNNING
- 目录结构中后台同步模块改为 BackgroundSyncRunner/BackgroundSyncService/
NetworkSyncWatcher
安全:已扫描确认 README 与模板不含任何口令、密钥或本机绝对路径。
|
2026-10-11 21:39:11 +08:00 |
|
laoyang
|
2b29f197e8
|
后台同步重构:移除不合规长时任务,改为分层调度
背景:用户反馈"后台常驻其实没实现,过一段时间常驻通知就没了"。调研确认这是
系统强制管控、不是 bug:DATA_TRANSFER 长时任务有一致性校验,**超 10 分钟未更新
传输进度即被强制取消**。日历同步是"间歇跑、平时无进度",不符合该语义 → 必被取消,
且频繁触发校验失败可能被列入管控名单。另:workScheduler 请求 20 分钟低于系统
"应用活跃分组"下限(活跃≥2h),被静默压制 → 名义 20 分钟实际从未生效。
改为分层调度(目标:用户要看时数据一定最新,而非追求进程常驻):
1. **移除 dataTransfer 长时任务**:BackgroundSyncService 不再申请;
module.json5 去掉 backgroundModes 与 ohos.permission.KEEP_BACKGROUND_RUNNING
2. **延迟任务周期 20 分钟 → 2 小时**(对齐系统下限,避免被压制);保留 isRepeat/isPersisted
3. **新增 BackgroundSyncRunner**:后台同步唯一执行体(抽取自 SyncWorkAbility),
供延迟任务/网络恢复/回到前台三处共用,避免逻辑漂移
4. **新增 NetworkSyncWatcher**:net.connection 监听,**从无网恢复到有网时立即补同步**
(带 20s 去抖;仅在 netLost→netAvailable 时触发,单纯切网不重复同步)
5. **回到前台补同步**:UIAbility.onForeground 立即同步 + 取消短时任务归还配额
6. **退后台短时任务**:onBackground 用 requestSuspendDelay 兜住退后台那次同步;
回到前台 cancelSuspendDelay(配额仅 10 分钟/日,必须归还)
7. **设置页开关语义调整**:"后台持续同步(常驻通知)" → "后台自动同步",
文案如实说明"系统最短约 2 小时一次 + 回前台/网络恢复立即补同步";
开关现在真正控制延迟任务的注册/取消
8. **官网文档同步**:sf/uh 改写后台同步说明;upa **移除 KEEP_BACKGROUND_RUNNING 权限行**
(权限表必须与实际声明一致);已重建 404 并部署线上
注:applyEfficiencyResources 在当前 SDK 不可用,未采用。
|
2026-10-10 12:49:11 +08:00 |
|
laoyang
|
da0249d9f4
|
版本号升至 0.0.6(versionCode 600)
|
2026-10-10 08:30:12 +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
|
cd2401242c
|
修复:编辑页挂了两个 bindSheet 导致时间选择框弹不出来
症状(用户反馈,本轮改动引入的回归):编辑/新建日程时点开始或结束时间,
白框一闪而过、日期时间选择器不出现。
根因:ArkUI 同一组件**只支持一个 bindSheet**,后挂的会覆盖先挂的。
上一轮新增"重复日程范围选择"弹层时,在同一根容器上挂了第二个 bindSheet,
于是它把原有的"时间选择"弹层顶掉了 —— 表现正是白框一闪即消失。
修法:合并为**唯一**一个 bindSheet,用 sheetMode 区分内容:
- sheetMode='picker' → 日期/时间选择(高度 380)
- sheetMode='scope' → 重复日程范围选择(高度 320)
新增 bottomSheet() builder 按模式分发;原 pickerShow/scopeConfirmShow
两个状态合并为 sheetShow + sheetMode。
|
2026-10-09 22:41:17 +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
|
5c80c0b773
|
重复日程支持单次/后续/全部范围;时间轴刻度改为 8-18 固定
重复日程编辑与删除:
- 编辑或删除重复日程前先让用户选范围:仅这一次 / 此次及以后 / 全部
- 仅这一次:主事件加 EXDATE 排除该次,另插一条带 RECURRENCE-ID 的覆盖行
- 此次及以后:主事件 RRULE 加 UNTIL 截断,剩余部分拆成新 uid 的新系列
- 全部:删除/改写整个系列
- IcsUtil 新增 vevent / buildSeries / rruleWithUntil / fmtIcsTime:
同一个 .ics 资源内写主事件 + 全部覆盖实例(标准写法),
此前对覆盖实例直接跳过推送、导致单次改动无法上传服务器
- SyncEngine.pushSeries:按 uid 取全系列行重建整份 .ics 后一次性 PUT
- EventDb 新增 queryByUid / purgeByUid / purgeByUidFrom / reassignOverridesFrom /
setExdate / setRruleForUid
时间轴刻度(App + 4x4/6x6 卡片同口径):
- 8:00~18:00 恒定显示;仅当 8 点前 / 18 点后有日程时才向该侧扩展,
其余时刻不再出现'只有一条日程就只剩一小段刻度'的观感
|
2026-10-09 21:40:53 +08:00 |
|
laoyang
|
d5e5d54d35
|
账号页空状态的「+」可点击;版本号升至 0.0.5(versionCode 500)
- AccountsPage.emptyState 的「+」改为可点击按钮,直接进入添加 CalDAV 账号页
(此前只是静态图标,点了没反应);提示文案同步更新
- 版本号 0.0.4 → 0.0.5
|
2026-10-09 19:23:29 +08:00 |
|
laoyang
|
c76c78b8a6
|
删除 CalDAV 账号时一并清除其系统日历镜像本
问题:在 App 里删除一个 CalDAV 账号后,之前镜像进系统日历的日历本
(synccalendar.mirror.<accId>-<hash> 账户) 仍留在系统日历里,形成幽灵账户。
真机实测确认:账号 1789564608312 已删,其名下 13 个镜像账户仍在(KEEP)。
根因:
- AccountsPage.confirmDeleteAccount 只清账号配置+本地库+设置,完全不碰系统日历;
- 自动兜底也失效:SystemCalendarMirror.syncNow 在解析不到任何选中本时提前 return,
删完账号后正是此状态,purgeOrphans 永不执行。
修复:
- SystemCalendarMirror.removeByAccount(accId):按 'mirror.<accId>-' 前缀删除该账号全部
镜像账户(兼容历史 '<accId>_' 桶名),逐个独立 try/catch + 清空日程降级;
绝不按 displayName 匹配 —— 不同账号下的同名日历本不会误删;同步清快照与选中项设置。
- AccountsPage 删除账号时先 removeByAccount(必须在改账号配置前,否则 bookId 解析不出)。
- EditAccountPage 取消勾选日历本时删除其镜像账户(用 href 算稳定 bookId)。
- SettingsPage 账号库重置时 removeAll。
- syncNow 提前 return 前加 purgeOrphanAccountMirrors 兜底,带安全阀(账号列表为空即跳过,
防读失败误删全量)。
|
2026-10-09 08:47:50 +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
|
b83ce83d43
|
版本号升至 0.0.4(versionCode 400)
|
2026-09-29 08:46:48 +08:00 |
|
laoyang
|
d0b8720c41
|
同步容错:服务器删除日历本后 404 不再导致整个账号同步失败
- DavClient 新增 DavHttpError(带 HTTP 状态码),REPORT/PUT/DELETE 统一抛出
- 同步引擎对单本 404 跳过并自动从账号移除(先按 href 清行再迁移序号),
全部日历本均 404 时保留账号配置并给出明确提示
- 推送阶段单条失败不再中断整个账号(401 凭据失效仍立即抛出)
- 编辑账号保存与自动移除都在序号迁移前先删被移除日历本的行,
修复行混入其它日历本 calKey 的老问题(实测「读书」988 行混进「轻舟」)
- applyRemote 刷新行的 href:日历本被删除重建后集合 URL 变化,
旧行不再残留死地址(编辑推送 404 / 永久卡待推送)
- pruneOrphanRows 追加 href 维度兜底清扫(跳过 dirty 行)
|
2026-09-29 08:46:01 +08:00 |
|
laoyang
|
8b5519bc31
|
更新 README:补充系统日历镜像、后台同步与备份、深色/沉浸式/大屏横竖屏与转场适配、设置项一览、隐私与权限策略、工程结构、构建说明、截图与相关链接;按现状修正视图与卡片描述
|
2026-09-28 15:23:54 +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
|
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
|
314ed4b75c
|
删除了设置页面中的功能引导选项。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-16 10:29:46 +08:00 |
|
laoyang
|
2173b06b43
|
新增了列表显示模式,用户可以在设置中进行选择。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-16 10:07:57 +08:00 |
|
laoyang
|
5ab9648b5a
|
修复了新增日历本颜色不对的问题。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-15 23:03:58 +08:00 |
|
laoyang
|
c53456a2e1
|
修复了添加账号时全选状态错误问题。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-15 16:28:02 +08:00 |
|
laoyang
|
7216024204
|
更改设置中软件特性和用户帮助为链接形式。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-15 16:09:34 +08:00 |
|
laoyang
|
7cf9203ec3
|
修改了用户协议等打开方式。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-15 15:07:42 +08:00 |
|
laoyang
|
297f76f00d
|
增加了几个截图,用来展示效果。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-15 12:52:22 +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
|
a7503a7a1f
|
更改了readme
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-14 22:02:03 +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
|
df3928c099
|
更改了2x4卡片的样式。
Signed-off-by: Yang Yongquan <i@yangyq.net>
|
2026-09-14 11:09:15 +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
|
e62416e5ce
|
为了持续同步,申请了常驻通知栏权限,在设置中开启后,可以常驻通知栏,进行后台同步。如果不需要,可以在设置中关闭。
|
2026-09-13 22:02:08 +08:00 |
|
laoyang
|
d9c212785d
|
新增了提醒静音功能,可以在设置中关闭某个日历本的提醒。
|
2026-09-13 21:56:51 +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 |
|
laoyang
|
e27c8c36d7
|
添加了后台同步功能,应用置于后台,仍然可以进行同步。
|
2026-09-13 16:33:53 +08:00 |
|
laoyang
|
0a204b6754
|
修复了卡片显示bug。如果有跨多天的日程,卡片会从多天日程的开始那一天开始显示,已经修复了。
|
2026-09-13 16:18:56 +08:00 |
|
laoyang
|
7091cc9535
|
增加了readme文档。
|
2026-09-13 16:12:19 +08:00 |
|
laoyang
|
2af19637b6
|
在日历首页上,将按钮进行合并收拢,放在一个设置按钮下。
|
2026-09-13 16:08:03 +08:00 |
|
laoyang
|
c1543a0e33
|
add LICENSE.
Signed-off-by: 老杨 <i@yangyq.net>
|
2026-09-13 08:02:50 +00:00 |
|
laoyang
|
bbb3ec5334
|
首次提交:SyncCalendar 项目
|
2026-09-13 15:50:37 +08:00 |
|