mirror of
https://github.com/flutter/flutter
synced 2024-08-27 12:00:58 +00:00
Revert "Support Scribble Handwriting" (#96615)
This commit is contained in:
parent
4fda08efa9
commit
9c23106711
1
AUTHORS
1
AUTHORS
|
@ -89,4 +89,3 @@ Pradumna Saraf <pradumnasaraf@gmail.com>
|
|||
Kai Yu <yk3372@gmail.com>
|
||||
Denis Grafov <grafov.denis@gmail.com>
|
||||
TheOneWithTheBraid <the-one@with-the-braid.cf>
|
||||
Twin Sun, LLC <google-contrib@twinsunsolutions.com>
|
||||
|
|
|
@ -297,7 +297,6 @@ class CupertinoTextField extends StatefulWidget {
|
|||
this.autofillHints = const <String>[],
|
||||
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 <String>[],
|
||||
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', textAlignVertical, defaultValue: null));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
|
||||
properties.add(DiagnosticsProperty<bool>('scribbleEnabled', scribbleEnabled, defaultValue: true));
|
||||
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
|
||||
}
|
||||
}
|
||||
|
@ -969,9 +963,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> 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<CupertinoTextField> with Restoratio
|
|||
autofillClient: this,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
restorationId: 'editable',
|
||||
scribbleEnabled: widget.scribbleEnabled,
|
||||
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -329,7 +329,6 @@ class TextField extends StatefulWidget {
|
|||
this.autofillHints = const <String>[],
|
||||
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', scrollController, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
|
||||
properties.add(DiagnosticsProperty<bool>('scribbleEnabled', scribbleEnabled, defaultValue: true));
|
||||
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
|
||||
}
|
||||
}
|
||||
|
@ -1034,7 +1029,7 @@ class _TextFieldState extends State<TextField> 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<TextField> with RestorationMixin implements
|
|||
autocorrectionTextRectColor: autocorrectionTextRectColor,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
restorationId: 'editable',
|
||||
scribbleEnabled: widget.scribbleEnabled,
|
||||
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1265,16 +1265,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
|||
// [assembleSemanticsNode] invocations.
|
||||
Queue<SemanticsNode>? _cachedChildNodes;
|
||||
|
||||
/// Returns a list of rects that bound the given selection.
|
||||
///
|
||||
/// See [TextPainter.getBoxesForSelection] for more details.
|
||||
List<Rect> getBoxesForSelection(TextSelection selection) {
|
||||
_computeTextMetricsIfNeeded();
|
||||
return _textPainter.getBoxesForSelection(selection)
|
||||
.map((TextBox textBox) => textBox.toRect().shift(_paintOffset))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
|
|
|
@ -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<SelectionRect> _cachedSelectionRects = <SelectionRect>[];
|
||||
|
||||
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<SelectionRect> selectionRects) {
|
||||
if (!listEquals(_cachedSelectionRects, selectionRects)) {
|
||||
_cachedSelectionRects = selectionRects;
|
||||
TextInput._instance._setSelectionRects(selectionRects.map((SelectionRect rect) {
|
||||
return <num>[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<String, ScribbleClient> _scribbleClients = <String, ScribbleClient>{};
|
||||
bool _scribbleInProgress = false;
|
||||
|
||||
/// Used for testing within the Flutter SDK to get the currently registered [ScribbleClient] list.
|
||||
@visibleForTesting
|
||||
static Map<String, ScribbleClient> get scribbleClients => TextInput._instance._scribbleClients;
|
||||
|
||||
/// Returns true if a scribble interaction is currently happening.
|
||||
bool get scribbleInProgress => _scribbleInProgress;
|
||||
|
||||
Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
|
||||
final String method = methodCall.method;
|
||||
if (method == 'TextInputClient.focusElement') {
|
||||
final List<dynamic> args = methodCall.arguments as List<dynamic>;
|
||||
_scribbleClients[args[0]]?.onScribbleFocus(Offset((args[1] as num).toDouble(), (args[2] as num).toDouble()));
|
||||
return;
|
||||
} else if (method == 'TextInputClient.requestElementsInRect') {
|
||||
final List<double> args = (methodCall.arguments as List<dynamic>).cast<num>().map<double>((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 <dynamic>[elementIdentifier, ...<dynamic>[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<List<num>> args) {
|
||||
_channel.invokeMethod<void>(
|
||||
'TextInput.setSelectionRects',
|
||||
args,
|
||||
);
|
||||
}
|
||||
|
||||
void _setStyle(Map<String, dynamic> args) {
|
||||
_channel.invokeMethod<void>(
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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', scrollPhysics, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Iterable<String>>('autofillHints', autofillHints, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<bool>('scribbleEnabled', scribbleEnabled, defaultValue: true));
|
||||
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
|
||||
}
|
||||
}
|
||||
|
@ -1888,7 +1873,7 @@ class EditableTextState extends State<EditableText> 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<EditableText> 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<EditableText> 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<Rect> 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<SelectionRect> rects = List<SelectionRect?>.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) => 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<EditableText> 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<EditableText> 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,14 +2991,6 @@ class EditableTextState extends State<EditableText> 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,
|
||||
|
@ -3164,7 +3040,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||
clipBehavior: widget.clipBehavior,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -3192,24 +3067,6 @@ class EditableTextState extends State<EditableText> 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: <InlineSpan>[
|
||||
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<PlaceholderDimensions>? 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.
|
||||
///
|
||||
|
|
|
@ -2368,7 +2368,7 @@ void main() {
|
|||
);
|
||||
|
||||
final RenderEditable renderEditable = tester.renderObject<RenderEditable>(
|
||||
find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last,
|
||||
find.byElementPredicate((Element element) => element.renderObject is RenderEditable),
|
||||
);
|
||||
|
||||
List<TextSelectionPoint> lastCharEndpoint = renderEditable.getEndpointsForSelection(
|
||||
|
@ -3166,7 +3166,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
tester.renderObject<RenderEditable>(
|
||||
find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last,
|
||||
find.byElementPredicate((Element element) => element.renderObject is RenderEditable),
|
||||
).text!.style!.color,
|
||||
isSameColorAs(CupertinoColors.white),
|
||||
);
|
||||
|
|
|
@ -9255,38 +9255,6 @@ void main() {
|
|||
expect(right.opacity.value, equals(1.0));
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ 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<FadeTransition> transitions =
|
||||
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).cast<FadeTransition>().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>{ TargetPlatform.iOS }));
|
||||
|
||||
testWidgets('Tap shows handles but not toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(<String, dynamic>{
|
||||
'args': <dynamic>[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(<String, dynamic>{
|
||||
'args': <dynamic>[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(<String, dynamic>{
|
||||
'args': <dynamic>[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(<String, dynamic>{
|
||||
'args': <dynamic>[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<FakeScribbleElement> targetElements = <FakeScribbleElement>[
|
||||
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<FakeScribbleElement> otherElements = <FakeScribbleElement>[
|
||||
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);
|
||||
|
||||
<FakeScribbleElement>[...targetElements, ...otherElements].forEach(registerElements);
|
||||
|
||||
// Send requestElementsInRect message.
|
||||
final ByteData? messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[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;
|
||||
},
|
||||
);
|
||||
|
||||
<FakeScribbleElement>[...targetElements, ...otherElements].forEach(unregisterElements);
|
||||
|
||||
final List<List<dynamic>> responses = (const JSONMessageCodec().decodeMessage(responseBytes) as List<dynamic>).cast<List<dynamic>>();
|
||||
expect(responses.first.length, 2);
|
||||
expect(responses.first.first, containsAllInOrder(<dynamic>[targetElements.first.elementIdentifier, 0.0, 0.0, 100.0, 100.0]));
|
||||
expect(responses.first.last, containsAllInOrder(<dynamic>[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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>{ 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>{ 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<dynamic> elementEntry = <dynamic>[TextInput.scribbleClients.keys.first, 0.0, 0.0, 800.0, 600.0];
|
||||
|
||||
List<List<dynamic>> 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: <Widget>[
|
||||
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>{ 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>{ 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>{ 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<MethodCall> log = <MethodCall>[];
|
||||
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: <Widget>[
|
||||
EditableText(
|
||||
key: ValueKey<String>(controller.text),
|
||||
controller: controller,
|
||||
focusNode: FocusNode(),
|
||||
style: Typography.material2018().black.subtitle1!,
|
||||
cursorColor: Colors.blue,
|
||||
backgroundCursorColor: Colors.grey,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.showKeyboard(find.byKey(ValueKey<String>(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<dynamic>).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>{ TargetPlatform.iOS })); // [intended]
|
||||
|
||||
testWidgets('selection rects are not sent if scribbleEnabled is false', (WidgetTester tester) async {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
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: <Widget>[
|
||||
EditableText(
|
||||
key: ValueKey<String>(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<String>(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>{ TargetPlatform.iOS })); // [intended]
|
||||
|
||||
testWidgets('text styling info is sent on show keyboard', (WidgetTester tester) async {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async {
|
||||
|
|
|
@ -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<void> startScribbleInteraction() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.scribbleInteractionBegan',
|
||||
<dynamic>[_client ?? -1,]
|
||||
),
|
||||
),
|
||||
(ByteData? data) { /* response from framework is discarded */ },
|
||||
);
|
||||
}
|
||||
|
||||
/// Simulates a Scribble focus.
|
||||
Future<void> scribbleFocusElement(String elementIdentifier, Offset offset) async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.focusElement',
|
||||
<dynamic>[elementIdentifier, offset.dx, offset.dy]
|
||||
),
|
||||
),
|
||||
(ByteData? data) { /* response from framework is discarded */ },
|
||||
);
|
||||
}
|
||||
|
||||
/// Simulates iOS asking for the list of Scribble elements during UIIndirectScribbleInteraction.
|
||||
Future<List<List<dynamic>>> scribbleRequestElementsInRect(Rect rect) async {
|
||||
assert(isRegistered);
|
||||
List<List<dynamic>> response = <List<dynamic>>[];
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.requestElementsInRect',
|
||||
<dynamic>[rect.left, rect.top, rect.width, rect.height]
|
||||
),
|
||||
),
|
||||
(ByteData? data) {
|
||||
response = (SystemChannels.textInput.codec.decodeEnvelope(data!) as List<dynamic>).map((dynamic element) => element as List<dynamic>).toList();
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Simulates iOS inserting a UITextPlaceholder during a long press with the pencil.
|
||||
Future<void> scribbleInsertPlaceholder() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.insertTextPlaceholder',
|
||||
<dynamic>[_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<void> scribbleRemovePlaceholder() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.removeTextPlaceholder',
|
||||
<dynamic>[_client ?? -1]
|
||||
),
|
||||
),
|
||||
(ByteData? data) { /* response from framework is discarded */ },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue