- 21~26 Flutter 社区接入五单 + 字典 v3 白名单(flutter 286→502、api 325→334) - 27 收口总表:社区 demo 三页消亡、M3 四条验收标准逐条取证、 乐观更新与媒体链路端到端、实现期修正记录 - device-verification.md 的 M3 四项真机步骤已由各单收口补全 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# 25 M3 第三波:帖子详情页替换 + 互动接线(T3-15/T3-16)
|
||||
|
||||
**执行日期**:2026-09-09
|
||||
**工单**:T3-15 帖子详情页整页替换(demo 数据层退役)+ T3-16 互动接线(ToggleSync UI 层),同域合并交付
|
||||
**依赖**:21 号(T3-12 数据层,ToggleSync/CommunityController 就位)、24 号(T3-14 组件与遗留交接)、05 号 UI 规范 §2.2/§3.4/§3.5/§4、22 号事件白名单 v3、17 号后端评论/互动语义
|
||||
**提交**:patbond-flutter dev `92524da`(T3-16 基建)+ `f873acf`(T3-15/16 页面与接线),基线 `8aac8c5`,已推送 origin/dev
|
||||
|
||||
---
|
||||
|
||||
## 0. 概要
|
||||
|
||||
`post_detail_page.dart` 整页重写为真实数据:四态首屏、媒体全量渲染(真九宫格 +
|
||||
全屏大图)、作者卡关注双态、评论区(游标列表 / 输入条创建 / 仅本人可删)全部
|
||||
落地;点赞/收藏经共享 ToggleSync 接入 Feed 卡片与详情页(同一 controller 实例,
|
||||
互动状态跨页一致),按 05 号 §4 三层视觉抑制实现;互动域 8 事件挂接完成。
|
||||
Feed 整卡点按导航详情接通,T3-14 的占位 SnackBar 与禁用态移除。**未动
|
||||
patbond-api**;create 页 demo 留给 T3-17。
|
||||
|
||||
**质量门禁**:`flutter test` 458/458 全绿(基线 421,+37;另 2 个 env 门控
|
||||
compose 冒烟默认跳过)、`flutter analyze` 0 问题、`dart format
|
||||
--set-exit-if-changed` 无 diff、compose 六容器真链路实测通过(§5,含断网
|
||||
点赞回滚)。
|
||||
|
||||
## 1. 详情页四态与结构(T3-15)
|
||||
|
||||
| 态 | 渲染 | widget 测试 |
|
||||
|----|------|------------|
|
||||
| loading(无内存副本) | 居中转圈;评论区独立骨架 2 个(32 圆 + 圆角 16 块高 72,05 §3.7) | ✓ |
|
||||
| 内存副本先渲染 | 进入即展示 controller 缓存内容,`getPost` 后台拉新静默替换(Feed 卡片互动字段一并回写) | ✓ |
|
||||
| error | `InlineErrorBanner`(pets 同款话术映射)+ 「重试」;有副本时后台刷新失败不打断阅读 | ✓ |
|
||||
| **40403 不存在态** | SnackBar「帖子不存在或已被删除」→ **返回 Feed 并触发整体刷新**(失效帖剔除);详情 / 评论 / 评论创建三条路径均可触发,单次守卫防重复 pop | ✓ |
|
||||
| ready | 媒体区 → 作者卡 → 正文卡 → 操作行 → 评论区,底部固定输入条 | ✓ |
|
||||
|
||||
结构落点(05 §2.2 对照):
|
||||
|
||||
- **媒体区(本单裁定:真九宫格,D11 轮播方案弃用)**:单图原比例通栏、高度
|
||||
钳制 [宽×0.75, 宽×1.33](widthPx/heightPx 缺失回落 4:3);多图走
|
||||
`PostMediaGrid` 全量形态(24 号预留的列数规则 2/4→2 列、3/5–9→3 列与
|
||||
超 9 折叠「+N」直接生效)。点格进全屏大图:黑底 + `InteractiveViewer`
|
||||
(03 号拍板 E 选①内置方案,零依赖)+ 横滑翻页 + 双击定点 2.5x 缩放 +
|
||||
右上「n/N」ink 胶囊(13.50:1)与关闭钮。**偏差**:05 §2.2 的「下滑关闭」
|
||||
与 InteractiveViewer 平移手势冲突,本版未做(关闭钮 + 返回手势可退出),
|
||||
留待 photo_view 复评(03 号 E 的升级条件「体验不达标」)。
|
||||
- **作者卡**:`PetAvatar` md44 + 名字/相对时间;关注双态钮见 §3。
|
||||
- **正文卡**:标题 titleMedium + 求助帖 `TagPill(accent)` + 正文 14/1.6 全文 +
|
||||
「发布于 …」12 `inkSoft`。契约 Post 无话题字段,TopicChip 不涉本单。
|
||||
- **操作行**:与 Feed 卡片同一套组件卡外裸排(demo 的 FilledButton.tonalIcon
|
||||
弃用);评论锚点钮点按聚焦底部输入框(唤起键盘直接开写)。
|
||||
- **输入条**:surface 底 + 顶部 border 1px 分隔线(demo 缺失,已补)+ isDense
|
||||
输入框 + filled 发送钮(空文本禁用;发送中 18 转圈锁尺寸)。
|
||||
- **AppBar 分享**:占位 SnackBar「分享功能即将上线」(无契约端点)。
|
||||
|
||||
## 2. 评论区(T3-15)
|
||||
|
||||
- **游标列表**:`(created_at DESC, id DESC)` 服务端序原样渲染,触底(余量
|
||||
400)携 nextCursor 补页,失败态只走显式重试(Feed 同款守卫);空态
|
||||
「还没有评论,来抢沙发」(装饰图标 muted 合法、文案 inkSoft,DEBT-2 零新增)。
|
||||
- **创建**:仓库层 Idempotency-Key 每次提交换新键(21 号已测线上语义);成功
|
||||
插入列表头 + `adjustCommentCount(+1)` 同源写入(详情副本与 Feed 卡片
|
||||
commentCount 一并更新)+ 清空输入收起键盘;失败保留输入 + 按类型话术
|
||||
SnackBar(40000 →「评论内容不合规」等),撞 40403 走不存在态流程。
|
||||
- **仅本人可删(UI 呈现)**:`currentUserId`(app.dart 注入 sessionManager.userId)
|
||||
与评论 author.userId 相等才渲染「删除」入口——权限判定只做 UI 自见性,
|
||||
服务端 40301/40404 仍是裁决者(17 号 §2.3)。删除经确认弹窗 → 软删成功
|
||||
剔除 + 计数 -1;40404(已在别处删)本地同步剔除;40301 提示无权限。
|
||||
- **CommentTile 升共享组件**(`lib/core/widgets/comment_tile.dart`,05 §3.4):
|
||||
PetAvatar sm32 + 气泡(surface/border 1px/圆角 16/padding 12);@ 回复以
|
||||
「回复 @昵称:」前缀呈现(响应 replyToUser,含降级「宠友」占位);删除
|
||||
in-flight 转圈锁定。**取舍**:评论点赞(§3.4 底行右端)无契约端点不渲染;
|
||||
@ 回复的**发起** UI 与长按操作 sheet(回复/复制/举报)留待后续工单
|
||||
(数据层 replyToUserId 已支持,isReply 埋点属性预留)。
|
||||
|
||||
## 3. 互动视觉实现(T3-16,05 §3.5/§4 三层抑制对照)
|
||||
|
||||
| 层 | 规范 | 实现落点 |
|
||||
|----|------|---------|
|
||||
| 即时反馈 | 点按即刻翻转 + 激活动画 | ToggleSync 乐观写入同帧 notify;LikeButton 升 Stateful——点按驱动的激活播 240ms 弹性缩放(1→1.25→1)+ 120ms 图标淡入,取消仅 120ms 颜色渐出无缩放 |
|
||||
| 连点合并 | 只发最终态 | 由数据层单飞合并承担(在途链只并入 pendingTarget、完成后按最终意图至多补发一次,连点至多两在途);UI 不再叠加 600ms 计时防抖——ToggleSync 已保证「合并后只发最终态」的语义,双状态机会打架(D8 的跨角色确认以 21 号定稿为准) |
|
||||
| 回滚静默化 | 零动画 + 成对恢复 + SnackBar | 非点按驱动的状态变化(回滚/对账)直接跳变;**计数与展示态成对更新**(不出现「心已灭计数未减」中间帧);激活动画未播完等播完再跳(§4.3a);`toggleError` 一次性消费出 SnackBar「操作失败,请重试」(Feed 页与详情页共用消费口,先消费者清空,同帧恰一条) |
|
||||
| 对账不打扰 | 静默替换计数 | 服务端权威计数与乐观值不同(他人并发)时数字直接替换、无动画(LikeButton 对「状态不变的计数变化」不播任何过渡) |
|
||||
|
||||
关注钮同策略(§4.5):乐观翻转、失败直接跳回 + SnackBar;取关先确认
|
||||
「不再关注 TA?」;本人帖不渲染(自关注 42204 不给触发面);关注状态经
|
||||
`getFollowStats.followedByMe` 拉取,拉取失败不渲染钮(不阻塞阅读)。
|
||||
系统「减弱动态效果」开启时全部动画降级瞬变(LikeButton 与 FeedSkeleton 同口径)。
|
||||
|
||||
**跨页一致**:Feed 卡片与详情页共享同一 CommunityController/ToggleSync 实例,
|
||||
互动写入经 `_writeInteraction` 同帧更新详情副本与 Feed 卡片(widget 测试从
|
||||
UI 侧断言「详情点赞、卡片同帧 +1」)。
|
||||
|
||||
## 4. 埋点挂接清单(8 事件 + 口径)
|
||||
|
||||
新增 `community_interaction_analytics.dart`(22 号白名单键集逐一对齐,
|
||||
编译期锁死):
|
||||
|
||||
| # | 事件 | 触发点 | props | 挂接位置 |
|
||||
|---|------|--------|-------|---------|
|
||||
| 1 | `post_liked` | 点赞**成功响应后** | source(feed / post_detail) | CommunityController send 闭包(触点在 toggle 调用处归因) |
|
||||
| 2 | `post_unliked` | 取消点赞成功响应后 | source | 同上 |
|
||||
| 3 | `post_favorited` | 收藏成功响应后 | source | 同上 |
|
||||
| 4 | `post_unfavorited` | 取消收藏成功响应后 | source | 同上 |
|
||||
| 5 | `comment_create_succeeded` | 评论创建成功响应后 | durationMs、isReply、textLengthBucket | 详情页提交回调 |
|
||||
| 6 | `comment_create_failed` | 评论创建失败 | failureReason、errorCode、httpStatus、attemptSeq | 详情页提交回调 |
|
||||
| 7 | `user_followed` | 关注成功响应后 | source=post_detail | 详情页关注钮 |
|
||||
| 8 | `user_unfollowed` | 取关成功响应后 | source=post_detail | 详情页关注钮 |
|
||||
|
||||
口径说明(widget/单元测试逐字段断言):
|
||||
|
||||
- **成功才报**:乐观翻转与失败回滚不报(06 §1.4「点赞/收藏/关注不埋失败」);
|
||||
单飞合并链每个**实际抵达服务端并成功**的状态变更各报一条(快速连点合并后
|
||||
至多两条、方向相反,与「成功响应后」字典口径一致)。
|
||||
- **comment_create_started 不发**(22 号锁死 unknown);`durationMs` 取
|
||||
「输入会话首字符 → 成功响应」(评论无 started 事件,时长随成功事件带出);
|
||||
`textLengthBucket` 分桶 empty/short(≤50)/medium(51–500)/long(>500),精确
|
||||
字数不出端(红线 1);`attemptSeq` 输入会话内从 1 递增,成功或清空输入重置;
|
||||
`httpStatus = code ~/ 100`(pet 域同款);会话失效不上报;postId/commentId
|
||||
等内容 ID 一律不进 props(红线 2)。
|
||||
- **follow UI 判定**:详情页作者卡有关注钮(demo 形态保留升级双态),故
|
||||
user_followed/unfollowed 本单接通;user_profile / follow_list 触点随
|
||||
后续页面启用。
|
||||
|
||||
## 5. compose 真链路实测
|
||||
|
||||
后端 patbond-api dev@`8089c06` 六容器 `docker compose up -d --build` 全部
|
||||
Up、postgres/minio healthy;测毕 `docker compose down` 干净退出,patbond-api
|
||||
零改动。
|
||||
|
||||
**(a)互动一轮(`test/smoke/detail_interactions_smoke_test.dart`,env 门控
|
||||
`PATBOND_DETAIL_SMOKE=1`,生产 ApiClient/Repository/Controller 全真实现)**:
|
||||
注册一次性账号 → 发帖(published)→ 点赞(`{liked:true, likeCount:1}`)→
|
||||
**重复 PUT 幂等不重复计数** → 收藏/取消(计数 1→0)→ 评论创建
|
||||
(Idempotency-Key,`commentCount` 0→1)→ 仅作者删除评论(`commentCount`
|
||||
回 0、列表剔除)→ 权威计数逐步对账。**一次通过**。
|
||||
|
||||
**(b)断网点赞回滚(同测试内,生产 CommunityController + ToggleSync)**:
|
||||
Feed 刷新拿到该帖(likedByMe=true/count=1)→ community 端点整体切至不可达
|
||||
端口模拟断网(连接拒绝走生产 ApiClient 的真实 ApiNetworkException 链路)→
|
||||
`toggleLike`:乐观翻转**同帧可见**(false/0)→ 请求失败后**快照成对回滚**
|
||||
(true/1)、`toggleError` 为 ApiNetworkException(SnackBar 消费口就位)→
|
||||
恢复网络再 toggle → 服务端权威终态收敛(likedByMe=false/likeCount=0)。
|
||||
**一次通过**(首轮实测暴露测试自身竞态:以乐观值判收敛会早退,已改为轮询
|
||||
服务端权威终态)。
|
||||
|
||||
**(c)互动 8 事件白名单验真(curl,客户端真实 payload 形状)**:
|
||||
`POST /api/v1/events`(user :8082)一批 8 条(platform=android)→
|
||||
**202 accepted 8 / rejected 0**,`platform.product_events` 落库 props 完整
|
||||
(source / durationMs+isReply+textLengthBucket / failureReason+attemptSeq+
|
||||
errorCode+httpStatus 逐键核对无剥离)。
|
||||
|
||||
真机专属项(乐观更新手感:连点合并请求数、回滚动画帧率、跨页一致、减弱
|
||||
动态降级)已补全 device-verification.md「M3 预登记」第 2 项的步骤与通过标准。
|
||||
|
||||
## 6. 测试数变化
|
||||
|
||||
| 项 | 基线 | 本单后 |
|
||||
|----|------|--------|
|
||||
| flutter test | 421(+1 门控冒烟跳过) | **458(+37,门控冒烟跳过 2)** |
|
||||
| flutter analyze | 0 | 0 |
|
||||
| dart format | 无 diff | 无 diff |
|
||||
|
||||
新增分布:post_detail_page widget 测试 17(四态 5 含 40403 弹回刷新与内存
|
||||
副本先渲染、媒体九宫格与全屏大图 1、评论区 5——游标补页/失败重试/创建成败
|
||||
与 attemptSeq/删除权限与 40301、互动 4——乐观翻转/失败回滚/收藏事件/跨页
|
||||
一致、关注 5)、LikeButton 动画 6(点按激活缩放/取消无缩放/外部零动画跳变/
|
||||
动画中回滚等播完/对账静默/禁用态)、CommentTile 3、互动埋点单测 6(键集/
|
||||
分桶边界/失败原因映射)、home_page 更新 3(导航接通替换 T3-14 占位断言、
|
||||
点赞接线成功事件、失败回滚 SnackBar)、helpers 扩展(评论/关注假仓钩子)。
|
||||
另 compose 冒烟 1 条(env 门控,默认跳过不计入套件)。
|
||||
|
||||
## 7. 遗留与交接
|
||||
|
||||
- **T3-17 发布页**:create 页 demo 数据层(AppState.publishPost/updatePost 与
|
||||
`posts` 字段本体)随发布页真实化退役;demo 发布流现只回 Feed 不再导航
|
||||
(demo 详情页已消亡);PostMediaGrid 编辑态 + UploadProgressOverlay 已在。
|
||||
- **@ 回复发起 UI / 长按操作 sheet(举报)**:数据层与埋点属性(isReply)
|
||||
已支持,交互留待范围拍板。
|
||||
- **大图浏览下滑关闭**:与 InteractiveViewer 平移手势冲突未做,photo_view
|
||||
复评条件不变(03 号 E)。
|
||||
- **真机项**:device-verification.md M3 预登记第 2 项待真机执行(连点合并
|
||||
请求数 ≤2 的抓包核对只能在真机/真网完成)。
|
||||
|
||||
---
|
||||
**Frontend Developer(Flutter)**
|
||||
**日期**:2026-09-09
|
||||
Reference in New Issue
Block a user