diff --git a/android/app/src/main/kotlin/com/stormg/lumadrama/MainActivity.kt b/android/app/src/main/kotlin/com/stormg/lumadrama/MainActivity.kt index c247e9c..3fc470e 100644 --- a/android/app/src/main/kotlin/com/stormg/lumadrama/MainActivity.kt +++ b/android/app/src/main/kotlin/com/stormg/lumadrama/MainActivity.kt @@ -8,6 +8,8 @@ import android.content.res.Configuration import android.os.Build import android.util.Rational import android.content.pm.PackageManager +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -16,6 +18,8 @@ class MainActivity : FlutterActivity() { private val shareRequestCode = 9041 private var pendingShare: MethodChannel.Result? = null private var pipChannel: MethodChannel? = null + private var keyboardChannel: MethodChannel? = null + private var imeInset = 0 override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) @@ -62,6 +66,37 @@ class MainActivity : FlutterActivity() { result.error("share_failed", error.message, null) } } + keyboardChannel = MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + "com.stormg.lumadrama/keyboard" + ) + keyboardChannel?.setMethodCallHandler { call, result -> + if (call.method != "get") { + result.notImplemented() + return@setMethodCallHandler + } + val inset = currentImeInset() + emitImeInset(inset) + result.success(inset) + } + val decorView = window.decorView + ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, insets -> + emitImeInset(insets.getInsets(WindowInsetsCompat.Type.ime()).bottom) + ViewCompat.onApplyWindowInsets(view, insets) + } + ViewCompat.requestApplyInsets(decorView) + } + + private fun currentImeInset(): Int = + ViewCompat.getRootWindowInsets(window.decorView) + ?.getInsets(WindowInsetsCompat.Type.ime()) + ?.bottom + ?: 0 + + private fun emitImeInset(value: Int) { + if (value == imeInset) return + imeInset = value + keyboardChannel?.invokeMethod("insetsChanged", value) } override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { @@ -77,4 +112,11 @@ class MainActivity : FlutterActivity() { pendingShare = null } } + + override fun onDestroy() { + ViewCompat.setOnApplyWindowInsetsListener(window.decorView, null) + keyboardChannel?.setMethodCallHandler(null) + keyboardChannel = null + super.onDestroy() + } } diff --git a/lib/core/platform/keyboard_inset.dart b/lib/core/platform/keyboard_inset.dart new file mode 100644 index 0000000..dfa0783 --- /dev/null +++ b/lib/core/platform/keyboard_inset.dart @@ -0,0 +1,48 @@ +// Author: fruit.com.cn + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +/// Provides the Android IME height even when `adjustNothing` keeps +/// `MediaQuery.viewInsets` at zero. +class KeyboardInsetNotifier extends ChangeNotifier { + KeyboardInsetNotifier._(); + + static final KeyboardInsetNotifier instance = KeyboardInsetNotifier._(); + static const MethodChannel _channel = MethodChannel( + 'com.stormg.lumadrama/keyboard', + ); + + /// Raw physical-pixel inset reported by Android. + int _inset = 0; + bool _started = false; + + int get value => _inset; + + Future start() async { + if (_started || kIsWeb || defaultTargetPlatform != TargetPlatform.android) { + return; + } + _started = true; + _channel.setMethodCallHandler((call) async { + if (call.method != 'insetsChanged') return; + _update(call.arguments); + }); + try { + _update(await _channel.invokeMethod('get')); + } catch (_) { + // Tests and non-embedded Android hosts may not provide the channel. + } + } + + void _update(Object? value) { + final next = switch (value) { + int value => value, + num value => value.round(), + _ => 0, + }; + if (next == _inset) return; + _inset = next; + notifyListeners(); + } +} diff --git a/lib/pages/player/widgets/drama_comments_sheet.dart b/lib/pages/player/widgets/drama_comments_sheet.dart index f6d8bef..914c6bd 100644 --- a/lib/pages/player/widgets/drama_comments_sheet.dart +++ b/lib/pages/player/widgets/drama_comments_sheet.dart @@ -1,6 +1,7 @@ // Author: fruit.com.cn import 'dart:async'; +import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -13,6 +14,7 @@ import '../../../core/data/services/comment_controller.dart'; import '../../../core/data/services/auth_controller.dart'; import '../../../core/formatting/error_text.dart'; import '../../../core/platform/haptics.dart'; +import '../../../core/platform/keyboard_inset.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/widgets/auth_gate.dart'; import '../../../core/widgets/empty_state.dart'; @@ -43,20 +45,26 @@ class DramaCommentsSheet extends StatefulWidget { State createState() => _DramaCommentsSheetState(); } -class _DramaCommentsSheetState extends State { +class _DramaCommentsSheetState extends State + with WidgetsBindingObserver { final _input = TextEditingController(); final _inputFocus = FocusNode(); + final _keyboardInset = KeyboardInsetNotifier.instance; late final CommentRepository _comments; late final CommentController _controller; DramaComment? _replyTo; int? _serverCount; int _countRequest = 0; + bool _keyboardWasVisible = false; @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); _inputFocus.addListener(_onFocusChanged); _input.addListener(_onInputChanged); + _keyboardInset.addListener(_onKeyboardInsetChanged); + unawaited(_keyboardInset.start()); _comments = context.read(); _controller = CommentController( comments: _comments, @@ -69,6 +77,8 @@ class _DramaCommentsSheetState extends State { @override void dispose() { + WidgetsBinding.instance.removeObserver(this); + _keyboardInset.removeListener(_onKeyboardInsetChanged); _inputFocus.removeListener(_onFocusChanged); _input.removeListener(_onInputChanged); _controller.dispose(); @@ -85,6 +95,45 @@ class _DramaCommentsSheetState extends State { if (mounted) setState(() {}); } + void _onKeyboardInsetChanged() { + if (!mounted) return; + setState(() {}); + _syncKeyboardVisibility( + math.max( + MediaQuery.viewInsetsOf(context).bottom, + _keyboardInset.value / MediaQuery.devicePixelRatioOf(context), + ), + ); + } + + @override + void didChangeMetrics() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _syncKeyboardVisibility( + math.max( + MediaQuery.viewInsetsOf(context).bottom, + _keyboardInset.value / MediaQuery.devicePixelRatioOf(context), + ), + ); + }); + } + + void _syncKeyboardVisibility(num keyboardHeight) { + if (keyboardHeight > 0) { + _keyboardWasVisible = true; + return; + } + if (!_keyboardWasVisible) return; + _keyboardWasVisible = false; + if (_inputFocus.hasFocus) _inputFocus.unfocus(); + } + + double _keyboardHeight(BuildContext context) => math.max( + MediaQuery.viewInsetsOf(context).bottom, + _keyboardInset.value / MediaQuery.devicePixelRatioOf(context), + ); + Future _post() async { if (_controller.submitting) return; if (!await ensureSignedIn(context) || !mounted) return; @@ -145,142 +194,50 @@ class _DramaCommentsSheetState extends State { final maxHeight = screenLimit < _maxSheetHeight ? screenLimit : _maxSheetHeight; - if (_inputFocus.hasFocus) { - return _expandedComposer(context, maxHeight); - } - return Container( - key: const Key('player-comments-sheet'), - decoration: const BoxDecoration( - color: AppColors.card, - borderRadius: BorderRadius.vertical(top: Radius.circular(_sheetRadius)), - ), - child: SafeArea( - top: false, - child: ListenableBuilder( - listenable: _controller, - builder: (context, _) { - final minHeight = _controller.comments.isEmpty - ? (_minSheetHeight < maxHeight ? _minSheetHeight : maxHeight) - : 0.0; - return ConstrainedBox( - key: const Key('comment-sheet-constraints'), - constraints: BoxConstraints( - minHeight: minHeight, - maxHeight: maxHeight, - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _content(context, maxHeight), - ), - ); - }, - ), - ), - ); - } - - Widget _expandedComposer(BuildContext context, double maxHeight) { - final l10n = AppLocalizations.of(context)!; - final signedIn = context.watch().isSignedIn; - final hasContent = _input.text.trim().isNotEmpty; - final canPost = signedIn && hasContent && !_controller.submitting; + final focused = _inputFocus.hasFocus; + final keyboardHeight = _keyboardHeight(context); return AnimatedPadding( duration: const Duration(milliseconds: 150), curve: Curves.easeOut, - padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom), - child: Container( - key: const Key('player-comments-sheet'), - decoration: const BoxDecoration( - color: AppColors.card, - borderRadius: BorderRadius.vertical( - top: Radius.circular(_sheetRadius), + padding: EdgeInsets.only(bottom: focused ? keyboardHeight : 0), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: focused ? _inputFocus.unfocus : null, + child: Container( + key: const Key('player-comments-sheet'), + decoration: const BoxDecoration( + color: AppColors.card, + borderRadius: BorderRadius.vertical( + top: Radius.circular(_sheetRadius), + ), ), - ), - child: SafeArea( - top: false, - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: maxHeight), - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), - child: Column( - key: const Key('comment-expanded-composer'), - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (_replyTo != null) _replyBanner(l10n, _replyTo!), - Container( - decoration: BoxDecoration( - color: AppColors.commentInput, - borderRadius: BorderRadius.circular(_expandedInputRadius), - ), - child: TextField( - key: const Key('comment-input'), - controller: _input, - focusNode: _inputFocus, - autofocus: true, - readOnly: !signedIn, - canRequestFocus: signedIn, - minLines: 1, - maxLines: 4, - maxLength: _maxCommentLength, - textAlignVertical: TextAlignVertical.top, - style: const TextStyle( - color: AppColors.cream, - fontSize: 15, - ), - cursorColor: AppColors.blue, - decoration: InputDecoration( - hintText: l10n.writeComment, - 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 : _authenticateForComment, + child: SafeArea( + top: false, + child: ListenableBuilder( + listenable: _controller, + builder: (context, _) { + final minHeight = !focused && _controller.comments.isEmpty + ? (_minSheetHeight < maxHeight + ? _minSheetHeight + : maxHeight) + : 0.0; + return ConstrainedBox( + key: const Key('comment-sheet-constraints'), + constraints: BoxConstraints( + minHeight: minHeight, + maxHeight: maxHeight, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _content( + context, + maxHeight: maxHeight, + focused: focused, + keyboardHeight: keyboardHeight, ), ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerRight, - child: SizedBox( - height: _postButtonHeight, - child: TextButton( - key: const Key('comment-post'), - onPressed: canPost ? _post : 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, - ), - ), - ), - ), - ), - ], - ), + ); + }, ), ), ), @@ -288,7 +245,12 @@ class _DramaCommentsSheetState extends State { ); } - Widget _content(BuildContext context, double maxHeight) { + Widget _content( + BuildContext context, { + required double maxHeight, + required bool focused, + required double keyboardHeight, + }) { final l10n = AppLocalizations.of(context)!; final count = _serverCount ?? _controller.exactCount; final replyReserve = _replyTo == null ? 0.0 : _replyBannerHeight; @@ -298,83 +260,72 @@ class _DramaCommentsSheetState extends State { _composerHeight - _composerTopGap - replyReserve; - return Stack( + return Column( + key: const Key('comment-sheet-content'), + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - key: const Key('comment-sheet-content'), - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: _headerHeight, - width: double.infinity, - child: Stack( - alignment: Alignment.center, - children: [ - Text( - count == null - ? l10n.comments - : l10n.commentsWithCount(count), - key: const Key('comment-count-label'), - textAlign: TextAlign.center, - style: const TextStyle( - color: AppColors.onSurface, - fontSize: 16, - fontWeight: FontWeight.w600, + SizedBox( + height: focused ? 0 : _headerHeight, + width: double.infinity, + child: focused + ? null + : Stack( + alignment: Alignment.center, + children: [ + Text( + count == null + ? l10n.comments + : l10n.commentsWithCount(count), + key: const Key('comment-count-label'), + textAlign: TextAlign.center, + style: const TextStyle( + color: AppColors.onSurface, + fontSize: 16, + fontWeight: FontWeight.w600, + ), ), - ), - Positioned( - left: 0, - child: SizedBox.square( - dimension: 44, - child: IconButton( - key: const Key('player-comments-close'), - tooltip: l10n.close, - padding: EdgeInsets.zero, - onPressed: () => Navigator.pop(context), - icon: const Icon( - Icons.keyboard_arrow_down_rounded, - size: 24, - color: AppColors.cream, + Positioned( + left: 0, + child: SizedBox.square( + dimension: 44, + child: IconButton( + key: const Key('player-comments-close'), + tooltip: l10n.close, + padding: EdgeInsets.zero, + onPressed: () => Navigator.pop(context), + icon: const Icon( + Icons.keyboard_arrow_down_rounded, + size: 24, + color: AppColors.cream, + ), ), ), ), - ), - ], - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: listMaxHeight < 0 ? 0 : listMaxHeight, - ), - child: _list(l10n), - ), - if (_replyTo != null) _replyBanner(l10n, _replyTo!), - const SizedBox(height: _composerHeight + _composerTopGap), - ], + ], + ), ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: AnimatedPadding( - duration: const Duration(milliseconds: 150), - curve: Curves.easeOut, - padding: EdgeInsets.only( - bottom: MediaQuery.viewInsetsOf(context).bottom, - ), - child: Padding( - padding: const EdgeInsets.only(top: _composerTopGap), - child: _composer(l10n), - ), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: focused || listMaxHeight < 0 ? 0 : listMaxHeight, ), + child: focused + ? const SizedBox.shrink() + : _list(l10n, keyboardHeight: keyboardHeight), + ), + if (_replyTo != null) _replyBanner(l10n, _replyTo!), + Padding( + padding: EdgeInsets.only( + top: focused ? 12 : _composerTopGap, + bottom: focused ? 12 : 0, + ), + child: _composer(l10n, focused: focused), ), ], ); } - Widget _list(AppLocalizations l10n) { - final keyboardHeight = MediaQuery.viewInsetsOf(context).bottom; + Widget _list(AppLocalizations l10n, {required double keyboardHeight}) { if (_controller.loading && _controller.comments.isEmpty) { return const Padding( padding: EdgeInsets.only(top: 4), @@ -472,9 +423,11 @@ class _DramaCommentsSheetState extends State { ), ); - InputDecoration _composerDecoration(String hint) { - const radius = BorderRadius.all(Radius.circular(_composerRadius)); - const clear = OutlineInputBorder( + InputDecoration _composerDecoration(String hint, {required bool focused}) { + final radius = BorderRadius.circular( + focused ? _expandedInputRadius : _composerRadius, + ); + final clear = OutlineInputBorder( borderRadius: radius, borderSide: BorderSide.none, ); @@ -482,7 +435,10 @@ class _DramaCommentsSheetState extends State { hintText: hint, hintStyle: const TextStyle(color: AppColors.muted, fontSize: 14), counterText: '', - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: focused ? 12 : 0, + ), isDense: true, border: clear, enabledBorder: clear, @@ -490,57 +446,130 @@ class _DramaCommentsSheetState extends State { ); } - Widget _composer(AppLocalizations l10n) { + Widget _composer(AppLocalizations l10n, {required bool focused}) { final signedIn = context.watch().isSignedIn; + final hasContent = _input.text.trim().isNotEmpty; + final canPost = signedIn && hasContent && !_controller.submitting; return Container( key: const Key('comment-composer'), - height: _composerHeight, - padding: const EdgeInsets.only(right: 3), + padding: focused ? EdgeInsets.zero : const EdgeInsets.only(right: 3), decoration: BoxDecoration( - color: AppColors.commentInput, - borderRadius: BorderRadius.circular(_composerRadius), + color: focused ? Colors.transparent : AppColors.commentInput, + borderRadius: BorderRadius.circular(focused ? 0 : _composerRadius), ), - child: Row( - children: [ - Expanded( - child: SizedBox( - height: _composerHeight, - child: TextField( - key: const Key('comment-input'), - controller: _input, - focusNode: _inputFocus, - 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, + child: TextFieldTapRegion( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AnimatedContainer( + key: const Key('comment-input-surface'), + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + decoration: BoxDecoration( + color: focused ? AppColors.commentInput : Colors.transparent, + borderRadius: BorderRadius.circular( + focused ? _expandedInputRadius : _composerRadius, + ), + ), + child: SizedBox( + height: focused ? null : _composerHeight, + child: Row( + crossAxisAlignment: focused + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: focused ? null : _composerHeight, + child: TextField( + key: const Key('comment-input'), + controller: _input, + focusNode: _inputFocus, + readOnly: !signedIn, + canRequestFocus: signedIn, + minLines: 1, + maxLines: focused ? 4 : 1, + maxLength: _maxCommentLength, + textAlignVertical: focused + ? TextAlignVertical.top + : TextAlignVertical.center, + style: TextStyle( + color: AppColors.cream, + fontSize: focused ? 15 : 14, + ), + cursorColor: AppColors.blue, + decoration: _composerDecoration( + signedIn + ? l10n.writeComment + : l10n.commentLoginHint, + focused: focused, + ), + onTap: signedIn ? null : _authenticateForComment, + onTapOutside: (_) => _inputFocus.unfocus(), + onSubmitted: focused ? null : (_) => _post(), + ), + ), + ), + if (!focused) + SizedBox.square( + dimension: _sendButtonSize, + child: IconButton.filled( + key: const Key('comment-post'), + tooltip: l10n.postComment, + padding: EdgeInsets.zero, + iconSize: 18, + onPressed: _controller.submitting ? null : _post, + icon: _controller.submitting + ? const SizedBox.square( + dimension: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : const Icon(Icons.send_rounded), + ), + ), + ], ), - onTap: signedIn ? null : _authenticateForComment, - onSubmitted: (_) => _post(), ), ), - ), - SizedBox.square( - dimension: _sendButtonSize, - child: IconButton.filled( - key: const Key('comment-post'), - tooltip: l10n.postComment, - padding: EdgeInsets.zero, - iconSize: 18, - onPressed: _controller.submitting ? null : _post, - icon: _controller.submitting - ? const SizedBox.square( - dimension: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.send_rounded), - ), - ), - ], + if (focused) ...[ + const SizedBox(height: 12), + Align( + alignment: Alignment.centerRight, + child: SizedBox( + height: _postButtonHeight, + child: TextButton( + key: const Key('comment-post'), + onPressed: canPost ? _post : 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/test/comment/drama_comments_sheet_test.dart b/test/comment/drama_comments_sheet_test.dart index 135b2e9..866e4f7 100644 --- a/test/comment/drama_comments_sheet_test.dart +++ b/test/comment/drama_comments_sheet_test.dart @@ -246,7 +246,7 @@ void main() { expect(tester.widget(input).controller?.text, isEmpty); expect(tester.widget(input).focusNode?.hasFocus, isTrue); - expect(find.byKey(const Key('comment-expanded-composer')), findsOneWidget); + expect(tester.widget(input).maxLines, 4); expect(find.byKey(const Key('comment-list')), findsNothing); expect( tester @@ -275,14 +275,27 @@ void main() { await _pumpSheet(tester, repository: repository); expect(find.byKey(const Key('comment-list')), findsOneWidget); + final editableBefore = tester.state( + find.descendant( + of: find.byKey(const Key('comment-input')), + matching: find.byType(EditableText), + ), + ); await tester.tap(find.byKey(const Key('comment-input'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('comment-list')), findsNothing); expect(find.byKey(const Key('comment-count-label')), findsNothing); - expect(find.byKey(const Key('comment-composer')), findsNothing); - expect(find.byKey(const Key('comment-expanded-composer')), findsOneWidget); + expect(find.byKey(const Key('comment-composer')), findsOneWidget); + expect(find.byKey(const Key('comment-input-surface')), findsOneWidget); + final editableAfter = tester.state( + find.descendant( + of: find.byKey(const Key('comment-input')), + matching: find.byType(EditableText), + ), + ); + expect(identical(editableBefore, editableAfter), isTrue); final field = tester.widget( find.byKey(const Key('comment-input')), ); @@ -313,17 +326,35 @@ void main() { tester.view.viewInsets = const FakeViewPadding(bottom: 180); await tester.pump(); await tester.pump(const Duration(milliseconds: 160)); + expect(find.byKey(const Key('player-comments-sheet')), findsOneWidget); + expect(find.byKey(const Key('comment-list')), findsNothing); expect( tester.getBottomLeft(find.byKey(const Key('player-comments-sheet'))).dy, closeTo(844 - 180, 0.1), ); + expect( + tester.getBottomLeft(find.byKey(const Key('comment-input'))).dy, + lessThanOrEqualTo(844 - 180), + ); tester.view.viewInsets = const FakeViewPadding(); - FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpAndSettle(); - expect(find.byKey(const Key('comment-expanded-composer')), findsNothing); expect(find.byKey(const Key('comment-list')), findsOneWidget); expect(find.byKey(const Key('comment-composer')), findsOneWidget); + expect( + tester + .widget(find.byKey(const Key('comment-input'))) + .controller + ?.text, + 'Nice', + ); + expect( + tester + .widget(find.byKey(const Key('comment-input'))) + .focusNode + ?.hasFocus, + isFalse, + ); }); testWidgets('should_use_polished_comment_sizes', (tester) async { @@ -482,37 +513,6 @@ void main() { ); }); - testWidgets('should_keep_sheet_fixed_and_move_only_composer_for_keyboard', ( - tester, - ) async { - tester.view.physicalSize = const Size(390, 844); - tester.view.devicePixelRatio = 1; - addTearDown(tester.view.resetPhysicalSize); - addTearDown(tester.view.resetDevicePixelRatio); - addTearDown(tester.view.resetViewInsets); - final repository = FakeCommentRepository( - pages: [CommentPage(comments: _comments(30))], - ); - - await _pumpSheet(tester, repository: repository); - final sheet = find.byKey(const Key('player-comments-sheet')); - final input = find.byKey(const Key('comment-composer')); - final first = find.byKey(const Key('comment-item-10')); - final sheetTop = tester.getTopLeft(sheet).dy; - final inputTop = tester.getTopLeft(input).dy; - final inputBottom = tester.getBottomLeft(input).dy; - final firstTop = tester.getTopLeft(first).dy; - - tester.view.viewInsets = const FakeViewPadding(bottom: 180); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 160)); - - expect(tester.getTopLeft(sheet).dy, closeTo(sheetTop, 0.1)); - expect(tester.getTopLeft(first).dy, closeTo(firstTop, 0.1)); - expect(tester.getTopLeft(input).dy, lessThan(inputTop - 100)); - expect(tester.getBottomLeft(input).dy, closeTo(inputBottom - 180, 0.1)); - }); - testWidgets('should_hide_comment_like_controls', (tester) async { final repository = FakeCommentRepository( pages: [