import 'package:flutter/material.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/widgets/common.dart'; /// 图片九宫格展示态(05 号规范 §3.2)。 /// /// - 列数规则:2、4 图 → 2 列;3、5–9 图 → 3 列;全部 1:1 cover、 /// 格间距 4、单格圆角 `sm`(12),`RemoteImage` 复用(loading tint 块 / /// 失败图标兜底,缓存 key 已剥签名)。 /// - 超出折叠:最多显 9 格,[totalCount] 超过显示数时末格叠 /// `ink` 80% scrim + 白字「+N」20/w800(合成最亮白图 7.10:1;60% 档 /// 3.88:1 不达标弃用,§5.1 精算)。 /// - 单图折叠形态:Feed 卡片契约只带封面 + mediaCount(FeedCard 裁剪), /// [urls] 单元素而 [totalCount] > 1 时渲染 4:3 单格 + 右下「+N」角标 /// 胶囊(同 80% scrim 精算)。 /// /// 编辑态(「+」格 / 删除角标)随发布页工单(T3-17)扩展。 class PostMediaGrid extends StatelessWidget { const PostMediaGrid({ required this.urls, super.key, this.totalCount, this.onCellTap, }) : assert(urls.length > 0, 'PostMediaGrid 至少一张图'); /// 可展示的图片 URL(Feed 卡片仅封面一张;详情页全量)。 final List urls; /// 实际总张数(缺省 = urls.length);大于可展示数时渲染「+N」。 final int? totalCount; /// 格子点按(全屏浏览入口,T3-15 详情页接线)。 final ValueChanged? onCellTap; static const _maxCells = 9; static const _spacing = 4.0; int get _effectiveTotal => totalCount ?? urls.length; /// 列数规则(§3.2):2、4 → 2 列;其余 → 3 列(1 图不走网格)。 static int columnsFor(int cellCount) => (cellCount == 2 || cellCount == 4) ? 2 : 3; @override Widget build(BuildContext context) { if (urls.length == 1) { return _CollapsedCover( url: urls.first, hiddenCount: _effectiveTotal - 1, onTap: onCellTap == null ? null : () => onCellTap!(0), ); } final cellCount = urls.length > _maxCells ? _maxCells : urls.length; final overflow = _effectiveTotal - cellCount; final columns = columnsFor(cellCount); return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: columns, mainAxisSpacing: _spacing, crossAxisSpacing: _spacing, ), itemCount: cellCount, itemBuilder: (context, index) { final isOverflowCell = overflow > 0 && index == cellCount - 1; Widget cell = RemoteImage( url: urls[index], borderRadius: BorderRadius.circular(AppRadius.sm), ); if (isOverflowCell) { cell = Stack( fit: StackFit.expand, children: [ cell, DecoratedBox( decoration: BoxDecoration( color: AppColors.ink.withAlpha(204), borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Center( child: Text( '+$overflow', style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w800, ), ), ), ), ], ); } if (onCellTap != null) { cell = InkWell( onTap: () => onCellTap!(index), borderRadius: BorderRadius.circular(AppRadius.sm), child: cell, ); } return cell; }, ); } } /// 单图折叠形态:4:3 圆角封面 + 右下「+N」胶囊角标。 class _CollapsedCover extends StatelessWidget { const _CollapsedCover({ required this.url, required this.hiddenCount, this.onTap, }); final String url; final int hiddenCount; final VoidCallback? onTap; @override Widget build(BuildContext context) { Widget cover = AspectRatio( aspectRatio: 4 / 3, child: Stack( fit: StackFit.expand, children: [ RemoteImage( url: url, borderRadius: BorderRadius.circular(AppRadius.sm), ), if (hiddenCount > 0) Positioned( right: 8, bottom: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppColors.ink.withAlpha(204), borderRadius: BorderRadius.circular(AppRadius.pill), ), child: Text( '+$hiddenCount', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700, ), ), ), ), ], ), ); if (onTap != null) { cover = InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppRadius.sm), child: cover, ); } return cover; } }