diff --git a/lib/pages/player/widgets/drama_comments_sheet.dart b/lib/pages/player/widgets/drama_comments_sheet.dart index 564ee4f..083c30d 100644 --- a/lib/pages/player/widgets/drama_comments_sheet.dart +++ b/lib/pages/player/widgets/drama_comments_sheet.dart @@ -184,54 +184,67 @@ class _DramaCommentsSheetState extends State { final maxHeight = screenLimit < _maxSheetHeight ? screenLimit : _maxSheetHeight; - final focused = _inputFocus.hasFocus; + final inputFocused = _inputFocus.hasFocus; final keyboardHeight = _keyboardHeight(context); + final keyboardOpen = keyboardHeight > 0; + final expanded = inputFocused && keyboardOpen; + final availableHeight = math.max(0.0, screenLimit - keyboardHeight); + final keyboardPanelHeight = math.min( + _sheetHeightWithoutKeyboard ?? maxHeight, + availableHeight, + ); _measureSheetWithoutKeyboard(keyboardHeight); - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: focused ? _inputFocus.unfocus : null, - child: SizedBox( - key: _sheetMeasureKey, - child: Container( - key: const Key('player-comments-sheet'), - decoration: const BoxDecoration( - color: AppColors.card, - borderRadius: BorderRadius.vertical( - top: Radius.circular(_sheetRadius), + return AnimatedPadding( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + padding: EdgeInsets.only(bottom: keyboardHeight), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: inputFocused ? _inputFocus.unfocus : null, + child: SizedBox( + key: _sheetMeasureKey, + 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: ListenableBuilder( - listenable: _controller, - builder: (context, _) { - final minHeight = _controller.comments.isEmpty - ? (_minSheetHeight < maxHeight - ? _minSheetHeight - : maxHeight) - : 0.0; - final pinnedHeight = - keyboardHeight > 0 && _sheetHeightWithoutKeyboard != null - ? math.min(_sheetHeightWithoutKeyboard!, maxHeight) - : null; - return ConstrainedBox( - key: const Key('comment-sheet-constraints'), - constraints: pinnedHeight == null - ? BoxConstraints( - minHeight: minHeight, - maxHeight: maxHeight, - ) - : BoxConstraints.tightFor(height: pinnedHeight), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _content( - context, - focused: focused, - keyboardHeight: keyboardHeight, + child: SafeArea( + top: false, + child: ListenableBuilder( + listenable: _controller, + builder: (context, _) { + final minHeight = _controller.comments.isEmpty + ? (_minSheetHeight < maxHeight + ? _minSheetHeight + : maxHeight) + : 0.0; + return AnimatedSize( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + key: const Key('comment-sheet-constraints'), + constraints: keyboardOpen + ? BoxConstraints.tightFor(height: keyboardPanelHeight) + : BoxConstraints( + minHeight: minHeight, + maxHeight: maxHeight, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _content( + context, + expanded: expanded, + keyboardHeight: keyboardHeight, + ), + ), ), - ), - ); - }, + ); + }, + ), ), ), ), @@ -241,12 +254,12 @@ class _DramaCommentsSheetState extends State { Widget _content( BuildContext context, { - required bool focused, + required bool expanded, required double keyboardHeight, }) { final l10n = AppLocalizations.of(context)!; final count = _serverCount ?? _controller.exactCount; - final composerReserve = focused + final composerReserve = expanded ? _expandedComposerReserve : _composerHeight + _composerTopGap; return Column( @@ -299,18 +312,13 @@ class _DramaCommentsSheetState extends State { ), ), if (_replyTo != null) _replyBanner(l10n, _replyTo!), - AnimatedContainer( - key: const Key('comment-input-lift'), - duration: const Duration(milliseconds: 200), - curve: Curves.easeOut, - transform: Matrix4.translationValues(0, -keyboardHeight, 0), - child: Padding( - padding: EdgeInsets.only( - top: focused ? 12 : _composerTopGap, - bottom: focused ? 12 : 0, - ), - child: _composer(l10n, focused: focused), + Padding( + key: const Key('comment-composer-area'), + padding: EdgeInsets.only( + top: expanded ? 12 : _composerTopGap, + bottom: expanded ? 12 : 0, ), + child: _composer(l10n, focused: expanded), ), ], ); diff --git a/test/comment/drama_comments_sheet_test.dart b/test/comment/drama_comments_sheet_test.dart index 5382678..2ea4107 100644 --- a/test/comment/drama_comments_sheet_test.dart +++ b/test/comment/drama_comments_sheet_test.dart @@ -222,6 +222,7 @@ void main() { testWidgets('should_clear_and_keep_input_focused_after_successful_post', ( tester, ) async { + addTearDown(tester.view.resetViewInsets); final repository = FakeCommentRepository( pages: [const CommentPage()], publishResult: commentFixture(id: '11', content: 'Nice demo', mine: true), @@ -230,8 +231,9 @@ void main() { await _pumpSheet(tester, repository: repository); final input = find.byKey(const Key('comment-input')); + tester.view.viewInsets = const FakeViewPadding(bottom: 180); await tester.tap(input); - await tester.pump(); + await tester.pumpAndSettle(); expect(tester.widget(input).focusNode?.hasFocus, isTrue); await tester.enterText(input, 'Nice demo'); await tester.pump(); @@ -258,7 +260,7 @@ void main() { expect(repository.countCalls, ['5', '5']); }); - testWidgets('should_expand_composer_only_while_input_is_focused', ( + testWidgets('should_keep_composer_separate_when_keyboard_opens', ( tester, ) async { tester.view.physicalSize = const Size(390, 844); @@ -290,6 +292,11 @@ void main() { expect(find.byKey(const Key('comment-count-label')), findsOneWidget); expect(find.byKey(const Key('comment-composer')), findsOneWidget); expect(find.byKey(const Key('comment-input-surface')), findsOneWidget); + expect( + tester.widget(find.byKey(const Key('comment-input'))).maxLines, + 1, + ); + expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44); final editableAfter = tester.state( find.descendant( of: find.byKey(const Key('comment-input')), @@ -297,6 +304,16 @@ void main() { ), ); expect(identical(editableBefore, editableAfter), isTrue); + + final sheet = find.byKey(const Key('player-comments-sheet')); + final list = find.byKey(const Key('comment-list')); + tester.view.viewInsets = const FakeViewPadding(bottom: 180); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + + expect(sheet, findsOneWidget); + expect(list, findsOneWidget); + expect(find.byKey(const Key('comment-count-label')), findsOneWidget); final field = tester.widget( find.byKey(const Key('comment-input')), ); @@ -310,6 +327,10 @@ void main() { isNull, ); expect(tester.getSize(find.byKey(const Key('comment-post'))).height, 32); + final listRect = tester.getRect(list); + final inputRect = tester.getRect(find.byKey(const Key('comment-input'))); + expect(listRect.overlaps(inputRect), isFalse); + expect(listRect.bottom, lessThanOrEqualTo(inputRect.top)); expect(find.byIcon(Icons.image_outlined), findsNothing); expect(find.byIcon(Icons.emoji_emotions_outlined), findsNothing); expect(find.byIcon(Icons.alternate_email_rounded), findsNothing); @@ -323,16 +344,6 @@ void main() { isNotNull, ); - final sheet = find.byKey(const Key('player-comments-sheet')); - final list = find.byKey(const Key('comment-list')); - final sheetTop = tester.getTopLeft(sheet).dy; - final listTop = tester.getTopLeft(list).dy; - tester.view.viewInsets = const FakeViewPadding(bottom: 180); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 200)); - expect(sheet, findsOneWidget); - expect(list, findsOneWidget); - expect(find.byKey(const Key('comment-count-label')), findsOneWidget); final editableDuringKeyboard = tester.state( find.descendant( of: find.byKey(const Key('comment-input')), @@ -340,8 +351,7 @@ void main() { ), ); expect(identical(editableAfter, editableDuringKeyboard), isTrue); - expect(tester.getTopLeft(sheet).dy, closeTo(sheetTop, 0.1)); - expect(tester.getTopLeft(list).dy, closeTo(listTop, 0.1)); + expect(tester.getBottomLeft(sheet).dy, closeTo(844 - 180, 0.1)); expect( tester.widget(list).padding?.resolve(TextDirection.ltr).bottom, greaterThanOrEqualTo(180 + 44), @@ -363,6 +373,12 @@ void main() { expect(list, findsOneWidget); expect(find.byKey(const Key('comment-count-label')), findsOneWidget); expect(find.byKey(const Key('comment-composer')), findsOneWidget); + final collapsedField = tester.widget( + find.byKey(const Key('comment-input')), + ); + expect(collapsedField.maxLines, 1); + expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44); + expect(tester.getSize(sheet).height, lessThan(520)); final editableAfterKeyboard = tester.state( find.descendant( of: find.byKey(const Key('comment-input')), @@ -370,8 +386,6 @@ void main() { ), ); expect(identical(editableAfter, editableAfterKeyboard), isTrue); - expect(tester.getTopLeft(sheet).dy, closeTo(sheetTop, 0.1)); - expect(tester.getTopLeft(list).dy, closeTo(listTop, 0.1)); expect( tester .widget(find.byKey(const Key('comment-input')))