From 5d459dd08a3dd11da2fa2279c0fe6d708071c36d Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 21 Oct 2026 13:59:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(player):=20=E8=AF=84=E8=AE=BA=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E6=8F=90=E7=A4=BA=E6=94=B9=E9=BB=91=E8=89=B2=E6=B5=AE?= =?UTF-8?q?=E5=B1=82=E5=B9=B6=E5=8E=BB=E9=99=A4=E8=BE=93=E5=85=A5=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E9=81=AE=E7=BD=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/comment_composer_sheet.dart | 256 ++++++++++-------- .../player/widgets/drama_comments_sheet.dart | 168 +++++++----- test/comment/drama_comments_sheet_test.dart | 50 +++- 3 files changed, 274 insertions(+), 200 deletions(-) diff --git a/lib/pages/player/widgets/comment_composer_sheet.dart b/lib/pages/player/widgets/comment_composer_sheet.dart index 6c28530..1636240 100644 --- a/lib/pages/player/widgets/comment_composer_sheet.dart +++ b/lib/pages/player/widgets/comment_composer_sheet.dart @@ -155,130 +155,160 @@ class _CommentComposerSheetState extends State final canPost = signedIn && hasContent && !widget.controller.submitting; final multiline = _input.text.contains('\n'); - return AnimatedPadding( - duration: const Duration(milliseconds: 200), - curve: Curves.easeOut, - padding: EdgeInsets.only(bottom: keyboardHeight), - child: SizedBox( - key: const Key('comment-composer-sheet'), - width: double.infinity, - child: Material( - color: AppColors.card, - clipBehavior: Clip.antiAlias, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(_sheetRadius), + return Stack( + fit: StackFit.expand, + children: [ + Positioned.fill( + child: GestureDetector( + key: const Key('comment-composer-dismiss-layer'), + behavior: HitTestBehavior.opaque, + onTap: () { + _inputFocus.unfocus(); + _closeSheet(); + }, ), - child: SafeArea( - top: false, - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), - child: TextFieldTapRegion( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (widget.replyTo != null) - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Text( - l10n.replyingTo( - widget.replyTo!.nickname.isEmpty - ? l10n.anonymousUser - : widget.replyTo!.nickname, - ), - style: const TextStyle( - color: AppColors.muted, - fontSize: 12, - ), - ), - ), - Container( - decoration: BoxDecoration( - color: AppColors.commentInput, - borderRadius: BorderRadius.circular(_inputRadius), - ), - child: TextField( - key: const Key('comment-composer-input'), - autofocus: true, - controller: _input, - focusNode: _inputFocus, - readOnly: !signedIn, - canRequestFocus: signedIn, - minLines: 1, - maxLines: 4, - maxLength: _maxCommentLength, - textAlignVertical: multiline - ? TextAlignVertical.top - : TextAlignVertical.center, - style: TextStyle( - color: AppColors.cream, - fontSize: 15, - height: multiline ? 1.4 : null, - ), - cursorColor: AppColors.blue, - decoration: InputDecoration( - hintText: signedIn - ? l10n.writeComment - : l10n.commentLoginHint, - hintStyle: const TextStyle( - color: AppColors.muted, - fontSize: 14, - ), - counterText: '', - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - ), - onTap: signedIn ? null : _authenticate, - onTapOutside: (_) { - _inputFocus.unfocus(); - _closeSheet(); - }, - ), - ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerRight, - child: SizedBox( - height: _postButtonHeight, - child: TextButton( - key: const Key('comment-composer-post'), - onPressed: canPost ? _submit : null, - style: TextButton.styleFrom( - minimumSize: const Size(0, _postButtonHeight), - padding: const EdgeInsets.symmetric(horizontal: 16), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - backgroundColor: hasContent - ? AppColors.blue.withValues(alpha: 0.12) - : AppColors.raised, - foregroundColor: AppColors.blue, - disabledForegroundColor: AppColors.muted, - disabledBackgroundColor: AppColors.raised, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + ), + Align( + alignment: Alignment.bottomCenter, + child: AnimatedPadding( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + padding: EdgeInsets.only(bottom: keyboardHeight), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () {}, + child: SizedBox( + key: const Key('comment-composer-sheet'), + width: double.infinity, + child: Material( + color: AppColors.card, + clipBehavior: Clip.antiAlias, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(_sheetRadius), + ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + child: TextFieldTapRegion( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (widget.replyTo != null) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + l10n.replyingTo( + widget.replyTo!.nickname.isEmpty + ? l10n.anonymousUser + : widget.replyTo!.nickname, + ), + style: const TextStyle( + color: AppColors.muted, + fontSize: 12, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: AppColors.commentInput, + borderRadius: BorderRadius.circular( + _inputRadius, + ), + ), + child: TextField( + key: const Key('comment-composer-input'), + autofocus: true, + controller: _input, + focusNode: _inputFocus, + readOnly: !signedIn, + canRequestFocus: signedIn, + minLines: 1, + maxLines: 4, + maxLength: _maxCommentLength, + textAlignVertical: multiline + ? TextAlignVertical.top + : TextAlignVertical.center, + style: TextStyle( + color: AppColors.cream, + fontSize: 15, + height: multiline ? 1.4 : null, + ), + cursorColor: AppColors.blue, + decoration: InputDecoration( + hintText: signedIn + ? l10n.writeComment + : l10n.commentLoginHint, + hintStyle: const TextStyle( + color: AppColors.muted, + fontSize: 14, + ), + counterText: '', + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + onTap: signedIn ? null : _authenticate, + onTapOutside: (_) { + _inputFocus.unfocus(); + _closeSheet(); + }, + ), ), - ), - child: Text( - l10n.postComment, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, + const SizedBox(height: 12), + Align( + alignment: Alignment.centerRight, + child: SizedBox( + height: _postButtonHeight, + child: TextButton( + key: const Key('comment-composer-post'), + onPressed: canPost ? _submit : null, + style: TextButton.styleFrom( + minimumSize: const Size( + 0, + _postButtonHeight, + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + tapTargetSize: + MaterialTapTargetSize.shrinkWrap, + backgroundColor: hasContent + ? AppColors.blue.withValues(alpha: 0.12) + : AppColors.raised, + foregroundColor: AppColors.blue, + disabledForegroundColor: AppColors.muted, + disabledBackgroundColor: AppColors.raised, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text( + l10n.postComment, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ), ), - ), + ], ), ), ), - ], + ), ), ), ), ), ), - ), + ], ); } } diff --git a/lib/pages/player/widgets/drama_comments_sheet.dart b/lib/pages/player/widgets/drama_comments_sheet.dart index 9feef57..3daa972 100644 --- a/lib/pages/player/widgets/drama_comments_sheet.dart +++ b/lib/pages/player/widgets/drama_comments_sheet.dart @@ -41,7 +41,8 @@ class DramaCommentsSheet extends StatefulWidget { class _DramaCommentsSheetState extends State { late final CommentRepository _comments; late final CommentController _controller; - final _messengerKey = GlobalKey(); + OverlayEntry? _successOverlay; + Timer? _successOverlayTimer; int? _serverCount; int _countRequest = 0; @@ -60,6 +61,8 @@ class _DramaCommentsSheetState extends State { @override void dispose() { + _successOverlayTimer?.cancel(); + _successOverlay?.remove(); _controller.dispose(); super.dispose(); } @@ -88,6 +91,7 @@ class _DramaCommentsSheetState extends State { isScrollControlled: true, isDismissible: true, backgroundColor: Colors.transparent, + barrierColor: Colors.transparent, showDragHandle: false, builder: (_) => CommentComposerSheet(controller: _controller, replyTo: replyTo), @@ -103,32 +107,19 @@ class _DramaCommentsSheetState extends State { } void _showPostSuccess() { - final messenger = _messengerKey.currentState; - if (messenger == null) return; + _successOverlayTimer?.cancel(); + _successOverlay?.remove(); 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), - ), - ); + final entry = OverlayEntry( + builder: (_) => + _CommentPostSuccessOverlay(message: l10n.commentPostSuccess), + ); + _successOverlay = entry; + Overlay.of(context, rootOverlay: true).insert(entry); + _successOverlayTimer = Timer(const Duration(milliseconds: 1200), () { + if (entry.mounted) entry.remove(); + if (identical(_successOverlay, entry)) _successOverlay = null; + }); } double _sheetHeight(BuildContext context) { @@ -142,60 +133,51 @@ class _DramaCommentsSheetState extends State { @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), - ); - }, - ), - ), + 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), + ); + }, ), ), ), ), ), - ], + ), ), - ), + ], ); } @@ -372,6 +354,44 @@ class _DramaCommentsSheetState extends State { } } +class _CommentPostSuccessOverlay extends StatelessWidget { + const _CommentPostSuccessOverlay({required this.message}); + + final String message; + + @override + Widget build(BuildContext context) { + return Positioned.fill( + child: IgnorePointer( + child: SafeArea( + child: Align( + alignment: const Alignment(0, 0.55), + child: Semantics( + liveRegion: true, + child: Container( + key: const Key('comment-post-success-snackbar'), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.82), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + message, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ), + ); + } +} + class _CommentThread extends StatelessWidget { const _CommentThread({ required this.comment, diff --git a/test/comment/drama_comments_sheet_test.dart b/test/comment/drama_comments_sheet_test.dart index bd50a7f..e1dc5b6 100644 --- a/test/comment/drama_comments_sheet_test.dart +++ b/test/comment/drama_comments_sheet_test.dart @@ -291,21 +291,32 @@ void main() { await tester.pump(const Duration(milliseconds: 500)); expect(find.byKey(const Key('comment-composer-sheet')), findsNothing); - final successSnackBar = find.byKey( + final successOverlay = find.byKey( const Key('comment-post-success-snackbar'), ); - expect(successSnackBar, findsOneWidget); + expect(successOverlay, 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(find.byType(SnackBar), findsNothing); + final capsule = tester.widget(successOverlay); + final decoration = capsule.decoration! as BoxDecoration; + expect(decoration.color, Colors.black.withValues(alpha: 0.82)); + expect(decoration.borderRadius, BorderRadius.circular(8)); expect( - tester.getRect(find.text('发表成功')).bottom, - lessThanOrEqualTo( - tester.getRect(find.byKey(const Key('player-comments-sheet'))).top, - ), + capsule.padding, + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), ); + final successText = tester.widget(find.text('发表成功')); + expect(successText.style?.color, Colors.white); + expect(successText.style?.fontSize, 14); + final placeholderRect = tester.getRect( + find.byKey(const Key('comment-input-placeholder')), + ); + expect( + tester.getRect(successOverlay).bottom, + lessThan(placeholderRect.top), + ); + await tester.pump(const Duration(milliseconds: 1200)); + expect(successOverlay, findsNothing); 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); @@ -341,6 +352,10 @@ void main() { final composer = find.byKey(const Key('comment-composer-sheet')); final composerInput = find.byKey(const Key('comment-composer-input')); expect(composer, findsOneWidget); + expect( + ModalRoute.of(tester.element(composer))?.barrierColor, + Colors.transparent, + ); final compactHeight = tester.getSize(composer).height; expect(compactHeight, inInclusiveRange(100, 150)); expect(tester.widget(composerInput).focusNode?.hasFocus, isTrue); @@ -406,12 +421,21 @@ void main() { await tester.tap(find.byKey(const Key('comment-input-placeholder'))); await tester.pumpAndSettle(); expect(sheet, findsOneWidget); - expect(find.byKey(const Key('comment-composer-sheet')), findsOneWidget); + final composer = find.byKey(const Key('comment-composer-sheet')); + expect(composer, findsOneWidget); + expect( + find.byKey(const Key('comment-composer-dismiss-layer')), + findsOneWidget, + ); - await tester.tapAt(const Offset(20, 20)); + final sheetRect = tester.getRect(sheet); + final composerRect = tester.getRect(composer); + await tester.tapAt( + Offset(sheetRect.center.dx, (sheetRect.top + composerRect.top) / 2), + ); await tester.pumpAndSettle(); expect(sheet, findsOneWidget); - expect(find.byKey(const Key('comment-composer-sheet')), findsNothing); + expect(composer, findsNothing); await tester.tapAt(const Offset(20, 20)); await tester.pumpAndSettle();