// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../../core/data/models/comment.dart'; import '../../../core/data/services/comment_controller.dart'; import '../../../core/formatting/localized_date_time.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; const String _deleteAction = 'delete'; const String _reportAction = 'report'; /// 评论条目的交互回调集合。 class CommentInteractionHandlers { const CommentInteractionHandlers({ required this.onReply, required this.onLike, required this.onDelete, required this.onReport, }); final ValueChanged onReply; final Future Function(DramaComment) onLike; final Future Function(DramaComment) onDelete; final Future Function(DramaComment) onReport; } /// 单条评论或回复的展示与操作行。 class CommentTile extends StatelessWidget { const CommentTile({ super.key, required this.comment, required this.controller, required this.handlers, this.isReply = false, }); final DramaComment comment; final CommentController controller; final CommentInteractionHandlers handlers; final bool isReply; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final name = comment.nickname.isEmpty ? l10n.anonymousUser : comment.nickname; return Padding( key: Key('comment-item-${comment.id}'), padding: const EdgeInsets.symmetric(vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _avatar(), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( name, style: const TextStyle( color: AppColors.cream, fontSize: 13, fontWeight: FontWeight.w700, ), ), ), if (comment.createdAt != null) Text( formatLocalizedDateTime(context, comment.createdAt!), style: const TextStyle( color: AppColors.subtle, fontSize: 11, ), ), const SizedBox(width: 4), PopupMenuButton( key: Key('comment-menu-${comment.id}'), tooltip: l10n.more, onSelected: (value) { if (value == _deleteAction) { handlers.onDelete(comment); } else if (value == _reportAction) { handlers.onReport(comment); } }, itemBuilder: (_) => [ if (comment.mine) PopupMenuItem( value: _deleteAction, child: Text(l10n.deleteComment), ), PopupMenuItem( value: _reportAction, child: Text(l10n.reportComment), ), ], ), ], ), Text( comment.content, style: const TextStyle( color: AppColors.cream, fontSize: 14, height: 1.4, ), ), if (comment.isPending) Padding( padding: const EdgeInsets.only(top: 4), child: Text( l10n.underReview, key: Key('comment-pending-${comment.id}'), style: const TextStyle( color: AppColors.blueSoft, fontSize: AppDimens.caption, ), ), ), Row( children: [ TextButton.icon( key: Key('comment-like-${comment.id}'), style: TextButton.styleFrom( foregroundColor: AppColors.muted, padding: const EdgeInsets.symmetric(horizontal: 4), minimumSize: const Size(0, 28), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: controller.likingComment(comment.id) ? null : () => handlers.onLike(comment), icon: Icon( comment.liked ? Icons.favorite_rounded : Icons.favorite_border_rounded, size: 16, ), label: Text( '${comment.likeCount}', style: const TextStyle(fontSize: 11), ), ), if (!isReply) TextButton( key: Key('comment-reply-${comment.id}'), style: TextButton.styleFrom( foregroundColor: AppColors.blue, textStyle: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, ), padding: const EdgeInsets.symmetric(horizontal: 4), minimumSize: const Size(0, 28), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: () => handlers.onReply(comment), child: Text(l10n.reply), ), ], ), ], ), ), ], ), ); } Widget _avatar() { final size = isReply ? 24.0 : 32.0; if (comment.avatarUrl.isEmpty) { return CircleAvatar( radius: size / 2, backgroundColor: AppColors.raised, child: Icon( Icons.person_rounded, size: isReply ? 14 : 18, color: AppColors.cream, ), ); } return ClipOval( child: SizedBox( width: size, height: size, child: RemoteImage(comment.avatarUrl, fit: BoxFit.cover), ), ); } }