@@ -23,11 +23,17 @@ export class AppSettings {
|
|||||||
// 否则一旦某次因故没显示出来,旧标记会把功能永久锁死(v2 正是这样被锁住 → 本次升到 v3)。
|
// 否则一旦某次因故没显示出来,旧标记会把功能永久锁死(v2 正是这样被锁住 → 本次升到 v3)。
|
||||||
private static readonly KEY_TIPS_SHOWN: string = 'tips_shown_v3';
|
private static readonly KEY_TIPS_SHOWN: string = 'tips_shown_v3';
|
||||||
|
|
||||||
/** 隐私政策网址(华为 AGC 隐私政策托管服务生成,上架时填此地址) */
|
// 隐私政策 / 用户服务协议网址(自托管,AGC 上架的"隐私政策网址"填 PRIVACY_URL)。
|
||||||
static readonly PRIVACY_URL: string =
|
// ⚠️ 两份文件必须与安装包实际申请的 user_grant 权限保持一致:
|
||||||
'https://agreement-drcn.hispace.dbankcloud.cn/index.html?lang=zh&agreementId=2039401852717510592';
|
// 应用仅申请 READ_CALENDAR / READ_WHOLE_CALENDAR / LOCATION / APPROXIMATELY_LOCATION(均前台),
|
||||||
|
// 文档中不得出现"在后台获取位置"(LOCATION_IN_BACKGROUND)与"添加/修改/删除日历活动"(WRITE_CALENDAR)。
|
||||||
|
/** 隐私政策网址 */
|
||||||
|
static readonly PRIVACY_URL: string = 'https://synccalendar.yangyq.net/upa.html';
|
||||||
/** 用户服务协议网址 */
|
/** 用户服务协议网址 */
|
||||||
static readonly AGREEMENT_URL: string = 'https://synccalendar.yangyq.net/usa.html';
|
static readonly AGREEMENT_URL: string = 'https://synccalendar.yangyq.net/usa.html';
|
||||||
|
/** 文档标题(内置浏览器标题栏用) */
|
||||||
|
static readonly PRIVACY_TITLE: string = '隐私政策';
|
||||||
|
static readonly AGREEMENT_TITLE: string = '用户服务协议';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已同意《隐私政策》与《用户协议》(首启同意弹窗)。
|
* 是否已同意《隐私政策》与《用户协议》(首启同意弹窗)。
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// entry/src/main/ets/common/DocViewer.ets
|
||||||
|
// 内置文档浏览器:整页覆盖层 + Web 组件,用于在应用内展示《隐私政策》《用户服务协议》。
|
||||||
|
// 为什么不用系统浏览器 / 弹窗:
|
||||||
|
// 1) 用户要求"点了就在应用里看,随手可关",不跳出应用;
|
||||||
|
// 2) 本环境实测系统弹层(CustomDialogController / bindSheet)由异步流程驱动时不可靠,
|
||||||
|
// 而"必须有"的界面一律用整页覆盖层(与首启同意门禁 consentGate 同一套路)。
|
||||||
|
// 宿主用法:@State showDoc / docTitle / docUrl,根 Stack 最后一层 if (this.showDoc) { DocViewer({...}) }。
|
||||||
|
import { webview } from '@kit.ArkWeb';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export struct DocViewer {
|
||||||
|
/** 标题栏文案(如"隐私政策") */
|
||||||
|
title: string = '';
|
||||||
|
/** 文档地址(http / https) */
|
||||||
|
url: string = '';
|
||||||
|
/** 点击"关闭"时回调 —— 由宿主把 showDoc 置回 false */
|
||||||
|
onClose: () => void = () => {};
|
||||||
|
|
||||||
|
private controller: webview.WebviewController = new webview.WebviewController();
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
// 标题栏:左标题、右关闭(点击即退出,符合"随手关掉"的预期)
|
||||||
|
Row() {
|
||||||
|
Text(this.title)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor($r('app.color.text_primary'))
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
.layoutWeight(1)
|
||||||
|
Text('✕')
|
||||||
|
.fontSize(16)
|
||||||
|
.fontColor($r('app.color.text_secondary'))
|
||||||
|
.padding({ left: 14, right: 14, top: 10, bottom: 10 })
|
||||||
|
.onClick((): void => {
|
||||||
|
this.onClose();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(52)
|
||||||
|
.padding({ left: 16, right: 4 })
|
||||||
|
.backgroundColor($r('app.color.card_bg'))
|
||||||
|
.border({ width: { bottom: 0.5 }, color: { bottom: $r('app.color.shadow_color') } })
|
||||||
|
|
||||||
|
// 文档正文:交给系统 Web 内核渲染(页面自身已适配移动端与深色模式)
|
||||||
|
Web({ src: this.url, controller: this.controller })
|
||||||
|
.width('100%')
|
||||||
|
.layoutWeight(1)
|
||||||
|
.domStorageAccess(true)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor($r('app.color.page_bg'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { CardDataService } from '../common/CardDataService';
|
|||||||
import { ReminderService } from '../common/ReminderService';
|
import { ReminderService } from '../common/ReminderService';
|
||||||
import { AppSettings } from '../common/AppSettings';
|
import { AppSettings } from '../common/AppSettings';
|
||||||
import { TipsPanel } from '../common/TipsPanel';
|
import { TipsPanel } from '../common/TipsPanel';
|
||||||
|
import { DocViewer } from '../common/DocViewer';
|
||||||
import { EventDb, LocalEvent, RemoteEvent } from '../common/EventDb';
|
import { EventDb, LocalEvent, RemoteEvent } from '../common/EventDb';
|
||||||
import { RruleUtil } from '../common/RruleUtil';
|
import { RruleUtil } from '../common/RruleUtil';
|
||||||
import { IcsUtil } from '../common/IcsUtil';
|
import { IcsUtil } from '../common/IcsUtil';
|
||||||
@@ -80,6 +81,10 @@ struct Index {
|
|||||||
@State policyAgreed: boolean = false; // 是否已同意《隐私政策》与《用户协议》
|
@State policyAgreed: boolean = false; // 是否已同意《隐私政策》与《用户协议》
|
||||||
private notifAsked: boolean = false; // 通知权限是否已申请(同意后才申请)
|
private notifAsked: boolean = false; // 通知权限是否已申请(同意后才申请)
|
||||||
@State showTips: boolean = false; // 首启功能引导(整页覆盖层:遮罩 + 底部卡片 + Swiper 三页)
|
@State showTips: boolean = false; // 首启功能引导(整页覆盖层:遮罩 + 底部卡片 + Swiper 三页)
|
||||||
|
// ---- 内置文档浏览器(《隐私政策》/《用户服务协议》,整页 Web 覆盖层,不跳出应用) ----
|
||||||
|
@State showDoc: boolean = false;
|
||||||
|
@State docTitle: string = '';
|
||||||
|
@State docUrl: string = '';
|
||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
const ctx = this.getUIContext().getHostContext();
|
const ctx = this.getUIContext().getHostContext();
|
||||||
@@ -277,17 +282,18 @@ struct Index {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开外部链接(隐私政策 / 用户协议 / 开源仓库等) */
|
/**
|
||||||
private async openExternalUrl(url: string): Promise<void> {
|
* 用内置浏览器(整页 Web 覆盖层)打开法律文档 —— 不跳出应用、随时可关闭。
|
||||||
const ctx = this.getUIContext().getHostContext();
|
* 仅放行 http(s):避免 URL 被误传为 file://、自定义 scheme 等而拉起本地文件或任意第三方应用。
|
||||||
if (ctx === undefined) {
|
*/
|
||||||
|
private openDoc(title: string, url: string): void {
|
||||||
|
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
||||||
|
this.getUIContext().getPromptAction().showToast({ message: '仅支持打开 http(s) 链接' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
this.docTitle = title;
|
||||||
await (ctx as common.UIAbilityContext).openLink(url);
|
this.docUrl = url;
|
||||||
} catch (err) {
|
this.showDoc = true;
|
||||||
this.getUIContext().getPromptAction().showToast({ message: '无法打开链接' });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 工具 ----------
|
// ---------- 工具 ----------
|
||||||
@@ -1016,6 +1022,18 @@ struct Index {
|
|||||||
if (this.showTips) {
|
if (this.showTips) {
|
||||||
this.tipsOverlay()
|
this.tipsOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内置文档浏览器(《隐私政策》/《用户服务协议》):
|
||||||
|
// 根 Stack 最上层 —— 未同意时也能从同意门禁直接打开,看完点"✕"即刻回到门禁,无需切回应用。
|
||||||
|
if (this.showDoc) {
|
||||||
|
DocViewer({
|
||||||
|
title: this.docTitle,
|
||||||
|
url: this.docUrl,
|
||||||
|
onClose: (): void => {
|
||||||
|
this.showDoc = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
@@ -1054,7 +1072,7 @@ struct Index {
|
|||||||
.backgroundColor($r('app.color.card_bg'))
|
.backgroundColor($r('app.color.card_bg'))
|
||||||
.fontColor($r('app.color.brand'))
|
.fontColor($r('app.color.brand'))
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.openExternalUrl(AppSettings.PRIVACY_URL);
|
this.openDoc(AppSettings.PRIVACY_TITLE, AppSettings.PRIVACY_URL);
|
||||||
})
|
})
|
||||||
Button('《用户服务协议》')
|
Button('《用户服务协议》')
|
||||||
.width('100%')
|
.width('100%')
|
||||||
@@ -1062,7 +1080,7 @@ struct Index {
|
|||||||
.backgroundColor($r('app.color.card_bg'))
|
.backgroundColor($r('app.color.card_bg'))
|
||||||
.fontColor($r('app.color.brand'))
|
.fontColor($r('app.color.brand'))
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.openExternalUrl(AppSettings.AGREEMENT_URL);
|
this.openDoc(AppSettings.AGREEMENT_TITLE, AppSettings.AGREEMENT_URL);
|
||||||
})
|
})
|
||||||
Blank()
|
Blank()
|
||||||
Text('点击“同意并继续”,即表示你已阅读并同意上述两份文件。')
|
Text('点击“同意并继续”,即表示你已阅读并同意上述两份文件。')
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { BackgroundSyncService } from '../common/BackgroundSyncService';
|
|||||||
import { AccountStore, DavAccount } from '../common/AccountStore';
|
import { AccountStore, DavAccount } from '../common/AccountStore';
|
||||||
import { ReminderService } from '../common/ReminderService';
|
import { ReminderService } from '../common/ReminderService';
|
||||||
import { TipsPanel } from '../common/TipsPanel';
|
import { TipsPanel } from '../common/TipsPanel';
|
||||||
|
import { DocViewer } from '../common/DocViewer';
|
||||||
|
|
||||||
const INTERVAL_OPTIONS: number[] = [1, 5, 15, 30, 60];
|
const INTERVAL_OPTIONS: number[] = [1, 5, 15, 30, 60];
|
||||||
|
|
||||||
@@ -34,6 +35,10 @@ struct SettingsPage {
|
|||||||
@State showFeatures: boolean = false; // 软件特性弹层
|
@State showFeatures: boolean = false; // 软件特性弹层
|
||||||
@State showHelp: boolean = false; // 使用帮助弹层
|
@State showHelp: boolean = false; // 使用帮助弹层
|
||||||
@State showLegal: boolean = false; // 隐私政策与用户协议弹层
|
@State showLegal: boolean = false; // 隐私政策与用户协议弹层
|
||||||
|
// 内置文档浏览器(《隐私政策》/《用户服务协议》):整页 Web 覆盖层
|
||||||
|
@State showDoc: boolean = false;
|
||||||
|
@State docTitle: string = '';
|
||||||
|
@State docUrl: string = '';
|
||||||
@State showTips: boolean = false; // 功能引导卡片(底部弹出)
|
@State showTips: boolean = false; // 功能引导卡片(底部弹出)
|
||||||
@State defaultView: string = 'month'; // 打开 App 默认视图:month | week | agenda
|
@State defaultView: string = 'month'; // 打开 App 默认视图:month | week | agenda
|
||||||
private context?: common.Context;
|
private context?: common.Context;
|
||||||
@@ -377,7 +382,23 @@ struct SettingsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用内置浏览器(整页 Web 覆盖层)打开法律文档 —— 不跳出应用、随时可关闭。
|
||||||
|
* 先收起"隐私政策与用户协议"弹层,再显示覆盖层,避免两层叠在一起看不清。
|
||||||
|
*/
|
||||||
|
private openDoc(title: string, url: string): void {
|
||||||
|
if (!url.startsWith('https://') && !url.startsWith('http://')) {
|
||||||
|
this.getUIContext().getPromptAction().showToast({ message: '仅支持打开 http(s) 链接' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.showLegal = false;
|
||||||
|
this.docTitle = title;
|
||||||
|
this.docUrl = url;
|
||||||
|
this.showDoc = true;
|
||||||
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
|
Stack() {
|
||||||
Column() {
|
Column() {
|
||||||
// 顶部
|
// 顶部
|
||||||
Row({ space: 10 }) {
|
Row({ space: 10 }) {
|
||||||
@@ -937,6 +958,20 @@ struct SettingsPage {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
.backgroundColor($r('app.color.page_bg'))
|
.backgroundColor($r('app.color.page_bg'))
|
||||||
|
|
||||||
|
// 内置文档浏览器(《隐私政策》/《用户服务协议》):整页覆盖层,点"✕"即刻返回设置页
|
||||||
|
if (this.showDoc) {
|
||||||
|
DocViewer({
|
||||||
|
title: this.docTitle,
|
||||||
|
url: this.docUrl,
|
||||||
|
onClose: (): void => {
|
||||||
|
this.showDoc = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 功能引导卡片(与首页共用 TipsPanel) */
|
/** 功能引导卡片(与首页共用 TipsPanel) */
|
||||||
@@ -959,14 +994,14 @@ struct SettingsPage {
|
|||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.backgroundColor($r('app.color.brand'))
|
.backgroundColor($r('app.color.brand'))
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.openUrl(AppSettings.PRIVACY_URL);
|
this.openDoc(AppSettings.PRIVACY_TITLE, AppSettings.PRIVACY_URL);
|
||||||
})
|
})
|
||||||
Button('查看《用户服务协议》')
|
Button('查看《用户服务协议》')
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.backgroundColor($r('app.color.brand'))
|
.backgroundColor($r('app.color.brand'))
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.openUrl(AppSettings.AGREEMENT_URL);
|
this.openDoc(AppSettings.AGREEMENT_TITLE, AppSettings.AGREEMENT_URL);
|
||||||
})
|
})
|
||||||
Text('《隐私政策》说明本应用如何收集、使用与保护你的信息(日历、位置等);《用户服务协议》说明使用本应用的权利与义务。两份文件随功能调整更新,最新版本以上述链接为准。')
|
Text('《隐私政策》说明本应用如何收集、使用与保护你的信息(日历、位置等);《用户服务协议》说明使用本应用的权利与义务。两份文件随功能调整更新,最新版本以上述链接为准。')
|
||||||
.fontSize(12)
|
.fontSize(12)
|
||||||
|
|||||||
Reference in New Issue
Block a user