diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7fc3372..526f378 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -211,6 +211,7 @@ "writeComment": "Write a comment", "commentLoginHint": "Sign in to comment", "postComment": "Post", + "commentPostSuccess": "Comment posted", "loadMore": "Load more", "commentsLoadFailed": "Comments could not be loaded.", "commentPostFailed": "Could not post your comment.", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 24ab80c..b707d25 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -185,6 +185,7 @@ "writeComment": "Tulis komentar", "commentLoginHint": "Masuk untuk berkomentar", "postComment": "Kirim", + "commentPostSuccess": "Komentar terkirim", "loadMore": "Muat lebih banyak", "commentsLoadFailed": "Komentar tidak dapat dimuat.", "commentPostFailed": "Komentar tidak dapat dikirim.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0e0842b..447651c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -946,6 +946,12 @@ abstract class AppLocalizations { /// **'Post'** String get postComment; + /// No description provided for @commentPostSuccess. + /// + /// In en, this message translates to: + /// **'Comment posted'** + String get commentPostSuccess; + /// No description provided for @loadMore. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index b043759..d36f78e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -462,6 +462,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get postComment => 'Post'; + @override + String get commentPostSuccess => 'Comment posted'; + @override String get loadMore => 'Load more'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index ae57fa3..b5c5ee0 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -463,6 +463,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get postComment => 'Kirim'; + @override + String get commentPostSuccess => 'Komentar terkirim'; + @override String get loadMore => 'Muat lebih banyak'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index b449f10..3bfa8c4 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -457,6 +457,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get postComment => '发布'; + @override + String get commentPostSuccess => '发表成功'; + @override String get loadMore => '加载更多'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 5b4b709..32ecfa9 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -185,6 +185,7 @@ "writeComment": "写评论", "commentLoginHint": "登录后参与评论", "postComment": "发布", + "commentPostSuccess": "发表成功", "loadMore": "加载更多", "commentsLoadFailed": "评论加载失败,请重试。", "commentPostFailed": "评论发布失败,请重试。", diff --git a/lib/pages/player/widgets/comment_composer_sheet.dart b/lib/pages/player/widgets/comment_composer_sheet.dart index 7d43806..6c28530 100644 --- a/lib/pages/player/widgets/comment_composer_sheet.dart +++ b/lib/pages/player/widgets/comment_composer_sheet.dart @@ -129,6 +129,7 @@ class _CommentComposerSheetState extends State ); if (!mounted) return; if (result.isSuccess) { + _input.clear(); _closing = true; Navigator.pop(context, true); return; diff --git a/lib/pages/player/widgets/drama_comments_sheet.dart b/lib/pages/player/widgets/drama_comments_sheet.dart index 683702b..9feef57 100644 --- a/lib/pages/player/widgets/drama_comments_sheet.dart +++ b/lib/pages/player/widgets/drama_comments_sheet.dart @@ -41,6 +41,7 @@ class DramaCommentsSheet extends StatefulWidget { class _DramaCommentsSheetState extends State { late final CommentRepository _comments; late final CommentController _controller; + final _messengerKey = GlobalKey(); int? _serverCount; int _countRequest = 0; @@ -91,65 +92,110 @@ class _DramaCommentsSheetState extends State { builder: (_) => CommentComposerSheet(controller: _controller, replyTo: replyTo), ); - if (published == true && mounted) await _refreshCount(); + if (published == true && mounted) { + _showPostSuccess(); + await _refreshCount(); + } } void _handleDismissLayerTap() { Navigator.pop(context); } - @override - Widget build(BuildContext context) { + void _showPostSuccess() { + final messenger = _messengerKey.currentState; + if (messenger == null) return; + final l10n = AppLocalizations.of(context)!; + messenger + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + key: const Key('comment-post-success-snackbar'), + content: Text( + l10n.commentPostSuccess, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + behavior: SnackBarBehavior.floating, + duration: const Duration(milliseconds: 1500), + backgroundColor: AppColors.ink, + elevation: 6, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + margin: EdgeInsets.fromLTRB(16, 0, 16, _sheetHeight(context) + 12), + ), + ); + } + + double _sheetHeight(BuildContext context) { final screenHeight = MediaQuery.sizeOf(context).height; final topSafeArea = MediaQuery.paddingOf(context).top; final halfHeight = (screenHeight * 0.5).roundToDouble(); final maxHeight = math.max(0.0, screenHeight - topSafeArea); - final sheetHeight = math.min(halfHeight, maxHeight); - return Stack( - fit: StackFit.expand, - children: [ - Positioned.fill( - child: GestureDetector( - key: const Key('player-comments-dismiss-layer'), - behavior: HitTestBehavior.opaque, - onTap: _handleDismissLayerTap, - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () {}, - child: KeyedSubtree( - key: const Key('player-comments-sheet'), - child: SizedBox( - key: const Key('comment-sheet-constraints'), - height: sheetHeight, - child: Container( - decoration: const BoxDecoration( - color: AppColors.card, - borderRadius: BorderRadius.vertical( - top: Radius.circular(_sheetRadius), - ), - ), - child: SafeArea( - top: false, - child: ListenableBuilder( - listenable: _controller, - builder: (context, _) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _content(context), - ); - }, + return math.min(halfHeight, maxHeight); + } + + @override + Widget build(BuildContext context) { + final sheetHeight = _sheetHeight(context); + return ScaffoldMessenger( + key: _messengerKey, + child: Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Stack( + fit: StackFit.expand, + children: [ + Positioned.fill( + child: GestureDetector( + key: const Key('player-comments-dismiss-layer'), + behavior: HitTestBehavior.opaque, + onTap: _handleDismissLayerTap, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () {}, + child: KeyedSubtree( + key: const Key('player-comments-sheet'), + child: SizedBox( + key: const Key('comment-sheet-constraints'), + height: sheetHeight, + child: Container( + decoration: const BoxDecoration( + color: AppColors.card, + borderRadius: BorderRadius.vertical( + top: Radius.circular(_sheetRadius), + ), + ), + child: SafeArea( + top: false, + child: ListenableBuilder( + listenable: _controller, + builder: (context, _) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: _content(context), + ); + }, + ), + ), ), ), ), ), ), - ), + ], ), - ], + ), ); } diff --git a/test/comment/drama_comments_sheet_test.dart b/test/comment/drama_comments_sheet_test.dart index 5dc2891..bd50a7f 100644 --- a/test/comment/drama_comments_sheet_test.dart +++ b/test/comment/drama_comments_sheet_test.dart @@ -234,7 +234,11 @@ void main() { publishError: const ApiException(400, '评论包含敏感词'), ); - await _pumpSheet(tester, repository: repository); + await _pumpSheet( + tester, + repository: repository, + locale: const Locale('zh'), + ); await tester.tap(find.byKey(const Key('comment-input-placeholder'))); await tester.pumpAndSettle(); await tester.enterText( @@ -250,6 +254,7 @@ void main() { find.byKey(const Key('comment-composer-input')), ); expect(field.controller?.text, 'blocked text'); + expect(find.text('发表成功'), findsNothing); // 发送失败时保留输入与焦点,用户可以直接改完再发。 expect(field.focusNode?.hasFocus, isTrue); }, @@ -263,7 +268,11 @@ void main() { countResult: 1, ); - await _pumpSheet(tester, repository: repository); + await _pumpSheet( + tester, + repository: repository, + locale: const Locale('zh'), + ); final input = find.byKey(const Key('comment-composer-input')); tester.view.viewInsets = const FakeViewPadding(bottom: 180); await tester.tap(find.byKey(const Key('comment-input-placeholder'))); @@ -278,9 +287,25 @@ void main() { isNotNull, ); await tester.tap(find.byKey(const Key('comment-composer-post'))); - await tester.pumpAndSettle(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); expect(find.byKey(const Key('comment-composer-sheet')), findsNothing); + final successSnackBar = find.byKey( + const Key('comment-post-success-snackbar'), + ); + expect(successSnackBar, findsOneWidget); + expect(find.text('发表成功'), findsOneWidget); + final snackBar = tester.widget(successSnackBar); + expect(snackBar.action, isNull); + expect(snackBar.behavior, SnackBarBehavior.floating); + expect(snackBar.duration, const Duration(milliseconds: 1500)); + expect( + tester.getRect(find.text('发表成功')).bottom, + lessThanOrEqualTo( + tester.getRect(find.byKey(const Key('player-comments-sheet'))).top, + ), + ); expect(find.byKey(const Key('comment-list')), findsOneWidget); expect(find.byKey(const Key('comment-count-label')), findsOneWidget); expect(find.byKey(const Key('comment-input-placeholder')), findsOneWidget); @@ -669,6 +694,7 @@ Future _pumpSheet( WidgetTester tester, { required FakeCommentRepository repository, FakeHaptics? haptics, + Locale locale = const Locale('en'), }) async { final auth = await _auth(); await tester.pumpWidget( @@ -682,7 +708,7 @@ Future _pumpSheet( Provider.value(value: haptics ?? FakeHaptics()), ], child: MaterialApp( - locale: const Locale('en'), + locale: locale, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, initialRoute: '/comments',