|
@@ -1,3 +1,5 @@
|
|
|
|
|
+import { AppNotificationService } from '../../../core/app-notification.service';
|
|
|
|
|
+import { isNativeApp } from '../../../core/api/platform-proxy.service';
|
|
|
import { ChatService } from '../../../core/chat.service';
|
|
import { ChatService } from '../../../core/chat.service';
|
|
|
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
|
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
|
|
import { Router } from '@angular/router';
|
|
import { Router } from '@angular/router';
|
|
@@ -83,6 +85,27 @@ import { UiMDialog } from '../../../ui/mobile/components/dialog/dialog';
|
|
|
</ul>
|
|
</ul>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 通知(2026-11-15):把「开不开系统通知」这件事摆到明面上。
|
|
|
|
|
+ 🔴 为什么要有这一项:Android 13+ 首次拒绝通知权限后,系统**不再弹窗**,
|
|
|
|
|
+ 用户会一直以为「这个 App 就是不推送」,也不知道去哪改。
|
|
|
|
|
+ 这里给一个明确入口 + 状态显示,点一下就去系统设置。
|
|
|
|
|
+ -->
|
|
|
|
|
+ @if (isNative()) {
|
|
|
|
|
+ <section class="m-section">
|
|
|
|
|
+ <h2 class="m-section-title">通知</h2>
|
|
|
|
|
+ <div class="m-card">
|
|
|
|
|
+ <button type="button" class="m-row" (click)="onNotificationRow()">
|
|
|
|
|
+ <span class="m-row-body">
|
|
|
|
|
+ <span class="m-row-t1">消息与任务通知</span>
|
|
|
|
|
+ <span class="m-row-sub">{{ notifyStateText() }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <i class="fa-solid fa-chevron-right m-chev" aria-hidden="true"></i>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
<section class="m-section">
|
|
<section class="m-section">
|
|
|
<h2 class="m-section-title">外观</h2>
|
|
<h2 class="m-section-title">外观</h2>
|
|
|
<div class="m-card">
|
|
<div class="m-card">
|
|
@@ -431,6 +454,7 @@ export class MbMinePage {
|
|
|
constructor() {
|
|
constructor() {
|
|
|
// 字号偏好按用户存储,进入设置页时载入(幂等)
|
|
// 字号偏好按用户存储,进入设置页时载入(幂等)
|
|
|
this.font.load();
|
|
this.font.load();
|
|
|
|
|
+ this.loadNotificationState();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Token 只展示尾部 4 位 —— 完整令牌不应出现在屏幕上 */
|
|
/** Token 只展示尾部 4 位 —— 完整令牌不应出现在屏幕上 */
|
|
@@ -465,6 +489,44 @@ export class MbMinePage {
|
|
|
this.theme.toggleMode();
|
|
this.theme.toggleMode();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ---------- 通知权限(2026-11-15) ----------
|
|
|
|
|
+
|
|
|
|
|
+ private readonly notify = inject(AppNotificationService);
|
|
|
|
|
+ /** 通知权限状态:null = 未知/查询中,true = 已开,false = 未开 */
|
|
|
|
|
+ protected readonly notifyGranted = signal<boolean | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ /** 是否原生壳(浏览器里不显示「通知」入口 —— 浏览器没有这个系统权限) */
|
|
|
|
|
+ protected readonly isNative = signal(isNativeApp());
|
|
|
|
|
+
|
|
|
|
|
+ /** 进页面时查一次通知权限状态(构造函数里调用,不另开第二个 constructor) */
|
|
|
|
|
+ private loadNotificationState(): void {
|
|
|
|
|
+ void this.notify.isGranted().then((g) => this.notifyGranted.set(g));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected notifyStateText(): string {
|
|
|
|
|
+ const g = this.notifyGranted();
|
|
|
|
|
+ if (g === null) return '检查中…';
|
|
|
|
|
+ return g ? '已开启' : '未开启 · 点此去开启';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点「通知」行:
|
|
|
|
|
+ * · 已开 → 去系统设置(可关闭)
|
|
|
|
|
+ * · 未开 → 先申请;被系统拒绝(不再弹窗)则引导去设置页
|
|
|
|
|
+ */
|
|
|
|
|
+ protected async onNotificationRow(): Promise<void> {
|
|
|
|
|
+ if (this.notifyGranted() === true) {
|
|
|
|
|
+ await this.notify.openNotificationSettings();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const ok = await this.notify.request();
|
|
|
|
|
+ this.notifyGranted.set(ok);
|
|
|
|
|
+ if (!ok) {
|
|
|
|
|
+ /* 系统不再弹窗了 → 只能引导用户自己去设置里开 */
|
|
|
|
|
+ await this.notify.openNotificationSettings();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** 字号滑块:吸附到最近的档位(与桌面设置同一组档位) */
|
|
/** 字号滑块:吸附到最近的档位(与桌面设置同一组档位) */
|
|
|
protected onFont(e: Event): void {
|
|
protected onFont(e: Event): void {
|
|
|
const v = Number((e.target as HTMLInputElement).value);
|
|
const v = Number((e.target as HTMLInputElement).value);
|