跨端: 预设档补遮罩(WebUI 的 --bg-dim 不分档位)+ 主题变化时重算"我们自己算的值";缓存键差异记入表
pi 读 WebUI 源码后发现两处两边不一致,都处理了。这一提交同时改 harmony 与 electron,故自报家门。
## 1 预设档遮罩:**补上**(选"与 WebUI 一致",因为那服务的是可读性)
pi 的证据:WebUI 的 `applyBackground()` **无条件**写 `--bg-dim`(默认 24),
`.app-backdrop::after` 是 `rgb(var(--bg-scrim) / var(--bg-dim))` —— **遮罩不区分档位**;
它的注释写着目的「背景越花,正文越需要一层遮罩才读得动」。
而鸿蒙当时只在 image 档压(`resolveBackground` 里那行注释还写着"preset 档不用"),
且我们已经让出了页面底 → 正文直接压在原色渐变上,**比 WebUI 更艳更亮、更不好读**。
现在两档用**同一个浓度**(同一个服务端字段),页面在预设分支的渐变之上加一层
系统遮罩色 × 浓度(浅色由系统洗白、深色压黑,不自己写 alpha)。
判据:旧断言"preset 档不压暗"**反过来**(留着理由),另加一条钉"两档同一浓度"+
"两处遮盖层都在"+"WebUI 确实无条件写 --bg-dim"(这条差异有据可查)。
变异:预设档不压 → 红 2 条;页面预设分支的遮盖层被删 → 红。
## 2 多账号缓存键:**鸿蒙是对的,不许退回**(pi 点名)
WebUI 的键是全局常量 `agentmail.background`,后果是切到服务端没有记录的账号时
`saved=false` 分支会把**上一个账号的外观** push 上去(新账号"继承"了外观,还写进了服务端)。
这条差异进 §7.12「有意差异」表,**明确写"鸿蒙是对的"**,
判据防的就是"将来有人为了两边一致把它改回去":取键函数的**正文**里必须拼账号 id
(按块取,不是看调用点出现过 `accountId` 就当数 —— "判结构要配对/解析"那条对我自己也适用),
且不许出现 WebUI 那个全局键。变异:`prefKey` 去掉账号 → 红 2 条。
## 3 "未做" → 按 pi 的三档口径改成**已知不一致**,并把系统侧的接法做掉
pi 指出这不是"没做":色板确实按主题算了,只是没接环境变化事件 ——
现象是运行期切系统深浅色时"系统语义色/材质立刻跟随、我们自己算的色板不重算"的**撕裂**。
他给的规则我记成了通用规则(会咬到 P5/P6):**系统自动跟随的东西不会顺带把
"我们自己计算/缓存的值"一起更新** —— 凡随主题变化的自算值都要挂在**同一个主题变化事件**上。
照做:`applicationContext.on('environment')` → `onConfigurationUpdated` 里
**只在 `colorMode` 真换向时**重算(`applyAppearance()`),页面销毁 `off` 退订。
状态 = **未验**(只有真机/模拟器能验:切一次系统深浅色看预设是否跟着换)。
SDK 锚点与两个编译错都记进 §7.17b:`EnvironmentCallback.onConfigurationUpdated(config: Configuration)`
(`Configuration` 从 `@kit.AbilityKit` 取,**不在** `common` 命名空间下)、
`Configuration.colorMode` 是**枚举 | undefined**(字段写成 `number` 直接编译失败)、
`on('environment')` 返回 **number 型 callbackId**。变异:不订阅 → 红。
## 4 文档
- §7.12 加两行:缓存键(含 WebUI 侧开项)、遮罩浓度默认值那两套(`24/8` vs `12/4`,
按 `clamp` 的 12/4 对齐,因为服务端缺字段时落地的是它)。
- §四 验收纪律改成**四档口径**:没做 / 未验 / 已知不一致(要写触发条件与现象)/
机制上确定不同(必须判),并把 pi 那条"自算值必须挂主题事件"的可复用规则写进去。
## 验证
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;harmony-appearance 20 → 23 条)。
This commit is contained in:
@ -221,7 +221,18 @@ export class BackgroundPlan {
|
||||
dark: boolean = false;
|
||||
presetId: string = '';
|
||||
layers: PresetLayer[] = [];
|
||||
/** 压暗(0~1)—— image 档压在图上;preset 档不用 */
|
||||
/**
|
||||
* 遮盖浓度(0~1):**两档都用**(预设档也压),用系统遮罩色刷一层。
|
||||
*
|
||||
* ⚠️ 这里原来写的是"preset 档不用",pi 2026-09-14 读 WebUI 源码后指出**两边不一致**:
|
||||
* WebUI 的 `applyBackground()` **无条件**写 `--bg-dim`(默认 24),
|
||||
* `.app-backdrop::after` 是 `rgb(var(--bg-scrim) / var(--bg-dim))` ——
|
||||
* 遮罩**不区分档位**,预设档一样被压。它的注释写着目的:
|
||||
* **「背景越花,正文越需要一层遮罩才读得动」** —— 遮罩服务的是可读性,不是装饰。
|
||||
*
|
||||
* 对这边的实际后果:我们已经让出了页面底(`bgActive` 一路到三个 pane),
|
||||
* 于是正文直接压在原色渐变上 —— 比 WebUI 更艳更亮、更不好读。
|
||||
*/
|
||||
scrim: number = 0;
|
||||
}
|
||||
|
||||
@ -239,6 +250,8 @@ export function resolveBackground(bgKind: string, presetId: string, scrim: numbe
|
||||
plan.kind = 'preset';
|
||||
plan.presetId = normalizePreset(presetId);
|
||||
plan.layers = layersFor(plan.presetId, dark);
|
||||
// 预设档**也**套遮罩:与 WebUI 的 `--bg-dim` 一致(它不区分档位),服务的是可读性
|
||||
plan.scrim = scrim;
|
||||
return plan;
|
||||
}
|
||||
if (bgKind === 'image' && hasImage) {
|
||||
|
||||
@ -11,6 +11,7 @@ import { MailApi, InboxResponse } from '../api/MailApi';
|
||||
import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService, SseEvent } from '../api/SseService';
|
||||
import { AppearanceStore } from '../common/AppearanceStore';
|
||||
import { Configuration, ConfigurationConstant, EnvironmentCallback } from '@kit.AbilityKit';
|
||||
import { image } from '@kit.ImageKit';
|
||||
import { AppearanceSnapshot, isDarkMode, scrimOpacity } from '../model/Appearance';
|
||||
import { BackgroundPlan, PresetLayer, TRANSPARENT, resolveBackground } from '../model/Wallpaper';
|
||||
@ -1477,6 +1478,14 @@ struct MainPage {
|
||||
* 逐个加 class 必然漏(漏掉的那块就是一张不透明卡片浮在背景上)」。
|
||||
*/
|
||||
@State bgActive: boolean = false;
|
||||
/** 环境变化回调 id(-1 = 没订阅);`lastColorMode` 用来只在真的换向时重算 */
|
||||
private envCallbackId: number = -1;
|
||||
/**
|
||||
* 上一次看到的系统深浅色。类型跟着 SDK 走(`Configuration.colorMode` 是
|
||||
* `ConfigurationConstant.ColorMode | undefined`)—— 我先写成 `number` 初值取 `NOT_SET`,
|
||||
* 编译直接报"枚举类型不能赋给 number",于是照 SDK 的类型写。
|
||||
*/
|
||||
private lastColorMode: ConfigurationConstant.ColorMode | undefined = undefined;
|
||||
@State wallpaperImage: image.PixelMap | null = null;
|
||||
private gridSettings: RenderingContextSettings = new RenderingContextSettings(true);
|
||||
private gridCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.gridSettings);
|
||||
@ -1484,6 +1493,57 @@ struct MainPage {
|
||||
aboutToAppear(): void {
|
||||
// 主界面也要应用外观:只在设置页生效的话"一进主界面就变回去"(WebUI 侧踩过)
|
||||
this.applyAppearance();
|
||||
this.watchEnvironment();
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
this.unwatchEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅系统环境变化(深浅色切换),**重算我们自己算出来的那部分**。
|
||||
*
|
||||
* pi 2026-09-14 给的规则(这条会被 P5/P6 咬到):
|
||||
* **系统自动跟随的东西(语义色、材质)不会顺带把"我们自己算出来的值"一起更新** ——
|
||||
* 凡是我们计算/缓存且随主题变化的值,都必须挂在**同一个主题变化事件**上重算,
|
||||
* 否则它迟早是界面上唯一一处不跟随的。
|
||||
*
|
||||
* 这里"我们自己算的"就是预设色板(`layersFor(id, dark)`):系统的 surface/文字/材质
|
||||
* 会立刻跟着深浅色换,而色板是我们算的 —— 不重算就会出现"一部分跟随、一部分不跟随"的撕裂,
|
||||
* 正是这一整轮在治的病。所以除了 `applyAppearance()`(把色板按当前深浅重算一遍),
|
||||
* 不引入别的机制。
|
||||
*/
|
||||
private watchEnvironment(): void {
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this.envCallbackId >= 0) {
|
||||
return; // 已经订阅过(aboutToAppear 可能被多次触发)
|
||||
}
|
||||
const cb: EnvironmentCallback = {
|
||||
onConfigurationUpdated: (config: Configuration) => {
|
||||
if (config.colorMode !== this.lastColorMode) {
|
||||
this.lastColorMode = config.colorMode;
|
||||
// 只重算我们自己的那部分;系统色/材质由系统自己换
|
||||
this.applyAppearance();
|
||||
}
|
||||
},
|
||||
onMemoryLevel: () => {
|
||||
}
|
||||
};
|
||||
this.envCallbackId = ctx.getApplicationContext().on('environment', cb);
|
||||
}
|
||||
|
||||
private unwatchEnvironment(): void {
|
||||
if (this.envCallbackId < 0) {
|
||||
return;
|
||||
}
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx !== undefined) {
|
||||
ctx.getApplicationContext().off('environment', this.envCallbackId);
|
||||
}
|
||||
this.envCallbackId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1585,6 +1645,15 @@ struct MainPage {
|
||||
})
|
||||
}
|
||||
}, (layer: PresetLayer, idx: number) => layer.kind + idx)
|
||||
/*
|
||||
* 遮盖层:**预设档也要**(与 WebUI 的 `--bg-dim` 一致,它不区分档位)。
|
||||
* 用**系统遮罩色** + 服务端浓度:浅色下由系统"洗白"、深色下"压黑",
|
||||
* 不自己写 alpha。目的不是装饰,是让压在渐变上的正文读得动。
|
||||
*/
|
||||
Column()
|
||||
.width('100%').height('100%')
|
||||
.backgroundColor(Theme.overlay)
|
||||
.opacity(this.bgPlan.scrim)
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
} else if (this.bgPlan.kind === 'image' && this.wallpaperImage !== null) {
|
||||
|
||||
Reference in New Issue
Block a user