跨端: 修「输入框打开时回复球还在上面」——正是 morph 缺掉的那半截

用户:「你的图标变成输入框,图标变成邮件的动画呢?」

## 真 bug:球没有 `if`,一直挂在树上

实测证据(宽屏 3184,点球打开回复条后抓图):
    底层面板已展开(「回复给 pi@root.realtext」+ 输入框 + 取消/发送),
    而**蓝球仍然浮在它上方** —— 测得蓝块 `x 2948..3104 y 1500..1628`,
    1008 个蓝像素。球不但与「发送」抢同一块地方,还压住输入框右端。

WebUI 是 `if (!open) return <button ... />`(`MailView.tsx:990`)——
**收起态才渲染球**。点开时球从 DOM 消失,才有"球长成输入框"。

## 讽刺的是:注释写的就是对的,代码没跟上

我 2026-09-21 加 morph 时在那段注释里写的是:

    `follow: true` 是给"if 范式下**始终在组件树上**的组件"用的;
    我们这两端是 `if/else` 互斥出现(一端在树上时另一端不在),
    所以用默认 `follow: false`。

——「互斥出现」。可球那一端**根本没有 `if`**。
⇒ `geometryTransition` 的 out 端从未消失:
   · 视觉上:球压在展开的面板上(上面的截图);
   · 动画上:没有"球这个形态结束"的那一刻,morph 只剩 in 端在动,
     用户看到的就不是"图标变成输入框",而是"球还在,下面忽然多了个框"。

★ 教训:`geometryTransition` 的契约是"**两个**组件、一个 in 一个 out"。
  我按契约写了注释,却没按契约写代码 —— 而注释不会报错。
  这类"文档与实现分叉"只有**在设备上看一眼**才抓得到。

修:`if (!this.showReplyBox && !this.showForwardBox) { ... }`
(转发条也占底部同一块地方,所以两个条件都要)。

## 验证

✓ 设备实测:打开回复条后**蓝球已消失**(同一段像素扫描,0 个蓝像素)
✓ 探针复测 morph 中间帧:把 `Motion.durMorph` 临时改成 6000ms 后连拍 5 帧,
  面板顶边 y = **2201 → 1707 → 1679 → 1679 → 1679**
  ⇒ 中间帧真实存在,不是"瞬移"(改回 220ms 后重新编译安装)
✓ 编译通过、无新 jscrash

✗ 未验证:220ms 原始时长下的**观感**——抓图往返 1.5-3s,探针比被测对象
  慢一个数量级(同 `harmony-morph-unverified-middleframes`)。
  只能说"动画在播且播的是几何形变",不能说"220ms 手感对"。
This commit is contained in:
2026-09-21 20:39:44 +08:00
parent dfcf13ff9d
commit 506f207c64

View File

@ -1208,13 +1208,35 @@ export struct MailDetailView {
* 转发用描边球(`surfaceMuted` 底 + 品牌色图标)—— 两个实心球并排
* 会让人分不出哪个更主要。
*/
Row({ space: 12 }) {
Button() {
AmIcon({ iconName: 'chatBubble', iconSize: 24, iconColor: Theme.accentFg })
}
.width(56).height(56)
.borderRadius(28)
.backgroundColor(Theme.accent)
/*
* ★★ 2026-09-21 修(真 bug,设备实测撞出来的):**球在输入框打开时必须消失**。
*
* 实测证据:点球打开回复条后抓图,底层面板已经展开
*(「回复给 pi@root.realtext」+ 输入框 + 取消/发送),
* 而**蓝球仍然浮在它上方**(测得蓝块 `x 2948..3104 y 1500..1628`,1008 个蓝像素)——
* 球和「发送」按钮抢同一块地方,球还压住了输入框右端。
*
* WebUI 是 `if (!open) return <button .../>`(`MailView.tsx:990`)——
* **收起态才渲染球**。
*
* 而且这**恰好是上面那段注释写明的形态**:
* 「这两端是 `if/else` 互斥出现(一端在树上时另一端不在),
* 所以用默认 `follow: false`」
* ——可球原来根本**没有 `if`**,一直挂在树上。
* 即:注释描述的是正确设计,代码没跟上。
* ⇒ `geometryTransition` 缺了 out 端的消失,morph 也就没有"从球长出来"
* 那半截(用户说"图标变成输入框的动画呢"——这就是原因之一)。
*
* 两个弹层任一打开都要藏球(转发条也占底部同一块地方)。
*/
if (!this.showReplyBox && !this.showForwardBox) {
Row({ space: 12 }) {
Button() {
AmIcon({ iconName: 'chatBubble', iconSize: 24, iconColor: Theme.accentFg })
}
.width(56).height(56)
.borderRadius(28)
.backgroundColor(Theme.accent)
/*
* ★★ 2026-09-21 加**共享元素转场**(用户:「webui 行为是按钮变成
* 对应的写邮件页面或输入框吧,你做的啥?」)。
@ -1250,10 +1272,11 @@ export struct MailDetailView {
*/
.geometryTransition('reply-morph')
.onClick(() => { this.openReplyWithMorph(); })
}
.alignItems(VerticalAlign.Bottom)
/* 让出底部悬浮条(右下只剩这一个回复球,与 WebUI `reply-fab` 同位) */
.margin({ right: 16, bottom: this.navReserve + 16 })
}
.alignItems(VerticalAlign.Bottom)
/* 让出底部悬浮条(右下只剩这一个回复球,与 WebUI `reply-fab` 同位) */
.margin({ right: 16, bottom: this.navReserve + 16 })
}
.width('100%').layoutWeight(1)