跨端: 预设色板两套(pi:这不是"观感未验"而是机制上确定不同)+ 手写色清册跨文件 + TMPDIR 按会话分家 + 提交归属可判
pi 读完 `model/Wallpaper.ts` 后指出四处,全部处理。这一提交同时改了
`client/harmony/` 与 `client/electron/`(跨端改动),所以 subject 按新约定自报家门。
## 1 预设色板不随主题 —— **类别判错了:不是"未验",是机制上确定不同**
我上一版把"深色档预设"记成"观感未验"。pi 指出:WebUI 的 `.bg-preset-*` 写的是
`rgb(var(--c-blue-100))`,而 `--c-*` 在 `.dark` 里整体换了一套(blue-100 → `30 43 67`),
所以 **WebUI 的预设自动随主题变**;这边只有浅色那套 = 深色主题下"浅色渐变垫在深色系统表面之下",
正是这一整轮在治的病。**它不需要真机就能判**(机制写在代码里)—— 我把可判的东西
记成了"未验",这跟上一轮把"没做"写成"没验"是同一类错。
选 pi 倾向的那条(跟随主题,与 WebUI 一致):
- 色板两套:`LIGHT_*` 取 CSS `:root`、`DARK_*` 取 CSS `.dark`;`paletteFor(dark)` 选一套,
`layersFor(id, dark)` 按主题出层;
- `isDarkMode(theme, systemColorMode)` 放在纯逻辑里:选了 dark/light 就照办,
`system` 看系统当时的 `colorMode`(锚到 SDK:`COLOR_MODE_DARK = 0` / `COLOR_MODE_LIGHT = 1`;
读不到按浅色,与 WebUI 的 `:root` 默认一致);
- 系统深浅从 `resourceManager.getConfigurationSync().colorMode` 读
(`Context` 基类没有 `config`;`UIAbilityContext.config` 要转型;两个枚举取值一致,都核过 SDK);
- 判据:两套值与 `:root`/`.dark` **逐个相等**;每个预设的深浅两套**必须真的不同**
(否则"两套"是抄了两遍);网格线色也要换;`isDarkMode` 五种输入。
- **未做**:运行期间改系统深浅色不会自动重算(要重进页面)——系统侧正确做法是订阅
`applicationContext.on('environment', …)`,记在 §7.17b。
变异:`DARK_BLUE_100` 偏一位 → 红;`paletteFor` 永远返回浅色(= 我原来那个状态)→ 红;
`isDarkMode` 把系统深浅记反 → 红;页面把深浅写死成 false → 红。
## 2 手写色清册**跨文件按类扫**(原 A2 只保护 `Theme.ets`)
`Wallpaper.ts` 也有手写色。若对照是"按名字枚举"的,第 15 个色就会逃掉 ——
与 A2 要防的是同一件事,只是换了文件。现在一份清册按类扫:全 `ets/` 树里每个
`X: string = '#RRGGBB'` 都必须登记(Theme 的品牌/业务语义色,或预设色板 ——
后者常量名必须带 `LIGHT_`/`DARK_` 前缀,值由 CSS 两段比对负责)。反向也判清册过期。
变异:`Theme.ets` 加未登记色 → 红;`Wallpaper.ts` 加未登记色 → 红;
加一个"看着合规"的 `DARK_EXTRA` → 红。
## 3 `TMPDIR` 互踩(pi 提出)
这个 worktree 可能同时有多个 agent 跑构建,而 fpm 会把 291MB 的 `linux-unpacked`
**整份复制**进 `TMPDIR` —— 撞车就是随机的产物损坏。`whoami` 区分不开(大家都是 root),
所以按**会话**分家:`TMPDIR=$PWD/.tmp/${DSH_SESSION_ID:-$(id -un)-$$}`
(进了 `npm run build:linux` 与 BUILD.md 的手敲命令;普通终端退化成"用户+PID")。
## 4 提交归属变成**跑判据就看得出来**(pi 给的形状)
新的 `test/commit-hygiene.test.mjs`:扫最近 40 条提交,**同时改两侧目录**的提交
必须在 subject 里自报家门(`跨端:`)。两条防腐:基线 = 该判据文件自己的引入提交
(**历史不改**,规则管从今往后);分类逻辑拿合成输入自检
(未标注的混合提交必须判红、标注过的不许红)——否则"解析没跑起来"时它会全绿。
变异:`COMMIT_HYGIENE_BASELINE` 指到老提交 → 历史里那两个被卷进去的提交立刻判红。
## 验证
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;`commit-hygiene` 在本提交落地后基线生效)。
This commit is contained in:
@ -222,6 +222,30 @@ export function colorModeValue(theme: string): number {
|
||||
return -1; // COLOR_MODE_NOT_SET(跟随系统)
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前主题**实际**算不算深色 —— 预设色板要不要用深色那套由它决定。
|
||||
*
|
||||
* 为什么不直接看 `theme` 就完事:`theme === 'system'` 时我们并不知道系统当时是深是浅,
|
||||
* 要把系统反馈进来的 `colorMode` 一起看。数值同样与 SDK 的
|
||||
* `ConfigurationConstant.ColorMode` 对齐(0=深、1=浅、-1=未设置)。
|
||||
*
|
||||
* **未设置/读不到时按浅色算**:与 WebUI 的默认(`:root` 那套)一致 ——
|
||||
* 宁可"系统深浅还没定下来时先用浅色那套",也不要拿深色色板去配浅色系统表面。
|
||||
*
|
||||
* ⚠️ 已知边界:用户在**应用运行期间**改系统深浅色,这里不会自动重算
|
||||
* (要重新进一次页面/重进应用)。系统侧的正确做法是订阅
|
||||
* `applicationContext.on('environment', ...)` 的配置变化回调 —— 记在文档里,没做。
|
||||
*/
|
||||
export function isDarkMode(theme: string, systemColorMode: number): boolean {
|
||||
if (theme === 'dark') {
|
||||
return true;
|
||||
}
|
||||
if (theme === 'light') {
|
||||
return false;
|
||||
}
|
||||
return systemColorMode === 0; // COLOR_MODE_DARK
|
||||
}
|
||||
|
||||
/** 遮罩浓度:0~90 的"压暗"值 → 0~1(系统遮罩色 + 这个不透明度) */
|
||||
export function scrimOpacity(bgDim: number): number {
|
||||
const d: number = clampNumber(bgDim, 0, 90, 12);
|
||||
|
||||
@ -41,18 +41,63 @@ export class PresetLayer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设色板(浅色档,取自 WebUI 的调色板变量)。
|
||||
* 预设色板 —— **两套**(浅色 / 深色),值逐个取自 WebUI `index.css` 的调色板变量。
|
||||
*
|
||||
* 这些色**不走系统语义色**:它们是这套预设的设计内容(与品牌蓝同类),
|
||||
* 系统里没有"极光渐变"这种东西。深色档下预设的观感**未验**(见文档)。
|
||||
* 这些色不走系统语义色:它们是这套预设的设计内容(与品牌蓝同类),
|
||||
* 系统里没有"极光渐变"这种东西。
|
||||
*
|
||||
* ⚠️ **为什么必须两套**(pi 2026-09-14 指出,这一条不是"观感未验"而是机制上确定不同):
|
||||
* WebUI 的 `.bg-preset-*` 写的是 `rgb(var(--c-gray-200) / 0.55)`、`rgb(var(--c-blue-100))`,
|
||||
* 而 `--c-*` 在 `.dark` 里**整体换了一套**(blue-100 → `30 43 67`)——
|
||||
* 也就是说 WebUI 的预设**自动随主题变**。若这边只有浅色那套,
|
||||
* 深色主题下就会是"浅色渐变垫在深色系统表面之下",正是这一整轮在治的那种病。
|
||||
* 它不需要真机就能发现:机制写在代码里。
|
||||
*
|
||||
* 判据把两套值与 `index.css` 的 `:root` 段、`.dark` 段**逐个比对**(和抓 `#BFDCFE` 同一个手法)。
|
||||
*/
|
||||
const GRAY_100: string = '#F3F4F6'; // --c-gray-100
|
||||
const GRAY_200: string = '#EAECF1'; // --c-gray-200
|
||||
const BLUE_100: string = '#DBEAFE'; // --c-blue-100
|
||||
const BLUE_200: string = '#BFDBFE'; // --c-blue-200(191 219 254)
|
||||
const GREEN_100: string = '#DCFCE7'; // --c-green-100
|
||||
const AMBER_100: string = '#FEF3C7'; // --c-amber-100
|
||||
const ORANGE_100: string = '#FFEDD5'; // --c-orange-100
|
||||
const LIGHT_GRAY_100: string = '#F3F4F6'; // --c-gray-100
|
||||
const LIGHT_GRAY_200: string = '#EAECF1'; // --c-gray-200
|
||||
const LIGHT_BLUE_100: string = '#DBEAFE'; // --c-blue-100
|
||||
const LIGHT_BLUE_200: string = '#BFDBFE'; // --c-blue-200(191 219 254)
|
||||
const LIGHT_GREEN_100: string = '#DCFCE7'; // --c-green-100
|
||||
const LIGHT_AMBER_100: string = '#FEF3C7'; // --c-amber-100
|
||||
const LIGHT_ORANGE_100: string = '#FFEDD5'; // --c-orange-100
|
||||
|
||||
const DARK_GRAY_100: string = '#20242C'; // --c-gray-100(.dark:32 36 44)
|
||||
const DARK_GRAY_200: string = '#272B34'; // --c-gray-200(.dark:39 43 52)
|
||||
const DARK_BLUE_100: string = '#1E2B43'; // --c-blue-100(.dark:30 43 67)
|
||||
const DARK_BLUE_200: string = '#203454'; // --c-blue-200(.dark:32 52 84)
|
||||
const DARK_GREEN_100: string = '#1A362B'; // --c-green-100(.dark:26 54 43)
|
||||
const DARK_AMBER_100: string = '#3B301D'; // --c-amber-100(.dark:59 48 29)
|
||||
const DARK_ORANGE_100: string = '#3C291F'; // --c-orange-100(.dark:60 41 31)
|
||||
|
||||
/** 一套色板(浅或深)。做成类而不是散着的常量:选哪套是**一处**决定。 */
|
||||
export class Palette {
|
||||
dark: boolean = false;
|
||||
gray100: string = LIGHT_GRAY_100;
|
||||
gray200: string = LIGHT_GRAY_200;
|
||||
blue100: string = LIGHT_BLUE_100;
|
||||
blue200: string = LIGHT_BLUE_200;
|
||||
green100: string = LIGHT_GREEN_100;
|
||||
amber100: string = LIGHT_AMBER_100;
|
||||
orange100: string = LIGHT_ORANGE_100;
|
||||
}
|
||||
|
||||
/** 当前该用哪套色板 */
|
||||
export function paletteFor(dark: boolean): Palette {
|
||||
const p: Palette = new Palette();
|
||||
p.dark = dark;
|
||||
if (dark) {
|
||||
p.gray100 = DARK_GRAY_100;
|
||||
p.gray200 = DARK_GRAY_200;
|
||||
p.blue100 = DARK_BLUE_100;
|
||||
p.blue200 = DARK_BLUE_200;
|
||||
p.green100 = DARK_GREEN_100;
|
||||
p.amber100 = DARK_AMBER_100;
|
||||
p.orange100 = DARK_ORANGE_100;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* `transparent` 用**关键字**而不是 8 位半透明色值:
|
||||
@ -119,30 +164,31 @@ function linear(angle: number, from: string, to: string): PresetLayer {
|
||||
* 层序 = 绘制顺序:先画的在下层。WebUI 的 CSS 里第一段是最上层(CSS 多重背景
|
||||
* 是后面的在下),为保持观感一致,这里**反过来给**:CSS 里最后一段先画。
|
||||
*/
|
||||
export function layersFor(id: string): PresetLayer[] {
|
||||
export function layersFor(id: string, dark: boolean): PresetLayer[] {
|
||||
const key: string = normalizePreset(id);
|
||||
const pal: Palette = paletteFor(dark);
|
||||
if (key === 'dusk') {
|
||||
return [
|
||||
radial(85, 25, '55%', BLUE_200, 55),
|
||||
radial(12, 80, '55%', ORANGE_100, 55)
|
||||
radial(85, 25, '55%', pal.blue200, 55),
|
||||
radial(12, 80, '55%', pal.orange100, 55)
|
||||
];
|
||||
}
|
||||
if (key === 'mint') {
|
||||
return [
|
||||
radial(78, 70, '55%', BLUE_100, 55),
|
||||
radial(25, 30, '55%', GREEN_100, 55)
|
||||
radial(78, 70, '55%', pal.blue100, 55),
|
||||
radial(25, 30, '55%', pal.green100, 55)
|
||||
];
|
||||
}
|
||||
if (key === 'sand') {
|
||||
return [
|
||||
radial(80, 75, '55%', ORANGE_100, 55),
|
||||
radial(20, 25, '60%', AMBER_100, 60)
|
||||
radial(80, 75, '55%', pal.orange100, 55),
|
||||
radial(20, 25, '60%', pal.amber100, 60)
|
||||
];
|
||||
}
|
||||
if (key === 'ink') {
|
||||
return [
|
||||
linear(160, GRAY_100, GRAY_200),
|
||||
radial(30, 20, '60%', GRAY_200, 60)
|
||||
linear(160, pal.gray100, pal.gray200),
|
||||
radial(30, 20, '60%', pal.gray200, 60)
|
||||
];
|
||||
}
|
||||
if (key === 'mesh') {
|
||||
@ -153,17 +199,17 @@ export function layersFor(id: string): PresetLayer[] {
|
||||
* 所以这一档用系统 `Canvas` 画线(仍是系统绘制能力,不是手写模糊那一类)。
|
||||
* 线色与间隔照抄 CSS:gray-200 / 0.55 / 28px。
|
||||
*/
|
||||
g.lineColor = GRAY_200;
|
||||
g.lineColor = pal.gray200;
|
||||
g.lineAlpha = 0.55;
|
||||
g.step = 28;
|
||||
const base: PresetLayer = linear(180, GRAY_100, GRAY_100);
|
||||
const base: PresetLayer = linear(180, pal.gray100, pal.gray100);
|
||||
return [base, g];
|
||||
}
|
||||
// aurora(默认)
|
||||
return [
|
||||
radial(68, 82, '55%', BLUE_100, 55),
|
||||
radial(82, 12, '50%', GREEN_100, 50),
|
||||
radial(18, 22, '55%', BLUE_200, 55)
|
||||
radial(68, 82, '55%', pal.blue100, 55),
|
||||
radial(82, 12, '50%', pal.green100, 50),
|
||||
radial(18, 22, '55%', pal.blue200, 55)
|
||||
];
|
||||
}
|
||||
|
||||
@ -171,6 +217,8 @@ export function layersFor(id: string): PresetLayer[] {
|
||||
export class BackgroundPlan {
|
||||
/** 'none' | 'preset' | 'image' */
|
||||
kind: string = 'none';
|
||||
/** 用的是深色那套色板吗(跟着主题走,不是"未验") */
|
||||
dark: boolean = false;
|
||||
presetId: string = '';
|
||||
layers: PresetLayer[] = [];
|
||||
/** 压暗(0~1)—— image 档压在图上;preset 档不用 */
|
||||
@ -184,12 +232,13 @@ export class BackgroundPlan {
|
||||
* 这里决定**画成什么**。`image` 档但图没取回来 → `none`:
|
||||
* 宁可什么都不画,也不要画一块空白(用户会以为壁纸坏了)。
|
||||
*/
|
||||
export function resolveBackground(bgKind: string, presetId: string, scrim: number, hasImage: boolean): BackgroundPlan {
|
||||
export function resolveBackground(bgKind: string, presetId: string, scrim: number, hasImage: boolean, dark: boolean): BackgroundPlan {
|
||||
const plan: BackgroundPlan = new BackgroundPlan();
|
||||
plan.dark = dark;
|
||||
if (bgKind === 'preset') {
|
||||
plan.kind = 'preset';
|
||||
plan.presetId = normalizePreset(presetId);
|
||||
plan.layers = layersFor(plan.presetId);
|
||||
plan.layers = layersFor(plan.presetId, dark);
|
||||
return plan;
|
||||
}
|
||||
if (bgKind === 'image' && hasImage) {
|
||||
|
||||
@ -12,7 +12,7 @@ import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService, SseEvent } from '../api/SseService';
|
||||
import { AppearanceStore } from '../common/AppearanceStore';
|
||||
import { image } from '@kit.ImageKit';
|
||||
import { AppearanceSnapshot, scrimOpacity } from '../model/Appearance';
|
||||
import { AppearanceSnapshot, isDarkMode, scrimOpacity } from '../model/Appearance';
|
||||
import { BackgroundPlan, PresetLayer, TRANSPARENT, resolveBackground } from '../model/Wallpaper';
|
||||
import { MailSummary, Contact, PermissionRequest, DecideResponse, SentResponse, PendingResponse } from '../model/Models';
|
||||
import {
|
||||
@ -1505,7 +1505,20 @@ struct MainPage {
|
||||
await store.syncFromServer(ctx, client);
|
||||
const snap: AppearanceSnapshot = store.current();
|
||||
this.wallpaperImage = store.wallpaper;
|
||||
this.bgPlan = resolveBackground(snap.bgKind, snap.bgPresetId, scrimOpacity(snap.bgDim), store.wallpaper !== null);
|
||||
/*
|
||||
* 预设色板要跟着主题走:`system` 时把系统当时反馈的 colorMode 一起看
|
||||
* (`isDarkMode`)。否则深色主题下会是"浅色渐变垫在深色系统表面之下"。
|
||||
*/
|
||||
/*
|
||||
* 系统当时的深浅色:从 `resourceManager` 的配置读(`Context` 基类没有 `config`,
|
||||
* `UIAbilityContext.config` 要转型;而 `ctx.resourceManager.getConfigurationSync()`
|
||||
* 在基类上就有)。两个枚举的取值一致,都核过 SDK:
|
||||
* `ConfigurationConstant.ColorMode.DARK = 0 / LIGHT = 1`,
|
||||
* `resourceManager.ColorMode.DARK = 0 / LIGHT = 1`。
|
||||
*/
|
||||
const systemMode: number = ctx.resourceManager.getConfigurationSync().colorMode;
|
||||
const dark: boolean = isDarkMode(snap.theme, systemMode);
|
||||
this.bgPlan = resolveBackground(snap.bgKind, snap.bgPresetId, scrimOpacity(snap.bgDim), store.wallpaper !== null, dark);
|
||||
this.bgActive = this.bgPlan.kind !== 'none';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user