Files
ReelShort/lib/pages/player/widgets/comment_tile.dart
T

150 lines
4.4 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/data/models/comment.dart';
import '../../../core/formatting/relative_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';
/// 评论条目的交互回调集合。
class CommentInteractionHandlers {
const CommentInteractionHandlers({required this.onReply});
final ValueChanged<DramaComment> onReply;
}
/// 单条评论或回复的展示与操作行。
class CommentTile extends StatelessWidget {
const CommentTile({
super.key,
required this.comment,
required this.handlers,
this.isReply = false,
});
final DramaComment comment;
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.only(top: 10, bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_avatar(),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
color: AppColors.muted,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 2),
Text(
comment.content,
style: const TextStyle(
color: AppColors.cream,
fontSize: 16,
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,
),
),
),
if (comment.createdAt != null || !isReply)
Padding(
padding: const EdgeInsets.only(top: 4),
child: _metadata(context, l10n),
),
],
),
),
],
),
);
}
Widget _avatar() {
final size = isReply ? 28.0 : 40.0;
if (comment.avatarUrl.isEmpty) {
return CircleAvatar(
radius: size / 2,
backgroundColor: AppColors.raised,
child: Icon(
Icons.person_rounded,
size: isReply ? 16 : 22,
color: AppColors.cream,
),
);
}
return ClipOval(
child: SizedBox(
width: size,
height: size,
child: RemoteImage(comment.avatarUrl, fit: BoxFit.cover),
),
);
}
Widget _metadata(BuildContext context, AppLocalizations l10n) {
final createdAt = comment.createdAt;
return Row(
children: [
if (createdAt != null)
Text(
formatRelativeDateTime(createdAt, l10n),
style: const TextStyle(color: AppColors.subtle, fontSize: 12),
),
if (createdAt != null && !isReply) ...[
const SizedBox(width: 8),
const Text(
'·',
style: TextStyle(color: AppColors.muted, fontSize: 12),
),
const SizedBox(width: 8),
],
if (!isReply)
TextButton(
key: Key('comment-reply-${comment.id}'),
style: TextButton.styleFrom(
foregroundColor: AppColors.muted,
padding: EdgeInsets.zero,
minimumSize: const Size(0, 24),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () => handlers.onReply(comment),
child: Text(
l10n.reply,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
),
),
],
);
}
}