161 lines
5.5 KiB
Plaintext
161 lines
5.5 KiB
Plaintext
/**
|
|||
|
|
* 家书 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<void> {
|
||
|
|
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<void> {
|
||
|
|
this.view = v;
|
||
|
|
await this.put(K_VIEW, v);
|
||
|
|
}
|
||
|
|
|
||
|
|
async setStyle(s: FbCalStyle): Promise<void> {
|
||
|
|
this.style = s;
|
||
|
|
await this.put(K_STYLE, s);
|
||
|
|
}
|
||
|
|
|
||
|
|
async setBookId(id: number): Promise<void> {
|
||
|
|
this.bookId = id;
|
||
|
|
await this.put(K_BOOK, id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 设置某本日历的可见性 */
|
||
|
|
async setVisible(id: number, visible: boolean): Promise<void> {
|
||
|
|
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<void> {
|
||
|
|
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();
|