增加了用户隐私协议等,增加了引导卡片。

Signed-off-by: Yang Yongquan <i@yangyq.net>
This commit is contained in:
2026-09-15 09:06:15 +08:00
parent a7503a7a1f
commit 088f7b3773
11 changed files with 663 additions and 73 deletions
+63
View File
@@ -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> {
+117
View File
@@ -0,0 +1,117 @@
// entry/src/main/ets/common/TipsPanel.ets
// 首启功能引导卡片:底部弹出,Swiper 三页,左下"上一步" / 右下"下一步"(末页变"开始使用")。
// 数据源 items 中图标为 media 下的 SVGtip_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'))
}
}
@@ -5,6 +5,7 @@ import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { BackgroundSyncService } from '../common/BackgroundSyncService';
import { LogUtil } from '../common/LogUtil';
import { AppSettings } from '../common/AppSettings';
const DOMAIN = 0x0000;
@@ -18,14 +19,22 @@ export default class EntryAbility extends UIAbility {
BackgroundSyncService.initOnLaunch(this.context);
// 服务卡片"添加"按钮跳转:把 widgetAction=add 透传给主页面(在 AppStorage 中暂存)
this.handleWidgetParam(want);
// 申请通知权限:日程提醒、后台同步常驻通知都依赖它(系统只弹一次授权框)
notificationManager.isNotificationEnabled()
.then((enabled: boolean): Promise<void> => {
if (!enabled) {
return notificationManager.requestEnableNotification(this.context)
.catch((): void => {});
// 通知权限:日程提醒、后台同步常驻通知都依赖它(系统只弹一次授权框)
// 必须在用户同意《隐私政策》之后才申请 —— 同意前不得申请任何权限;
// 首次安装此处跳过,用户点"同意并继续"后由 Index.initPermissionAndLoad() 补申请。
AppSettings.getPolicyAgreed(this.context)
.then((agreed: boolean): Promise<void> => {
if (!agreed) {
LogUtil.write('未同意隐私政策:跳过通知权限申请');
return Promise.resolve();
}
return Promise.resolve();
return notificationManager.isNotificationEnabled()
.then((enabled: boolean): Promise<void> => {
if (!enabled) {
return notificationManager.requestEnableNotification(this.context).catch((): void => {});
}
return Promise.resolve();
});
})
.catch((): void => {});
}
+260 -7
View File
@@ -5,6 +5,7 @@ import { mediaquery, router } from '@kit.ArkUI';
import { common, abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit';
import { DavAccount, AccountStore, CalSource, TYPE_CALDAV } from '../common/AccountStore';
import { DisplayEvent, CalendarDataService } from '../common/CalendarDataService';
import { SyncEngine } from '../common/SyncEngine';
@@ -13,6 +14,7 @@ import { LunarUtil } from '../common/LunarUtil';
import { CardDataService } from '../common/CardDataService';
import { ReminderService } from '../common/ReminderService';
import { AppSettings } from '../common/AppSettings';
import { TipsPanel } from '../common/TipsPanel';
import { EventDb, LocalEvent, RemoteEvent } from '../common/EventDb';
import { RruleUtil } from '../common/RruleUtil';
import { IcsUtil } from '../common/IcsUtil';
@@ -73,6 +75,12 @@ struct Index {
@State dayTimeline: DayTimeline = new DayTimeline(); // 选中日(月/周视图用)
@State agendaTimelines: AgendaTimelineGroup[] = []; // 列表视图:每天一条时间轴
// ---- 隐私政策与用户协议首启同意(未同意前不申请任何权限、不加载数据) ----
@State policyChecked: boolean = false; // 是否已读取过同意状态(避免首帧闪烁)
@State policyAgreed: boolean = false; // 是否已同意《隐私政策》与《用户协议》
private notifAsked: boolean = false; // 通知权限是否已申请(同意后才申请)
@State showTips: boolean = false; // 首启功能引导(整页覆盖层:遮罩 + 底部卡片 + Swiper 三页)
aboutToAppear(): void {
const ctx = this.getUIContext().getHostContext();
if (ctx !== undefined) {
@@ -85,14 +93,17 @@ struct Index {
this.selectedDate = this.startOfDay(now.getTime());
this.rebuildPages();
this.initLandscapeListener();
this.initPermissionAndLoad();
// 读取"默认视图"设置:打开 App 后按用户选择展示(月/周/列表,默认月)
if (ctx !== undefined) {
AppSettings.getDefaultView(ctx).then((v: string): void => {
this.mode = v;
if (v === 'agenda') {
this.ensureAgendaData();
// 首启同意门禁:未同意《隐私政策》与《用户协议》前,不申请任何系统权限、不加载/同步数据(AGC 硬性要求)
if (ctx === undefined) {
this.policyChecked = true;
this.startApp(ctx);
} else {
AppSettings.getPolicyAgreed(ctx).then((ok: boolean): void => {
this.policyChecked = true;
if (ok) {
this.startApp(ctx);
}
// 未同意:仅显示整页同意门禁(consentGate),不申请权限、不加载数据
});
}
// 红线"播放"效果:每 30s 更新 nowMs,让当前时间红线随真实时间推进(同时刷新色块"进行中"标记)
@@ -129,6 +140,15 @@ struct Index {
/** 从设置页/账号页返回时刷新(同步间隔、系统日历开关立即生效),并处理编辑账号后的待同步 */
onPageShow(): void {
// 未同意《隐私政策》前不加载、不同步任何数据
if (!this.policyAgreed) {
return;
}
// 首启功能引导兜底:已同意用户若在 aboutToAppear 阶段漏触发,这里再补一次(幂等:已展示过会直接跳过)
const tipCtx = this.getUIContext().getHostContext();
if (tipCtx !== undefined) {
this.maybeShowTips(tipCtx);
}
// 卡片"添加"按钮深链:首次冷启动经 onCreate→AppStorage 落到这里消费
this.consumeWidgetAction();
// 无条件刷新:之前"列表为空就跳过"的条件会导致账号列表卡死在空状态,
@@ -164,6 +184,7 @@ struct Index {
return;
}
this.reloadAll();
this.ensureNotificationPermission(context as common.UIAbilityContext);
if (!this.permissionAsked) {
this.permissionAsked = true;
// 申请读取全部日历权限(用于混合展示系统日程)
@@ -174,6 +195,101 @@ struct Index {
}
}
/**
* 申请通知权限(日程提醒、后台同步常驻通知都依赖它)。
* 必须在用户同意《隐私政策》之后才调用 —— 同意前不得申请任何权限。
*/
private ensureNotificationPermission(context: common.UIAbilityContext): void {
if (this.notifAsked) {
return;
}
this.notifAsked = true;
notificationManager.isNotificationEnabled()
.then((enabled: boolean): Promise<void> => {
if (!enabled) {
return notificationManager.requestEnableNotification(context).catch((): void => {});
}
return Promise.resolve();
})
.catch((): void => {});
}
/**
* 同意之后(或"已同意用户"启动时)的正式初始化:
* 申请权限、加载数据、读取默认视图,并首启弹一次功能引导卡片。
* 首次同意(acceptPolicy)与已同意直接启动(aboutToAppear)都走这里,保证两条路径都能弹出引导。
*/
private startApp(ctx: common.Context | undefined): void {
this.policyAgreed = true;
this.initPermissionAndLoad();
if (ctx !== undefined) {
// 读取"默认视图"设置:打开 App 后按用户选择展示(月/周/列表,默认月)
AppSettings.getDefaultView(ctx).then((v: string): void => {
this.mode = v;
if (v === 'agenda') {
this.ensureAgendaData();
}
});
this.maybeShowTips(ctx);
}
}
/**
* 首启功能引导:仅在用户从未看过时显示一次(整页覆盖层),之后不再出现。
* 注意:这里**不写"已看过"标记** —— 标记由 hideTips() 在用户主动关闭时写入。
* 若在此处提前写标记,一旦显示环节出问题就会把引导永久锁死(历史踩过坑)。
*/
private maybeShowTips(ctx: common.Context): void {
AppSettings.getTipsShown(ctx).then((seen: boolean): void => {
if (seen) {
return;
}
// 略延迟:先让主页面完成首帧布局,并尽量错开紧随其后的权限申请系统弹窗
setTimeout((): void => {
this.showTips = true;
}, 600);
});
}
/** 关闭功能引导覆盖层("开始使用" / ✕ / 点遮罩 均走这里),此刻才标记"已看过" */
private hideTips(): void {
this.showTips = false;
const ctx = this.getUIContext().getHostContext();
if (ctx !== undefined) {
AppSettings.setTipsShown(ctx, true);
}
}
/** 用户点击"同意并继续":持久化同意标记,之后才申请权限、加载数据 */
private acceptPolicy(): void {
const ctx = this.getUIContext().getHostContext();
if (ctx !== undefined) {
AppSettings.setPolicyAgreed(ctx, true);
}
this.startApp(ctx);
}
/** 用户点击"不同意并退出":不申请任何权限、不处理任何数据,直接退出应用 */
private rejectPolicy(): void {
const ctx = this.getUIContext().getHostContext();
if (ctx !== undefined) {
(ctx as common.UIAbilityContext).terminateSelf();
}
}
/** 打开外部链接(隐私政策 / 用户协议 / 开源仓库等) */
private async openExternalUrl(url: string): Promise<void> {
const ctx = this.getUIContext().getHostContext();
if (ctx === undefined) {
return;
}
try {
await (ctx as common.UIAbilityContext).openLink(url);
} catch (err) {
this.getUIContext().getPromptAction().showToast({ message: '无法打开链接' });
}
}
// ---------- 工具 ----------
private startOfDay(ms: number): number {
const d = new Date(ms);
@@ -881,6 +997,23 @@ struct Index {
.onClick(() => {
this.addEvent();
})
// 未同意《隐私政策》与《用户协议》:整页门禁盖在最上层(不申请权限、不加载数据)
if (!this.policyChecked) {
// 读取同意状态期间的占位(极短),避免闪一下主界面或门禁页
Column() {
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.page_bg'))
} else if (!this.policyAgreed) {
this.consentGate()
}
// 功能引导:整页覆盖层,放根 Stack 最后一层(盖住主界面与悬浮按钮)
if (this.showTips) {
this.tipsOverlay()
}
}
.width('100%')
.height('100%')
@@ -892,6 +1025,126 @@ struct Index {
})
}
/**
* 首启隐私政策同意门禁(整页渲染,不用弹窗 —— 弹窗在本环境实测无法弹出)。
* AGC 要求:明确同意前不得申请权限、不得收集/处理个人信息,故未同意时整页挡住主界面。
*/
@Builder
consentGate() {
Column({ space: 14 }) {
Text('隐私政策与用户协议')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.margin({ top: 40 })
Text('欢迎使用「同步日历」。在使用前,请阅读并同意以下条款。')
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.width('100%')
Text('我们不会保存你的日程与账号数据:日历仅在你的设备与你自己填写的 CalDAV 服务器之间同步;位置信息仅在你点击“导航”时用于调用第三方地图,不会上传。')
.fontSize(13)
.fontColor($r('app.color.text_secondary'))
.width('100%')
Button('《隐私政策》')
.width('100%')
.fontSize(14)
.backgroundColor($r('app.color.card_bg'))
.fontColor($r('app.color.brand'))
.onClick(() => {
this.openExternalUrl(AppSettings.PRIVACY_URL);
})
Button('《用户服务协议》')
.width('100%')
.fontSize(14)
.backgroundColor($r('app.color.card_bg'))
.fontColor($r('app.color.brand'))
.onClick(() => {
this.openExternalUrl(AppSettings.AGREEMENT_URL);
})
Blank()
Text('点击“同意并继续”,即表示你已阅读并同意上述两份文件。')
.fontSize(12)
.fontColor($r('app.color.text_hint'))
.width('100%')
Row({ space: 10 }) {
Button('不同意并退出')
.fontSize(15)
.layoutWeight(1)
.backgroundColor($r('app.color.card_bg'))
.fontColor($r('app.color.text_secondary'))
.onClick(() => {
this.rejectPolicy();
})
Button('同意并继续')
.fontSize(15)
.layoutWeight(1)
.backgroundColor($r('app.color.brand'))
.onClick(() => {
this.acceptPolicy();
})
}
.width('100%')
.margin({ bottom: 24 })
}
.width('100%')
.height('100%')
.padding({ left: 24, right: 24, top: 20, bottom: 12 })
.backgroundColor($r('app.color.page_bg'))
}
/**
* 首启功能引导:整页覆盖层(半透明遮罩 + 底部卡片),由 TipsPanel 提供 Swiper 三页与
* 左下"上一步" / 右下"下一步"。
* 用页面内容渲染而非 bindSheet/CustomDialog —— 本环境从"启动时的异步回调"里弹出系统弹层不可靠
* (详见 MEMORYCustomDialogController 静默失败;bindSheet 同样实测弹不出来)。
*/
@Builder
tipsOverlay() {
Column() {
// 上部留白:露出被遮罩压暗的主界面,制造"从下方弹出"的层次(点空白处也可关闭)
Column() {
}
.width('100%')
.layoutWeight(1)
.onClick((): void => {
this.hideTips();
})
// 底部引导卡片
Column() {
// 右上角关闭
Row() {
Blank()
Text('✕')
.fontSize(16)
.fontColor($r('app.color.text_hint'))
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.onClick((): void => {
this.hideTips();
})
}
.width('100%')
.padding({ left: 12, right: 12, top: 8 })
Column() {
TipsPanel({ onClose: (): void => { this.hideTips(); } })
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('70%')
.backgroundColor($r('app.color.page_bg'))
.borderRadius({ topLeft: 20, topRight: 20 })
.clip(true)
.shadow({ radius: 16, color: $r('app.color.shadow_color'), offsetX: 0, offsetY: -2 })
}
.width('100%')
.height('100%')
.backgroundColor('#66000000')
}
@Builder
header() {
Row({ space: 10 }) {
+118
View File
@@ -7,6 +7,7 @@ import { AppSettings } from '../common/AppSettings';
import { BackgroundSyncService } from '../common/BackgroundSyncService';
import { AccountStore, DavAccount } from '../common/AccountStore';
import { ReminderService } from '../common/ReminderService';
import { TipsPanel } from '../common/TipsPanel';
const INTERVAL_OPTIONS: number[] = [1, 5, 15, 30, 60];
@@ -32,6 +33,8 @@ struct SettingsPage {
@State notifyEnabled: boolean = true; // 通知权限状态(提醒依赖)
@State showFeatures: boolean = false; // 软件特性弹层
@State showHelp: boolean = false; // 使用帮助弹层
@State showLegal: boolean = false; // 隐私政策与用户协议弹层
@State showTips: boolean = false; // 功能引导卡片(底部弹出)
@State defaultView: string = 'month'; // 打开 App 默认视图:month | week | agenda
private context?: common.Context;
@@ -692,6 +695,78 @@ struct SettingsPage {
title: { title: '使用帮助' }
})
// 功能引导(首启引导卡片,可在设置里再看一次;bindSheet 挂在本卡片上)
Column({ space: 8 }) {
Row({ space: 10 }) {
Column({ space: 2 }) {
Text('功能引导')
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor($r('app.color.text_primary'))
Text('图文介绍安全、系统日历与只读 / 静音')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('')
.fontSize(18)
.fontColor($r('app.color.text_hint'))
}
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(14)
.borderRadius(12)
.backgroundColor($r('app.color.card_bg'))
.border({ width: 1, color: $r('app.color.shadow_color') })
.onClick(() => {
this.showTips = true;
})
.bindSheet($$this.showTips, this.tipsSheet(), {
height: '72%',
dragBar: true,
showClose: true,
title: { title: '功能引导' }
})
// 隐私政策与用户协议(点击弹层;bindSheet 挂在本卡片上,避免与其它弹层冲突)
Column({ space: 8 }) {
Row({ space: 10 }) {
Column({ space: 2 }) {
Text('隐私政策与用户协议')
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor($r('app.color.text_primary'))
Text('查看本应用如何处理你的信息,以及使用条款')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('')
.fontSize(18)
.fontColor($r('app.color.text_hint'))
}
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(14)
.borderRadius(12)
.backgroundColor($r('app.color.card_bg'))
.border({ width: 1, color: $r('app.color.shadow_color') })
.onClick(() => {
this.showLegal = true;
})
.bindSheet($$this.showLegal, this.legalSheet(), {
height: 400,
dragBar: true,
showClose: true,
title: { title: '隐私政策与用户协议' }
})
// 日历本只读 / 静音(放在最下方;本多时列表较长)
Column({ space: 8 }) {
Row({ space: 10 }) {
@@ -774,6 +849,49 @@ struct SettingsPage {
.backgroundColor($r('app.color.page_bg'))
}
/** 功能引导卡片(与首页共用 TipsPanel */
@Builder
tipsSheet() {
TipsPanel({ onClose: (): void => { this.showTips = false; } })
}
/** 隐私政策与用户协议弹层 */
@Builder
legalSheet() {
Scroll() {
Column({ space: 12 }) {
Text('我们遵循"数据自持、最小必要"原则:开发者不保存你的日程与账号数据,不收集通讯录、相册、剪贴板等信息,不含任何第三方统计或广告 SDK。')
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.width('100%')
Button('查看《隐私政策》')
.width('100%')
.fontSize(15)
.backgroundColor($r('app.color.brand'))
.onClick(() => {
this.openUrl(AppSettings.PRIVACY_URL);
})
Button('查看《用户服务协议》')
.width('100%')
.fontSize(15)
.backgroundColor($r('app.color.brand'))
.onClick(() => {
this.openUrl(AppSettings.AGREEMENT_URL);
})
Text('《隐私政策》说明本应用如何收集、使用与保护你的信息(日历、位置等);《用户服务协议》说明使用本应用的权利与义务。两份文件随功能调整更新,最新版本以上述链接为准。')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.width('100%')
}
.width('100%')
.align(Alignment.Top)
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.page_bg'))
}
@Builder
featureSheet() {
Scroll() {