跨端: 全屏是**窗口级**的 —— 光给 MainPage 让位,等于把黑边换成顶栏压字
上一提交(cac026e)把黑边消掉了,但**只给 `MainPage` 加了避让**。
实测截图硬证:写邮件页的「取消」与时钟「09:49」重叠、「发送」与 wifi/电量图标重叠。
## 根因:`setWindowLayoutFullScreen(true)` 不只作用于当前页
它是**窗口级**的:一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页
(写邮件/会话/收件箱/邮件详情/用户管理)都从 y=0 开始画。
所以上次那个错与更早那次(6861934 只删全屏不留避让)**同源**:
都是"同一件事只做了一半"。上次少的是**步骤**,这次少的是**页面**。
## 改法:把"消费避让"变成每个 @Entry 页都得做的事
- `model/WindowInsets.ts` 加 `topInset(insets)`:取 0 时(未全屏/取不到)
表达式的值与旧代码**逐字相同** ⇒ 没全屏的环境行为不变,不会把谁顶下去。
- 五个页各按自己的形状让位:
- 固定 56vp 顶栏(写邮件/会话/收件箱/用户管理):
`height(56 + topInset(...))` **与** `padding(… top: topInset(...))` 一起加 ——
只加 padding 会把固定的 56 切掉 39(按钮压扁),只加 height 则内容仍贴 y=0。
- 满高容器(邮件详情):`padding({ top })` 加在 `@Entry` 包装层。
★ **不能加在 `MailDetailView` 里面**:它同时被 `MainPage` 的 Navigation 内嵌复用,
而那层已经让过位了 —— 加在里面就变成让两次(39vp 变 78vp)。
这类"同一组件两种入口"的坑与"悬浮加号要放在 Navigation 内部"同源:
**让位的量取决于它被挂在哪一层**。
## 判据(harmony-window 8 → 9 条)
接线⑤ 枚举**所有** `@Entry` 页并要求它们消费避让 —— 口径是
"有人在窗口上开了全屏 ⇒ 每个 @Entry 页都得让",而不是"检查 MainPage 做了没有"。
后者在新增一个推上来的页时会静默逃掉,而"新增一个页"正是最常发生的事。
豁免要带**可机器复核**的理由(不再是"这个页先不管"):
- `Index.ets`:DevEco 模板欢迎页,不在 `main_pages.json` 流程里;
- `LoginPage.ets`:根容器 `.align(Alignment.Center)` ⇒ 结构上碰不到 y=0。
但"居中"是可能被改掉的性质 ⇒ 判据**断言那个居中写法仍然存在**,
谁把它改成贴顶,这条先红,逼他回来重新想这个页要不要避让。
**变异自检两个方向都跑过**:
- 把 ComposePage 避让整个拿掉(重演"只给 MainPage 加")⇒ 红 ✓
- 只加 padding 不加 height(会压扁按钮)⇒ 红 ✓
★ 期间还修掉两处"判据锚在当时的字符串上"(不是放宽,是它把"加一个正当的避让"
与"真犯那个错"判得一模一样):
- `harmony-nav` ④ 的留白断言、`harmony-widescreen` ④ 的 navReserve 断言,
都改成剥注释后验**形状与不变量**,而不是写死字面表达式。改完变异仍咬得住。
This commit is contained in:
@ -30,6 +30,7 @@
|
||||
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { prose } from './lib/read.mjs';
|
||||
@ -165,6 +166,118 @@ test('★ 接线④:`.ets` 不许 import `WindowInsets` 的 SDK 类型(判
|
||||
'WindowInsets.ts 必须保持纯净(无 SDK 依赖),否则判据连编都编不过');
|
||||
});
|
||||
|
||||
test('★ 接线⑤:**每一个 `@Entry` 页**都要消费避让(全屏是窗口级的,不止 MainPage)', () => {
|
||||
/*
|
||||
* ── 这条为什么是全文件第二重要(2026-09-18 实测踩到)──
|
||||
*
|
||||
* `setWindowLayoutFullScreen(true)` 是**窗口级**的:一旦设上,窗口里**所有**
|
||||
* 用 `router.pushUrl` 推上来的页都从 y=0 开始画,而不只是 `MainPage`。
|
||||
*
|
||||
* 我第一版只给 `MainPage` 加了避让 ⇒ 写邮件页实拍:「取消」与时钟「09:49」重叠、
|
||||
* 「发送」与 wifi/电量图标重叠。**把黑边换成了另一种坏** —— 而且两个错同源:
|
||||
* 都是"同一件事只做了一半"(上次少了避让,这次少了页面)。
|
||||
*
|
||||
* ★ 所以口径是「**有人在窗口上开了全屏 ⇒ 每个 `@Entry` 页都得让**」,
|
||||
* 而不是「检查 MainPage 做了没有」。后者下新增一个推上来的页会静默逃掉 ——
|
||||
* 而"新增一个页"正是最常发生的事。
|
||||
*/
|
||||
const pageDir = join(HARMONY, 'entry', 'src', 'main', 'ets', 'pages');
|
||||
const entries = readdirSync(pageDir)
|
||||
.filter(f => f.endsWith('.ets'))
|
||||
.filter(f => /^@Entry\b/m.test(prose(join(pageDir, f))));
|
||||
/*
|
||||
* 自检:扫到的 `@Entry` 页数要 ≥ 5(`main_pages.json` 里登记 7 个)。
|
||||
* 防的是"目录改名/遍历写错 ⇒ 这条变成空判据而依然全绿"——
|
||||
* 与 `harmony-system-api` 里那条"扫到的文件数 ≥ N"同形。
|
||||
*/
|
||||
assert.ok(entries.length >= 5, `要扫到全部 @Entry 页(实际 ${entries.length}:${entries.join('、')})`);
|
||||
|
||||
/*
|
||||
* 两种合法的"消费"写法:
|
||||
* · `topInset(this.windowInsets)` —— 直白的(顶栏页用这个);
|
||||
* · 直接读 `this.windowInsets.statusBar` —— MainPage 要的,它得把避让嵌进
|
||||
* `isWide ? paneGap : statusBar` 这个条件表达式里,套不进 `topInset()`。
|
||||
* 两者都算"把值用起来了";形状不同而要求相同(与 `harmony-nav` 那条同口径:
|
||||
* 判的是"读到的东西有没有落到布局上",不是"用的哪个函数")。
|
||||
*/
|
||||
const consumes = (src) => /topInset\(\s*this\.windowInsets\s*\)/.test(src)
|
||||
|| /this\.windowInsets\.statusBar/.test(src);
|
||||
|
||||
/*
|
||||
* 豁免名单:每条都必须是**可机器复核的理由**,不是"这个页先不管"。
|
||||
*
|
||||
* `LoginPage` 的根容器是 `.align(Alignment.Center)` 的满高容器 —— 内容是**居中**的,
|
||||
* 最顶上的那个像素也远在状态栏之下,结构上碰不到 y=0。所以它不需要避让。
|
||||
*
|
||||
* ★ 但"居中"是个可能被改掉的性质(哪天有人给它加个贴顶的标题栏,理由就没了)。
|
||||
* 所以这里不只看一次理由字符串 —— 下面**断言那个居中写法仍然存在**:
|
||||
* 谁把它改成贴顶,这条先红,逼他回来重新想这个页要不要避让。
|
||||
* 这正是本仓那条"豁免得带可复核的理由"(`harmony-system-api` 的 ALLOW 同形)。
|
||||
*/
|
||||
const EXEMPT = new Map([
|
||||
['Index.ets', { why: 'DevEco 模板欢迎页,不在 main_pages.json 的流程里,从不被路由到', check: null }],
|
||||
['LoginPage.ets', {
|
||||
why: '根容器 .align(Alignment.Center):内容居中,结构上碰不到 y=0',
|
||||
check: (src) => /\.align\(Alignment\.Center\)/.test(stripped(src))
|
||||
&& !/Alignment\.Top/.test(stripped(src))
|
||||
}]
|
||||
]);
|
||||
|
||||
const missing = [];
|
||||
for (const f of entries) {
|
||||
const src = prose(join(pageDir, f));
|
||||
const exempt = EXEMPT.get(f);
|
||||
if (exempt) {
|
||||
if (exempt.check) {
|
||||
assert.ok(exempt.check(src),
|
||||
`${f} 被豁免的理由已不存在(原理由:${exempt.why})—— ` +
|
||||
'要么恢复那个形状,要么把它当普通页消费避让,不要默默留着豁免');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!consumes(src)) missing.push(f);
|
||||
}
|
||||
assert.deepEqual(missing, [],
|
||||
`这些 @Entry 页没有消费窗口避让:${missing.join('、')} —— ` +
|
||||
'全屏是窗口级的,漏一个页就是一处「顶栏被时钟压住」(实测过:写邮件页的取消/发送压着 09:49 与电量)');
|
||||
|
||||
/*
|
||||
* `\u2605` 顶栏那对"高度 + padding-top"只对**固定高度**的顶栏成立(`height(56)`)。
|
||||
*
|
||||
* `MailDetailPage` 是另一种形状:它的 `@Entry` 包装层是 `height('100%')` 的**满高列**,
|
||||
* 避让用 `padding({ top: … })` 加在满高容器上(内容自然往下,没有"从 56 里切"的问题)——
|
||||
* 这是满高容器的正确写法,不该被要求写成 `56 + topInset(...)`。
|
||||
*
|
||||
* 所以这里只对**真的写了固定 height(56) 顶栏**的页判那对同现,
|
||||
* 其余页(满高容器)只要求"避让落到了布局上"(上面已经判过)。
|
||||
*/
|
||||
const halfDone = [];
|
||||
for (const f of entries) {
|
||||
if (EXEMPT.has(f)) continue;
|
||||
const code_ = stripped(prose(join(pageDir, f)));
|
||||
if (!/topInset\(\s*this\.windowInsets\s*\)/.test(code_)) continue; // 不是顶栏形状(MainPage)
|
||||
/*
|
||||
* 只看**同一个修饰符链**:包含 `padding(top: topInset(...))` 的那一段往前找它的 `.height(...)`。
|
||||
*
|
||||
* 不能满文件搜 `height(56)` —— `MailDetailPage` 里的悬浮回复球是 `.width(56).height(56)`,
|
||||
* 与顶栏毫无关系(我第一版就是这么误报的:把一个 56×56 的圆球当成了"固定 56 顶栏")。
|
||||
* 判据要锚在"这一段的形状"上,而不是"文件里有没有出现过这个数"。
|
||||
*/
|
||||
const pad = /\.padding\(\{[^}]*top:\s*topInset\(\s*this\.windowInsets\s*\)[^}]*\}\)/.exec(code_);
|
||||
if (!pad) continue;
|
||||
const chain = code_.slice(Math.max(0, pad.index - 300), pad.index + pad[0].length);
|
||||
const hasFixed56 = /\.height\(\s*56\s*\)/.test(chain);
|
||||
if (!hasFixed56) continue; // 满高容器形状(MailDetailPage):没有从 56 里切的问题
|
||||
const heightGrew = /\.height\(56 \+ topInset\(\s*this\.windowInsets\s*\)\)/.test(chain);
|
||||
if (!heightGrew) {
|
||||
halfDone.push(f);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(halfDone, [],
|
||||
`这些页的顶栏没把"高度 + padding-top"一起加:${halfDone.join('、')} —— ` +
|
||||
'只加一个会把固定 56vp 切掉一截(按钮压扁)或让位没生效(内容仍贴 y=0)');
|
||||
});
|
||||
|
||||
/* ───────────────────────── ③ 设备行为(有设备才跑)───────────────────────── */
|
||||
|
||||
/*
|
||||
|
||||
@ -82,7 +82,7 @@ const SUITE = [
|
||||
// `setWindowLayoutFullScreen(true)`(消黑边)+ `getWindowAvoidArea`(让开时钟/手势条)。
|
||||
// 只做前半 ⇒ 页签被时钟盖住(上一次就是这样退回去的,黑边于是留了三天);
|
||||
// 只做后半 ⇒ 黑边照旧。**两半缺哪一半都要判红**,所以变异自检两个方向都跑过。
|
||||
['test/harmony-window.test.mjs', ['--experimental-strip-types', '--no-warnings'], 8],
|
||||
['test/harmony-window.test.mjs', ['--experimental-strip-types', '--no-warnings'], 9],
|
||||
// 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号
|
||||
['test/appearance-defaults.test.mjs', [], 4],
|
||||
['test/build-stamp.test.mjs', [], 7],
|
||||
|
||||
@ -120,3 +120,27 @@ export interface AvoidAreaLike {
|
||||
topRect?: InsetsRect;
|
||||
bottomRect?: InsetsRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶栏该往下让多少 —— **每一个 `@Entry` 页都要用这个值**。
|
||||
*
|
||||
* ── 为什么这不能只是 `MainPage` 的事(2026-09-18 实测踩到)──
|
||||
*
|
||||
* `setWindowLayoutFullScreen(true)` 是**窗口级**的:一旦设上,窗口里**所有**
|
||||
* 用 `router.pushUrl`/`replaceUrl` 推上来的页(`ComposePage`/`SessionsPage`/
|
||||
* `MailDetailPage`/`AdminUsersPage`)都从 y=0 开始画,而不只是 `MainPage`。
|
||||
*
|
||||
* 所以只给 `MainPage` 加避让 = **把黑边换成了另一种坏**:同一次实测截图里,
|
||||
* 写邮件页的「取消」与时钟「09:49」重叠、「发送」与 wifi/电量图标重叠。
|
||||
* 这正是上次退回的那种错(只做一半)—— 只是这次"一半"是**页面**而不是步骤。
|
||||
*
|
||||
* ★ 所以判定口径是:**只要有人在窗口上开了全屏,每个 `@Entry` 页就必须消费它**。
|
||||
* 判据 `harmony-window.test.mjs` 按这个口径枚举所有 `@Entry` 页 ——
|
||||
* 不是"检查 MainPage 做了没有"(那样新增一个推上来的页就静默逃掉)。
|
||||
*
|
||||
* ★ 取 0 时(未全屏/取不到避让)这个函数就是 0,页面表现与修之前完全一样 ——
|
||||
* 即"没用上就退回到旧行为",不会把一个不知情的页顶下去。
|
||||
*/
|
||||
export function topInset(insets: Insets | undefined): number {
|
||||
return insets !== undefined && insets.statusBar > 0 ? insets.statusBar : 0;
|
||||
}
|
||||
|
||||
@ -36,11 +36,23 @@ import { toggled, lastLoginLabel, messageOfApiError, isAdminRole, isRestricted }
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { LIST_FADE_LENGTH } from '../model/NavItems';
|
||||
import { LengthMetrics } from '@kit.ArkUI';
|
||||
import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct AdminUsersPage {
|
||||
@State users: AdminUser[] = [];
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
*
|
||||
* ★ 推上来的页**也要**消费它:`setWindowLayoutFullScreen(true)` 是**窗口级**的,
|
||||
* 一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页都从 y=0 开始画。
|
||||
* 本页顶栏原来贴 y=0 ⇒ 全屏后「取消/发送」会与时钟、wifi/电量图标重叠
|
||||
* (2026-09-18 实测截图硬证)。只给 `MainPage` 加避让就是把"黑边"换成
|
||||
* "顶栏被状态栏压住",同属只做一半。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets();
|
||||
|
||||
@State scopes: AdminScopes = new AdminScopes();
|
||||
@State loading: boolean = false;
|
||||
@State errorText: string = '';
|
||||
@ -337,7 +349,8 @@ struct AdminUsersPage {
|
||||
AmIcon({ iconName: 'repeat', iconSize: 20, iconColor: Theme.accent }).width(40).height(40)
|
||||
.onClick(() => { this.load(); })
|
||||
}
|
||||
.width('100%').height(56).padding({ left: 8, right: 8 })
|
||||
/* 高度与 padding-top 一起加避让 —— 理由见 `ComposePage` 顶栏(同形状) */
|
||||
.width('100%').height(56 + topInset(this.windowInsets)).padding({ left: 8, right: 8, top: topInset(this.windowInsets) })
|
||||
.backgroundColor(Theme.surface)
|
||||
}
|
||||
|
||||
|
||||
@ -12,11 +12,23 @@ import { ComposeParams } from '../model/RouteParams';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { picker } from '@kit.CoreFileKit';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct ComposePage {
|
||||
@State to: string = '';
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
*
|
||||
* ★ 推上来的页**也要**消费它:`setWindowLayoutFullScreen(true)` 是**窗口级**的,
|
||||
* 一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页都从 y=0 开始画。
|
||||
* 本页顶栏原来贴 y=0 ⇒ 全屏后「取消/发送」会与时钟、wifi/电量图标重叠
|
||||
* (2026-09-18 实测截图硬证)。只给 `MainPage` 加避让就是把"黑边"换成
|
||||
* "顶栏被状态栏压住",同属只做一半。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets();
|
||||
|
||||
@State subject: string = '';
|
||||
@State body: string = '';
|
||||
@State replyTo: string = '';
|
||||
@ -198,8 +210,15 @@ struct ComposePage {
|
||||
.enabled(!this.sending && !this.uploading)
|
||||
.onClick(() => { this.doSend(); })
|
||||
}
|
||||
.width('100%').height(56)
|
||||
.padding({ left: 12, right: 12 })
|
||||
/*
|
||||
* ★ 高度与 padding-top 一起加避让:只加 padding 会把 56 里切掉 39,
|
||||
* 按钮被压扁;只加 height 内容还是贴着状态栏。
|
||||
* 背景照常铺到 y=0(`backgroundColor` 画在 border box 上,含 padding 区),
|
||||
* 所以黑边不会回来 —— 「背景铺满、内容让开」正是 `MainPage` 的同一口径。
|
||||
* 取 0 时(未全屏)表达式的值与旧代码**逐字相同**:height=56、无 top。
|
||||
*/
|
||||
.width('100%').height(56 + topInset(this.windowInsets))
|
||||
.padding({ left: 12, right: 12, top: topInset(this.windowInsets) })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
Divider().color(Theme.border)
|
||||
|
||||
@ -10,11 +10,23 @@ import { router } from '@kit.ArkUI';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { LIST_FADE_LENGTH } from '../model/NavItems';
|
||||
import { LengthMetrics } from '@kit.ArkUI';
|
||||
import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct InboxPage {
|
||||
@State mails: MailSummary[] = [];
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
*
|
||||
* ★ 推上来的页**也要**消费它:`setWindowLayoutFullScreen(true)` 是**窗口级**的,
|
||||
* 一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页都从 y=0 开始画。
|
||||
* 本页顶栏原来贴 y=0 ⇒ 全屏后「取消/发送」会与时钟、wifi/电量图标重叠
|
||||
* (2026-09-18 实测截图硬证)。只给 `MainPage` 加避让就是把"黑边"换成
|
||||
* "顶栏被状态栏压住",同属只做一半。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets();
|
||||
|
||||
@State loading: boolean = false;
|
||||
@State limit: number = 20;
|
||||
@State total: number = 0;
|
||||
@ -78,9 +90,10 @@ struct InboxPage {
|
||||
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
|
||||
}
|
||||
}
|
||||
/* 高度与 padding-top 一起加避让 —— 理由见 `ComposePage` 顶栏(同形状) */
|
||||
.width('100%')
|
||||
.height(56)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.height(56 + topInset(this.windowInsets))
|
||||
.padding({ left: 16, right: 16, top: topInset(this.windowInsets) })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
// 列表
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
mailReplyTarget,
|
||||
formatAddress
|
||||
} from '../model/ReplyTarget';
|
||||
import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
|
||||
/**
|
||||
* 本地时间:与 WebUI `new Date(x).toLocaleString('zh-CN')` 同一口径。
|
||||
@ -553,10 +554,24 @@ export struct MailDetailView {
|
||||
@Entry
|
||||
@Component
|
||||
struct MailDetailPage {
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
*
|
||||
* ★ 避让加在**这个 `@Entry` 包装层**,**不在** `MailDetailView` 里面:
|
||||
* 同一个 `MailDetailView` 还被 `MainPage` 的 `Navigation` 内嵌复用
|
||||
* (`MailDetailDestination`),而 `MainPage` 已经给内容层加过避让了 ——
|
||||
* 加在里面就变成**让两次**(39vp 变 78vp,白白多一条空档)。
|
||||
* 这类"同一个组件两种入口"的坑与 `harmony-nav` 里那条"悬浮加号要放在
|
||||
* Navigation 内部、不能当它的兄弟"同源:**让位的量取决于它被挂在哪一层**。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets();
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
MailDetailView()
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
.padding({ top: topInset(this.windowInsets) })
|
||||
.backgroundColor(Theme.surface)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,11 +8,23 @@ import { MailApi } from '../api/MailApi';
|
||||
import { Session } from '../model/Models';
|
||||
import { LIST_FADE_LENGTH } from '../model/NavItems';
|
||||
import { LengthMetrics } from '@kit.ArkUI';
|
||||
import { Insets, KEY_WINDOW_INSETS, topInset } from '../model/WindowInsets';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct SessionsPage {
|
||||
@State sessions: Session[] = [];
|
||||
/**
|
||||
* 窗口避让区 —— 由 `EntryAbility.setupFullScreenWindow` 写进 AppStorage。
|
||||
*
|
||||
* ★ 推上来的页**也要**消费它:`setWindowLayoutFullScreen(true)` 是**窗口级**的,
|
||||
* 一旦设上,这个窗口里**所有**用 `router.pushUrl` 推上来的页都从 y=0 开始画。
|
||||
* 本页顶栏原来贴 y=0 ⇒ 全屏后「取消/发送」会与时钟、wifi/电量图标重叠
|
||||
* (2026-09-18 实测截图硬证)。只给 `MainPage` 加避让就是把"黑边"换成
|
||||
* "顶栏被状态栏压住",同属只做一半。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) windowInsets: Insets = new Insets();
|
||||
|
||||
@State loading: boolean = false;
|
||||
@State error: string = '';
|
||||
|
||||
@ -54,8 +66,9 @@ struct SessionsPage {
|
||||
Text('会话')
|
||||
.fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
}
|
||||
.width('100%').height(56)
|
||||
.padding({ left: 16 })
|
||||
/* 高度与 padding-top 一起加避让 —— 理由见 `ComposePage` 顶栏(同形状) */
|
||||
.width('100%').height(56 + topInset(this.windowInsets))
|
||||
.padding({ left: 16, top: topInset(this.windowInsets) })
|
||||
.backgroundColor(Theme.surface)
|
||||
|
||||
if (this.loading) {
|
||||
|
||||
Reference in New Issue
Block a user