From 9c231067117bd6bd5122de2babb31463b0f8e021 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 13 Jan 2022 12:00:19 -0800 Subject: [PATCH] Revert "Support Scribble Handwriting" (#96615) --- AUTHORS | 1 - .../flutter/lib/src/cupertino/text_field.dart | 10 - .../flutter/lib/src/material/text_field.dart | 8 +- .../flutter/lib/src/rendering/editable.dart | 10 - .../flutter/lib/src/services/text_input.dart | 158 +------ .../lib/src/widgets/editable_text.dart | 377 +++------------- .../test/cupertino/text_field_test.dart | 4 +- .../test/material/text_field_test.dart | 32 -- .../flutter/test/services/autofill_test.dart | 17 - .../test/services/delta_text_input_test.dart | 16 - .../test/services/text_input_test.dart | 158 ------- .../test/services/text_input_utils.dart | 27 -- .../test/widgets/editable_text_test.dart | 402 ------------------ .../flutter_test/lib/src/test_text_input.dart | 82 ---- 14 files changed, 53 insertions(+), 1249 deletions(-) diff --git a/AUTHORS b/AUTHORS index f79d1ccebab..c6f1a901e81 100644 --- a/AUTHORS +++ b/AUTHORS @@ -89,4 +89,3 @@ Pradumna Saraf Kai Yu Denis Grafov TheOneWithTheBraid -Twin Sun, LLC diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index a1e37a9ebb5..c2efc0408cf 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -297,7 +297,6 @@ class CupertinoTextField extends StatefulWidget { this.autofillHints = const [], this.clipBehavior = Clip.hardEdge, this.restorationId, - this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), assert(readOnly != null), @@ -455,7 +454,6 @@ class CupertinoTextField extends StatefulWidget { this.autofillHints = const [], this.clipBehavior = Clip.hardEdge, this.restorationId, - this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), assert(readOnly != null), @@ -800,9 +798,6 @@ class CupertinoTextField extends StatefulWidget { /// {@macro flutter.material.textfield.restorationId} final String? restorationId; - /// {@macro flutter.widgets.editableText.scribbleEnabled} - final bool scribbleEnabled; - /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} final bool enableIMEPersonalizedLearning; @@ -848,7 +843,6 @@ class CupertinoTextField extends StatefulWidget { properties.add(DiagnosticsProperty('textAlignVertical', textAlignVertical, defaultValue: null)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)); - properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, defaultValue: true)); properties.add(DiagnosticsProperty('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true)); } } @@ -969,9 +963,6 @@ class _CupertinoTextFieldState extends State with Restoratio if (cause == SelectionChangedCause.keyboard) return false; - if (cause == SelectionChangedCause.scribble) - return true; - if (_effectiveController.text.isNotEmpty) return true; @@ -1301,7 +1292,6 @@ class _CupertinoTextFieldState extends State with Restoratio autofillClient: this, clipBehavior: widget.clipBehavior, restorationId: 'editable', - scribbleEnabled: widget.scribbleEnabled, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, ), ), diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 076f084ca6d..0fc2a87e1b9 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -329,7 +329,6 @@ class TextField extends StatefulWidget { this.autofillHints = const [], this.clipBehavior = Clip.hardEdge, this.restorationId, - this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), assert(readOnly != null), @@ -769,9 +768,6 @@ class TextField extends StatefulWidget { /// {@endtemplate} final String? restorationId; - /// {@macro flutter.widgets.editableText.scribbleEnabled} - final bool scribbleEnabled; - /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} final bool enableIMEPersonalizedLearning; @@ -816,7 +812,6 @@ class TextField extends StatefulWidget { properties.add(DiagnosticsProperty('scrollController', scrollController, defaultValue: null)); properties.add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)); - properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, defaultValue: true)); properties.add(DiagnosticsProperty('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true)); } } @@ -1034,7 +1029,7 @@ class _TextFieldState extends State with RestorationMixin implements if (!_isEnabled) return false; - if (cause == SelectionChangedCause.longPress || cause == SelectionChangedCause.scribble) + if (cause == SelectionChangedCause.longPress) return true; if (_effectiveController.text.isNotEmpty) @@ -1278,7 +1273,6 @@ class _TextFieldState extends State with RestorationMixin implements autocorrectionTextRectColor: autocorrectionTextRectColor, clipBehavior: widget.clipBehavior, restorationId: 'editable', - scribbleEnabled: widget.scribbleEnabled, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, ), ), diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 5f8cebfd7b8..f8113e3121b 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1265,16 +1265,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, // [assembleSemanticsNode] invocations. Queue? _cachedChildNodes; - /// Returns a list of rects that bound the given selection. - /// - /// See [TextPainter.getBoxesForSelection] for more details. - List getBoxesForSelection(TextSelection selection) { - _computeTextMetricsIfNeeded(); - return _textPainter.getBoxesForSelection(selection) - .map((TextBox textBox) => textBox.toRect().shift(_paintOffset)) - .toList(); - } - @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 7bf468ab60f..aaddbf579c9 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -955,9 +955,6 @@ enum SelectionChangedCause { /// The user used the mouse to change the selection by dragging over a piece /// of text. drag, - - /// The user used iPadOS 14+ Scribble to change the selection. - scribble, } /// A mixin for manipulating the selection, provided for toolbar or shortcut @@ -1108,76 +1105,6 @@ abstract class TextInputClient { /// /// [TextInputClient] should cleanup its connection and finalize editing. void connectionClosed(); - - /// Requests that the client show the editing toolbar, for example when the - /// platform changes the selection through a non-flutter method such as - /// scribble. - void showToolbar() {} - - /// Requests that the client add a text placeholder to reserve visual space - /// in the text. - /// - /// For example, this is called when responding to UIKit requesting - /// a text placeholder be added at the current selection, such as when - /// requesting additional writing space with iPadOS14 Scribble. - void insertTextPlaceholder(Size size) {} - - /// Requests that the client remove the text placeholder. - void removeTextPlaceholder() {} -} - -/// An interface to receive focus from the engine. -/// -/// This is currently only used to handle UIIndirectScribbleInteraction. -abstract class ScribbleClient { - /// A unique identifier for this element. - String get elementIdentifier; - - /// Called by the engine when the [ScribbleClient] should receive focus. - /// - /// For example, this method is called during a UIIndirectScribbleInteraction. - void onScribbleFocus(Offset offset); - - /// Tests whether the [ScribbleClient] overlaps the given rectangle bounds. - bool isInScribbleRect(Rect rect); - - /// The current bounds of the [ScribbleClient]. - Rect get bounds; -} - -/// Represents a selection rect for a character and it's position in the text. -/// -/// This is used to report the current text selection rect and position data -/// to the engine for Scribble support on iPadOS 14. -@immutable -class SelectionRect { - /// Constructor for creating a [SelectionRect] from a text [position] and - /// [bounds]. - const SelectionRect({required this.position, required this.bounds}); - - /// The position of this selection rect within the text String. - final int position; - - /// The rectangle representing the bounds of this selection rect within the - /// currently focused [RenderEditable]'s coordinate space. - final Rect bounds; - - @override - bool operator ==(Object other) { - if (identical(this, other)) - return true; - if (runtimeType != other.runtimeType) - return false; - return other is SelectionRect - && other.position == position - && other.bounds == bounds; - } - - @override - int get hashCode => hashValues(position, bounds); - - @override - String toString() => 'SelectionRect($position, $bounds)'; } /// An interface to receive granular information from [TextInput]. @@ -1227,7 +1154,6 @@ class TextInputConnection { Matrix4? _cachedTransform; Rect? _cachedRect; Rect? _cachedCaretRect; - List _cachedSelectionRects = []; static int _nextId = 1; final int _id; @@ -1250,12 +1176,6 @@ class TextInputConnection { /// Whether this connection is currently interacting with the text input control. bool get attached => TextInput._instance._currentConnection == this; - /// Whether there is currently a Scribble interaction in progress. - /// - /// This is used to make sure selection handles are shown when UIKit changes - /// the selection during a Scribble interaction. - bool get scribbleInProgress => TextInput._instance.scribbleInProgress; - /// Requests that the text input control become visible. void show() { assert(attached); @@ -1354,19 +1274,6 @@ class TextInputConnection { ); } - /// Send the bounding boxes of the current selected glyphs in the client to - /// the platform's text input plugin. - /// - /// These are used by the engine during a UIDirectScribbleInteraction. - void setSelectionRects(List selectionRects) { - if (!listEquals(_cachedSelectionRects, selectionRects)) { - _cachedSelectionRects = selectionRects; - TextInput._instance._setSelectionRects(selectionRects.map((SelectionRect rect) { - return [rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position]; - }).toList()); - } - } - /// Send text styling information. /// /// This information is used by the Flutter Web Engine to change the style @@ -1628,43 +1535,10 @@ class TextInput { TextInputConnection? _currentConnection; late TextInputConfiguration _currentConfiguration; - final Map _scribbleClients = {}; - bool _scribbleInProgress = false; - - /// Used for testing within the Flutter SDK to get the currently registered [ScribbleClient] list. - @visibleForTesting - static Map get scribbleClients => TextInput._instance._scribbleClients; - - /// Returns true if a scribble interaction is currently happening. - bool get scribbleInProgress => _scribbleInProgress; - Future _handleTextInputInvocation(MethodCall methodCall) async { - final String method = methodCall.method; - if (method == 'TextInputClient.focusElement') { - final List args = methodCall.arguments as List; - _scribbleClients[args[0]]?.onScribbleFocus(Offset((args[1] as num).toDouble(), (args[2] as num).toDouble())); - return; - } else if (method == 'TextInputClient.requestElementsInRect') { - final List args = (methodCall.arguments as List).cast().map((num value) => value.toDouble()).toList(); - return _scribbleClients.keys.where((String elementIdentifier) { - final Rect rect = Rect.fromLTWH(args[0], args[1], args[2], args[3]); - if (!(_scribbleClients[elementIdentifier]?.isInScribbleRect(rect) ?? false)) - return false; - final Rect bounds = _scribbleClients[elementIdentifier]?.bounds ?? Rect.zero; - return !(bounds == Rect.zero || bounds.hasNaN || bounds.isInfinite); - }).map((String elementIdentifier) { - final Rect bounds = _scribbleClients[elementIdentifier]!.bounds; - return [elementIdentifier, ...[bounds.left, bounds.top, bounds.width, bounds.height]]; - }).toList(); - } else if (method == 'TextInputClient.scribbleInteractionBegan') { - _scribbleInProgress = true; - return; - } else if (method == 'TextInputClient.scribbleInteractionFinished') { - _scribbleInProgress = false; - return; - } if (_currentConnection == null) return; + final String method = methodCall.method; // The requestExistingInputState request needs to be handled regardless of // the client ID, as long as we have a _currentConnection. @@ -1756,15 +1630,6 @@ class TextInput { case 'TextInputClient.showAutocorrectionPromptRect': _currentConnection!._client.showAutocorrectionPromptRect(args[1] as int, args[2] as int); break; - case 'TextInputClient.showToolbar': - _currentConnection!._client.showToolbar(); - break; - case 'TextInputClient.insertTextPlaceholder': - _currentConnection!._client.insertTextPlaceholder(Size((args[1] as num).toDouble(), (args[2] as num).toDouble())); - break; - case 'TextInputClient.removeTextPlaceholder': - _currentConnection!._client.removeTextPlaceholder(); - break; default: throw MissingPluginException(); } @@ -1838,13 +1703,6 @@ class TextInput { ); } - void _setSelectionRects(List> args) { - _channel.invokeMethod( - 'TextInput.setSelectionRects', - args, - ); - } - void _setStyle(Map args) { _channel.invokeMethod( 'TextInput.setStyle', @@ -1907,18 +1765,4 @@ class TextInput { shouldSave, ); } - - /// Registers a [ScribbleClient] with [elementIdentifier] that can be focused - /// by the engine. - /// - /// For example, the registered [ScribbleClient] list is used to respond to - /// UIIndirectScribbleInteraction on an iPad. - static void registerScribbleElement(String elementIdentifier, ScribbleClient scribbleClient) { - TextInput._instance._scribbleClients[elementIdentifier] = scribbleClient; - } - - /// Unregisters a [ScribbleClient] with [elementIdentifier]. - static void unregisterScribbleElement(String elementIdentifier) { - TextInput._instance._scribbleClients.remove(elementIdentifier); - } } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index fe13c458ba5..67f5674426e 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'dart:math' as math; import 'dart:ui' as ui hide TextStyle; -import 'package:characters/characters.dart' show CharacterRange, StringCharacters; +import 'package:characters/characters.dart' show CharacterRange; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/rendering.dart'; @@ -58,10 +58,6 @@ const Duration _kCursorBlinkWaitForStart = Duration(milliseconds: 150); // is shown in an obscured text field. const int _kObscureShowLatestCharCursorTicks = 3; -// The minimum width of an iPad screen. The smallest iPad is currently the -// iPad Mini 6th Gen according to ios-resolution.com. -const double _kIPadWidth = 1488.0; - /// A controller for an editable text field. /// /// Whenever the user modifies a text field with an associated @@ -527,7 +523,6 @@ class EditableText extends StatefulWidget { this.clipBehavior = Clip.hardEdge, this.restorationId, this.scrollBehavior, - this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, }) : assert(controller != null), assert(focusNode != null), @@ -1215,15 +1210,6 @@ class EditableText extends StatefulWidget { /// [scrollPhysics]. final ScrollPhysics? scrollPhysics; - /// {@template flutter.widgets.editableText.scribbleEnabled} - /// Whether iOS 14 Scribble features are enabled for this widget. - /// - /// Only available on iPads. - /// - /// Defaults to true. - /// {@endtemplate} - final bool scribbleEnabled; - /// {@template flutter.widgets.editableText.selectionEnabled} /// Same as [enableInteractiveSelection]. /// @@ -1524,7 +1510,6 @@ class EditableText extends StatefulWidget { properties.add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty>('autofillHints', autofillHints, defaultValue: null)); properties.add(DiagnosticsProperty('textHeightBehavior', textHeightBehavior, defaultValue: null)); - properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, defaultValue: true)); properties.add(DiagnosticsProperty('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true)); } } @@ -1888,7 +1873,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (value.text == _value.text && value.composing == _value.composing) { // `selection` is the only change. - _handleSelectionChanged(value.selection, (_textInputConnection?.scribbleInProgress ?? false) ? SelectionChangedCause.scribble : SelectionChangedCause.keyboard); + _handleSelectionChanged(value.selection, SelectionChangedCause.keyboard); } else { hideToolbar(); _currentPromptRectRange = null; @@ -2671,11 +2656,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien // Place cursor at the end if the selection is invalid when we receive focus. _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), null); } - - _cachedText = ''; - _cachedFirstRect = null; - _cachedSize = Size.zero; - _cachedPlaceholder = -1; } else { WidgetsBinding.instance!.removeObserver(this); setState(() { _currentPromptRectRange = null; }); @@ -2683,78 +2663,13 @@ class EditableTextState extends State with AutomaticKeepAliveClien updateKeepAlive(); } - String _cachedText = ''; - Rect? _cachedFirstRect; - Size _cachedSize = Size.zero; - int _cachedPlaceholder = -1; - TextStyle? _cachedTextStyle; - - void _updateSelectionRects({bool force = false}) { - if (!widget.scribbleEnabled) - return; - if (defaultTargetPlatform != TargetPlatform.iOS) - return; - // This is to avoid sending selection rects on non-iPad devices. - if (WidgetsBinding.instance!.window.physicalSize.shortestSide < _kIPadWidth) - return; - - final String text = renderEditable.text?.toPlainText(includeSemanticsLabels: false, includePlaceholders: false) ?? ''; - final List firstSelectionBoxes = renderEditable.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 1)); - final Rect? firstRect = firstSelectionBoxes.isNotEmpty ? firstSelectionBoxes.first : null; - final ScrollDirection scrollDirection = _scrollController.position.userScrollDirection; - final Size size = renderEditable.size; - final bool textChanged = text != _cachedText; - final bool textStyleChanged = _cachedTextStyle != widget.style; - final bool firstRectChanged = _cachedFirstRect != firstRect; - final bool sizeChanged = _cachedSize != size; - final bool placeholderChanged = _cachedPlaceholder != _placeholderLocation; - if (scrollDirection == ScrollDirection.idle && (force || textChanged || textStyleChanged || firstRectChanged || sizeChanged || placeholderChanged)) { - _cachedText = text; - _cachedFirstRect = firstRect; - _cachedTextStyle = widget.style; - _cachedSize = size; - _cachedPlaceholder = _placeholderLocation; - bool belowRenderEditableBottom = false; - final List rects = List.generate( - _cachedText.characters.length, - (int i) { - if (belowRenderEditableBottom) - return null; - - final int offset = _cachedText.characters.getRange(0, i).string.length; - final SelectionRect selectionRect = SelectionRect( - bounds: renderEditable.getBoxesForSelection(TextSelection(baseOffset: offset, extentOffset: offset + _cachedText.characters.characterAt(i).string.length)).first, - position: offset, - ); - if (renderEditable.paintBounds.bottom < selectionRect.bounds.top) { - belowRenderEditableBottom = true; - return null; - } - return selectionRect; - }, - ).where((SelectionRect? selectionRect) { - if (selectionRect == null) - return false; - if (renderEditable.paintBounds.right < selectionRect.bounds.left || selectionRect.bounds.right < renderEditable.paintBounds.left) - return false; - if (renderEditable.paintBounds.bottom < selectionRect.bounds.top || selectionRect.bounds.bottom < renderEditable.paintBounds.top) - return false; - return true; - }).map((SelectionRect? selectionRect) => selectionRect!).toList(); - _textInputConnection!.setSelectionRects(rects); - } - } - void _updateSizeAndTransform() { if (_hasInputConnection) { final Size size = renderEditable.size; final Matrix4 transform = renderEditable.getTransformTo(null); _textInputConnection!.setEditableSizeAndTransform(size, transform); - _updateSelectionRects(); SchedulerBinding.instance! .addPostFrameCallback((Duration _) => _updateSizeAndTransform()); - } else if (_placeholderLocation != -1) { - removeTextPlaceholder(); } } @@ -2837,7 +2752,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien /// /// Returns `false` if a toolbar couldn't be shown, such as when the toolbar /// is already shown, or when no text selection currently exists. - @override bool showToolbar() { // Web is using native dom elements to enable clipboard functionality of the // toolbar: copy, paste, select, cut. It might also provide additional @@ -2876,36 +2790,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien } } - // Tracks the location a [_ScribblePlaceholder] should be rendered in the - // text. - // - // A value of -1 indicates there should be no placeholder, otherwise the - // value should be between 0 and the length of the text, inclusive. - int _placeholderLocation = -1; - - @override - void insertTextPlaceholder(Size size) { - if (!widget.scribbleEnabled) - return; - - if (!widget.controller.selection.isValid) - return; - - setState(() { - _placeholderLocation = _value.text.length - widget.controller.selection.end; - }); - } - - @override - void removeTextPlaceholder() { - if (!widget.scribbleEnabled) - return; - - setState(() { - _placeholderLocation = -1; - }); - } - @override String get autofillId => 'EditableText-$hashCode'; @@ -3107,62 +2991,53 @@ class EditableTextState extends State with AutomaticKeepAliveClien onCopy: _semanticsOnCopy(controls), onCut: _semanticsOnCut(controls), onPaste: _semanticsOnPaste(controls), - child: _ScribbleFocusable( - focusNode: widget.focusNode, - editableKey: _editableKey, - enabled: widget.scribbleEnabled, - updateSelectionRects: () { - _openInputConnection(); - _updateSelectionRects(force: true); - }, - child: _Editable( - key: _editableKey, - startHandleLayerLink: _startHandleLayerLink, - endHandleLayerLink: _endHandleLayerLink, - inlineSpan: buildTextSpan(), - value: _value, - cursorColor: _cursorColor, - backgroundCursorColor: widget.backgroundCursorColor, - showCursor: EditableText.debugDeterministicCursor - ? ValueNotifier(widget.showCursor) - : _cursorVisibilityNotifier, - forceLine: widget.forceLine, - readOnly: widget.readOnly, - hasFocus: _hasFocus, - maxLines: widget.maxLines, - minLines: widget.minLines, - expands: widget.expands, - strutStyle: widget.strutStyle, - selectionColor: widget.selectionColor, - textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context), - textAlign: widget.textAlign, - textDirection: _textDirection, - locale: widget.locale, - textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), - textWidthBasis: widget.textWidthBasis, - obscuringCharacter: widget.obscuringCharacter, - obscureText: widget.obscureText, - autocorrect: widget.autocorrect, - smartDashesType: widget.smartDashesType, - smartQuotesType: widget.smartQuotesType, - enableSuggestions: widget.enableSuggestions, - offset: offset, - onCaretChanged: _handleCaretChanged, - rendererIgnoresPointer: widget.rendererIgnoresPointer, - cursorWidth: widget.cursorWidth, - cursorHeight: widget.cursorHeight, - cursorRadius: widget.cursorRadius, - cursorOffset: widget.cursorOffset ?? Offset.zero, - selectionHeightStyle: widget.selectionHeightStyle, - selectionWidthStyle: widget.selectionWidthStyle, - paintCursorAboveText: widget.paintCursorAboveText, - enableInteractiveSelection: widget.enableInteractiveSelection, - textSelectionDelegate: this, - devicePixelRatio: _devicePixelRatio, - promptRectRange: _currentPromptRectRange, - promptRectColor: widget.autocorrectionTextRectColor, - clipBehavior: widget.clipBehavior, - ), + child: _Editable( + key: _editableKey, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + inlineSpan: buildTextSpan(), + value: _value, + cursorColor: _cursorColor, + backgroundCursorColor: widget.backgroundCursorColor, + showCursor: EditableText.debugDeterministicCursor + ? ValueNotifier(widget.showCursor) + : _cursorVisibilityNotifier, + forceLine: widget.forceLine, + readOnly: widget.readOnly, + hasFocus: _hasFocus, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + strutStyle: widget.strutStyle, + selectionColor: widget.selectionColor, + textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context), + textAlign: widget.textAlign, + textDirection: _textDirection, + locale: widget.locale, + textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context), + textWidthBasis: widget.textWidthBasis, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + offset: offset, + onCaretChanged: _handleCaretChanged, + rendererIgnoresPointer: widget.rendererIgnoresPointer, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorOffset: widget.cursorOffset ?? Offset.zero, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + paintCursorAboveText: widget.paintCursorAboveText, + enableInteractiveSelection: widget.enableInteractiveSelection, + textSelectionDelegate: this, + devicePixelRatio: _devicePixelRatio, + promptRectRange: _currentPromptRectRange, + promptRectColor: widget.autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, ), ), ); @@ -3192,24 +3067,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien } return TextSpan(style: widget.style, text: text); } - if (_placeholderLocation >= 0 && _placeholderLocation <= _value.text.length) { - final List<_ScribblePlaceholder> placeholders = <_ScribblePlaceholder>[]; - final int placeholderLocation = _value.text.length - _placeholderLocation; - if (_isMultiline) { - // The zero size placeholder here allows the line to break and keep the caret on the first line. - placeholders.add(const _ScribblePlaceholder(child: SizedBox(), size: Size.zero)); - placeholders.add(_ScribblePlaceholder(child: const SizedBox(), size: Size(renderEditable.size.width, 0.0))); - } else { - placeholders.add(const _ScribblePlaceholder(child: SizedBox(), size: Size(100.0, 0.0))); - } - return TextSpan(style: widget.style, children: [ - TextSpan(text: _value.text.substring(0, placeholderLocation)), - ...placeholders, - TextSpan(text: _value.text.substring(placeholderLocation)), - ], - ); - } - // Read only mode should not paint text composing. return widget.controller.buildTextSpan( context: context, @@ -3414,142 +3271,6 @@ class _Editable extends MultiChildRenderObjectWidget { } } -class _ScribbleFocusable extends StatefulWidget { - const _ScribbleFocusable({ - Key? key, - required this.child, - required this.focusNode, - required this.editableKey, - required this.updateSelectionRects, - required this.enabled, - }): super(key: key); - - final Widget child; - final FocusNode focusNode; - final GlobalKey editableKey; - final VoidCallback updateSelectionRects; - final bool enabled; - - @override - _ScribbleFocusableState createState() => _ScribbleFocusableState(); -} - -class _ScribbleFocusableState extends State<_ScribbleFocusable> implements ScribbleClient { - _ScribbleFocusableState(): _elementIdentifier = (_nextElementIdentifier++).toString(); - - @override - void initState() { - super.initState(); - if (widget.enabled) { - TextInput.registerScribbleElement(elementIdentifier, this); - } - } - - @override - void didUpdateWidget(_ScribbleFocusable oldWidget) { - super.didUpdateWidget(oldWidget); - if (!oldWidget.enabled && widget.enabled) { - TextInput.registerScribbleElement(elementIdentifier, this); - } - - if (oldWidget.enabled && !widget.enabled) { - TextInput.unregisterScribbleElement(elementIdentifier); - } - } - - @override - void dispose() { - TextInput.unregisterScribbleElement(elementIdentifier); - super.dispose(); - } - - RenderEditable? get renderEditable => widget.editableKey.currentContext?.findRenderObject() as RenderEditable?; - - static int _nextElementIdentifier = 1; - final String _elementIdentifier; - - @override - String get elementIdentifier => _elementIdentifier; - - @override - void onScribbleFocus(Offset offset) { - widget.focusNode.requestFocus(); - renderEditable?.selectPositionAt(from: offset, cause: SelectionChangedCause.scribble); - widget.updateSelectionRects(); - } - - @override - bool isInScribbleRect(Rect rect) { - final Rect _bounds = bounds; - if (renderEditable?.readOnly ?? false) - return false; - if (_bounds == Rect.zero) - return false; - if (!_bounds.overlaps(rect)) - return false; - final Rect intersection = _bounds.intersect(rect); - final HitTestResult result = HitTestResult(); - WidgetsBinding.instance?.hitTest(result, intersection.center); - return result.path.any((HitTestEntry entry) => entry.target == renderEditable); - } - - @override - Rect get bounds { - final RenderBox? box = context.findRenderObject() as RenderBox?; - if (box == null || !mounted || !box.attached) - return Rect.zero; - final Matrix4 transform = box.getTransformTo(null); - return MatrixUtils.transformRect(transform, Rect.fromLTWH(0, 0, box.size.width, box.size.height)); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -} - -class _ScribblePlaceholder extends WidgetSpan { - const _ScribblePlaceholder({ - required Widget child, - ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom, - TextBaseline? baseline, - TextStyle? style, - required this.size, - }) : assert(child != null), - assert(baseline != null || !( - identical(alignment, ui.PlaceholderAlignment.aboveBaseline) || - identical(alignment, ui.PlaceholderAlignment.belowBaseline) || - identical(alignment, ui.PlaceholderAlignment.baseline) - )), - super( - alignment: alignment, - baseline: baseline, - style: style, - child: child, - ); - - /// The size of the span, used in place of adding a placeholder size to the [TextPainter]. - final Size size; - - @override - void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List? dimensions }) { - assert(debugAssertIsValid()); - final bool hasStyle = style != null; - if (hasStyle) { - builder.pushStyle(style!.getTextStyle(textScaleFactor: textScaleFactor)); - } - builder.addPlaceholder( - size.width, - size.height, - alignment, - scale: textScaleFactor, - ); - if (hasStyle) { - builder.pop(); - } - } -} - /// An interface for retriving the logical text boundary (left-closed-right-open) /// at a given location in a document. /// diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 874aa1177e4..6d66fd18102 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -2368,7 +2368,7 @@ void main() { ); final RenderEditable renderEditable = tester.renderObject( - find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last, + find.byElementPredicate((Element element) => element.renderObject is RenderEditable), ); List lastCharEndpoint = renderEditable.getEndpointsForSelection( @@ -3166,7 +3166,7 @@ void main() { expect( tester.renderObject( - find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last, + find.byElementPredicate((Element element) => element.renderObject is RenderEditable), ).text!.style!.color, isSameColorAs(CupertinoColors.white), ); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 7464689d0cb..3fef7373b60 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -9255,38 +9255,6 @@ void main() { expect(right.opacity.value, equals(1.0)); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); - testWidgets('iPad Scribble selection change shows selection handles', (WidgetTester tester) async { - const String testText = 'lorem ipsum'; - final TextEditingController controller = TextEditingController(text: testText); - - await tester.pumpWidget( - MaterialApp( - home: Material( - child: TextField( - controller: controller, - ), - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - await tester.testTextInput.startScribbleInteraction(); - tester.testTextInput.updateEditingValue(const TextEditingValue( - text: testText, - selection: TextSelection(baseOffset: 2, extentOffset: 7), - )); - await tester.pumpAndSettle(); - - final List transitions = - find.byType(FadeTransition).evaluate().map((Element e) => e.widget).cast().toList(); - expect(transitions.length, 2); - final FadeTransition left = transitions[0]; - final FadeTransition right = transitions[1]; - - expect(left.opacity.value, equals(1.0)); - expect(right.opacity.value, equals(1.0)); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); - testWidgets('Tap shows handles but not toolbar', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( text: 'abc def ghi', diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart index e463a08fc60..11fdcd93eab 100644 --- a/packages/flutter/test/services/autofill_test.dart +++ b/packages/flutter/test/services/autofill_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui'; - import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -143,21 +141,6 @@ class FakeAutofillClient implements TextInputClient, AutofillClient { @override void autofill(TextEditingValue newEditingValue) => updateEditingValue(newEditingValue); - - @override - void showToolbar() { - latestMethodCall = 'showToolbar'; - } - - @override - void insertTextPlaceholder(Size size) { - latestMethodCall = 'insertTextPlaceholder'; - } - - @override - void removeTextPlaceholder() { - latestMethodCall = 'removeTextPlaceholder'; - } } class FakeAutofillScope with AutofillScopeMixin implements AutofillScope { diff --git a/packages/flutter/test/services/delta_text_input_test.dart b/packages/flutter/test/services/delta_text_input_test.dart index eca5455aff6..e87a66521f8 100644 --- a/packages/flutter/test/services/delta_text_input_test.dart +++ b/packages/flutter/test/services/delta_text_input_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:convert' show jsonDecode; -import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -115,20 +114,5 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient { latestMethodCall = 'showAutocorrectionPromptRect'; } - @override - void insertTextPlaceholder(Size size) { - latestMethodCall = 'insertTextPlaceholder'; - } - - @override - void removeTextPlaceholder() { - latestMethodCall = 'removeTextPlaceholder'; - } - - @override - void showToolbar() { - latestMethodCall = 'showToolbar'; - } - TextInputConfiguration get configuration => const TextInputConfiguration(enableDeltaModel: true); } diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index 8591a04a8c5..09e5831110d 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -4,7 +4,6 @@ import 'dart:convert' show jsonDecode; -import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -497,148 +496,6 @@ void main() { expect(client.latestMethodCall, 'showAutocorrectionPromptRect'); }); - - test('TextInputClient showToolbar method is called', () async { - // Assemble a TextInputConnection so we can verify its change in state. - final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty); - const TextInputConfiguration configuration = TextInputConfiguration(); - TextInput.attach(client, configuration); - - expect(client.latestMethodCall, isEmpty); - - // Send showToolbar message. - final ByteData? messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [1, 0, 1], - 'method': 'TextInputClient.showToolbar', - }); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/textinput', - messageBytes, - (ByteData? _) {}, - ); - - expect(client.latestMethodCall, 'showToolbar'); - }); - }); - - group('Scribble interactions', () { - tearDown(() { - TextInputConnection.debugResetId(); - }); - - test('TextInputClient scribbleInteractionBegan and scribbleInteractionFinished', () async { - // Assemble a TextInputConnection so we can verify its change in state. - final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty); - const TextInputConfiguration configuration = TextInputConfiguration(); - final TextInputConnection connection = TextInput.attach(client, configuration); - - expect(connection.scribbleInProgress, false); - - // Send scribbleInteractionBegan message. - ByteData? messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [1, 0, 1], - 'method': 'TextInputClient.scribbleInteractionBegan', - }); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/textinput', - messageBytes, - (ByteData? _) {}, - ); - - expect(connection.scribbleInProgress, true); - - // Send scribbleInteractionFinished message. - messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [1, 0, 1], - 'method': 'TextInputClient.scribbleInteractionFinished', - }); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/textinput', - messageBytes, - (ByteData? _) {}, - ); - - expect(connection.scribbleInProgress, false); - }); - - test('TextInputClient focusElement', () async { - // Assemble a TextInputConnection so we can verify its change in state. - final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty); - const TextInputConfiguration configuration = TextInputConfiguration(); - TextInput.attach(client, configuration); - - final FakeScribbleElement targetElement = FakeScribbleElement(elementIdentifier: 'target'); - TextInput.registerScribbleElement(targetElement.elementIdentifier, targetElement); - final FakeScribbleElement otherElement = FakeScribbleElement(elementIdentifier: 'other'); - TextInput.registerScribbleElement(otherElement.elementIdentifier, otherElement); - - expect(targetElement.latestMethodCall, isEmpty); - expect(otherElement.latestMethodCall, isEmpty); - - // Send focusElement message. - final ByteData? messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [targetElement.elementIdentifier, 0.0, 0.0], - 'method': 'TextInputClient.focusElement', - }); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/textinput', - messageBytes, - (ByteData? _) {}, - ); - - TextInput.unregisterScribbleElement(targetElement.elementIdentifier); - TextInput.unregisterScribbleElement(otherElement.elementIdentifier); - - expect(targetElement.latestMethodCall, 'onScribbleFocus'); - expect(otherElement.latestMethodCall, isEmpty); - }); - - test('TextInputClient requestElementsInRect', () async { - // Assemble a TextInputConnection so we can verify its change in state. - final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty); - const TextInputConfiguration configuration = TextInputConfiguration(); - TextInput.attach(client, configuration); - - final List targetElements = [ - FakeScribbleElement(elementIdentifier: 'target1', bounds: const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0)), - FakeScribbleElement(elementIdentifier: 'target2', bounds: const Rect.fromLTWH(0.0, 100.0, 100.0, 100.0)), - ]; - final List otherElements = [ - FakeScribbleElement(elementIdentifier: 'other1', bounds: const Rect.fromLTWH(100.0, 0.0, 100.0, 100.0)), - FakeScribbleElement(elementIdentifier: 'other2', bounds: const Rect.fromLTWH(100.0, 100.0, 100.0, 100.0)), - ]; - - void registerElements(FakeScribbleElement element) => TextInput.registerScribbleElement(element.elementIdentifier, element); - void unregisterElements(FakeScribbleElement element) => TextInput.unregisterScribbleElement(element.elementIdentifier); - - [...targetElements, ...otherElements].forEach(registerElements); - - // Send requestElementsInRect message. - final ByteData? messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [0.0, 50.0, 50.0, 100.0], - 'method': 'TextInputClient.requestElementsInRect', - }); - ByteData? responseBytes; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - 'flutter/textinput', - messageBytes, - (ByteData? response) { - responseBytes = response; - }, - ); - - [...targetElements, ...otherElements].forEach(unregisterElements); - - final List> responses = (const JSONMessageCodec().decodeMessage(responseBytes) as List).cast>(); - expect(responses.first.length, 2); - expect(responses.first.first, containsAllInOrder([targetElements.first.elementIdentifier, 0.0, 0.0, 100.0, 100.0])); - expect(responses.first.last, containsAllInOrder([targetElements.last.elementIdentifier, 0.0, 100.0, 100.0, 100.0])); - }); }); test('TextEditingValue.isComposingRangeValid', () async { @@ -710,20 +567,5 @@ class FakeTextInputClient implements TextInputClient { latestMethodCall = 'showAutocorrectionPromptRect'; } - @override - void showToolbar() { - latestMethodCall = 'showToolbar'; - } - TextInputConfiguration get configuration => const TextInputConfiguration(); - - @override - void insertTextPlaceholder(Size size) { - latestMethodCall = 'insertTextPlaceholder'; - } - - @override - void removeTextPlaceholder() { - latestMethodCall = 'removeTextPlaceholder'; - } } diff --git a/packages/flutter/test/services/text_input_utils.dart b/packages/flutter/test/services/text_input_utils.dart index 2598c098a85..e1e3ddf9b59 100644 --- a/packages/flutter/test/services/text_input_utils.dart +++ b/packages/flutter/test/services/text_input_utils.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:convert' show utf8; -import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -65,29 +64,3 @@ class FakeTextChannel implements MethodChannel { } } } - -class FakeScribbleElement implements ScribbleClient { - FakeScribbleElement({required String elementIdentifier, Rect bounds = Rect.zero}) - : _elementIdentifier = elementIdentifier, - _bounds = bounds; - - final String _elementIdentifier; - final Rect _bounds; - String latestMethodCall = ''; - - @override - Rect get bounds => _bounds; - - @override - String get elementIdentifier => _elementIdentifier; - - @override - bool isInScribbleRect(Rect rect) { - return _bounds.overlaps(rect); - } - - @override - void onScribbleFocus(Offset offset) { - latestMethodCall = 'onScribbleFocus'; - } -} diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 0010656cdd8..d94fd0037ec 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -1662,329 +1662,6 @@ void main() { } }); - testWidgets('Selection changes during Scribble interaction should have the scribble cause', (WidgetTester tester) async { - late SelectionChangedCause selectionCause; - - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) { - if (cause != null) - selectionCause = cause; - }, - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - - // A normal selection update from the framework has 'keyboard' as the cause. - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 2, extentOffset: 3), - )); - await tester.pumpAndSettle(); - - expect(selectionCause, SelectionChangedCause.keyboard); - - // A selection update during a scribble interaction has 'scribble' as the cause. - await tester.testTextInput.startScribbleInteraction(); - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 3, extentOffset: 4), - )); - await tester.pumpAndSettle(); - - expect(selectionCause, SelectionChangedCause.scribble); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); - - testWidgets('Requests focus and changes the selection when onScribbleFocus is called', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - late SelectionChangedCause selectionCause; - - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) { - if (cause != null) - selectionCause = cause; - }, - ), - ), - ); - - await tester.testTextInput.scribbleFocusElement(TextInput.scribbleClients.keys.first, Offset.zero); - - expect(focusNode.hasFocus, true); - expect(selectionCause, SelectionChangedCause.scribble); - - // On web, we should rely on the browser's implementation of Scribble, so the selection changed cause - // will never be SelectionChangedCause.scribble. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - - testWidgets('Declares itself for Scribble interaction if the bounds overlap the scribble rect and the widget is touchable', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - ), - ), - ); - - final List elementEntry = [TextInput.scribbleClients.keys.first, 0.0, 0.0, 800.0, 600.0]; - - List> elements = await tester.testTextInput.scribbleRequestElementsInRect(const Rect.fromLTWH(0, 0, 1, 1)); - expect(elements.first, containsAll(elementEntry)); - - // Touch is outside the bounds of the widget. - elements = await tester.testTextInput.scribbleRequestElementsInRect(const Rect.fromLTWH(-1, -1, 1, 1)); - expect(elements.length, 0); - - // Widget is read only. - await tester.pumpWidget( - MaterialApp( - home: EditableText( - readOnly: true, - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - ), - ), - ); - - elements = await tester.testTextInput.scribbleRequestElementsInRect(const Rect.fromLTWH(0, 0, 1, 1)); - expect(elements.length, 0); - - // Widget is not touchable. - await tester.pumpWidget( - MaterialApp( - home: Stack(children: [ - EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - ), - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Container(color: Colors.black), - ), - ], - ), - ), - ); - - elements = await tester.testTextInput.scribbleRequestElementsInRect(const Rect.fromLTWH(0, 0, 1, 1)); - expect(elements.length, 0); - - // Widget has scribble disabled. - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - scribbleEnabled: false, - ), - ), - ); - - elements = await tester.testTextInput.scribbleRequestElementsInRect(const Rect.fromLTWH(0, 0, 1, 1)); - expect(elements.length, 0); - - - // On web, we should rely on the browser's implementation of Scribble, so the engine will - // never request the scribble elements. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - - testWidgets('single line Scribble fields can show a horizontal placeholder', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 5, extentOffset: 5), - )); - await tester.pumpAndSettle(); - - await tester.testTextInput.scribbleInsertPlaceholder(); - await tester.pumpAndSettle(); - - TextSpan textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children!.length, 3); - expect((textSpan.children![0] as TextSpan).text, 'Lorem'); - expect(textSpan.children![1] is WidgetSpan, true); - expect((textSpan.children![2] as TextSpan).text, ' ipsum dolor sit amet'); - - await tester.testTextInput.scribbleRemovePlaceholder(); - await tester.pumpAndSettle(); - - textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children, null); - expect(textSpan.text, 'Lorem ipsum dolor sit amet'); - - // Widget has scribble disabled. - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - scribbleEnabled: false, - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 5, extentOffset: 5), - )); - await tester.pumpAndSettle(); - - await tester.testTextInput.scribbleInsertPlaceholder(); - await tester.pumpAndSettle(); - - textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children, null); - expect(textSpan.text, 'Lorem ipsum dolor sit amet'); - - // On web, we should rely on the browser's implementation of Scribble, so the framework - // will not handle placeholders. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - - testWidgets('multiline Scribble fields can show a vertical placeholder', (WidgetTester tester) async { - final TextEditingController controller = - TextEditingController(text: 'Lorem ipsum dolor sit amet'); - - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - maxLines: 2, - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 5, extentOffset: 5), - )); - await tester.pumpAndSettle(); - - await tester.testTextInput.scribbleInsertPlaceholder(); - await tester.pumpAndSettle(); - - TextSpan textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children!.length, 4); - expect((textSpan.children![0] as TextSpan).text, 'Lorem'); - expect(textSpan.children![1] is WidgetSpan, true); - expect(textSpan.children![2] is WidgetSpan, true); - expect((textSpan.children![3] as TextSpan).text, ' ipsum dolor sit amet'); - - await tester.testTextInput.scribbleRemovePlaceholder(); - await tester.pumpAndSettle(); - - textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children, null); - expect(textSpan.text, 'Lorem ipsum dolor sit amet'); - - // Widget has scribble disabled. - await tester.pumpWidget( - MaterialApp( - home: EditableText( - controller: controller, - backgroundCursorColor: Colors.grey, - focusNode: focusNode, - style: textStyle, - cursorColor: cursorColor, - selectionControls: materialTextSelectionControls, - maxLines: 2, - scribbleEnabled: false, - ), - ), - ); - - await tester.showKeyboard(find.byType(EditableText)); - - tester.testTextInput.updateEditingValue(TextEditingValue( - text: controller.text, - selection: const TextSelection(baseOffset: 5, extentOffset: 5), - )); - await tester.pumpAndSettle(); - - await tester.testTextInput.scribbleInsertPlaceholder(); - await tester.pumpAndSettle(); - - textSpan = findRenderEditable(tester).text! as TextSpan; - expect(textSpan.children, null); - expect(textSpan.text, 'Lorem ipsum dolor sit amet'); - - // On web, we should rely on the browser's implementation of Scribble, so the framework - // will not handle placeholders. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('Sends "updateConfig" when read-only flag is flipped', (WidgetTester tester) async { bool readOnly = true; late StateSetter setState; @@ -4017,85 +3694,6 @@ void main() { ); }); - testWidgets('selection rects are sent when they change', (WidgetTester tester) async { - final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - }); - - final TextEditingController controller = TextEditingController(); - controller.text = 'Text1'; - - await tester.pumpWidget( - MediaQuery( - data: const MediaQueryData(), - child: Directionality( - textDirection: TextDirection.ltr, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - EditableText( - key: ValueKey(controller.text), - controller: controller, - focusNode: FocusNode(), - style: Typography.material2018().black.subtitle1!, - cursorColor: Colors.blue, - backgroundCursorColor: Colors.grey, - ), - ], - ), - ), - ), - ); - await tester.showKeyboard(find.byKey(ValueKey(controller.text))); - - // There should be a new platform message updating the selection rects. - final MethodCall methodCall = log.firstWhere((MethodCall m) => m.method == 'TextInput.setSelectionRects'); - expect(methodCall.method, 'TextInput.setSelectionRects'); - expect((methodCall.arguments as List).length, 5); - - // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - - testWidgets('selection rects are not sent if scribbleEnabled is false', (WidgetTester tester) async { - final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - }); - - final TextEditingController controller = TextEditingController(); - controller.text = 'Text1'; - - await tester.pumpWidget( - MediaQuery( - data: const MediaQueryData(), - child: Directionality( - textDirection: TextDirection.ltr, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - EditableText( - key: ValueKey(controller.text), - controller: controller, - focusNode: FocusNode(), - style: Typography.material2018().black.subtitle1!, - cursorColor: Colors.blue, - backgroundCursorColor: Colors.grey, - scribbleEnabled: false, - ), - ], - ), - ), - ), - ); - await tester.showKeyboard(find.byKey(ValueKey(controller.text))); - - // There should be a new platform message updating the selection rects. - expect(log.where((MethodCall m) => m.method == 'TextInput.setSelectionRects').length, 0); - - // On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects. - }, skip: kIsWeb, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); // [intended] - testWidgets('text styling info is sent on show keyboard', (WidgetTester tester) async { final List log = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart index 1a4f5d9caff..a9787c04023 100644 --- a/packages/flutter_test/lib/src/test_text_input.dart +++ b/packages/flutter_test/lib/src/test_text_input.dart @@ -3,8 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:typed_data'; -import 'dart:ui' show Rect, Offset; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -273,84 +271,4 @@ class TestTextInput { (ByteData? data) { /* response from framework is discarded */ }, ); } - - /// Simulates a scribble interaction starting. - Future startScribbleInteraction() async { - assert(isRegistered); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.scribbleInteractionBegan', - [_client ?? -1,] - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); - } - - /// Simulates a Scribble focus. - Future scribbleFocusElement(String elementIdentifier, Offset offset) async { - assert(isRegistered); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.focusElement', - [elementIdentifier, offset.dx, offset.dy] - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); - } - - /// Simulates iOS asking for the list of Scribble elements during UIIndirectScribbleInteraction. - Future>> scribbleRequestElementsInRect(Rect rect) async { - assert(isRegistered); - List> response = >[]; - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.requestElementsInRect', - [rect.left, rect.top, rect.width, rect.height] - ), - ), - (ByteData? data) { - response = (SystemChannels.textInput.codec.decodeEnvelope(data!) as List).map((dynamic element) => element as List).toList(); - }, - ); - - return response; - } - - /// Simulates iOS inserting a UITextPlaceholder during a long press with the pencil. - Future scribbleInsertPlaceholder() async { - assert(isRegistered); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.insertTextPlaceholder', - [_client ?? -1, 0.0, 0.0] - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); - } - - /// Simulates iOS removing a UITextPlaceholder after a long press with the pencil is released. - Future scribbleRemovePlaceholder() async { - assert(isRegistered); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.removeTextPlaceholder', - [_client ?? -1] - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); - } }