f873acf9a3
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>
142 lines
5.6 KiB
Dart
142 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||
import 'package:patbond_flutter/core/widgets/pet_avatar.dart';
|
||
import 'package:patbond_flutter/features/community/community_display.dart';
|
||
import 'package:patbond_flutter/features/community/community_models.dart';
|
||
|
||
/// 评论条目(05 号规范 §3.4,demo 气泡形态升共享):
|
||
/// `PetAvatar sm32` + 10 + 气泡(`surface` 底、`border` 1px、圆角 16、
|
||
/// padding 12)——作者名 13/w700 → 4 → 内容 bodyMedium → 6 → 底行
|
||
/// (时间 11 `inkSoft` + 仅本人评论的「删除」入口)。
|
||
///
|
||
/// - @ 回复(单层平铺,契约 replyToUser)以「回复 @昵称:」前缀呈现;
|
||
/// 降级作者统一「宠友」占位(isDegraded 一个判定口)。
|
||
/// - [onDelete] 非 null 才渲染删除入口——**权限判定在调用方**(17 号
|
||
/// 后端语义:仅评论作者可删,他人可见评论 403/40301);[deleting]
|
||
/// 期间入口替换为 14 转圈防重复提交。
|
||
/// - 评论点赞(§3.4 底行右端)无契约端点,M3 不渲染。
|
||
class CommentTile extends StatelessWidget {
|
||
const CommentTile({
|
||
required this.comment,
|
||
super.key,
|
||
this.onDelete,
|
||
this.deleting = false,
|
||
this.now,
|
||
});
|
||
|
||
final PostComment comment;
|
||
|
||
/// 删除回调;null = 非本人评论,不渲染删除入口。
|
||
final VoidCallback? onDelete;
|
||
|
||
/// 删除请求在途(入口转圈锁定)。
|
||
final bool deleting;
|
||
|
||
/// 相对时间的参考时钟(测试注入;缺省取当前时间)。
|
||
final DateTime? now;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final replyTo = comment.replyToUser;
|
||
return Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
PetAvatar(size: PetAvatarSize.sm, url: comment.author.avatarUrl),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: AppColors.surface,
|
||
border: Border.all(color: AppColors.border),
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
authorDisplayName(comment.author),
|
||
style: const TextStyle(
|
||
color: AppColors.ink,
|
||
fontSize: 13,
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text.rich(
|
||
TextSpan(
|
||
children: [
|
||
if (replyTo != null)
|
||
TextSpan(
|
||
text: '回复 @${authorDisplayName(replyTo)}:',
|
||
style: const TextStyle(
|
||
color: AppColors.inkSoft,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
TextSpan(text: comment.content),
|
||
],
|
||
),
|
||
style: Theme.of(context).textTheme.bodyMedium,
|
||
),
|
||
const SizedBox(height: 6),
|
||
Row(
|
||
children: [
|
||
Text(
|
||
feedRelativeTime(comment.createdAt, now: now),
|
||
style: const TextStyle(
|
||
color: AppColors.inkSoft,
|
||
fontSize: 11,
|
||
),
|
||
),
|
||
const Spacer(),
|
||
if (onDelete != null)
|
||
deleting
|
||
? const Padding(
|
||
padding: EdgeInsets.all(4),
|
||
child: SizedBox(
|
||
width: 14,
|
||
height: 14,
|
||
child: CircularProgressIndicator(
|
||
strokeWidth: 2,
|
||
color: AppColors.inkSoft,
|
||
),
|
||
),
|
||
)
|
||
: Semantics(
|
||
label: '删除评论',
|
||
button: true,
|
||
child: InkWell(
|
||
onTap: onDelete,
|
||
borderRadius: BorderRadius.circular(
|
||
AppRadius.pill,
|
||
),
|
||
// 视觉 11 字,触控由 padding 撑到 ≥32
|
||
//(气泡内行高受限,不足 44 以热区扩展补偿)。
|
||
child: const Padding(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
'删除',
|
||
style: TextStyle(
|
||
color: AppColors.inkSoft,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|