// Author: fruit.com.cn // 评论楼中楼:展开回复、继续加载,没有下一页时给出结束提示。 import 'package:flutter/material.dart'; import '../../../core/data/models/comment.dart'; import '../../../core/data/services/comment_controller.dart'; import '../../../core/theme/app_colors.dart'; import '../../../l10n/app_localizations.dart'; import 'comment_tile.dart'; const double _replyIndent = 52; /// 一条评论及其回复。 class CommentThread extends StatelessWidget { const CommentThread({ super.key, required this.comment, required this.controller, required this.handlers, }); final DramaComment comment; final CommentController controller; final CommentInteractionHandlers handlers; @override Widget build(BuildContext context) { final replies = controller.repliesFor(comment.id); final expanded = controller.repliesExpanded(comment.id); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CommentTile(comment: comment, handlers: handlers), if (comment.replyCount > 0 || replies.isNotEmpty) _toggle(context, expanded), if (expanded) _replies(context, replies), ], ); } Widget _toggle(BuildContext context, bool expanded) { final l10n = AppLocalizations.of(context)!; return Padding( padding: const EdgeInsets.only(left: _replyIndent, bottom: 4), child: TextButton( key: Key('comment-replies-${comment.id}'), onPressed: () => _toggleReplies(context), style: TextButton.styleFrom( foregroundColor: AppColors.muted, padding: EdgeInsets.zero, minimumSize: const Size(0, 24), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container(width: 16, height: 1, color: AppColors.border), const SizedBox(width: 8), Text( expanded ? l10n.hideReplies : l10n.viewReplies(comment.replyCount), style: const TextStyle( color: AppColors.muted, fontSize: 12, fontWeight: FontWeight.w400, ), ), const SizedBox(width: 4), Icon( expanded ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, size: 14, color: AppColors.muted, ), ], ), ), ); } Widget _replies(BuildContext context, List replies) { final l10n = AppLocalizations.of(context)!; return Padding( padding: const EdgeInsets.only(left: _replyIndent), child: Column( children: [ if (controller.repliesLoading(comment.id)) const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: CircularProgressIndicator(), ), for (final reply in replies) CommentTile(comment: reply, handlers: handlers, isReply: true), if (controller.repliesHasMore(comment.id)) TextButton( onPressed: () => controller.loadMoreReplies(comment.id), child: Text(l10n.loadMore), ) else if (replies.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( l10n.noMoreItems, style: const TextStyle(color: AppColors.textHint, fontSize: 12), ), ), ], ), ); } Future _toggleReplies(BuildContext context) async { if (controller.repliesExpanded(comment.id)) { controller.hideReplies(comment.id); return; } await controller.loadReplies(comment.id); } }