新增:帖子详情页真实数据整页替换——四态/真九宫格大图/评论区/关注双态,Feed 导航与互动全接线,demo 详情退役(T3-15/16)
CI / flutter-gates (push) Failing after 20s

- post_detail_page 整页重写:内存副本先渲染 + getPost 拉新、四态
 (loading/error+重试/ready/40403 提示后返回 Feed 并刷新);媒体全量
  ——单图原比例(高度钳制 [0.75w,1.33w])、多图真九宫格(D11 轮播
  弃用,本单裁定)+ InteractiveViewer 全屏大图(03 号拍板 E)
- 评论区:游标列表触底补页 + 底部输入条创建(幂等键在数据层)+
  仅本人评论渲染删除入口(40301/40404 服务端兜底);成败埋点对
 (durationMs 取输入会话起点、attemptSeq 会话内递增);CommentTile
  升共享组件(@ 回复前缀呈现、降级作者「宠友」占位)
- 关注双态钮(tonal +关注 / Outlined 已关注 + 取关确认)乐观翻转、
  回滚零动画 + SnackBar,user_followed/unfollowed(post_detail) 挂接;
  本人帖不渲染
- Feed 接通:整卡与评论钮导航详情(T3-14 占位提示移除)、点赞/收藏
  接共享 ToggleSync(详情与卡片同一实例跨页一致)、toggleError
  一次性 SnackBar 消费
- 主壳/装配:openPost 按 postId push;create demo 流不再导航已退役的
  demo 详情(发布页真实化留 T3-17);AppState 在 post_detail/Feed 面
  消费清零
- compose 冒烟(env 门控默认跳过):真链路发帖/点赞幂等/收藏/评论/
  删评一轮 + 断网点赞回滚(生产 ApiClient 异常链)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 13:39:10 +08:00
