跨端: 深色模式下的品牌浅底不跟随 —— 12 处「选中/未读」底色在深色页上刺眼
## 真 bug(设备实测) 深色主题下,「我的」页**选中**的那张账号卡片仍是接近纯白的浅蓝 (实测像素 `(255,255,255)` 级别的浅底压在 `(32,34,36)` 的深色页上)。 根因:`Theme.accentSoft = '#EFF6FF'` 是**写死的浅色**,而它被当作 「选中态背景」用在 12 处(未读邮件行、选中账号、分段选中、登录页模式切换…)。 **为什么只有 WebUI 没这个问题**:它有 CSS 变量的**反转发**机制 —— `index.css:113` 的 `--c-blue-50: 239 246 255` 在 `.dark` 段(`:475`)被换成 `28 37 54`(深蓝黑)。ArkTS 的 `static readonly` **一个常量一个值**, 没有那层机制 ⇒ 静态常量必须自己提供两个取值。 ## 修法 1. `Theme.accentSoftDark = '#1C2536'`(对齐 WebUI `.dark --c-blue-50` 的 `28 37 54`) 2. `Theme.accentSoftFor(dark)` 作为**唯一入口** —— 不在页面里各自 `isDark ? a : b`:那样每处都会各写一遍,迟早漏一处 (WebUI 那条"由 test/theme.test.mjs 逐档断言"就是为防这个) 3. **深浅色从哪来**:只有 `MainPage` 算得出(它读 `resourceManager` 的 `colorMode`)。所以走 `AppStorage` 单向发布(与徽标、windowInsets 同一套): MainPage 算 → 写 `agentmail.appearance.isDark` → 各窗格 `@StorageProp` 读。 6 个文件、12 处,全部改用 `accentSoftFor(this.isDarkNow)`。 **设备实测**:深色下「我的」页账号卡片与收件箱未读行都变成深蓝底, 像素 `(32,34,36)` 与页面底一致(不再刺眼)。 ## 判据(这条是新加的,形状值得记) `cross-client-theme` 新增:**品牌浅底必须有深色变体**。断三件事: 1. 深色变体存在,且**取值从 WebUI 的 `.dark` 段反推**(不是随手挑一个深色); 2. 有按主题选值的**入口**(防"页面各自写三元"); 3. **用到它的地方真的走那个入口** —— 扫描所有 `backgroundColor(… accentSoft …)` 并排除 `accentSoftFor`,把漏改的位置**逐行报出来**。 第 3 条在我改到一半时**当场列出了剩下 9 处**(`CalendarPage:1172`、 `ComposePage:255`、`LoginPage:327`…)—— 这就是它该有的样子: 不是"断言存在某个常量",而是"断言没有一处漏改"。 ★ 写这条判据时踩了自己一次:JS 模板串里嵌了反引号包围的标识符 (`` `accentSoft` ``),直接 SyntaxError。改用字符串拼接。 ## 判据 `run-all.mjs` → `checks=506 pass=506 fail=0 skip=0 red=0 broken=0 unreported=0`。 `hvigorw assembleHap` 成功;前端重建。 **未验**:日历/登录页在深色下的观感(只逐处改了底色,没逐页截图)。
This commit is contained in:
@ -237,7 +237,7 @@ const SELF_OWNED_COLORS = [
|
||||
*
|
||||
* 三者的取值理由都已写在 `Theme.ets` 各自的注释里(本判据的要求)。
|
||||
*/
|
||||
'navActiveBg', 'navBrandFg', 'badgePlain',
|
||||
'navActiveBg', 'navBrandFg', 'badgePlain', 'accentSoftDark',
|
||||
/*
|
||||
* SSE 连接指示器的四个状态色(`WideSidebar.ets` 的 `sseColorOf`)。
|
||||
* 逐档对齐 WebUI 的 Tailwind 类(`ConnectionIndicator.tsx:22-25`):
|
||||
@ -820,3 +820,61 @@ test('★ 断点:两端各是多少、含义是什么、差异被登记(不
|
||||
+ '否则这条登记会变成没人在核的化石(登记也该跟着事实走)');
|
||||
}
|
||||
});
|
||||
|
||||
test('★ 品牌浅底必须有深色变体(深色下白底卡片 = 刺眼的 bug)', () => {
|
||||
/*
|
||||
* ★★ 2026-09-19 设备实测撞出来的真 bug:
|
||||
* 深色主题下「多账号」里**选中**的那张卡片仍是接近纯白的浅蓝
|
||||
* (`Theme.accentSoft = #EFF6FF` 是写死的),在深色页面上刺眼得像渲染错误。
|
||||
*
|
||||
* 根因:WebUI 靠 **CSS 变量在 `.dark` 段反转发**解决
|
||||
* (`index.css:113` 的 `--c-blue-50: 239 246 255` → `:475` 的 `28 37 54`),
|
||||
* 而 ArkTS 的 `static readonly` **没有那层机制** —— 一个常量一个值。
|
||||
*
|
||||
* 修法:显式提供深色取值 + 一个按当前主题选值的入口
|
||||
* (`Theme.accentSoftFor(dark)`),**不让页面各自 `isDark ? a : b`**
|
||||
* —— 那样每处都会各写一遍,迟早漏一处。
|
||||
*
|
||||
* 判据断三件事:
|
||||
* ① 深色变体存在,且**取值来自 WebUI 的 `.dark` 段**(不是随手挑一个深色);
|
||||
* ② 有按主题选值的入口(页面不该自己写三元);
|
||||
* ③ 用到它的地方**真的走那个入口**(定义了不接 = 那处深色下照样刺眼)。
|
||||
*/
|
||||
const themeSrc = prose(join(HARMONY_ETS, 'common/Theme.ets'));
|
||||
const webCss = prose(join(ROOT, 'client/electron/src/index.css'));
|
||||
|
||||
/* ① WebUI 深色下的 blue-50 是权威值 */
|
||||
const darkBlue50 = /\.dark\s*\{[\s\S]*?--c-blue-50:\s*(\d+)\s+(\d+)\s+(\d+)/.exec(webCss);
|
||||
assert.ok(darkBlue50, 'WebUI `.dark` 段要有 `--c-blue-50`(品牌浅底的深色取值)');
|
||||
const [r, g, b] = [darkBlue50[1], darkBlue50[2], darkBlue50[3]].map(Number);
|
||||
const hex = '#' + [r, g, b].map((v) => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
||||
|
||||
const darkVar = new RegExp(`static readonly accentSoftDark: string = '${hex}'`, 'i');
|
||||
assert.match(themeSrc, darkVar,
|
||||
'★ 鸿蒙要有 accentSoftDark = ' + hex + '(对齐 WebUI 的 .dark --c-blue-50)—— ' +
|
||||
'深色下用写死的浅底会让选中卡片在深色页上刺眼');
|
||||
|
||||
/* ② 有按主题选值的入口 */
|
||||
assert.match(themeSrc, /static accentSoftFor\(dark: boolean\): string/,
|
||||
'★ 要有 `accentSoftFor(dark)` 入口 —— 页面各自写 `isDark ? a : b` 迟早漏一处');
|
||||
|
||||
/* ③ 用到它的地方真的走入口(不是还在直接用浅色那个) */
|
||||
const pages = readdirSync(HARMONY_ETS + '/pages').filter((f) => f.endsWith('.ets'));
|
||||
const offenders = [];
|
||||
for (const f of pages) {
|
||||
const src = code(join(HARMONY_ETS, 'pages', f));
|
||||
/*
|
||||
* 找"把 accentSoft 当背景色用"的地方。用 accentSoftFor 的**不算**。
|
||||
* 只看 backgroundColor(...),因为 accentSoft 也可以当文字色
|
||||
* (那种场景下深浅色差异不刺眼,不在本条范围)。
|
||||
*/
|
||||
for (const m of src.matchAll(/backgroundColor\(([^)]*Theme\.accentSoft\b[^)]*)\)/g)) {
|
||||
const line = src.slice(0, m.index).split('\n').length;
|
||||
offenders.push(`${f}:${line}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(offenders, [],
|
||||
'★ 这些地方还在直接把浅色 accentSoft 当背景(深色下会刺眼):\n ' +
|
||||
offenders.join('\n ') +
|
||||
'\n改成 Theme.accentSoftFor(this.isDarkNow)(页面要有一个 isDarkNow 状态)');
|
||||
});
|
||||
|
||||
@ -68,7 +68,7 @@ const SUITE = [
|
||||
// 「只有通信页深色正常」的回归锁 —— 38 条里后 8 条是这次新增)。
|
||||
['test/theme.test.mjs', [], 38],
|
||||
['test/background.test.mjs', [], 44],
|
||||
['test/cross-client-theme.test.mjs', [], 16],
|
||||
['test/cross-client-theme.test.mjs', [], 17],
|
||||
// 左右滑动翻页的**语义契约**(两端逐项相同、数值各自定)—— 这是
|
||||
// `docs/DEBTS.json` 的 `gesture-semantics` 那条债:它写着「鸿蒙侧出现滑动
|
||||
// 手势代码时**立即建**(此前建 = 只有一端存在的假判据)」。
|
||||
|
||||
@ -137,7 +137,11 @@ export class Theme {
|
||||
* 品牌(跨客户端身份,系统给不了):
|
||||
* · accent #2563EB 与 WebUI 的 --c-blue-600 逐字一致
|
||||
* · accentFg #FFFFFF 品牌底上的文字
|
||||
* · accentSoft #EFF6FF 品牌浅底(选中态背景)
|
||||
* · accentSoft #EFF6FF 品牌浅底(选中态背景,浅色主题用)
|
||||
* · accentSoftDark #1C2536 品牌浅底在**深色**下的取值 —— 对齐 WebUI
|
||||
* `index.css:475` 的 `.dark --c-blue-50`(`28 37 54`)。
|
||||
* ★ 不登记会怎样:深色下选中卡片是接近纯白的浅蓝,
|
||||
* 在深色页面上刺眼得像 bug(2026-09-19 设备实测到)
|
||||
* · accentStrong #1D4ED8 品牌深色变体(选中文字/边框)
|
||||
*
|
||||
* 业务语义(系统只有 list/warning/alert 这一档,没有"同意/拒绝"):
|
||||
@ -191,6 +195,31 @@ export class Theme {
|
||||
static readonly accentFg: string = '#FFFFFF';
|
||||
/** 品牌蓝的浅底 / 深前景(与 WebUI 的 blue-50 / blue-700 成对) */
|
||||
static readonly accentSoft: string = '#EFF6FF';
|
||||
/**
|
||||
* 品牌浅底在**深色**下的取值 —— WebUI `index.css:475` 的 `.dark --c-blue-50`
|
||||
* 是 `28 37 54`(= `#1C2536`)。
|
||||
*
|
||||
* ★ 为什么需要它(2026-09-19 设备实测):`accentSoft` 是**写死的浅蓝**,
|
||||
* 而它被用作「选中态背景」(多账号卡片、导航选中项…)。深色主题下
|
||||
* 那几处仍是**接近纯白的浅蓝** —— 在深色页面上刺眼得像个 bug。
|
||||
* WebUI 靠 CSS 变量反转发解决(`.dark` 段把 `--c-blue-50` 换成深蓝黑),
|
||||
* 静态常量没有那层机制 ⇒ 必须显式提供深色取值。
|
||||
*
|
||||
* ★ 配套的 `accentSoftFor(isDark)` 才是使用入口 —— **不要在页面里
|
||||
* 自己 `isDark ? a : b`**:那样每处都会各写一遍,迟早漏一处
|
||||
* (WebUI 那条"由 test/theme.test.mjs 逐档断言"就是为防这个)。
|
||||
*/
|
||||
static readonly accentSoftDark: string = '#1C2536';
|
||||
|
||||
/**
|
||||
* 按当前主题给品牌浅底。
|
||||
*
|
||||
* @param dark 当前是否深色(页面的 `isDarkMode(...)` 算出来传进来 ——
|
||||
* `Theme` 是静态类,拿不到 Context,所以深浅色由调用方给)
|
||||
*/
|
||||
static accentSoftFor(dark: boolean): string {
|
||||
return dark ? Theme.accentSoftDark : Theme.accentSoft;
|
||||
}
|
||||
static readonly accentStrong: string = '#1D4ED8';
|
||||
|
||||
/** 导航文字:未选中 / 选中(对应 --nav-fg-muted / --nav-active-fg) */
|
||||
|
||||
@ -113,6 +113,16 @@ const WEEK_START: number = 1;
|
||||
|
||||
@Component
|
||||
export struct CalendarPage {
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中)
|
||||
* 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色
|
||||
* (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。
|
||||
* 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
|
||||
/** 背景开着时要让出页面底(与其它 pane 同一约定) */
|
||||
@Prop bgActive: boolean = false;
|
||||
/**
|
||||
@ -1169,7 +1179,7 @@ export struct CalendarPage {
|
||||
Row() {
|
||||
Button('今天')
|
||||
.fontSize(Theme.fontSmall)
|
||||
.backgroundColor(Theme.accentSoft)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow))
|
||||
.fontColor(Theme.accentStrong)
|
||||
.onClick(() => this.goToday())
|
||||
Blank()
|
||||
@ -1181,7 +1191,7 @@ export struct CalendarPage {
|
||||
Text(scaleLabel(s))
|
||||
.fontSize(Theme.fontSmall)
|
||||
.fontColor(this.calScale === s ? Theme.accentFg : Theme.textPrimary)
|
||||
.backgroundColor(this.calScale === s ? Theme.accent : Theme.accentSoft)
|
||||
.backgroundColor(this.calScale === s ? Theme.accent : Theme.accentSoftFor(this.isDarkNow))
|
||||
.textAlign(TextAlign.Center)
|
||||
.width(34)
|
||||
.height(26)
|
||||
|
||||
@ -17,6 +17,16 @@ import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
@Entry
|
||||
@Component
|
||||
struct ComposePage {
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中)
|
||||
* 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色
|
||||
* (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。
|
||||
* 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
|
||||
@State to: string = '';
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
@ -252,7 +262,7 @@ struct ComposePage {
|
||||
}
|
||||
}
|
||||
.width('100%').height(40).padding({ left: 40, right: 12 })
|
||||
.backgroundColor(this.selectedAccountId === acct.id ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(this.selectedAccountId === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.onClick(() => {
|
||||
this.selectedAccountId = acct.id;
|
||||
this.showAccountPicker = false;
|
||||
|
||||
@ -15,6 +15,16 @@ import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
@Entry
|
||||
@Component
|
||||
struct InboxPage {
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中)
|
||||
* 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色
|
||||
* (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。
|
||||
* 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
|
||||
@State mails: MailSummary[] = [];
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
@ -124,7 +134,7 @@ struct InboxPage {
|
||||
this.MailItem(mail)
|
||||
}
|
||||
.height(80)
|
||||
.backgroundColor(mail.status === 'unread' ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(mail.status === 'unread' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.onClick(() => {
|
||||
this.getUIContext().getRouter().pushUrl({
|
||||
url: 'pages/MailDetailPage',
|
||||
@ -149,7 +159,7 @@ struct InboxPage {
|
||||
}
|
||||
.layoutWeight(1).height('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.backgroundColor(Theme.accentSoft)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow))
|
||||
|
||||
Column() {
|
||||
AmIcon({ iconName: 'chatBubble', iconSize: 20, iconColor: Theme.textMuted })
|
||||
|
||||
@ -19,6 +19,16 @@ import { AmIcon } from '../common/Icons';
|
||||
@Entry
|
||||
@Component
|
||||
struct LoginPage {
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中)
|
||||
* 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色
|
||||
* (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。
|
||||
* 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
|
||||
@State serverAddr: string = DEFAULT_API_BASE;
|
||||
/** 地址合法但值得提醒(例如公网明文 http)—— 直接渲染在输入框下面,不靠一次性 toast */
|
||||
@State addrWarning: string = '';
|
||||
@ -273,7 +283,7 @@ struct LoginPage {
|
||||
.width(48)
|
||||
.height(48)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Theme.accentSoft)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow))
|
||||
|
||||
Text('AgentMail')
|
||||
.fontSize(16)
|
||||
@ -324,7 +334,7 @@ struct LoginPage {
|
||||
.fontSize(12)
|
||||
.fontWeight(this.mode === 0 ? FontWeight.Medium : FontWeight.Regular)
|
||||
.fontColor(this.mode === 0 ? Theme.accentStrong : Theme.textMuted)
|
||||
.backgroundColor(this.mode === 0 ? Theme.accentSoft : Color.Transparent)
|
||||
.backgroundColor(this.mode === 0 ? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent)
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.onClick(() => { this.mode = 0; })
|
||||
Button('用户密钥')
|
||||
@ -334,7 +344,7 @@ struct LoginPage {
|
||||
.fontSize(12)
|
||||
.fontWeight(this.mode === 1 ? FontWeight.Medium : FontWeight.Regular)
|
||||
.fontColor(this.mode === 1 ? Theme.accentStrong : Theme.textMuted)
|
||||
.backgroundColor(this.mode === 1 ? Theme.accentSoft : Color.Transparent)
|
||||
.backgroundColor(this.mode === 1 ? Theme.accentSoftFor(this.isDarkNow) : Color.Transparent)
|
||||
.borderRadius(Theme.radiusControl)
|
||||
.onClick(() => { this.mode = 1; })
|
||||
}
|
||||
|
||||
@ -46,6 +46,16 @@ function localDateTime(iso: string): string {
|
||||
|
||||
@Component
|
||||
export struct MailDetailView {
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 经 `AppStorage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下这些"浅蓝底"(未读行 / 选中项 / 分段选中)
|
||||
* 在深色页面上刺眼。根因是 `Theme.accentSoft` 写死浅色
|
||||
* (WebUI 靠 CSS 的 `.dark` 段反转发解决,ArkTS 静态常量没有那层机制)。
|
||||
* 窗格自己算不出深浅色 ⇒ 从 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
|
||||
/**
|
||||
* 既支持旧 router 页面,也支持 Navigation 的 NavDestination 内嵌复用。
|
||||
* 内嵌时由父级传入 mailId/accountId;独立页面仍从 router params 读取。
|
||||
@ -336,7 +346,7 @@ export struct MailDetailView {
|
||||
if (this.status === 'unread') {
|
||||
Text('未读')
|
||||
.fontSize(10).fontColor(Theme.accent)
|
||||
.backgroundColor(Theme.accentSoft).borderRadius(4)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4)
|
||||
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
|
||||
.margin({ left: 6 })
|
||||
}
|
||||
|
||||
@ -164,6 +164,14 @@ struct InboxTab {
|
||||
@Prop navReserve: number = 0;
|
||||
/** 选中邮件回调:宽屏内联到 Navigation 右栏,窄屏 push 到目标页 */
|
||||
onOpenMail: (mailId: string, accountId: string) => void = (): void => {};
|
||||
/*
|
||||
* 当前是否深色(`MainPage` 发布)—— 用来选品牌浅底的深浅变体。
|
||||
*
|
||||
* ★ 2026-09-19:深色主题下「未读」行/选中账号的**浅蓝底**在深色页面上刺眼,
|
||||
* 根因是 `Theme.accentSoft` 是写死的浅色(WebUI 靠 `.dark` 段反转发解决,
|
||||
* ArkTS 的静态常量没有那层机制)。窗格自己算不出深浅色 ⇒ 走 AppStorage 读。
|
||||
*/
|
||||
@StorageProp('agentmail.appearance.isDark') isDarkNow: boolean = false;
|
||||
@State mails: MailSummary[] = [];
|
||||
/** 按会话折叠后的列表(单封的组平铺渲染) */
|
||||
@State groups: SessionGroup[] = [];
|
||||
@ -433,7 +441,7 @@ struct InboxTab {
|
||||
.fontSize(14).fontColor(this.accountFilter === 'all' ? Theme.accent : Theme.textPrimary)
|
||||
.fontWeight(this.accountFilter === 'all' ? FontWeight.Bold : FontWeight.Normal)
|
||||
.width('100%').height(40).padding({ left: 16 })
|
||||
.backgroundColor(this.accountFilter === 'all' ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(this.accountFilter === 'all' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.onClick(() => {
|
||||
this.accountFilter = 'all';
|
||||
this.accountName = '全部邮箱';
|
||||
@ -454,7 +462,7 @@ struct InboxTab {
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%').height(48).padding({ left: 16 })
|
||||
.backgroundColor(this.accountFilter === acct.id ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(this.accountFilter === acct.id ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.onClick(() => {
|
||||
this.accountFilter = acct.id;
|
||||
this.accountName = acct.displayName;
|
||||
@ -535,7 +543,7 @@ struct InboxTab {
|
||||
this.MailItem(mail)
|
||||
}
|
||||
.width('100%').height(64)
|
||||
.backgroundColor(mail.status === 'unread' ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(mail.status === 'unread' ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
@ -626,7 +634,7 @@ struct InboxTab {
|
||||
if (this.accountFilter === 'all' && mail.source_account_name.length > 0) {
|
||||
Text(mail.source_account_name)
|
||||
.fontSize(10).fontColor(Theme.accent)
|
||||
.backgroundColor(Theme.accentSoft).borderRadius(4)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4)
|
||||
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
|
||||
.margin({ left: 6 })
|
||||
}
|
||||
@ -2170,6 +2178,14 @@ struct MainPage {
|
||||
@State currentIndex: number = 0;
|
||||
/** 宽屏模式(≥768vp):复刻 WebUI 的三栏布局(Sidebar | 内容,无底部导航条) */
|
||||
@State isWide: boolean = false;
|
||||
/**
|
||||
* "当前是否深色"的发布键(`AppStorage`)。
|
||||
*
|
||||
* 各窗格里有若干"品牌浅底"(`Theme.accentSoft`),深色下必须换深色变体。
|
||||
* 窗格自己算不出这件事(`Theme` 是静态类、拿不到 Context)⇒
|
||||
* 由 MainPage 算好发布、窗格 `@StorageProp` 读(与徽标同一套机制)。
|
||||
*/
|
||||
private readonly KEY_IS_DARK: string = 'agentmail.appearance.isDark';
|
||||
/*
|
||||
* 壁纸层(背景的**画法**在 `model/Wallpaper.ts` 里算好,这里只负责画)。
|
||||
*
|
||||
@ -2377,6 +2393,8 @@ struct MainPage {
|
||||
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(() => {});
|
||||
}
|
||||
@ -2479,6 +2497,14 @@ struct MainPage {
|
||||
const dark: boolean = isDarkMode(snap.theme, systemMode);
|
||||
/* 侧栏底部那个主题按钮的图标跟着它走(WebUI `ThemeToggleButton` 同口径) */
|
||||
this.isDarkNow = dark;
|
||||
/*
|
||||
* 把"当前是不是深色"**发布出去** —— 各窗格(通信/日历/联系/我的)里
|
||||
* 有若干"品牌浅底"(`Theme.accentSoft`),深色下必须换成深色变体。
|
||||
* 那些窗格自己算不出这件事(`Theme` 是静态类、拿不到 Context),
|
||||
* 所以走 `AppStorage` 单向发布(与徽标、windowInsets 同一套机制):
|
||||
* MainPage 算 → 写 → 窗格 `@StorageProp` 读。
|
||||
*/
|
||||
AppStorage.setOrCreate(this.KEY_IS_DARK, dark);
|
||||
/*
|
||||
* 侧栏头像的前两字:当前**活跃账号**的展示名(不是 `accountName`,
|
||||
* 那是收件箱筛选器选的)。拿不到就让它空着 —— 侧栏会显示 `?`。
|
||||
|
||||
@ -20,7 +20,7 @@ import { AccountManager, AccountInfo } from '../api/AccountManager';
|
||||
import { SseService } from '../api/SseService';
|
||||
import { AppearanceStore } from '../common/AppearanceStore';
|
||||
import { AppearanceApi } from '../api/AppearanceApi';
|
||||
import { AppearanceSnapshot, statusLabel } from '../model/Appearance';
|
||||
import { AppearanceSnapshot, isDarkMode, statusLabel } from '../model/Appearance';
|
||||
import { MeApi } from '../api/AdminApi';
|
||||
import { AdminUser, UserKey } from '../model/Models';
|
||||
import { isAdminRole } from '../model/AdminUsers';
|
||||
@ -49,6 +49,15 @@ export struct SettingsPane {
|
||||
* 页面只保存**显示用的一份副本** —— ArkTS 的 @State 观察不到类内部字段的变化。
|
||||
*/
|
||||
@State appearanceTheme: string = 'system';
|
||||
/**
|
||||
* 当前**实际**是不是深色(`appearanceTheme='system'` 时要看系统反馈的模式)。
|
||||
*
|
||||
* ★ 2026-09-19 加:用来选品牌浅底(`Theme.accentSoftFor`)。
|
||||
* 实测问题:深色主题下「多账号」那张选中卡片仍是**接近纯白的浅蓝**
|
||||
* (`accentSoft` 是写死的 `#EFF6FF`)—— 在深色页面上刺眼得像 bug。
|
||||
* WebUI 靠 CSS 变量反转发解决,静态常量没有那层机制。
|
||||
*/
|
||||
@State isDarkNow: boolean = false;
|
||||
@State appearanceStatus: string = 'local-only';
|
||||
/** 当前账号是不是管理员 —— 只用来决定「管理」入口显不显示(口径见 model/AdminUsers.ts 的 isAdminRole) */
|
||||
@State isAdmin: boolean = false;
|
||||
@ -207,6 +216,12 @@ export struct SettingsPane {
|
||||
/** 把 store 里的快照复制进 @State(@State 观察不到类内部字段变化) */
|
||||
adoptSnapshot(snap: AppearanceSnapshot, status: string): void {
|
||||
this.appearanceTheme = snap.theme;
|
||||
/* `system` 时要看系统反馈的色彩模式(`Theme` 是静态类,算不出这件事) */
|
||||
const ctx2 = this.getUIContext().getHostContext();
|
||||
if (ctx2 !== undefined) {
|
||||
const mode: number = ctx2.resourceManager.getConfigurationSync().colorMode;
|
||||
this.isDarkNow = isDarkMode(snap.theme, mode);
|
||||
}
|
||||
this.bgKind = snap.bgKind;
|
||||
this.bgPresetId = snap.bgPresetId;
|
||||
this.bgDim = snap.bgDim;
|
||||
@ -1158,7 +1173,7 @@ export struct SettingsPane {
|
||||
if (account.id === this.activeId) {
|
||||
Text(' 默认')
|
||||
.fontSize(11).fontColor(Theme.accent)
|
||||
.backgroundColor(Theme.accentSoft).borderRadius(4)
|
||||
.backgroundColor(Theme.accentSoftFor(this.isDarkNow)).borderRadius(4)
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
.margin({ left: 6 })
|
||||
}
|
||||
@ -1188,7 +1203,7 @@ export struct SettingsPane {
|
||||
.width('100%').height(72)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.backgroundColor(account.id === this.activeId ? Theme.accentSoft : Theme.surface)
|
||||
.backgroundColor(account.id === this.activeId ? Theme.accentSoftFor(this.isDarkNow) : Theme.surface)
|
||||
.border({ width: { bottom: 1 }, color: Theme.border })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user