diff --git a/client/electron/test/harmony-2in1.test.mjs b/client/electron/test/harmony-2in1.test.mjs index b2da8fd..4ace644 100644 --- a/client/electron/test/harmony-2in1.test.mjs +++ b/client/electron/test/harmony-2in1.test.mjs @@ -785,3 +785,43 @@ test('2in1|一言与签名要在客户端缓存(账号级),离线也能 assert.match(STORE, /'\/me\/topbar'/, '要打服务端那个端点(/me/topbar)'); }); + +/* + * ★★ 2026-10-01 真机实测抓到的假判据(MatePad Pro / MRDI-W00,HarmonyOS NEXT API 26)。 + * + * 顶部那行文案(一言 + 签名,`TopbarStore`)的渲染条件原来写的是 + * `this.windowInsets.windowDecor > 0` + * 本意"有装饰带才显示",在模拟器(真 2in1)上一直成立。 + * + * ★ 真机平板把它证伪了 —— 实测日志: + * `insets: statusBar=38.588235 navIndicator=27.764706 windowDecor=37` + * `param get const.product.devicetype` = **tablet** + * + * `getWindowDecorHeight()` 在平板上**照样返回 37vp**(官方给的是"全屏悬浮态 + * 固定 37vp"这个下限值),但平板**根本没有三键区** ⇒ 条件成立、文案渲染, + * 位置却按"三键在右边"算,于是浮在**系统状态栏**上(用户:与时钟/电量重叠)。 + * + * ⇒ `windowDecor > 0` 在平板上是个**恒为真的假信号**,不能当形态判据用。 + * 形态的真值是 `deviceInfo.deviceType === '2in1'`。 + * + * 本条钉的就是这个:**不许再用 windowDecor 的高度/正负当"是不是 2in1"。** + */ +test('★★ 形态判据必须用 deviceInfo.deviceType,不许拿 windowDecor 当 2in1 的信号', () => { + // ★ 用 `code()`(读时剥注释)而不是 `prose`:判据的锚要落在**代码**上, + // 不是落在代码对自己的描述上 —— 否则我写进注释里的旧写法会被自己判红。 + const code_ = code(join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'pages', 'MainPage.ets')); + + // 那行文案必须挂在 2in1 判据上。 + const guard = /if \(this\.topbarTexts\(\)\.length > 0 && deviceInfo\.deviceType === '2in1'\)/.test(code_); + assert.ok(guard, + '顶部文案必须只在 2in1 出现:`if (topbarTexts().length > 0 && deviceInfo.deviceType === \'2in1\')`\n' + + '真机实测(MatePad Pro,API 26):windowDecor=37(**非 0**)但 devicetype=tablet,\n' + + '旧条件 windowDecor > 0 在平板上恒成立 ⇒ 文案浮在系统状态栏上。'); + + // 反向:条件里不得再用 windowDecor 的正负/高度判形态。 + const bad = /windowInsets\.windowDecor\s*(>|!=|===|!==|==)\s*0/.exec(code_); + assert.equal(bad, null, + `别再用 ${bad ? bad[0] : 'windowDecor 与 0 比较'} 判「是不是 2in1」——\n` + + '真机平板上它恒为 37(官方"全屏悬浮态固定值"),不代表有三键区。\n' + + '用 deviceInfo.deviceType === \'2in1\'(与 EntryAbility 同一来源)。'); +}); diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 92378f3..0abb43e 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -7,6 +7,7 @@ * 已完成,所以入口现在上;日历是整个客户端里**常驻挂载**的那一个 pane(见 build() 里的说明)。 */ import { hilog } from '@kit.PerformanceAnalysisKit'; +import { deviceInfo } from '@kit.BasicServicesKit'; import { ApiClient, ApiError } from '../api/ApiClient'; import { Theme } from '../common/Theme'; import { AppHeader, CompositeModifier, GlassCardModifier, PaneModifier, PressEffectModifier, TintModifier } from '../common/Surface'; @@ -4303,11 +4304,27 @@ struct MainPage { * 而用户要的是自动轮播,不需要手动入口。 */ /* - * 只在**有装饰带**(2in1/PC)且**有内容**时显示: - * 手机形态 windowDecor=0(没有三键、也就没有那条带),此时不该出现 —— - * 否则它会浮在状态栏上,而手机状态栏本来就窄。 + * ★★ 2026-10-01 真机改判据(用户:「顶部与三键和状态栏重合,我觉得只应该在 2in1」)。 + * + * 原来写的是 `this.windowInsets.windowDecor > 0`,本意是"有装饰带才显示", + * 在模拟器(真 2in1)上成立。**真机平板把它证伪了**: + * + * 实测 insets: statusBar=38.588235 navIndicator=27.764706 **windowDecor=37** + * 设备类型 = **tablet**(不是 2in1) + * + * ⇒ `getWindowDecorHeight()` 在平板上**照样返回 37vp**(官方给的是 + * 「全屏悬浮态固定 37vp」这个下限值),但平板**根本没有三键区** —— + * 于是条件成立、文案渲染出来,位置却按"三键在右边"算, + * 结果浮在**系统状态栏**上(用户截图:与时钟/电量重叠)。 + * + * ★ 所以判据必须问「**是不是 2in1 形态**」,而不是「装饰带高不高」: + * 后者在这台设备上是个**恒为 37 的假信号**。 + * `deviceInfo.deviceType` 才是形态的真值(EntryAbility 判 2in1 用的同一个)。 + * + * 与 `EntryAbility.ets` 那句 `deviceInfo.deviceType !== '2in1'` 同源同口径 —— + * 形态判据全仓只用这一个来源,不让两处各判一套。 */ - if (this.topbarTexts().length > 0 && this.windowInsets.windowDecor > 0) { + if (this.topbarTexts().length > 0 && deviceInfo.deviceType === '2in1') { Row() { Text(this.topbarTexts()[this.topIndex % this.topbarTexts().length]) /*