From 153f2b0ac950c3f34e11e0af1e4880c9a98998cc Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Wed, 15 Jul 2026 22:17:57 -0400 Subject: [PATCH 1/3] fix(text-editor): reserve hit-box padding in editing preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The in-editor text preview (RoundedBackgroundTextField) passes enableHitBoxCorrection: false, so its rounded background hugs the glyphs, while the finished layer (LayerWidgetTextItem) renders the same RoundedBackgroundText with enableHitBoxCorrection: true. The rounded background box therefore grows and shifts (becoming left/right symmetric) the moment editing completes — a visible WYSIWYG mismatch between typing and the placed text layer. Reserve the same symmetric hit-box padding while editing: enable the correction on the preview background and inset the editable glyphs by the matching line-height ratio (0.3 horizontal, 0.1 vertical), so the box is identical during editing and after completion. Adds a widget test locking the preview background height to the finished layer's. --- .../rounded_background_text_field.dart | 146 ++++++++++++------ ...ded_background_text_field_hitbox_test.dart | 90 +++++++++++ 2 files changed, 185 insertions(+), 51 deletions(-) create mode 100644 test/features/text_editor/rounded_background_text_field_hitbox_test.dart diff --git a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart index 96fd7f250..cd94b7fa9 100644 --- a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart +++ b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart @@ -121,6 +121,16 @@ class _RoundedBackgroundTextFieldState final fontSize = (widget.style.fontSize ?? defaultTextStyle.style.fontSize ?? 16); + // The background rectangle drawn by [RoundedBackgroundTextPainter] always + // extends the text by these paddings (see `paddingHorizontal`/ + // `paddingVertical` there). The finished layer reserves room for them via + // `enableHitBoxCorrection: true`; the editing preview must reserve the same + // room so the rounded background does not visibly grow and shift the moment + // editing completes. + final lineHeight = _preferredLineHeight(fontSize); + final hitBoxHorizontal = lineHeight * 0.3; + final hitBoxVertical = lineHeight * 0.1; + return MediaQuery( data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: Stack( @@ -134,12 +144,32 @@ class _RoundedBackgroundTextFieldState }, children: [ if (_textController.text.isNotEmpty) _buildBackgroundText(), - _buildEditableText(fontSize: fontSize), + _buildEditableText( + fontSize: fontSize, + hitBoxHorizontal: hitBoxHorizontal, + hitBoxVertical: hitBoxVertical, + ), ], ), ); } + /// The preferred line height for [widget.style] at [fontSize], computed the + /// same way [RoundedBackgroundText] lays the text out, so the hit-box padding + /// reserved here matches the rectangle the painter draws. + double _preferredLineHeight(double fontSize) { + final painter = TextPainter( + text: TextSpan( + style: TextStyle( + leadingDistribution: widget.configs.style.leadingDistribution, + ).merge(widget.style.copyWith(fontSize: fontSize)), + text: 'A', + ), + textDirection: Directionality.maybeOf(context) ?? TextDirection.ltr, + )..layout(); + return painter.preferredLineHeight; + } + Widget _buildBackgroundText() { final style = widget.style.copyWith( color: Colors.transparent, @@ -161,62 +191,76 @@ class _RoundedBackgroundTextFieldState cursorWidth: widget.cursorWidth, textAlign: widget.textAlign, backgroundColor: widget.backgroundColor, + // Match the finished layer (LayerWidgetTextItem) so the rounded + // background reserves symmetric padding while editing. + enableHitBoxCorrection: true, ), ), ); } - Widget _buildEditableText({required double fontSize}) { - return Material( - type: MaterialType.transparency, - child: TextField( - onTap: - _textController.text.isEmpty && - View.of(context).viewInsets.bottom <= 0 - ? () { - FocusManager.instance.primaryFocus?.unfocus(); - widget.focusNode.requestFocus(); - } - : null, - autofocus: widget.autofocus, - controller: _textController, - focusNode: widget.focusNode, - scrollPhysics: const NeverScrollableScrollPhysics(), - scrollController: _scrollCtrl, - scrollPadding: EdgeInsets.zero, - style: widget.style.copyWith( - fontSize: fontSize, - leadingDistribution: widget.configs.style.leadingDistribution, - height: widget.configs.style.textHeight, - ), - spellCheckConfiguration: widget.configs.spellCheckConfiguration, - decoration: InputDecoration.collapsed( - hintText: _textController.text.isEmpty ? widget.hint : '', - hintStyle: - (widget.hintStyle ?? - TextStyle(color: Theme.of(context).hintColor)) - .copyWith(fontSize: fontSize), - maintainHintSize: false, + Widget _buildEditableText({ + required double fontSize, + required double hitBoxHorizontal, + required double hitBoxVertical, + }) { + return Padding( + // Inset the editable glyphs by the same hit-box padding the background + // rectangle reserves, so the visible text stays centered inside the box + // and aligns with the finished layer. + padding: EdgeInsets.symmetric( + horizontal: hitBoxHorizontal, + vertical: hitBoxVertical, + ), + child: Material( + type: MaterialType.transparency, + child: TextField( + onTap: _textController.text.isEmpty && + View.of(context).viewInsets.bottom <= 0 + ? () { + FocusManager.instance.primaryFocus?.unfocus(); + widget.focusNode.requestFocus(); + } + : null, + autofocus: widget.autofocus, + controller: _textController, + focusNode: widget.focusNode, + scrollPhysics: const NeverScrollableScrollPhysics(), + scrollController: _scrollCtrl, + scrollPadding: EdgeInsets.zero, + style: widget.style.copyWith( + fontSize: fontSize, + leadingDistribution: widget.configs.style.leadingDistribution, + height: widget.configs.style.textHeight, + ), + spellCheckConfiguration: widget.configs.spellCheckConfiguration, + decoration: InputDecoration.collapsed( + hintText: _textController.text.isEmpty ? widget.hint : '', + hintStyle: (widget.hintStyle ?? + TextStyle(color: Theme.of(context).hintColor)) + .copyWith(fontSize: fontSize), + maintainHintSize: false, + ), + textAlign: widget.textAlign, + maxLines: null, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + textInputAction: TextInputAction.newline, + cursorColor: widget.configs.style.inputCursorColor, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + enableInteractiveSelection: true, + showCursor: true, + autocorrect: widget.configs.enableAutocorrect, + smartDashesType: SmartDashesType.enabled, + smartQuotesType: SmartQuotesType.enabled, + enableSuggestions: widget.configs.enableSuggestions, + clipBehavior: Clip.hardEdge, + onChanged: widget.onChanged, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, ), - textAlign: widget.textAlign, - maxLines: null, - keyboardType: TextInputType.multiline, - textCapitalization: TextCapitalization.sentences, - textInputAction: TextInputAction.newline, - cursorColor: widget.configs.style.inputCursorColor, - cursorWidth: widget.cursorWidth, - cursorHeight: widget.cursorHeight, - cursorRadius: widget.cursorRadius, - enableInteractiveSelection: true, - showCursor: true, - autocorrect: widget.configs.enableAutocorrect, - smartDashesType: SmartDashesType.enabled, - smartQuotesType: SmartQuotesType.enabled, - enableSuggestions: widget.configs.enableSuggestions, - clipBehavior: Clip.hardEdge, - onChanged: widget.onChanged, - onEditingComplete: widget.onEditingComplete, - onSubmitted: widget.onSubmitted, ), ); } diff --git a/test/features/text_editor/rounded_background_text_field_hitbox_test.dart b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart new file mode 100644 index 000000000..b41c03f61 --- /dev/null +++ b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pro_image_editor/core/models/editor_configs/text_editor_configs.dart'; +import 'package:pro_image_editor/features/text_editor/widgets/rounded_background_text/rounded_background_text.dart'; +import 'package:pro_image_editor/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart'; + +/// The in-editor text preview ([RoundedBackgroundTextField]) must reserve the +/// same hit-box padding around its rounded background as the finished layer +/// ([LayerWidgetTextItem], which renders [RoundedBackgroundText] with +/// `enableHitBoxCorrection: true`). +/// +/// Previously the preview passed `enableHitBoxCorrection: false`, so the +/// background box hugged the glyphs and the reserved room differed from the +/// finished layer — the box visibly grew (and became left/right symmetric) the +/// moment editing completed. This test locks the preview and the finished +/// render to the same background box height. +void main() { + const text = 'Aaaaa'; + const style = TextStyle(fontSize: 40, color: Colors.black); + const maxWidth = 400.0; + + testWidgets( + 'editing preview reserves the same background height as the finished layer', + (tester) async { + // 1) Finished layer render (source of truth). + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: maxWidth, + child: RoundedBackgroundText( + text, + style: style, + backgroundColor: Colors.white, + maxTextWidth: maxWidth, + enableHitBoxCorrection: true, + ), + ), + ), + ), + ), + ); + final finishedHeight = + tester.getSize(find.byType(RoundedBackgroundText)).height; + + // 2) In-editor preview render. + final controller = TextEditingController(text: text); + final focusNode = FocusNode(); + addTearDown(controller.dispose); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: maxWidth, + child: RoundedBackgroundTextField( + controller: controller, + focusNode: focusNode, + configs: const TextEditorConfigs(), + style: style, + backgroundColor: Colors.white, + textAlign: TextAlign.center, + maxTextWidth: maxWidth, + ), + ), + ), + ), + ), + ); + await tester.pump(); + + final previewHeight = + tester.getSize(find.byType(RoundedBackgroundText)).height; + + // The reserved hit-box room is line-height * 0.1 on top and bottom. If the + // preview dropped the correction it would be ~0.2 * line-height shorter. + expect( + previewHeight, + moreOrLessEquals(finishedHeight, epsilon: 0.5), + reason: + 'Preview background height ($previewHeight) must match the finished ' + 'layer ($finishedHeight); a mismatch means the hit-box padding was ' + 'not reserved while editing.', + ); + }, + ); +} From 25ea0e44975789d5c02af65e89d6624fe609e9d5 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 16 Jul 2026 07:23:46 -0400 Subject: [PATCH 2/3] fix(text-editor): couple editable/background wrap width; dispose+memo line height Addresses review feedback: - Horizontal room was reserved on the wrong side: the editable Padding shrank the TextField's wrap width by 2*hitBox while the background kept the full maxTextWidth, so they broke into different line counts (a horizontal jump-on-done). Subtract 2*hitBox from the background maxTextWidth so both wrap at the same column. - Dispose the TextPainter in _preferredLineHeight and memoize it (build runs every keystroke/scroll tick; inputs are unchanged per frame). - Tests: assert the editable glyph inset (the Padding half of the fix); add a wrap-column sweep across widths (editable line count via TextPainter vs the background box line count); reword the reference-render comment; keep lines within 80 chars. --- .../rounded_background_text_field.dart | 47 +++- ...ded_background_text_field_hitbox_test.dart | 207 ++++++++++++++---- 2 files changed, 205 insertions(+), 49 deletions(-) diff --git a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart index cd94b7fa9..1046e4aef 100644 --- a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart +++ b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart @@ -143,7 +143,8 @@ class _RoundedBackgroundTextFieldState TextAlign.center || _ => Alignment.topCenter, }, children: [ - if (_textController.text.isNotEmpty) _buildBackgroundText(), + if (_textController.text.isNotEmpty) + _buildBackgroundText(hitBoxHorizontal: hitBoxHorizontal), _buildEditableText( fontSize: fontSize, hitBoxHorizontal: hitBoxHorizontal, @@ -154,23 +155,49 @@ class _RoundedBackgroundTextFieldState ); } + double? _cachedLineHeight; + double? _cachedLineHeightFontSize; + TextStyle? _cachedLineHeightStyle; + TextLeadingDistribution? _cachedLineHeightLeading; + TextDirection? _cachedLineHeightDirection; + /// The preferred line height for [widget.style] at [fontSize], computed the /// same way [RoundedBackgroundText] lays the text out, so the hit-box padding - /// reserved here matches the rectangle the painter draws. + /// reserved here matches the rectangle the painter draws. Memoized because + /// [build] runs on every keystroke and scroll tick while none of the inputs + /// change per frame. double _preferredLineHeight(double fontSize) { + final leading = widget.configs.style.leadingDistribution; + final direction = Directionality.maybeOf(context) ?? TextDirection.ltr; + if (_cachedLineHeight != null && + _cachedLineHeightFontSize == fontSize && + _cachedLineHeightStyle == widget.style && + _cachedLineHeightLeading == leading && + _cachedLineHeightDirection == direction) { + return _cachedLineHeight!; + } + final painter = TextPainter( text: TextSpan( style: TextStyle( - leadingDistribution: widget.configs.style.leadingDistribution, + leadingDistribution: leading, ).merge(widget.style.copyWith(fontSize: fontSize)), text: 'A', ), - textDirection: Directionality.maybeOf(context) ?? TextDirection.ltr, + textDirection: direction, )..layout(); - return painter.preferredLineHeight; + final lineHeight = painter.preferredLineHeight; + painter.dispose(); + + _cachedLineHeight = lineHeight; + _cachedLineHeightFontSize = fontSize; + _cachedLineHeightStyle = widget.style; + _cachedLineHeightLeading = leading; + _cachedLineHeightDirection = direction; + return lineHeight; } - Widget _buildBackgroundText() { + Widget _buildBackgroundText({required double hitBoxHorizontal}) { final style = widget.style.copyWith( color: Colors.transparent, leadingDistribution: widget.configs.style.leadingDistribution, @@ -187,7 +214,13 @@ class _RoundedBackgroundTextFieldState withComposing: true, style: style, ), - maxTextWidth: widget.maxTextWidth - widget.cursorWidth, + // Wrap at the same column as the editable text: the editable glyphs + // are inset by `hitBoxHorizontal` on each side (see + // `_buildEditableText`), so the background must lay its glyphs out at + // that same reduced width. Otherwise the two disagree on the wrap + // column and the line count changes when editing completes. + maxTextWidth: + widget.maxTextWidth - widget.cursorWidth - 2 * hitBoxHorizontal, cursorWidth: widget.cursorWidth, textAlign: widget.textAlign, backgroundColor: widget.backgroundColor, diff --git a/test/features/text_editor/rounded_background_text_field_hitbox_test.dart b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart index b41c03f61..7ae7fbd4f 100644 --- a/test/features/text_editor/rounded_background_text_field_hitbox_test.dart +++ b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart @@ -4,25 +4,65 @@ import 'package:pro_image_editor/core/models/editor_configs/text_editor_configs. import 'package:pro_image_editor/features/text_editor/widgets/rounded_background_text/rounded_background_text.dart'; import 'package:pro_image_editor/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart'; -/// The in-editor text preview ([RoundedBackgroundTextField]) must reserve the -/// same hit-box padding around its rounded background as the finished layer -/// ([LayerWidgetTextItem], which renders [RoundedBackgroundText] with -/// `enableHitBoxCorrection: true`). +/// The in-editor text preview ([RoundedBackgroundTextField]) must render its +/// rounded background the same way the placed layer does — the layer renders a +/// [RoundedBackgroundText] with `enableHitBoxCorrection: true`. Before the fix +/// the preview passed `enableHitBoxCorrection: false`, so its background hugged +/// the glyphs and grew (becoming symmetric) the moment editing completed. /// -/// Previously the preview passed `enableHitBoxCorrection: false`, so the -/// background box hugged the glyphs and the reserved room differed from the -/// finished layer — the box visibly grew (and became left/right symmetric) the -/// moment editing completed. This test locks the preview and the finished -/// render to the same background box height. +/// These tests lock the two halves of the fix: +/// * the background reserves the same hit-box padding (vertical + horizontal); +/// * the editable glyphs are inset by that padding and wrap at the same column +/// as the background, so nothing shifts or re-wraps on done. void main() { - const text = 'Aaaaa'; - const style = TextStyle(fontSize: 40, color: Colors.black); - const maxWidth = 400.0; + Widget wrapPreview({ + required TextEditingController controller, + required FocusNode focusNode, + required TextStyle style, + required double width, + }) { + return MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: width, + child: RoundedBackgroundTextField( + controller: controller, + focusNode: focusNode, + configs: const TextEditorConfigs(), + style: style, + backgroundColor: Colors.white, + textAlign: TextAlign.center, + maxTextWidth: width, + ), + ), + ), + ), + ); + } + + double preferredLineHeight(TextStyle style) { + final painter = TextPainter( + text: TextSpan(text: 'A', style: style), + textDirection: TextDirection.ltr, + )..layout(); + final h = painter.preferredLineHeight; + painter.dispose(); + return h; + } testWidgets( - 'editing preview reserves the same background height as the finished layer', + 'preview background matches hit-box reference and insets glyphs', (tester) async { - // 1) Finished layer render (source of truth). + const text = 'Aaaaa'; + const style = TextStyle(fontSize: 40, color: Colors.black); + const maxWidth = 400.0; + final lineHeight = preferredLineHeight(style); + final hitBoxHorizontal = lineHeight * 0.3; + final hitBoxVertical = lineHeight * 0.1; + + // Reference: a bare RoundedBackgroundText with hit-box correction on, + // i.e. exactly what the placed layer renders. await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -41,49 +81,132 @@ void main() { ), ), ); - final finishedHeight = + final referenceHeight = tester.getSize(find.byType(RoundedBackgroundText)).height; - // 2) In-editor preview render. + // Preview. final controller = TextEditingController(text: text); final focusNode = FocusNode(); addTearDown(controller.dispose); addTearDown(focusNode.dispose); await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: SizedBox( - width: maxWidth, - child: RoundedBackgroundTextField( - controller: controller, - focusNode: focusNode, - configs: const TextEditorConfigs(), - style: style, - backgroundColor: Colors.white, - textAlign: TextAlign.center, - maxTextWidth: maxWidth, - ), - ), - ), - ), + wrapPreview( + controller: controller, + focusNode: focusNode, + style: style, + width: maxWidth, ), ); await tester.pump(); - final previewHeight = - tester.getSize(find.byType(RoundedBackgroundText)).height; + // 1) The background reserves the same room as the reference. + final previewBgRect = tester.getRect(find.byType(RoundedBackgroundText)); + expect( + previewBgRect.height, + moreOrLessEquals(referenceHeight, epsilon: 0.5), + reason: 'Preview background height must match the hit-box-corrected ' + 'reference; a mismatch means the padding was not reserved.', + ); - // The reserved hit-box room is line-height * 0.1 on top and bottom. If the - // preview dropped the correction it would be ~0.2 * line-height shorter. + // 2) The editable glyphs are inset by the hit-box padding, so they stay + // centered inside the box instead of shifting on done. + final editableRect = tester.getRect(find.byType(EditableText)); expect( - previewHeight, - moreOrLessEquals(finishedHeight, epsilon: 0.5), + editableRect.left - previewBgRect.left, + moreOrLessEquals(hitBoxHorizontal, epsilon: 1.0), reason: - 'Preview background height ($previewHeight) must match the finished ' - 'layer ($finishedHeight); a mismatch means the hit-box padding was ' - 'not reserved while editing.', + 'Editable glyphs must be inset horizontally by the hit-box pad.', + ); + expect( + editableRect.top - previewBgRect.top, + moreOrLessEquals(hitBoxVertical, epsilon: 1.0), + reason: 'Editable glyphs must be inset vertically by the hit-box pad.', + ); + }, + ); + + testWidgets( + 'editable text and background wrap at the same column across widths', + (tester) async { + const text = 'one two three four five six seven eight nine ten'; + const style = TextStyle(fontSize: 30, color: Colors.black); + + final controller = TextEditingController(); + final focusNode = FocusNode(); + addTearDown(controller.dispose); + addTearDown(focusNode.dispose); + + // Returns (editableWrapWidth, backgroundHeight) for the given text/width. + Future<(double, double)> measure(String txt, double width) async { + controller.text = txt; + await tester.pumpWidget( + wrapPreview( + controller: controller, + focusNode: focusNode, + style: style, + width: width, + ), + ); + await tester.pump(); + return ( + tester.getSize(find.byType(EditableText)).width, + tester.getSize(find.byType(RoundedBackgroundText)).height, + ); + } + + // Line count for the editable text: re-lay it at the editable's measured + // wrap width. EditableText's own height is non-linear in line count + // (constant caret/strut padding), so dividing it is unsafe; line breaking + // depends only on width and glyph advances. + int editableLineCount(double wrapWidth) { + final painter = TextPainter( + text: const TextSpan(text: text, style: style), + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + )..layout(maxWidth: wrapWidth); + final count = painter.computeLineMetrics().length; + painter.dispose(); + return count; + } + + // The background box height is linear in line count; calibrate + // `height = lines * perLine + constant` from explicit 1- and 2-line + // renders (the Positioned background's rect width can't reveal its wrap + // column, so height is the reliable signal here). + final (_, bg1) = await measure('A', 4000); + final (_, bg2) = await measure('A\nA', 4000); + final bgPerLine = bg2 - bg1; + final bgConst = bg1 - bgPerLine; + int backgroundLineCount(double height) => + ((height - bgConst) / bgPerLine).round(); + + var sawWrapping = false; + + // Sweep a band of widths straddling wrap boundaries. The editable glyphs + // and the background box that should hug them must break into the same + // number of lines. The horizontal-jump regression makes the background + // wrap at a different column than the editable text. + for (final width in [140.0, 160.0, 180.0, 200.0, 220.0, 240.0]) { + final (edWidth, bgHeight) = await measure(text, width); + final editableLines = editableLineCount(edWidth); + final backgroundLines = backgroundLineCount(bgHeight); + + if (editableLines > 1) sawWrapping = true; + + expect( + editableLines, + backgroundLines, + reason: 'At width=$width the editable text wrapped into ' + '$editableLines lines but the background box into ' + '$backgroundLines: the two disagree on the wrap column.', + ); + } + + expect( + sawWrapping, + isTrue, + reason: 'Test is vacuous unless the text actually wraps at some width.', ); }, ); From d52a21ba3380413ba6e80619255a144030a6219b Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Thu, 16 Jul 2026 18:58:27 -0400 Subject: [PATCH 3/3] style: format with Dart 3.12 tall-style (CI dart format) --- .../rounded_background_text_field.dart | 10 ++++++---- .../rounded_background_text_field_hitbox_test.dart | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart index 1046e4aef..67d79cb92 100644 --- a/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart +++ b/lib/features/text_editor/widgets/rounded_background_text/rounded_background_text_field.dart @@ -248,7 +248,8 @@ class _RoundedBackgroundTextFieldState child: Material( type: MaterialType.transparency, child: TextField( - onTap: _textController.text.isEmpty && + onTap: + _textController.text.isEmpty && View.of(context).viewInsets.bottom <= 0 ? () { FocusManager.instance.primaryFocus?.unfocus(); @@ -269,9 +270,10 @@ class _RoundedBackgroundTextFieldState spellCheckConfiguration: widget.configs.spellCheckConfiguration, decoration: InputDecoration.collapsed( hintText: _textController.text.isEmpty ? widget.hint : '', - hintStyle: (widget.hintStyle ?? - TextStyle(color: Theme.of(context).hintColor)) - .copyWith(fontSize: fontSize), + hintStyle: + (widget.hintStyle ?? + TextStyle(color: Theme.of(context).hintColor)) + .copyWith(fontSize: fontSize), maintainHintSize: false, ), textAlign: widget.textAlign, diff --git a/test/features/text_editor/rounded_background_text_field_hitbox_test.dart b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart index 7ae7fbd4f..badcb09ac 100644 --- a/test/features/text_editor/rounded_background_text_field_hitbox_test.dart +++ b/test/features/text_editor/rounded_background_text_field_hitbox_test.dart @@ -81,8 +81,9 @@ void main() { ), ), ); - final referenceHeight = - tester.getSize(find.byType(RoundedBackgroundText)).height; + final referenceHeight = tester + .getSize(find.byType(RoundedBackgroundText)) + .height; // Preview. final controller = TextEditingController(text: text); @@ -105,7 +106,8 @@ void main() { expect( previewBgRect.height, moreOrLessEquals(referenceHeight, epsilon: 0.5), - reason: 'Preview background height must match the hit-box-corrected ' + reason: + 'Preview background height must match the hit-box-corrected ' 'reference; a mismatch means the padding was not reserved.', ); @@ -197,7 +199,8 @@ void main() { expect( editableLines, backgroundLines, - reason: 'At width=$width the editable text wrapped into ' + reason: + 'At width=$width the editable text wrapped into ' '$editableLines lines but the background box into ' '$backgroundLines: the two disagree on the wrap column.', );