fix(player): 评论面板按参考图重做
This commit is contained in:
1 parent
bdd3a53632
commit
7862e0be09
3 files changed
+273
-244
No files matched your search
@@ -3,7 +3,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../core/data/models/comment.dart';
|
import '../../../core/data/models/comment.dart';
|
||||||
import '../../../core/data/services/comment_controller.dart';
|
|
||||||
import '../../../core/formatting/localized_date_time.dart';
|
import '../../../core/formatting/localized_date_time.dart';
|
||||||
import '../../../core/theme/app_colors.dart';
|
import '../../../core/theme/app_colors.dart';
|
||||||
import '../../../core/theme/app_dimens.dart';
|
import '../../../core/theme/app_dimens.dart';
|
||||||
@@ -17,13 +16,11 @@ const String _reportAction = 'report';
|
|||||||
class CommentInteractionHandlers {
|
class CommentInteractionHandlers {
|
||||||
const CommentInteractionHandlers({
|
const CommentInteractionHandlers({
|
||||||
required this.onReply,
|
required this.onReply,
|
||||||
required this.onLike,
|
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
required this.onReport,
|
required this.onReport,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ValueChanged<DramaComment> onReply;
|
final ValueChanged<DramaComment> onReply;
|
||||||
final Future<void> Function(DramaComment) onLike;
|
|
||||||
final Future<void> Function(DramaComment) onDelete;
|
final Future<void> Function(DramaComment) onDelete;
|
||||||
final Future<void> Function(DramaComment) onReport;
|
final Future<void> Function(DramaComment) onReport;
|
||||||
}
|
}
|
||||||
@@ -33,13 +30,11 @@ class CommentTile extends StatelessWidget {
|
|||||||
const CommentTile({
|
const CommentTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.comment,
|
required this.comment,
|
||||||
required this.controller,
|
|
||||||
required this.handlers,
|
required this.handlers,
|
||||||
this.isReply = false,
|
this.isReply = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DramaComment comment;
|
final DramaComment comment;
|
||||||
final CommentController controller;
|
|
||||||
final CommentInteractionHandlers handlers;
|
final CommentInteractionHandlers handlers;
|
||||||
final bool isReply;
|
final bool isReply;
|
||||||
|
|
||||||
@@ -51,66 +46,68 @@ class CommentTile extends StatelessWidget {
|
|||||||
: comment.nickname;
|
: comment.nickname;
|
||||||
return Padding(
|
return Padding(
|
||||||
key: Key('comment-item-${comment.id}'),
|
key: Key('comment-item-${comment.id}'),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.only(top: 12, bottom: 14),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_avatar(),
|
_avatar(),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
name,
|
name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.cream,
|
color: AppColors.muted,
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (comment.createdAt != null)
|
SizedBox.square(
|
||||||
Text(
|
dimension: 28,
|
||||||
formatLocalizedDateTime(context, comment.createdAt!),
|
child: PopupMenuButton<String>(
|
||||||
style: const TextStyle(
|
key: Key('comment-menu-${comment.id}'),
|
||||||
|
tooltip: l10n.more,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
iconSize: 18,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.more_horiz_rounded,
|
||||||
color: AppColors.subtle,
|
color: AppColors.subtle,
|
||||||
fontSize: 11,
|
|
||||||
),
|
),
|
||||||
),
|
onSelected: (value) {
|
||||||
const SizedBox(width: 4),
|
if (value == _deleteAction) {
|
||||||
PopupMenuButton<String>(
|
handlers.onDelete(comment);
|
||||||
key: Key('comment-menu-${comment.id}'),
|
} else if (value == _reportAction) {
|
||||||
tooltip: l10n.more,
|
handlers.onReport(comment);
|
||||||
onSelected: (value) {
|
}
|
||||||
if (value == _deleteAction) {
|
},
|
||||||
handlers.onDelete(comment);
|
itemBuilder: (_) => [
|
||||||
} else if (value == _reportAction) {
|
if (comment.mine)
|
||||||
handlers.onReport(comment);
|
PopupMenuItem(
|
||||||
}
|
value: _deleteAction,
|
||||||
},
|
child: Text(l10n.deleteComment),
|
||||||
itemBuilder: (_) => [
|
),
|
||||||
if (comment.mine)
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _deleteAction,
|
value: _reportAction,
|
||||||
child: Text(l10n.deleteComment),
|
child: Text(l10n.reportComment),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
],
|
||||||
value: _reportAction,
|
),
|
||||||
child: Text(l10n.reportComment),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
comment.content,
|
comment.content,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.cream,
|
color: AppColors.cream,
|
||||||
fontSize: 14,
|
fontSize: 16,
|
||||||
height: 1.4,
|
height: 1.4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -126,48 +123,11 @@ class CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
if (comment.createdAt != null || !isReply)
|
||||||
children: [
|
Padding(
|
||||||
TextButton.icon(
|
padding: const EdgeInsets.only(top: 6),
|
||||||
key: Key('comment-like-${comment.id}'),
|
child: _metadata(context, l10n),
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -177,14 +137,14 @@ class CommentTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _avatar() {
|
Widget _avatar() {
|
||||||
final size = isReply ? 24.0 : 32.0;
|
final size = isReply ? 28.0 : 40.0;
|
||||||
if (comment.avatarUrl.isEmpty) {
|
if (comment.avatarUrl.isEmpty) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: size / 2,
|
radius: size / 2,
|
||||||
backgroundColor: AppColors.raised,
|
backgroundColor: AppColors.raised,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.person_rounded,
|
Icons.person_rounded,
|
||||||
size: isReply ? 14 : 18,
|
size: isReply ? 16 : 22,
|
||||||
color: AppColors.cream,
|
color: AppColors.cream,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -197,4 +157,40 @@ class CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _metadata(BuildContext context, AppLocalizations l10n) {
|
||||||
|
final createdAt = comment.createdAt;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
if (createdAt != null)
|
||||||
|
Text(
|
||||||
|
formatLocalizedDateTime(context, createdAt),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,12 +23,14 @@ import 'comment_tile.dart';
|
|||||||
const double _minSheetHeight = 280;
|
const double _minSheetHeight = 280;
|
||||||
const double _maxSheetHeight = 520;
|
const double _maxSheetHeight = 520;
|
||||||
const int _maxCommentLength = 300;
|
const int _maxCommentLength = 300;
|
||||||
const double _composerRadius = 12;
|
const double _composerRadius = 22;
|
||||||
const double _composerHeight = 52;
|
const double _composerHeight = 44;
|
||||||
const double _sendButtonSize = 40;
|
const double _sendButtonSize = 38;
|
||||||
const double _sheetRadius = 20;
|
const double _sheetRadius = 20;
|
||||||
|
const double _headerHeight = 44;
|
||||||
|
const double _replyIndent = 52;
|
||||||
|
|
||||||
/// 服务端评论底部弹窗:匿名列表、游标翻页、回复、点赞和本人删除。
|
/// 服务端评论底部弹窗:匿名列表、游标翻页、回复、举报和本人删除。
|
||||||
class DramaCommentsSheet extends StatefulWidget {
|
class DramaCommentsSheet extends StatefulWidget {
|
||||||
const DramaCommentsSheet({super.key, required this.drama});
|
const DramaCommentsSheet({super.key, required this.drama});
|
||||||
|
|
||||||
@@ -98,17 +100,6 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
|||||||
_inputFocus.requestFocus();
|
_inputFocus.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _like(DramaComment comment) async {
|
|
||||||
if (!await ensureSignedIn(context) || !mounted) return;
|
|
||||||
unawaited(context.read<Haptics>().lightImpact());
|
|
||||||
final result = await _controller.toggleLike(comment);
|
|
||||||
if (!result.isSuccess && mounted) {
|
|
||||||
_showMessage(
|
|
||||||
result.errorMessage ?? AppLocalizations.of(context)!.commentPostFailed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _confirmDelete(DramaComment comment) async {
|
Future<void> _confirmDelete(DramaComment comment) async {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
@@ -203,48 +194,40 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
SizedBox(
|
||||||
child: Container(
|
height: _headerHeight,
|
||||||
key: const Key('player-comments-drag-handle'),
|
width: double.infinity,
|
||||||
width: 36,
|
child: Stack(
|
||||||
height: 4,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: AppColors.border,
|
Text(
|
||||||
borderRadius: BorderRadius.circular(999),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
count == null ? l10n.comments : l10n.commentsWithCount(count),
|
count == null ? l10n.comments : l10n.commentsWithCount(count),
|
||||||
key: count == null ? null : const Key('comment-count-label'),
|
key: const Key('comment-count-label'),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppColors.onSurface,
|
color: AppColors.onSurface,
|
||||||
fontSize: 15,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
IconButton(
|
left: 0,
|
||||||
key: const Key('player-comments-close'),
|
child: SizedBox.square(
|
||||||
tooltip: l10n.close,
|
dimension: 44,
|
||||||
onPressed: () => Navigator.pop(context),
|
child: IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
key: const Key('player-comments-close'),
|
||||||
icon: const Icon(Icons.close_rounded, size: 20),
|
tooltip: l10n.close,
|
||||||
),
|
padding: EdgeInsets.zero,
|
||||||
],
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
icon: const Icon(
|
||||||
Text(
|
Icons.keyboard_arrow_down_rounded,
|
||||||
widget.drama.title,
|
size: 24,
|
||||||
maxLines: 1,
|
color: AppColors.cream,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
style: const TextStyle(
|
),
|
||||||
color: AppColors.muted,
|
),
|
||||||
fontSize: 12,
|
),
|
||||||
fontWeight: FontWeight.w500,
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -290,7 +273,6 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
|||||||
controller: _controller,
|
controller: _controller,
|
||||||
handlers: CommentInteractionHandlers(
|
handlers: CommentInteractionHandlers(
|
||||||
onReply: _beginReply,
|
onReply: _beginReply,
|
||||||
onLike: _like,
|
|
||||||
onDelete: _confirmDelete,
|
onDelete: _confirmDelete,
|
||||||
onReport: _report,
|
onReport: _report,
|
||||||
),
|
),
|
||||||
@@ -349,9 +331,7 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
|||||||
hintText: hint,
|
hintText: hint,
|
||||||
hintStyle: const TextStyle(color: AppColors.muted, fontSize: 14),
|
hintStyle: const TextStyle(color: AppColors.muted, fontSize: 14),
|
||||||
counterText: '',
|
counterText: '',
|
||||||
filled: true,
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
fillColor: AppColors.commentInput,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
||||||
isDense: true,
|
isDense: true,
|
||||||
border: clear,
|
border: clear,
|
||||||
enabledBorder: clear,
|
enabledBorder: clear,
|
||||||
@@ -361,48 +341,56 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
|||||||
|
|
||||||
Widget _composer(AppLocalizations l10n) {
|
Widget _composer(AppLocalizations l10n) {
|
||||||
final signedIn = context.watch<AuthController>().isSignedIn;
|
final signedIn = context.watch<AuthController>().isSignedIn;
|
||||||
return Row(
|
return Container(
|
||||||
children: [
|
key: const Key('comment-composer'),
|
||||||
Expanded(
|
height: _composerHeight,
|
||||||
child: SizedBox(
|
padding: const EdgeInsets.only(right: 3),
|
||||||
height: _composerHeight,
|
decoration: BoxDecoration(
|
||||||
child: TextField(
|
color: AppColors.commentInput,
|
||||||
key: const Key('comment-input'),
|
borderRadius: BorderRadius.circular(_composerRadius),
|
||||||
controller: _input,
|
),
|
||||||
focusNode: _inputFocus,
|
child: Row(
|
||||||
readOnly: !signedIn,
|
children: [
|
||||||
canRequestFocus: signedIn,
|
Expanded(
|
||||||
maxLength: _maxCommentLength,
|
child: SizedBox(
|
||||||
maxLines: 1,
|
height: _composerHeight,
|
||||||
textAlignVertical: TextAlignVertical.center,
|
child: TextField(
|
||||||
style: const TextStyle(color: AppColors.cream, fontSize: 14),
|
key: const Key('comment-input'),
|
||||||
cursorColor: AppColors.blue,
|
controller: _input,
|
||||||
decoration: _composerDecoration(
|
focusNode: _inputFocus,
|
||||||
signedIn ? l10n.writeComment : l10n.commentLoginHint,
|
readOnly: !signedIn,
|
||||||
|
canRequestFocus: signedIn,
|
||||||
|
maxLength: _maxCommentLength,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlignVertical: TextAlignVertical.center,
|
||||||
|
style: const TextStyle(color: AppColors.cream, fontSize: 14),
|
||||||
|
cursorColor: AppColors.blue,
|
||||||
|
decoration: _composerDecoration(
|
||||||
|
signedIn ? l10n.writeComment : l10n.commentLoginHint,
|
||||||
|
),
|
||||||
|
onTap: signedIn ? null : _authenticateForComment,
|
||||||
|
onSubmitted: (_) => _post(),
|
||||||
),
|
),
|
||||||
onTap: signedIn ? null : _authenticateForComment,
|
|
||||||
onSubmitted: (_) => _post(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox.square(
|
||||||
const SizedBox(width: 8),
|
dimension: _sendButtonSize,
|
||||||
SizedBox.square(
|
child: IconButton.filled(
|
||||||
dimension: _sendButtonSize,
|
key: const Key('comment-post'),
|
||||||
child: IconButton.filled(
|
tooltip: l10n.postComment,
|
||||||
key: const Key('comment-post'),
|
padding: EdgeInsets.zero,
|
||||||
tooltip: l10n.postComment,
|
iconSize: 18,
|
||||||
padding: EdgeInsets.zero,
|
onPressed: _controller.submitting ? null : _post,
|
||||||
iconSize: 18,
|
icon: _controller.submitting
|
||||||
onPressed: _controller.submitting ? null : _post,
|
? const SizedBox.square(
|
||||||
icon: _controller.submitting
|
dimension: 16,
|
||||||
? const SizedBox.square(
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
dimension: 16,
|
)
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
: const Icon(Icons.send_rounded),
|
||||||
)
|
),
|
||||||
: const Icon(Icons.send_rounded),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,39 +413,51 @@ class _CommentThread extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CommentTile(
|
CommentTile(comment: comment, handlers: handlers),
|
||||||
comment: comment,
|
|
||||||
controller: controller,
|
|
||||||
handlers: handlers,
|
|
||||||
),
|
|
||||||
if (comment.replyCount > 0 || replies.isNotEmpty)
|
if (comment.replyCount > 0 || replies.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 42, bottom: 4),
|
padding: const EdgeInsets.only(left: _replyIndent, bottom: 4),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
key: Key('comment-replies-${comment.id}'),
|
key: Key('comment-replies-${comment.id}'),
|
||||||
onPressed: () => _toggleReplies(context),
|
onPressed: () => _toggleReplies(context),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: AppColors.blue,
|
foregroundColor: AppColors.muted,
|
||||||
textStyle: const TextStyle(
|
padding: EdgeInsets.zero,
|
||||||
fontSize: 12,
|
minimumSize: const Size(0, 24),
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
||||||
minimumSize: const Size(0, 28),
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Row(
|
||||||
expanded
|
mainAxisSize: MainAxisSize.min,
|
||||||
? AppLocalizations.of(context)!.hideReplies
|
children: [
|
||||||
: AppLocalizations.of(
|
Container(width: 16, height: 1, color: AppColors.border),
|
||||||
context,
|
const SizedBox(width: 8),
|
||||||
)!.viewReplies(comment.replyCount),
|
Text(
|
||||||
|
expanded
|
||||||
|
? AppLocalizations.of(context)!.hideReplies
|
||||||
|
: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.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,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (expanded)
|
if (expanded)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 28),
|
padding: const EdgeInsets.only(left: _replyIndent),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (controller.repliesLoading(comment.id))
|
if (controller.repliesLoading(comment.id))
|
||||||
@@ -468,7 +468,6 @@ class _CommentThread extends StatelessWidget {
|
|||||||
for (final reply in replies)
|
for (final reply in replies)
|
||||||
CommentTile(
|
CommentTile(
|
||||||
comment: reply,
|
comment: reply,
|
||||||
controller: controller,
|
|
||||||
handlers: handlers,
|
handlers: handlers,
|
||||||
isReply: true,
|
isReply: true,
|
||||||
),
|
),
|
||||||
@@ -480,10 +479,6 @@ class _CommentThread extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(left: 42),
|
|
||||||
child: Divider(height: 1, color: AppColors.border),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,10 @@ void main() {
|
|||||||
await _pumpSheet(tester, repository: repository);
|
await _pumpSheet(tester, repository: repository);
|
||||||
expect(find.text('Great episode'), findsOneWidget);
|
expect(find.text('Great episode'), findsOneWidget);
|
||||||
expect(find.byKey(const Key('comment-list-load-more')), findsOneWidget);
|
expect(find.byKey(const Key('comment-list-load-more')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('comment-count-label')), findsNothing);
|
expect(
|
||||||
|
tester.widget<Text>(find.byKey(const Key('comment-count-label'))).data,
|
||||||
|
'Comments',
|
||||||
|
);
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('comment-list-load-more')));
|
await tester.tap(find.byKey(const Key('comment-list-load-more')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -209,30 +212,89 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
tester.getSize(find.byKey(const Key('comment-post'))),
|
tester.getSize(find.byKey(const Key('comment-post'))),
|
||||||
const Size(40, 40),
|
const Size(38, 38),
|
||||||
);
|
);
|
||||||
expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 52);
|
expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44);
|
||||||
|
expect(find.byKey(const Key('player-comments-drag-handle')), findsNothing);
|
||||||
expect(
|
expect(
|
||||||
find.byKey(const Key('player-comments-drag-handle')),
|
tester.getSize(find.byKey(const Key('player-comments-close'))),
|
||||||
|
const Size(44, 44),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byKey(const Key('player-comments-close')),
|
||||||
|
matching: find.byIcon(Icons.keyboard_arrow_down_rounded),
|
||||||
|
),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
final title = tester.widget<Text>(
|
||||||
|
find.byKey(const Key('comment-count-label')),
|
||||||
|
);
|
||||||
|
expect(title.style?.fontSize, 16);
|
||||||
|
expect(title.style?.fontWeight, FontWeight.w600);
|
||||||
|
expect(
|
||||||
|
tester.getCenter(find.byKey(const Key('comment-count-label'))).dx,
|
||||||
|
closeTo(
|
||||||
|
tester.getCenter(find.byKey(const Key('player-comments-sheet'))).dx,
|
||||||
|
0.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
final avatar = tester.widget<CircleAvatar>(
|
final avatar = tester.widget<CircleAvatar>(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byKey(const Key('comment-item-10')),
|
of: find.byKey(const Key('comment-item-10')),
|
||||||
matching: find.byType(CircleAvatar),
|
matching: find.byType(CircleAvatar),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(avatar.radius, 16);
|
expect(avatar.radius, 20);
|
||||||
final body = tester.widget<Text>(find.text('Great episode'));
|
final body = tester.widget<Text>(find.text('Great episode'));
|
||||||
expect(body.style?.fontSize, 14);
|
expect(body.style?.fontSize, 16);
|
||||||
expect(body.style?.height, 1.4);
|
expect(body.style?.height, 1.4);
|
||||||
expect(
|
final composer = tester.widget<Container>(
|
||||||
tester
|
find.byKey(const Key('comment-composer')),
|
||||||
.widget<TextField>(find.byKey(const Key('comment-input')))
|
|
||||||
.decoration
|
|
||||||
?.fillColor,
|
|
||||||
AppColors.commentInput,
|
|
||||||
);
|
);
|
||||||
|
final decoration = composer.decoration! as BoxDecoration;
|
||||||
|
expect(decoration.color, AppColors.commentInput);
|
||||||
|
expect(decoration.borderRadius, BorderRadius.circular(22));
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byKey(const Key('comment-menu-10')),
|
||||||
|
matching: find.byIcon(Icons.more_horiz_rounded),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_hide_comment_like_controls', (tester) async {
|
||||||
|
final repository = FakeCommentRepository(
|
||||||
|
pages: [
|
||||||
|
CommentPage(
|
||||||
|
comments: [commentFixture(id: '10', content: 'No likes shown')],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await _pumpSheet(tester, repository: repository);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget.key is ValueKey<String> &&
|
||||||
|
(widget.key! as ValueKey<String>).value.startsWith('comment-like-'),
|
||||||
|
),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
expect(find.byIcon(Icons.favorite_rounded), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.favorite_border_rounded), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_close_sheet_from_down_arrow', (tester) async {
|
||||||
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
||||||
|
|
||||||
|
await _pumpSheet(tester, repository: repository);
|
||||||
|
await tester.tap(find.byKey(const Key('player-comments-close')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('player-comments-sheet')), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_show_review_badge_and_server_replaced_content', (
|
testWidgets('should_show_review_badge_and_server_replaced_content', (
|
||||||
@@ -306,34 +368,6 @@ void main() {
|
|||||||
expect(find.byKey(const Key('comment-list-retry')), findsOneWidget);
|
expect(find.byKey(const Key('comment-list-retry')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_update_comment_like_count_when_server_returns_count', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
final repository = FakeCommentRepository(
|
|
||||||
pages: [
|
|
||||||
CommentPage(
|
|
||||||
comments: [commentFixture(id: '10', content: 'Like me')],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
final interactions = FakeInteractionRepository()
|
|
||||||
..nextLikeResult = const LikeToggleResult(liked: true, count: 7);
|
|
||||||
final haptics = FakeHaptics();
|
|
||||||
|
|
||||||
await _pumpSheet(
|
|
||||||
tester,
|
|
||||||
repository: repository,
|
|
||||||
interactions: interactions,
|
|
||||||
haptics: haptics,
|
|
||||||
);
|
|
||||||
await tester.tap(find.byKey(const Key('comment-like-10')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(interactions.likeTargets, ['COMMENT:10']);
|
|
||||||
expect(haptics.lightImpacts, 1);
|
|
||||||
expect(find.text('7'), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('should_report_comment_and_show_success_when_submitted', (
|
testWidgets('should_report_comment_and_show_success_when_submitted', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
@@ -480,10 +514,14 @@ Future<AuthController> _pumpSheet(
|
|||||||
locale: const Locale('en'),
|
locale: const Locale('en'),
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
home: const Scaffold(
|
initialRoute: '/comments',
|
||||||
backgroundColor: AppColors.ink,
|
routes: {
|
||||||
body: DramaCommentsSheet(drama: _drama),
|
'/': (_) => const Scaffold(backgroundColor: AppColors.ink),
|
||||||
),
|
'/comments': (_) => const Scaffold(
|
||||||
|
backgroundColor: AppColors.ink,
|
||||||
|
body: DramaCommentsSheet(drama: _drama),
|
||||||
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user