diff --git a/client/electron/test/cross-client-theme.test.mjs b/client/electron/test/cross-client-theme.test.mjs index 3d13e71..4b1f545 100644 --- a/client/electron/test/cross-client-theme.test.mjs +++ b/client/electron/test/cross-client-theme.test.mjs @@ -251,6 +251,19 @@ const SELF_OWNED_COLORS = [ * 浅壁纸与深壁纸(深色档另有 `DARK_DIM_MIN` 兜底)。 */ 'glassCard', 'glassCardWall', + /* + * ★★ 2026-09-21 补登记:深色档的玻璃 alpha。 + * + * 与上面两个是**同一个理由的正反两面**:WebUI 深色下把白纱 alpha + * 从 0.92 降到 0.06(`.dark { --glass-card-a: 0.06; }`), + * 因为"白基材恒白、主题之间只差 alpha"。 + * + * 不登记的话界面会彻底变暗 —— 而这件事**实测抓到过**: + * 深色 + aurora 壁纸下,卡片缝隙实测 `rgb(199,199,199)` + * (= 0.78×255,那层白纱把黑壁纸糊成浅灰), + * 用户看到的"没有玻璃效果"就是这个。 + */ + 'glassCardDark', 'glassCardWallDark', // 业务语义色:系统没有对应物(同意 / 拒绝 / 警示) 'approve', 'danger', 'approveBg', 'approveFg', 'dangerBg', 'warnBg', 'warnFg', // 权限档位与预算档位的胶囊配色(档位是产品语义,系统不认识"plan/workspace/full") @@ -373,10 +386,27 @@ test('B|旧机制不得回来:手写玻璃 alpha、替系统猜深色、与 * 不是"只要是玻璃就放行"。加第三个半透明色 ⇒ 这条判据照样红, * 而那时得先回答"系统为什么没有对应物"这个问题。 * + * ★★ 2026-09-21 补登记:`glassCardDark` / `glassCardWallDark`。 + * + * 这一条判据**真的抓到过东西**:我给深色加两个更薄的 alpha 时它立刻变红, + * 逼我把"为什么系统没有对应物"重新回答一遍(而不是默默放行)。那个回答是: + * + * · 系统材质(`bgMaterial*` / `compBackground*`)是**不透明**整块色板, + * 没有"白 6% 叠在壁纸上"这一档; + * · 而 WebUI 深色档的玻璃观感**正是这个 alpha** + * (`.dark` 里 `--glass-card-a: 0.06`,`--glass-card-wall-a: 0.04`)—— + * 浅色是 0.92 / 0.78,**深浅差 15 倍**。 + * + * 所以深色这两个值与浅色那两个是**同一个理由**(同一条豁免的正反两面), + * 不是"又开了个口子"。 + * + * ⇒ glassCardDark = #0FFFFFFF(0.06×255 ≈ 15 = 0x0F) + * ⇒ glassCardWallDark = #0AFFFFFF(0.04×255 ≈ 10 = 0x0A) + * * ★ 白名单项与 `SELF_OWNED_COLORS` 是**两个不同的问题**: * 那个问"色值有没有登记理由",这个问"半透明这件事合不合法"。 */ - const TRANSLUCENT_OK = ['#EBFFFFFF', '#C7FFFFFF']; // glassCard / glassCardWall + const TRANSLUCENT_OK = ['#EBFFFFFF', '#C7FFFFFF', '#0FFFFFFF', '#0AFFFFFF']; // 玻璃卡 浅/深 两档 const translucent = rawColors(codeOnly) .filter(h => h.startsWith('#') && h.length === 9) .filter(h => !TRANSLUCENT_OK.includes(h.toUpperCase())); @@ -679,11 +709,49 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记( '卡片层不得挂 backgroundEffect —— WebUI `.glass-card` 没有 backdrop-filter,' + '卡片的玻璃感来自半透明白。加模糊会叠成灰雾并掉帧(已实测)。'); - /* ② 两个 alpha 令牌必须真的被卡片读(否则是孤儿令牌,见 C 的另一段) */ - assert.match(cardBody, /Theme\.glassCard\b/, - '卡片要读 Theme.glassCard(0.92 那档,照抄 WebUI rgb(255 255 255 / 0.92))'); - assert.match(cardBody, /Theme\.glassCardWall\b/, - '卡片要读 Theme.glassCardWall(0.78 那档,对应 WebUI html[data-bg=\'on\'])'); + /* + * ★★ 2026-09-21 修:卡片现在读的是 `Theme.glassCardFor(this.active)` —— + * **主题感知**的那个入口,它内部再分派到四个令牌: + * glassCard / glassCardWall (浅色:白 0.92 / 0.78) + * glassCardDark / glassCardWallDark (深色:白 0.06 / 0.04) + * + * 原来这里钉的是 `Theme.glassCard` / `Theme.glassCardWall` 两个字面量。 + * 而 2026-09-21 设备实测证明**单一值是不对的**: + * 深色 + aurora 壁纸下,卡片缝隙实测 `rgb(199,199,199)` + * (= 0.78×255,那层厚白纱把黑壁纸糊成浅灰), + * 用户报的「没有玻璃效果」正是这个。WebUI 深色把 alpha 降到 0.06(差 15 倍)。 + * + * ★ 为什么不改成 `assert.match(cardBody, /Theme\.glassCardDark/)`: + * 那就把"用哪一档"钉死了 —— 而"壁纸档用 wall、否则用 card"是**行为**, + * 应该由 `glassCardFor` 的一处实现决定,而不是由判据数调用点。 + * 所以这里改为:**入口存在**,且**四个令牌在 Theme 里都存在且被入口读到**。 + * 真正的行为验证在下面 ②' 那一段(深浅两个值必须不同)。 + */ + assert.match(cardBody, /Theme\.glassCardFor\(/, + '卡片要经 Theme.glassCardFor(...) 取底色 —— 它按当前深浅分派四档 alpha'); + + /* ②' 四个令牌都得在 Theme 里真的存在,且入口真的按深浅分派 */ + const themeSrcForGlass = code(join(HARMONY_ETS, 'common', 'Theme.ets')); + for (const name of ['glassCard', 'glassCardWall', 'glassCardDark', 'glassCardWallDark']) { + assert.match(themeSrcForGlass, new RegExp(`static readonly ${name}: string = '#[0-9A-Fa-f]{8}'`), + `Theme 里要有 ${name}(四档 alpha 缺一不可)`); + } + /* + * ★ 深色档必须**真的不同**于浅色档 —— 否则 "主题感知" 是假的。 + * 这条不依赖具体数值(那些数值已在 `C2` 与`SELF_OWNED_COLORS` 那边登记), + * 只钉"变了"这件事: + * 浅色 0.92 / 0.78 vs 深色 0.06 / 0.04 + * 若哪个改成与浅色同值,这条红。 + */ + const glassAlpha = (name) => { + const m = themeSrcForGlass.match(new RegExp(`static readonly ${name}: string = '#([0-9A-Fa-f]{2})`)); + return m ? m[1].toUpperCase() : ''; + }; + assert.notEqual(glassAlpha('glassCardDark'), glassAlpha('glassCard'), + '深色档 glassCardDark 不得与浅色 glassCard 同值 —— 同值等于没有主题感知'); + assert.notEqual(glassAlpha('glassCardWallDark'), glassAlpha('glassCardWall'), + '深色档 glassCardWallDark 不得与浅色 glassCardWall 同值;' + + '实测教训:深色下白纱 0.78 会把黑壁纸糊成 rgb(199,199,199) 的深灰纸板'); /* * ③ **不许**用 `backgroundEffect` —— 2026-09-21 实测否掉了我上一版的推断。 @@ -946,7 +1014,19 @@ test('遮罩:交给系统的遮罩语义色("随主题换向"这件事现在 * 而不是把这条判据整个放宽:遮罩那个真实约束**原样保留**。 * `overlay` / `wallpaperScrim` 若被写成 8 位单值,这条照样红。 */ - const glassAlphaOk = ['#EBFFFFFF', '#C7FFFFFF']; // 与 B 条同一份名单 + /* + * ★★ 2026-09-21 再修正:白名单里要加上**深色档**那两个。 + * + * 上面这轮修正把名单限制成"同名白名单",但写的是**两个**名字; + * 而 WebUI 的玻璃 alpha 本来就是**深浅两套** + * (`.dark` 里 `--glass-card-a: 0.06` / `--glass-card-wall-a: 0.04`)—— + * 我先前只搬了浅色那两个,正是「玻璃在深色下变成灰纸板」的成因。 + * + * ⇒ 现在四个一起列,与 B 条的 `TRANSLUCENT_OK` **同一份名单**。 + * 遮罩那个真实约束**原样保留**:`overlay` / `wallpaperScrim` + * 若被写成 8 位单值,这条照样红(下面这句断言就是干这个的)。 + */ + const glassAlphaOk = ['#EBFFFFFF', '#C7FFFFFF', '#0FFFFFFF', '#0AFFFFFF']; // 玻璃卡浅/深两档 const all8 = (stripComments(harmony).match(/#[0-9A-Fa-f]{8}/g) ?? []) .filter(h => !glassAlphaOk.includes(h.toUpperCase())); assert.deepEqual(all8, [], diff --git a/client/harmony/entry/src/main/ets/common/AppearanceStore.ets b/client/harmony/entry/src/main/ets/common/AppearanceStore.ets index 8242210..f410337 100644 --- a/client/harmony/entry/src/main/ets/common/AppearanceStore.ets +++ b/client/harmony/entry/src/main/ets/common/AppearanceStore.ets @@ -12,6 +12,8 @@ * 所以这里唯一的动作是 `setColorMode`,而不是换一套 Theme 常量。 */ import { common } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BusinessError } from '@kit.BasicServicesKit'; import { image } from '@kit.ImageKit'; import { preferences } from '@kit.ArkData'; import { ApiClient } from '../api/ApiClient'; @@ -113,7 +115,20 @@ export class AppearanceStore { const app = ctx.getApplicationContext(); app.setColorMode(colorModeValue(theme)); } catch (e) { - // 改不了色彩模式不该让页面挂掉(旧系统/权限):界面仍按当前模式渲染 + /* + * 改不了色彩模式不该让页面挂掉(旧系统/权限):界面仍按当前模式渲染。 + * + * ★★ 2026-09-21:**这里必须打日志**(原来只是静默吞掉)。 + * 设备实测发现「点『深色』后服务端记下了 dark、按钮也显示选中, + * 但整个界面仍是浅色,且文字是浅色压浅底(对比度 1.0:1,完全不可读)」—— + * 而**没有任何日志**说明 `setColorMode` 是失败了还是根本没被调用。 + * 静默 catch 把"失败"和"成功"变得无法区分,这正是这一整轮在治的形状。 + */ + const be = e as BusinessError; + hilog.error(0x0001, 'AppearanceStore', + 'setColorMode(%{public}d) failed: code=%{public}d msg=%{public}s', + colorModeValue(theme), be.code !== undefined ? be.code : 0, + be.message !== undefined ? be.message : ''); } } diff --git a/client/harmony/entry/src/main/ets/common/Surface.ets b/client/harmony/entry/src/main/ets/common/Surface.ets index bd74885..d2926db 100644 --- a/client/harmony/entry/src/main/ets/common/Surface.ets +++ b/client/harmony/entry/src/main/ets/common/Surface.ets @@ -407,7 +407,17 @@ export class GlassCardModifier implements AttributeModifier { * * ⇒ 照 WebUI:**白色 + alpha,不模糊**。 */ - instance.backgroundColor(this.active ? Theme.glassCardWall : Theme.glassCard); + /* + * ★★ 2026-09-21 改成**主题感知**的取值(`glassCardFor`)。 + * + * 原来这里是 `this.active ? Theme.glassCardWall : Theme.glassCard` —— + * **单一值、不分深浅**。设备实测(深色 + aurora 壁纸): + * 卡片缝隙 rgb(199,199,199) / 卡片内 rgb(27,36,53) + * 199 = 0.78×255,即那层"白纱"在深色下把壁纸糊成浅灰 —— + * 用户看到的"没有玻璃效果"就是这个。 + * 令牌侧的推理与 WebUI 出处见 `Theme.glassCardFor`。 + */ + instance.backgroundColor(Theme.glassCardFor(this.active)); /* * ★ 只模糊+饱和仍然"像一块平的白方块" —— 玻璃板之所以像板,靠的是**边缘**: * 它要看起来**浮在壁纸之上**。WebUI 的完整配方(`.app-shell > *`): diff --git a/client/harmony/entry/src/main/ets/common/Theme.ets b/client/harmony/entry/src/main/ets/common/Theme.ets index c30049c..34c056c 100644 --- a/client/harmony/entry/src/main/ets/common/Theme.ets +++ b/client/harmony/entry/src/main/ets/common/Theme.ets @@ -309,6 +309,61 @@ export class Theme { */ static readonly glassCard: string = '#EBFFFFFF'; static readonly glassCardWall: string = '#C7FFFFFF'; + /* + * ★★ 2026-09-21 **深色下的玻璃 alpha(这是「没有玻璃效果」的真正原因)**。 + * + * ── 实测症状(设备,深色主题,壁纸 aurora 开启)── + * + * 同一行上交替采样(`/tmp/g1.raw`,1008×2232): + * y=670 卡片缝隙 rgb(199,199,199) 卡片内 rgb(27,37,50) + * y=880 卡片缝隙 rgb(201,203,201) 卡片内 rgb(27,36,53) + * + * 缝隙是**浅灰 199**、卡片是**深色** —— 界面看起来像"深色卡片浮在灰纸板上"。 + * 而 199 恰好 = `0.78 × 255`(`glassCardWall` 的白 alpha 压在近黑壁纸上, + * 实测壁纸层 x=4..24 为 rgb(0,1,3))。 + * + * ── 根因:`glassCard`/`glassCardWall` 是**单一值、不分深浅**── + * + * 我当初写的理由是"基色恒为白,主题之间只差 alpha"——**后半句才是 WebUI 的实情**, + * 而我只抄了"基色恒白",把"只差 alpha"理解成了"alpha 也不用变"。 + * + * WebUI 深色档(`index.css` §`.dark`): + * --glass-card-a: 0.06 ← 浅色是 0.92 + * --glass-card-hover-a: 0.1 + * --glass-card-wall-a: 0.04 ← 浅色是 0.78 + * + * 即深色下白纱要**几乎撤掉**(0.92 → 0.06,差 **15 倍**),让深色壁纸透上来; + * 浅色下才用厚白纱盖住亮壁纸。我用同一个 0.92 配两套主题 ⇒ + * 深色下那层白纱把壁纸整片糊成浅灰 199,**玻璃感与深色同时消失**。 + * + * ── 修法 ── + * + * 与 `accentFor` / `textSubtleFor` 同一范式:`*For(dark?)` 访问器读 + * `Theme.isDarkNow()`(从 `AppStorage` 那个发布键读,不靠调用方自觉传)。 + * + * `0.06 × 255 ≈ 15 = 0x0F`、`0.04 × 255 ≈ 10 = 0x0A`。 + * 这两个值仍走登记过的 `SELF_OWNED_COLORS` 豁免(理由与浅色那两个相同: + * 系统材质没有"白 6% 叠壁纸"这一档,而玻璃观感正是这个 alpha)。 + * + * ★ 为什么不复用 `Theme.glassCard` 只调 `brightness`: + * `brightness` 是**乘性**的,改的是所有颜色而不只是那层白纱; + * 而这里要改的恰好只是"白纱有多厚"。两者不是同一个旋钮。 + */ + static readonly glassCardDark: string = '#0FFFFFFF'; + static readonly glassCardWallDark: string = '#0AFFFFFF'; + + /** + * 卡片底(**主题感知**)—— 浅色厚白纱、深色近乎透明。 + * + * 调用方只需给"是不是壁纸档",深浅由令牌自己从发布处读。 + */ + static glassCardFor(wall: boolean, dark?: boolean): string { + const isDark: boolean = Theme.isDarkNow(dark); + if (wall) { + return isDark ? Theme.glassCardWallDark : Theme.glassCardWall; + } + return isDark ? Theme.glassCardDark : Theme.glassCard; + } /** * 「悬浮玻璃板」的另外两半:**投影 + 发丝描边**。 * @@ -367,9 +422,30 @@ export class Theme { * 第 12 个**新加的手写色**(它不在名单里,于是 A/B/裸色值三条都碰不到它)。 * 「枚举挡实例,类才挡漂移」—— 这次枚举的是**名字**,所以要有名单。 * - * 登记项(26 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写, + * 登记项(28 个,名字与判据里的 SELF_OWNED_COLORS 逐字一致 —— 逐个列出而不是缩写, * 这样 grep 一个名字就能找到它的理由): * + * 玻璃(**白基材 + alpha**,深浅各一档;系统材质没有这一档): + * · glassCard / glassCardWall #EBFFFFFF / #C7FFFFFF 浅色:白 0.92 / 0.78 + * · glassCardDark / glassCardWallDark #0FFFFFFF / #0AFFFFFF 深色:白 0.06 / 0.04 + * ★★ 2026-09-21 **这一对是"没有玻璃效果"的真正原因**(设备实测撞出来的)。 + * 我原先把 `glassCard`/`glassCardWall` 写成**单一值、不分深浅**, + * 理由是"基色恒为白" —— 但 WebUI 那句话的后半句才是实情: + * 「基材恒为白,**主题之间只差 alpha**」(`index.css:406`、`:1546`)。 + * 我只抄了"基色恒白",把"只差 alpha"误读成"alpha 也不用变"。 + * + * WebUI `.dark`:`--glass-card-a: 0.06` / `--glass-card-wall-a: 0.04` + * (浅色是 0.92 / 0.78,**深浅差 15 倍**)。 + * + * 设备实测(深色 + aurora 壁纸,`/tmp/g1.raw` 1008×2232): + * 卡片缝隙 rgb(199,199,199) 卡片内 rgb(27,37,50) + * 199 恰好 = 0.78×255 —— 那层厚白纱把黑壁纸糊成了浅灰, + * 界面读起来像"深色卡片浮在灰纸板上",玻璃感与深色一起消失。 + * 改用 #0FFFFFFF(0.06)后同一处实测 rgb(3..16) —— 壁纸透过来了。 + * + * 入口:`Theme.glassCardFor(wall, dark?)`(从 `AppStorage` 的 + * `KEY_IS_DARK` 读深浅,不靠调用方自觉传)。 + * * 品牌(跨客户端身份,系统给不了): * · accent #2563EB 与 WebUI 的 --c-blue-600 逐字一致 * · accentFg #FFFFFF 品牌底上的文字 diff --git a/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets b/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets index e590401..c8e66a0 100644 --- a/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets +++ b/client/harmony/entry/src/main/ets/entryability/EntryAbility.ets @@ -13,7 +13,12 @@ * limitations under the License. */ -import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +/* + * `ConfigurationConstant` 本轮**不再需要**:原来为 `setColorMode(COLOR_MODE_NOT_SET)` + * 引的,那句已删(见 `onCreate` 里的说明)。应用色彩模式现在统一由 + * `MainPage.applyAppearance()` 按用户存下来的 `snap.theme` 决定。 + */ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; /* * ★ `KeyboardAvoidMode` 必须从 `@kit.ArkUI` 引(= `@ohos.arkui.UIContext` 那个)。 @@ -37,11 +42,37 @@ export default class EntryAbility extends UIAbility { private ledger: NotificationLedger = new NotificationLedger(50); onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { - try { - this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); - } catch (err) { - hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); - } + /* + * ★★ 2026-09-21 **删掉了这里的一句 `setColorMode(COLOR_MODE_NOT_SET)`**。 + * + * 原来这里是: + * this.context.getApplicationContext() + * .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + * + * 它的语义是"跟随系统",而它是**无条件**执行的 ⇒ 每次冷启都把应用色彩模式 + * 重置回跟系统走,**用户在「我的」页选的浅色/深色被丢掉**。 + * + * 实测症状:选「深色」后按钮显示选中、服务端也存了 dark,但界面仍是浅色 + * (背景 rgb(243,243,243) / 文字 rgb(243,243,243),对比度 ≈1:1,不可读)。 + * + * 为什么会这样:它是目录迁移时抄过来的(`git log -S` 指向 + * `f9d757b chore: directory migration`),**没有任何注释说明为什么**。 + * + * ── 现在由谁负责 ── + * + * `MainPage.applyAppearance()`:它读本机缓存 + 服务端合并出 `snap.theme`, + * 然后调用 `store.applyTheme(ctx, snap.theme)` 真正应用。 + * 那里才是"用户偏好的权威"所在。 + * + * ★ 为什么不在 ability 里也读一次 preferences: + * 主题是**按账号**存的(键 `appearance.`,见 `AppearanceStore`), + * 而 `onCreate` 时活跃账号可能还没加载出来 ⇒ 会读到错的账号、 + * 或者读到空值又退回默认。**一个事实一个权威来源**,这里不重复。 + * + * ★ 会不会因此丢掉"跟随系统"这条语义:不会 —— + * `theme === 'system'` 时 `colorModeValue()` 返回的正是 `COLOR_MODE_NOT_SET`, + * 由 `MainPage` 按用户**实际选中的档位**决定,而不是无条件。 + */ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); /* * ★ 2026-09-15:冷启点通知也要跳转。 diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 2185a39..a445ec8 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -3317,12 +3317,24 @@ struct MainPage { const store: AppearanceStore = AppearanceStore.getInstance(); const snap: AppearanceSnapshot = store.current(); snap.theme = next; + this.isDarkNow = next === 'dark'; + /* + * ★★ 2026-09-21 顺序修正:**先发布、再应用主题**。 + * + * 原来这里 `store.applyTheme()` 在 `AppStorage.setOrCreate(KEY_IS_DARK, …)` + * **之前**。而 2026-09-21 起 `Theme.glassCardFor()` 是**从那个键读深浅**的 + * (`Theme.isDarkNow()` → `AppStorage.get(KEY_IS_DARK)`)。 + * + * 后果:`setColorMode` 会触发一轮重渲染,那一轮重渲染里 + * `glassCardFor()` 读到的还是**旧**的深浅 ⇒ 卡片用错 alha 画一帧, + * 下一轮才纠正。切主题本来就慢(260ms 淡出),这一帧错色容易被看见。 + * + * ⇒ 顺序固定为:算 → 发布 → 应用(含重渲染)。 + */ + AppStorage.setOrCreate(this.KEY_IS_DARK, this.isDarkNow); /* 本机先生效(不等人看着);服务端写失败只影响"换设备带不带着走" */ store.applyTheme(ctx, next); store.saveLocal(ctx, snap); - this.isDarkNow = next === 'dark'; - /* 侧栏的主题快捷键也要把新状态发布出去(否则窗格里的浅底不跟着换) */ - AppStorage.setOrCreate(this.KEY_IS_DARK, this.isDarkNow); const client: ApiClient = ApiClient.getInstance(ctx); new AppearanceApi(client).put(snap, store.wallpaper !== null).catch(() => {}); } @@ -3425,6 +3437,42 @@ struct MainPage { const dark: boolean = isDarkMode(snap.theme, systemMode); /* 侧栏底部那个主题按钮的图标跟着它走(WebUI `ThemeToggleButton` 同口径) */ this.isDarkNow = dark; + /* + * ★★ 2026-09-21 **把用户存的主题真正应用上去**(这里原来漏了这一步)。 + * + * ── 实测到的症状 ── + * + * 「我的」页选「深色」后: + * · 按钮显示为选中(`appearanceTheme` 来自存储,是 'dark'); + * · 服务端也记下了 `theme:'dark'`(实测 `GET /me/appearance` 返回 dark); + * · 但**界面仍是浅色**,而且文字是浅色压浅底 —— 实测 + * 背景 `rgb(243,243,243)` / 文字 `rgb(243,243,243)`,对比度 ≈1.0:1,完全不可读。 + * + * ── 根因(两处叠加)── + * + * ① `EntryAbility.onCreate` 里有一句**无条件**的 + * `setColorMode(COLOR_MODE_NOT_SET)`(= 跟随系统)—— 每次冷启都把应用色彩模式 + * 重置回"跟系统走",用户的选择被丢掉。(那句是目录迁移时抄进来的, + * 没有任何注释说明为什么,也没有谁在用它。) + * ② 本函数算出了 `isDarkNow`、发布了 `AppStorage`,但**从来没有调用 + * `store.applyTheme(...)`** ⇒ 系统色彩模式没被设过。 + * + * ⇒ 两件事一起修:EntryAbility 那句删掉(改由这里按存储值决定), + * 本函数在算完之后**真的应用一次**。 + * + * ★ 为什么放在这里而不是"每次启动都设一遍默认值": + * 主题是**用户偏好**,权威在 `snap.theme`(本机缓存 + 服务端)。 + * 启动时不读它就是在丢用户的设置。 + * + * ★ 幂等:`setColorMode` 设同一个值不会有什么副作用 + * (`applyAppearance` 在启动、切账号、环境变化时都会被调用)。 + * + * ★★ 顺序很要緊:必须**先发布 `KEY_IS_DARK`、再应用主题**。 + * 下一条语句就是 `AppStorage.setOrCreate(this.KEY_IS_DARK, dark)`, + * 而 `Theme.glassCardFor()` 是从那个键读深浅的(`Theme.isDarkNow()`)。 + * 反过来的话,本函数内的卡片会用**上一次**的深浅重渲染一帧。 + * 所以这里的布局是:算 dark → 发布 dark → 应用主题(含重渲染)。 + */ /* * 把"当前是不是深色"**发布出去** —— 各窗格(通信/日历/联系/我的)里 * 有若干"品牌浅底"(`Theme.accentSoft`),深色下必须换成深色变体。 @@ -3433,6 +3481,13 @@ struct MainPage { * MainPage 算 → 写 → 窗格 `@StorageProp` 读。 */ AppStorage.setOrCreate(this.KEY_IS_DARK, dark); + /* + * ★★ 上面那段长注释说的就是这一行:把用户存的主题**真正应用上去**。 + * 位置在 `setOrCreate(KEY_IS_DARK, dark)` **之后” —— + * 因为 `Theme.glassCardFor()` 是从那个键读深浅的,先发布再重渲染, + * 这一次重渲染才会用对深浅。 + */ + store.applyTheme(ctx, snap.theme); /* * 侧栏头像的前两字:当前**活跃账号**的展示名(不是 `accountName`, * 那是收件箱筛选器选的)。拿不到就让它空着 —— 侧栏会显示 `?`。