@@ -17,6 +17,69 @@ export class AppSettings {
|
||||
private static readonly KEY_REMINDER_TICK: string = 'reminder_tick'; // 应用内提醒上次检查时间戳
|
||||
private static readonly KEY_FULL_REFETCH: string = 'full_refetch_done'; // 一次性全量重拉已完成
|
||||
private static readonly KEY_DEFAULT_VIEW: string = 'default_view'; // 打开 App 默认视图
|
||||
private static readonly KEY_POLICY_AGREED: string = 'policy_agreed'; // 是否已同意隐私政策与用户协议
|
||||
// 首启功能引导"用户已看过并关闭"的标记。键名带版本号:改动引导内容后把 vN 加 1,用户即可再看一次。
|
||||
// 注意:只在用户主动关闭("开始使用" / ✕ / 点遮罩)时才置 true,**不再"显示前就置 true"**——
|
||||
// 否则一旦某次因故没显示出来,旧标记会把功能永久锁死(v2 正是这样被锁住 → 本次升到 v3)。
|
||||
private static readonly KEY_TIPS_SHOWN: string = 'tips_shown_v3';
|
||||
|
||||
/** 隐私政策网址(华为 AGC 隐私政策托管服务生成,上架时填此地址) */
|
||||
static readonly PRIVACY_URL: string =
|
||||
'https://agreement-drcn.hispace.dbankcloud.cn/index.html?lang=zh&agreementId=2039401852717510592';
|
||||
/** 用户服务协议网址 */
|
||||
static readonly AGREEMENT_URL: string = 'https://synccalendar.yangyq.net/usa.html';
|
||||
|
||||
/**
|
||||
* 是否已同意《隐私政策》与《用户协议》(首启同意弹窗)。
|
||||
* 默认 false —— 未同意前不得申请任何系统权限、不得加载/同步数据。
|
||||
*/
|
||||
static async getPolicyAgreed(context: common.Context): Promise<boolean> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
return await store.get(AppSettings.KEY_POLICY_AGREED, false) as boolean;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async setPolicyAgreed(context: common.Context, value: boolean): Promise<void> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
await store.put(AppSettings.KEY_POLICY_AGREED, value);
|
||||
await store.flush();
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
console.error(`保存隐私政策同意状态失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 首启功能引导卡片是否已展示过(避免每次启动都弹)。
|
||||
* 默认 false —— 启动(首次同意后 / 已同意用户再次启动)时若为 false 会自动弹出一次并置为 true。
|
||||
*/
|
||||
static async getTipsShown(context: common.Context): Promise<boolean> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
return await store.get(AppSettings.KEY_TIPS_SHOWN, false) as boolean;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async setTipsShown(context: common.Context, value: boolean): Promise<void> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
await store.put(AppSettings.KEY_TIPS_SHOWN, value);
|
||||
await store.flush();
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
console.error(`保存功能引导展示状态失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开 App 后默认展示的视图:'month' | 'week' | 'agenda'(默认 month) */
|
||||
static async getDefaultView(context: common.Context): Promise<string> {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// entry/src/main/ets/common/TipsPanel.ets
|
||||
// 首启功能引导卡片:底部弹出,Swiper 三页,左下"上一步" / 右下"下一步"(末页变"开始使用")。
|
||||
// 数据源 items 中图标为 media 下的 SVG(tip_security / tip_calendar / tip_mute)。
|
||||
// 同时被首页(首启自动弹出)与设置页(手动 revisar)复用。
|
||||
import { AppSettings } from './AppSettings';
|
||||
|
||||
/** 单张引导卡的数据 */
|
||||
interface TipItem {
|
||||
icon: Resource; // app.media 下的 SVG 矢量图标
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
@Component
|
||||
export struct TipsPanel {
|
||||
@State current: number = 0;
|
||||
private controller: SwiperController = new SwiperController();
|
||||
/** 末页点击"开始使用"时回调(由宿主关闭弹层) */
|
||||
onClose: () => void = () => {};
|
||||
|
||||
private items: TipItem[] = [
|
||||
{
|
||||
icon: $r('app.media.tip_security'),
|
||||
title: '你的数据安全,由你做主',
|
||||
desc: '我们不在服务器保存任何日程或账号数据。日历只在你的设备与你自己填写的 CalDAV 服务器之间同步,' +
|
||||
'全程加密保存。开源、无后门,数据始终属于你。'
|
||||
},
|
||||
{
|
||||
icon: $r('app.media.tip_calendar'),
|
||||
title: '系统日历,也能一起管',
|
||||
desc: '手机系统日历可与 CalDAV 日历混合显示在同一时间轴;还能把系统日历本通过 CalDAV 同步备份,' +
|
||||
'一处编辑、处处更新。'
|
||||
},
|
||||
{
|
||||
icon: $r('app.media.tip_mute'),
|
||||
title: '只读与静音,自由设置',
|
||||
desc: '每个日历本都能单独设为只读或静音:只读日历不出现在新建选择中、避免误改;' +
|
||||
'静音日历照常显示,但不再推送提醒。'
|
||||
}
|
||||
];
|
||||
|
||||
build() {
|
||||
Column({ space: 0 }) {
|
||||
Swiper(this.controller) {
|
||||
ForEach(this.items, (item: TipItem, idx: number) => {
|
||||
Column({ space: 16 }) {
|
||||
Image(item.icon)
|
||||
.width(140)
|
||||
.height(140)
|
||||
.objectFit(ImageFit.Contain)
|
||||
.margin({ top: 24, bottom: 8 })
|
||||
Text(item.title)
|
||||
.fontSize(19)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
.margin({ bottom: 8 })
|
||||
Text(item.desc)
|
||||
.fontSize(14)
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
.textAlign(TextAlign.Center)
|
||||
.width('100%')
|
||||
.lineHeight(22)
|
||||
.padding({ left: 28, right: 28 })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
}, (item: TipItem, idx: number) => `tip_${idx}`)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.indicator(new DotIndicator().selectedColor('#007DFF').color('#CFD8E3').itemWidth(6).itemHeight(6))
|
||||
.loop(false)
|
||||
.onChange((index: number): void => {
|
||||
this.current = index;
|
||||
})
|
||||
|
||||
// 底部导航:左下角"上一步" / 右下角"下一步"(末页变"开始使用")
|
||||
Row() {
|
||||
Button('上一步')
|
||||
.fontSize(15)
|
||||
.width(96)
|
||||
.height(42)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.fontColor(this.current === 0 ? $r('app.color.text_hint') : $r('app.color.text_primary'))
|
||||
.enabled(this.current > 0)
|
||||
.onClick(() => {
|
||||
if (this.current > 0) {
|
||||
this.current -= 1;
|
||||
this.controller.changeIndex(this.current, false);
|
||||
}
|
||||
})
|
||||
Button(this.current >= this.items.length - 1 ? '开始使用' : '下一步')
|
||||
.fontSize(15)
|
||||
.width(96)
|
||||
.height(42)
|
||||
.backgroundColor($r('app.color.brand'))
|
||||
.fontColor($r('app.color.button_text'))
|
||||
.onClick(() => {
|
||||
if (this.current >= this.items.length - 1) {
|
||||
this.onClose();
|
||||
} else {
|
||||
this.current += 1;
|
||||
this.controller.changeIndex(this.current, false);
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.SpaceBetween)
|
||||
.padding({ left: 20, right: 20, top: 12, bottom: 20 })
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor($r('app.color.page_bg'))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user