parent 92524da8e2
commit f873acf9a3
10 changed files with 2160 additions and 249 deletions
+24 -9
View File
@@ -26,6 +26,7 @@ class HomePage extends StatefulWidget {
required this.onOpenServices,
required this.onOpenCreate,
super.key,
this.onOpenPost,
this.feedAnalytics,
this.isActive = true,
});
@@ -38,6 +39,9 @@ class HomePage extends StatefulWidget {
final ValueChanged<bool> onOpenServices;
final VoidCallback onOpenCreate;
/// 帖子详情导航(T3-15 接通;主壳 push PostDetailPage)。
final ValueChanged<String>? onOpenPost;
/// feed 域埋点(feed_viewed 聚合曝光 + feed_load_failed)。
final FeedAnalytics? feedAnalytics;
@@ -231,14 +235,6 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
}
}
/// T3-14 取舍:详情页数据层重写属 T3-15,demo 详情页无法按服务端
/// postId 渲染真实帖,故整卡点按先提示、互动按钮为纯展示禁用态。
void _showDetailPending() {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(const SnackBar(content: Text('帖子详情正在接入真实数据,敬请期待')));
}
/// 搜索词过滤(demo 交互保留:只过滤已加载的多页缓存,不发检索请求;
/// 契约 v1.3.0 无搜索端点)。
List<FeedCard> get _visibleCards {
@@ -309,6 +305,17 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
return ListenableBuilder(
listenable: _feed,
builder: (context, _) {
// 点赞/收藏对账失败的一次性 SnackBar(§3.5 回滚提示;与详情页
// 共用 controller 的 toggleError 消费口,先消费者清空)。
if (_feed.toggleError != null) {
_feed.clearToggleError();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('操作失败,请重试')));
});
}
// ready 后(含翻页追加)补一次可见性扫描:无滚动也能记首屏曝光。
if (_viewSegment != null && _feed.phase == FeedPhase.ready) {
_scheduleVisibilityScan();
@@ -467,7 +474,15 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
padding: const EdgeInsets.only(bottom: 16),
child: KeyedSubtree(
key: _cardKeys.putIfAbsent(card.id, GlobalKey.new),
child: PostCard(card: card, onTap: _showDetailPending),
// 点赞/收藏经共享 ToggleSyncsource=feed 缺省);整卡与
// 评论钮进详情(T3-15 导航接通,T3-14 的占位提示移除)。
child: PostCard(
card: card,
onTap: () => widget.onOpenPost?.call(card.id),
onCommentTap: () => widget.onOpenPost?.call(card.id),
onLikeTap: () => _feed.toggleLike(card.id),
onBookmarkTap: () => _feed.toggleBookmark(card.id),
),
),
),
// 搜索过滤中不渲染尾部(过滤只作用于已加载页,翻页语义混淆)。
+22 -10
View File
@@ -3,6 +3,7 @@ import 'package:patbond_flutter/analytics/analytics_page_name.dart';
import 'package:patbond_flutter/analytics/page_view_tracker.dart';
import 'package:patbond_flutter/core/theme/app_theme.dart';
import 'package:patbond_flutter/features/community/community_controller.dart';
import 'package:patbond_flutter/features/community/community_interaction_analytics.dart';
import 'package:patbond_flutter/features/community/feed_analytics.dart';
import 'package:patbond_flutter/features/create/create_page.dart';
import 'package:patbond_flutter/features/home/home_page.dart';
@@ -13,7 +14,6 @@ import 'package:patbond_flutter/features/pets/pets_page.dart';
import 'package:patbond_flutter/features/post/post_detail_page.dart';
import 'package:patbond_flutter/features/profile/profile_page.dart';
import 'package:patbond_flutter/features/services/services_page.dart';
import 'package:patbond_flutter/models/models.dart';
import 'package:patbond_flutter/state/app_state.dart';
import 'package:patbond_flutter/widgets/common.dart';
@@ -23,9 +23,11 @@ class MainShellPage extends StatefulWidget {
required this.petsController,
required this.communityController,
super.key,
this.currentUserId,
this.petAnalytics,
this.healthRecordAnalytics,
this.feedAnalytics,
this.interactionAnalytics,
this.pageViewTracker,
this.onLogout,
});
@@ -38,6 +40,9 @@ class MainShellPage extends StatefulWidget {
/// 社区状态(T3-12 数据层;首页 Feed segment 数据源,T3-14 接线)。
final CommunityController communityController;
/// 当前登录用户 id(详情页评论删除入口 / 关注钮自见性的 UI 判定)。
final String? currentUserId;
/// pet 域埋点强类型封装(建宠漏斗三事件)。
final PetAnalytics? petAnalytics;
@@ -47,6 +52,9 @@ class MainShellPage extends StatefulWidget {
/// feed 域埋点(T3-14 聚合曝光 + 加载失败)。
final FeedAnalytics? feedAnalytics;
/// 互动域埋点(T3-16 评论成败对 + 关注对;详情页消费)。
final CommunityInteractionAnalytics? interactionAnalytics;
/// Tab 曝光补点(IndexedStack 切换不产生路由事件,03 号评估 §3.2)。
final PageViewTracker? pageViewTracker;
@@ -93,12 +101,18 @@ class _MainShellPageState extends State<MainShellPage> {
widget.pageViewTracker?.reportTab(_tabPages[3]);
}
void openPost(PostModel post) {
/// 帖子详情(T3-15:真实数据整页;Feed 卡片与详情共享
/// communityController,互动状态跨页一致)。
void openPost(String postId) {
Navigator.of(context).push(
MaterialPageRoute<void>(
settings: RouteSettings(name: AnalyticsPageName.postDetail.pageName),
builder: (context) =>
PostDetailPage(appState: widget.appState, postId: post.id),
builder: (context) => PostDetailPage(
controller: widget.communityController,
postId: postId,
currentUserId: widget.currentUserId,
analytics: widget.interactionAnalytics,
),
),
);
}
@@ -122,15 +136,13 @@ class _MainShellPageState extends State<MainShellPage> {
isActive: currentIndex == 0,
onOpenServices: openServices,
onOpenCreate: () => selectTab(1),
onOpenPost: openPost,
),
CreatePage(
appState: widget.appState,
onPublished: (post) {
selectTab(0);
WidgetsBinding.instance.addPostFrameCallback(
(_) => openPost(post),
);
},
// demo 详情页已退役(T3-15):demo 发布流不再导航详情,
// 回首页 Feed;发布页真实化随 T3-17 收编。
onPublished: (_) => selectTab(0),
),
PetsPage(
controller: widget.petsController,
File diff suppressed because it is too large Load Diff