fix(player): 评论暂时隐藏举报与删除入口
This commit is contained in:
1 parent
838beaaa3e
commit
9bd46521aa
3 files changed
+45
-250
No files matched your search
@@ -9,20 +9,11 @@ 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.onDelete,
|
||||
required this.onReport,
|
||||
});
|
||||
const CommentInteractionHandlers({required this.onReply});
|
||||
|
||||
final ValueChanged<DramaComment> onReply;
|
||||
final Future<void> Function(DramaComment) onDelete;
|
||||
final Future<void> Function(DramaComment) onReport;
|
||||
}
|
||||
|
||||
/// 单条评论或回复的展示与操作行。
|
||||
@@ -56,51 +47,13 @@ class CommentTile extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox.square(
|
||||
dimension: 28,
|
||||
child: PopupMenuButton<String>(
|
||||
key: Key('comment-menu-${comment.id}'),
|
||||
tooltip: l10n.more,
|
||||
padding: EdgeInsets.zero,
|
||||
iconSize: 18,
|
||||
icon: const Icon(
|
||||
Icons.more_horiz_rounded,
|
||||
color: AppColors.subtle,
|
||||
),
|
||||
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(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
|
||||
@@ -17,7 +17,6 @@ import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/widgets/auth_gate.dart';
|
||||
import '../../../core/widgets/empty_state.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../interaction/widgets/report_dialog.dart';
|
||||
import 'comment_tile.dart';
|
||||
|
||||
const double _minSheetHeight = 280;
|
||||
@@ -34,7 +33,7 @@ const double _replyBannerHeight = 46;
|
||||
const double _expandedInputRadius = 12;
|
||||
const double _postButtonHeight = 32;
|
||||
|
||||
/// 服务端评论底部弹窗:匿名列表、游标翻页、回复、举报和本人删除。
|
||||
/// 服务端评论底部弹窗:匿名列表、游标翻页、回复与登录发布。
|
||||
class DramaCommentsSheet extends StatefulWidget {
|
||||
const DramaCommentsSheet({super.key, required this.drama});
|
||||
|
||||
@@ -122,39 +121,6 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
_inputFocus.requestFocus();
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete(DramaComment comment) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(l10n.deleteCommentTitle),
|
||||
content: Text(l10n.deleteCommentMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
key: const Key('comment-delete-confirm'),
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed != true || !mounted) return;
|
||||
final result = await _controller.delete(comment);
|
||||
if (!result.isSuccess) {
|
||||
if (!mounted) return;
|
||||
_showMessage(
|
||||
result.errorMessage ??
|
||||
AppLocalizations.of(context)!.commentDeleteFailed,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _refreshCount();
|
||||
}
|
||||
|
||||
Future<void> _refreshCount() async {
|
||||
final request = ++_countRequest;
|
||||
try {
|
||||
@@ -167,25 +133,6 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _report(DramaComment comment) async {
|
||||
if (!await ensureSignedIn(context) || !mounted) return;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final draft = await showReportDialog(context, title: l10n.reportComment);
|
||||
if (draft == null || !mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await _controller.report(
|
||||
comment,
|
||||
reason: draft.reason,
|
||||
description: draft.description,
|
||||
);
|
||||
if (!mounted) return;
|
||||
_showMessage(
|
||||
result.isSuccess
|
||||
? l10n.reportSubmitted
|
||||
: result.errorMessage ?? l10n.reportFailed,
|
||||
);
|
||||
}
|
||||
|
||||
void _showMessage(String message) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
@@ -474,11 +421,7 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
return _CommentThread(
|
||||
comment: _controller.comments[index],
|
||||
controller: _controller,
|
||||
handlers: CommentInteractionHandlers(
|
||||
onReply: _beginReply,
|
||||
onDelete: _confirmDelete,
|
||||
onReport: _report,
|
||||
),
|
||||
handlers: CommentInteractionHandlers(onReply: _beginReply),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/models/comment.dart';
|
||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||
import 'package:fruitshorts/core/data/models/interaction.dart';
|
||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||
import 'package:fruitshorts/core/data/remote/auth_api.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/comment_repository.dart';
|
||||
@@ -383,13 +382,32 @@ void main() {
|
||||
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_report_and_delete_actions', (tester) async {
|
||||
final repository = FakeCommentRepository(
|
||||
pages: [
|
||||
CommentPage(
|
||||
comments: [
|
||||
commentFixture(id: '10', content: 'My comment', mine: true),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await _pumpSheet(tester, repository: repository);
|
||||
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget.key is ValueKey<String> &&
|
||||
(widget.key! as ValueKey<String>).value.startsWith('comment-menu-'),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
expect(find.text('Delete comment'), findsNothing);
|
||||
expect(find.text('Report comment'), findsNothing);
|
||||
expect(find.byIcon(Icons.more_horiz_rounded), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_size_short_list_to_content', (tester) async {
|
||||
@@ -442,8 +460,14 @@ void main() {
|
||||
);
|
||||
|
||||
await _pumpSheet(tester, repository: repository);
|
||||
final scrolledComment = find.byKey(const Key('comment-item-12'));
|
||||
final before = tester.getTopLeft(scrolledComment).dy;
|
||||
final scrollable = find
|
||||
.descendant(
|
||||
of: find.byKey(const Key('comment-list')),
|
||||
matching: find.byType(Scrollable),
|
||||
)
|
||||
.first;
|
||||
final position = tester.state<ScrollableState>(scrollable).position;
|
||||
expect(position.maxScrollExtent, greaterThan(0));
|
||||
|
||||
await tester.drag(
|
||||
find.byKey(const Key('comment-list')),
|
||||
@@ -451,7 +475,7 @@ void main() {
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopLeft(scrolledComment).dy, lessThan(before));
|
||||
expect(position.pixels, greaterThan(0));
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('player-comments-sheet'))).height,
|
||||
520,
|
||||
@@ -597,136 +621,11 @@ void main() {
|
||||
expect(find.byKey(const Key('comment-list-error')), findsOneWidget);
|
||||
expect(find.byKey(const Key('comment-list-retry')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_report_comment_and_show_success_when_submitted', (
|
||||
tester,
|
||||
) async {
|
||||
final repository = FakeCommentRepository(
|
||||
pages: [
|
||||
CommentPage(
|
||||
comments: [commentFixture(id: '10', content: 'Report me')],
|
||||
),
|
||||
],
|
||||
);
|
||||
final interactions = FakeInteractionRepository();
|
||||
final haptics = FakeHaptics();
|
||||
|
||||
await _pumpSheet(
|
||||
tester,
|
||||
repository: repository,
|
||||
interactions: interactions,
|
||||
haptics: haptics,
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('comment-menu-10')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Report comment'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('report-submit')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
interactions.reports.single.targetType,
|
||||
InteractionTargetType.comment,
|
||||
);
|
||||
expect(interactions.reports.single.targetId, '10');
|
||||
expect(interactions.reports.single.reasonCode, 'SPAM');
|
||||
expect(haptics.lightImpacts, 1);
|
||||
expect(find.text('Report submitted. Thank you.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_backend_message_when_comment_report_is_duplicated', (
|
||||
tester,
|
||||
) async {
|
||||
final repository = FakeCommentRepository(
|
||||
pages: [
|
||||
CommentPage(
|
||||
comments: [commentFixture(id: '10', content: 'Report me')],
|
||||
),
|
||||
],
|
||||
);
|
||||
final interactions = FakeInteractionRepository()
|
||||
..reportError = const ApiException(200003, '你已举报过该内容,我们正在处理');
|
||||
|
||||
await _pumpSheet(
|
||||
tester,
|
||||
repository: repository,
|
||||
interactions: interactions,
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('comment-menu-10')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Report comment'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('report-submit')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('你已举报过该内容,我们正在处理'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_open_auth_gate_without_reporting_when_signed_out', (
|
||||
tester,
|
||||
) async {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
final repository = FakeCommentRepository(
|
||||
pages: [
|
||||
CommentPage(
|
||||
comments: [commentFixture(id: '10', content: 'Report me')],
|
||||
),
|
||||
],
|
||||
);
|
||||
final interactions = FakeInteractionRepository();
|
||||
|
||||
await _pumpSheet(
|
||||
tester,
|
||||
repository: repository,
|
||||
interactions: interactions,
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('comment-menu-10')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Report comment'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign in to continue'), findsOneWidget);
|
||||
expect(interactions.reports, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('should_show_backend_message_when_reporting_own_comment', (
|
||||
tester,
|
||||
) async {
|
||||
final repository = FakeCommentRepository(
|
||||
pages: [
|
||||
CommentPage(
|
||||
comments: [
|
||||
commentFixture(id: '10', content: 'My comment', mine: true),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
final interactions = FakeInteractionRepository()
|
||||
..reportError = const ApiException(400, '不能举报自己发布的内容');
|
||||
|
||||
await _pumpSheet(
|
||||
tester,
|
||||
repository: repository,
|
||||
interactions: interactions,
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('comment-menu-10')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Delete comment'), findsOneWidget);
|
||||
expect(find.text('Report comment'), findsOneWidget);
|
||||
await tester.tap(find.text('Report comment'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('report-submit')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('不能举报自己发布的内容'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Future<AuthController> _pumpSheet(
|
||||
WidgetTester tester, {
|
||||
required FakeCommentRepository repository,
|
||||
FakeInteractionRepository? interactions,
|
||||
FakeHaptics? haptics,
|
||||
}) async {
|
||||
final auth = await _auth();
|
||||
@@ -735,7 +634,7 @@ Future<AuthController> _pumpSheet(
|
||||
providers: [
|
||||
Provider<CommentRepository>.value(value: repository),
|
||||
Provider<InteractionRepository>.value(
|
||||
value: interactions ?? FakeInteractionRepository(),
|
||||
value: FakeInteractionRepository(),
|
||||
),
|
||||
ChangeNotifierProvider<AuthController>.value(value: auth),
|
||||
Provider<Haptics>.value(value: haptics ?? FakeHaptics()),
|
||||
|
||||
Reference in new issue
Block a user