/** * 家书 FamilyBook · 日程设置(鸿蒙端) * * 存"日程模块自己的偏好"——它们是**这台设备上的人怎么看日历**, * 与服务器无关、也不该同步给别人。 * * 🔴 用户 2026-10-15:「我们家书的日程功能模块,包括它的界面设计和功能, * 跟 SyncCalendar 做得一样,就差不多。……有几种不同的视图方式, * 然后包括下面日程的显示啊什么之类的。」 * ⇒ 对齐 SyncCalendar 的设置项: * · **默认视图**(月 / 周 / 列表)—— 打开日程页时先用哪个 * · **日程显示方式**(时间轴 / 列表)—— 同一天里日程怎么画 * · **新建日程落到哪本日历**(默认日历本) * · **哪些日历本可见**(隐藏不想看的那几本) * * 🔴 为什么用 `preferences` 而不是本地库:都是**单个键值**, * 不是多行记录(同一范式见 `FbTokenStore` 的说明)。 * ⚠️ 必须**同步读**(`get*()`):页面一渲染就要知道用哪个视图, * 若只能 await,每个调用点都要 async、把简单逻辑搞碎。 * ⇒ 用"启动时预加载进内存 + 写时同时更新内存"(同 FbTokenStore)。 */ import { preferences } from '@kit.ArkData'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { common } from '@kit.AbilityKit'; const DOMAIN: number = 0x0000; const TAG: string = 'FbCalSettings'; /** preferences 实例名 */ const STORE_NAME: string = 'fb_calendar'; const K_VIEW: string = 'default_view'; // month | week | agenda const K_STYLE: string = 'display_style'; // timeline | list const K_BOOK: string = 'default_book_id'; // 新建日程默认落到哪本 const K_HIDDEN: string = 'hidden_books'; // 逗号分隔的日历本 id /** 视图:月 / 周 / 列表(与 SyncCalendar 的 VIEW_ORDER 同一套) */ export type FbCalView = 'month' | 'week' | 'agenda'; /** 日程画法:时间轴 / 列表(与 SyncCalendar 的 displayStyle 同一套) */ export type FbCalStyle = 'timeline' | 'list'; class FbCalSettingsImpl { private view: FbCalView = 'month'; private style: FbCalStyle = 'timeline'; private bookId: number = 0; private hidden: string = ''; private store: preferences.Preferences | null = null; private ready: boolean = false; /** 预加载进内存(应用启动时调一次;幂等) */ async init(ctx: common.Context): Promise { if (this.ready) { return; } try { this.store = await preferences.getPreferences(ctx, STORE_NAME); const v: preferences.ValueType = await this.store.get(K_VIEW, 'month'); const s: preferences.ValueType = await this.store.get(K_STYLE, 'timeline'); const b: preferences.ValueType = await this.store.get(K_BOOK, 0); const h: preferences.ValueType = await this.store.get(K_HIDDEN, ''); this.view = (v === 'week' || v === 'agenda') ? v : 'month'; this.style = (s === 'list') ? 'list' : 'timeline'; this.bookId = typeof b === 'number' ? b : 0; this.hidden = typeof h === 'string' ? h : ''; this.ready = true; hilog.info(DOMAIN, TAG, '日程设置预加载完成:%{public}s / %{public}s', this.view, this.style); } catch (err) { // ⚠️ 读不到就用默认值,**不要**让启动挂掉 hilog.warn(DOMAIN, TAG, '日程设置预加载失败:%{public}s', JSON.stringify(err)); this.ready = true; } } isReady(): boolean { return this.ready; } // ---- 同步读 ---- getView(): FbCalView { return this.view; } getStyle(): FbCalStyle { return this.style; } getBookId(): number { return this.bookId; } /** 被隐藏的日历本 id 列表 */ hiddenIds(): number[] { const out: number[] = []; if (this.hidden === '') { return out; } for (const s of this.hidden.split(',')) { const n: number = parseInt(s, 10); if (!isNaN(n) && n > 0) { out.push(n); } } return out; } /** 某本日历是否可见(默认都可见) */ isVisible(id: number): boolean { return this.hiddenIds().indexOf(id) < 0; } // ---- 异步写(同时更新内存) ---- async setView(v: FbCalView): Promise { this.view = v; await this.put(K_VIEW, v); } async setStyle(s: FbCalStyle): Promise { this.style = s; await this.put(K_STYLE, s); } async setBookId(id: number): Promise { this.bookId = id; await this.put(K_BOOK, id); } /** 设置某本日历的可见性 */ async setVisible(id: number, visible: boolean): Promise { const ids: number[] = this.hiddenIds(); const at: number = ids.indexOf(id); if (visible && at >= 0) { ids.splice(at, 1); } else if (!visible && at < 0) { ids.push(id); } const parts: string[] = []; for (const n of ids) { parts.push(n.toString()); } this.hidden = parts.join(','); await this.put(K_HIDDEN, this.hidden); } private async put(key: string, val: preferences.ValueType): Promise { try { if (this.store === null) { hilog.warn(DOMAIN, TAG, '还没有 store,设置没能落盘(重启会丢)'); return; } await this.store.put(key, val); // ⚠️ `put` 只写内存,**必须 `flush`** 才落盘(同 FbTokenStore 的坑) await this.store.flush(); } catch (err) { hilog.error(DOMAIN, TAG, '日程设置保存失败:%{public}s', JSON.stringify(err)); } } } /** 全局单例 */ export const FB_CAL_SETTINGS: FbCalSettingsImpl = new FbCalSettingsImpl();