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>
|
Kai Yu <yk3372@gmail.com>
|
||||||
Denis Grafov <grafov.denis@gmail.com>
|
Denis Grafov <grafov.denis@gmail.com>
|
||||||
TheOneWithTheBraid <the-one@with-the-braid.cf>
|
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.autofillHints = const <String>[],
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.restorationId,
|
this.restorationId,
|
||||||
this.scribbleEnabled = true,
|
|
||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
}) : assert(textAlign != null),
|
}) : assert(textAlign != null),
|
||||||
assert(readOnly != null),
|
assert(readOnly != null),
|
||||||
|
@ -455,7 +454,6 @@ class CupertinoTextField extends StatefulWidget {
|
||||||
this.autofillHints = const <String>[],
|
this.autofillHints = const <String>[],
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.restorationId,
|
this.restorationId,
|
||||||
this.scribbleEnabled = true,
|
|
||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
}) : assert(textAlign != null),
|
}) : assert(textAlign != null),
|
||||||
assert(readOnly != null),
|
assert(readOnly != null),
|
||||||
|
@ -800,9 +798,6 @@ class CupertinoTextField extends StatefulWidget {
|
||||||
/// {@macro flutter.material.textfield.restorationId}
|
/// {@macro flutter.material.textfield.restorationId}
|
||||||
final String? restorationId;
|
final String? restorationId;
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.scribbleEnabled}
|
|
||||||
final bool scribbleEnabled;
|
|
||||||
|
|
||||||
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
|
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
|
||||||
final bool enableIMEPersonalizedLearning;
|
final bool enableIMEPersonalizedLearning;
|
||||||
|
|
||||||
|
@ -848,7 +843,6 @@ class CupertinoTextField extends StatefulWidget {
|
||||||
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
|
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
|
||||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
|
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));
|
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -969,9 +963,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
||||||
if (cause == SelectionChangedCause.keyboard)
|
if (cause == SelectionChangedCause.keyboard)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.scribble)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (_effectiveController.text.isNotEmpty)
|
if (_effectiveController.text.isNotEmpty)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -1301,7 +1292,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
||||||
autofillClient: this,
|
autofillClient: this,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: widget.clipBehavior,
|
||||||
restorationId: 'editable',
|
restorationId: 'editable',
|
||||||
scribbleEnabled: widget.scribbleEnabled,
|
|
||||||
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -329,7 +329,6 @@ class TextField extends StatefulWidget {
|
||||||
this.autofillHints = const <String>[],
|
this.autofillHints = const <String>[],
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.restorationId,
|
this.restorationId,
|
||||||
this.scribbleEnabled = true,
|
|
||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
}) : assert(textAlign != null),
|
}) : assert(textAlign != null),
|
||||||
assert(readOnly != null),
|
assert(readOnly != null),
|
||||||
|
@ -769,9 +768,6 @@ class TextField extends StatefulWidget {
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
final String? restorationId;
|
final String? restorationId;
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.scribbleEnabled}
|
|
||||||
final bool scribbleEnabled;
|
|
||||||
|
|
||||||
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
|
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
|
||||||
final bool enableIMEPersonalizedLearning;
|
final bool enableIMEPersonalizedLearning;
|
||||||
|
|
||||||
|
@ -816,7 +812,6 @@ class TextField extends StatefulWidget {
|
||||||
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
|
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
|
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));
|
properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1034,7 +1029,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
||||||
if (!_isEnabled)
|
if (!_isEnabled)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.longPress || cause == SelectionChangedCause.scribble)
|
if (cause == SelectionChangedCause.longPress)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (_effectiveController.text.isNotEmpty)
|
if (_effectiveController.text.isNotEmpty)
|
||||||
|
@ -1278,7 +1273,6 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
||||||
autocorrectionTextRectColor: autocorrectionTextRectColor,
|
autocorrectionTextRectColor: autocorrectionTextRectColor,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: widget.clipBehavior,
|
||||||
restorationId: 'editable',
|
restorationId: 'editable',
|
||||||
scribbleEnabled: widget.scribbleEnabled,
|
|
||||||
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1265,16 +1265,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// [assembleSemanticsNode] invocations.
|
// [assembleSemanticsNode] invocations.
|
||||||
Queue<SemanticsNode>? _cachedChildNodes;
|
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
|
@override
|
||||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
super.describeSemanticsConfiguration(config);
|
super.describeSemanticsConfiguration(config);
|
||||||
|
|
|
@ -955,9 +955,6 @@ enum SelectionChangedCause {
|
||||||
/// The user used the mouse to change the selection by dragging over a piece
|
/// The user used the mouse to change the selection by dragging over a piece
|
||||||
/// of text.
|
/// of text.
|
||||||
drag,
|
drag,
|
||||||
|
|
||||||
/// The user used iPadOS 14+ Scribble to change the selection.
|
|
||||||
scribble,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mixin for manipulating the selection, provided for toolbar or shortcut
|
/// 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.
|
/// [TextInputClient] should cleanup its connection and finalize editing.
|
||||||
void connectionClosed();
|
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].
|
/// An interface to receive granular information from [TextInput].
|
||||||
|
@ -1227,7 +1154,6 @@ class TextInputConnection {
|
||||||
Matrix4? _cachedTransform;
|
Matrix4? _cachedTransform;
|
||||||
Rect? _cachedRect;
|
Rect? _cachedRect;
|
||||||
Rect? _cachedCaretRect;
|
Rect? _cachedCaretRect;
|
||||||
List<SelectionRect> _cachedSelectionRects = <SelectionRect>[];
|
|
||||||
|
|
||||||
static int _nextId = 1;
|
static int _nextId = 1;
|
||||||
final int _id;
|
final int _id;
|
||||||
|
@ -1250,12 +1176,6 @@ class TextInputConnection {
|
||||||
/// Whether this connection is currently interacting with the text input control.
|
/// Whether this connection is currently interacting with the text input control.
|
||||||
bool get attached => TextInput._instance._currentConnection == this;
|
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.
|
/// Requests that the text input control become visible.
|
||||||
void show() {
|
void show() {
|
||||||
assert(attached);
|
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.
|
/// Send text styling information.
|
||||||
///
|
///
|
||||||
/// This information is used by the Flutter Web Engine to change the style
|
/// This information is used by the Flutter Web Engine to change the style
|
||||||
|
@ -1628,43 +1535,10 @@ class TextInput {
|
||||||
TextInputConnection? _currentConnection;
|
TextInputConnection? _currentConnection;
|
||||||
late TextInputConfiguration _currentConfiguration;
|
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 {
|
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)
|
if (_currentConnection == null)
|
||||||
return;
|
return;
|
||||||
|
final String method = methodCall.method;
|
||||||
|
|
||||||
// The requestExistingInputState request needs to be handled regardless of
|
// The requestExistingInputState request needs to be handled regardless of
|
||||||
// the client ID, as long as we have a _currentConnection.
|
// the client ID, as long as we have a _currentConnection.
|
||||||
|
@ -1756,15 +1630,6 @@ class TextInput {
|
||||||
case 'TextInputClient.showAutocorrectionPromptRect':
|
case 'TextInputClient.showAutocorrectionPromptRect':
|
||||||
_currentConnection!._client.showAutocorrectionPromptRect(args[1] as int, args[2] as int);
|
_currentConnection!._client.showAutocorrectionPromptRect(args[1] as int, args[2] as int);
|
||||||
break;
|
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:
|
default:
|
||||||
throw MissingPluginException();
|
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) {
|
void _setStyle(Map<String, dynamic> args) {
|
||||||
_channel.invokeMethod<void>(
|
_channel.invokeMethod<void>(
|
||||||
'TextInput.setStyle',
|
'TextInput.setStyle',
|
||||||
|
@ -1907,18 +1765,4 @@ class TextInput {
|
||||||
shouldSave,
|
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:math' as math;
|
||||||
import 'dart:ui' as ui hide TextStyle;
|
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/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
@ -58,10 +58,6 @@ const Duration _kCursorBlinkWaitForStart = Duration(milliseconds: 150);
|
||||||
// is shown in an obscured text field.
|
// is shown in an obscured text field.
|
||||||
const int _kObscureShowLatestCharCursorTicks = 3;
|
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.
|
/// A controller for an editable text field.
|
||||||
///
|
///
|
||||||
/// Whenever the user modifies a text field with an associated
|
/// Whenever the user modifies a text field with an associated
|
||||||
|
@ -527,7 +523,6 @@ class EditableText extends StatefulWidget {
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
this.restorationId,
|
this.restorationId,
|
||||||
this.scrollBehavior,
|
this.scrollBehavior,
|
||||||
this.scribbleEnabled = true,
|
|
||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
}) : assert(controller != null),
|
}) : assert(controller != null),
|
||||||
assert(focusNode != null),
|
assert(focusNode != null),
|
||||||
|
@ -1215,15 +1210,6 @@ class EditableText extends StatefulWidget {
|
||||||
/// [scrollPhysics].
|
/// [scrollPhysics].
|
||||||
final ScrollPhysics? 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}
|
/// {@template flutter.widgets.editableText.selectionEnabled}
|
||||||
/// Same as [enableInteractiveSelection].
|
/// Same as [enableInteractiveSelection].
|
||||||
///
|
///
|
||||||
|
@ -1524,7 +1510,6 @@ class EditableText extends StatefulWidget {
|
||||||
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Iterable<String>>('autofillHints', autofillHints, defaultValue: null));
|
properties.add(DiagnosticsProperty<Iterable<String>>('autofillHints', autofillHints, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, 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));
|
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) {
|
if (value.text == _value.text && value.composing == _value.composing) {
|
||||||
// `selection` is the only change.
|
// `selection` is the only change.
|
||||||
_handleSelectionChanged(value.selection, (_textInputConnection?.scribbleInProgress ?? false) ? SelectionChangedCause.scribble : SelectionChangedCause.keyboard);
|
_handleSelectionChanged(value.selection, SelectionChangedCause.keyboard);
|
||||||
} else {
|
} else {
|
||||||
hideToolbar();
|
hideToolbar();
|
||||||
_currentPromptRectRange = null;
|
_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.
|
// Place cursor at the end if the selection is invalid when we receive focus.
|
||||||
_handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), null);
|
_handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cachedText = '';
|
|
||||||
_cachedFirstRect = null;
|
|
||||||
_cachedSize = Size.zero;
|
|
||||||
_cachedPlaceholder = -1;
|
|
||||||
} else {
|
} else {
|
||||||
WidgetsBinding.instance!.removeObserver(this);
|
WidgetsBinding.instance!.removeObserver(this);
|
||||||
setState(() { _currentPromptRectRange = null; });
|
setState(() { _currentPromptRectRange = null; });
|
||||||
|
@ -2683,78 +2663,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||||
updateKeepAlive();
|
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() {
|
void _updateSizeAndTransform() {
|
||||||
if (_hasInputConnection) {
|
if (_hasInputConnection) {
|
||||||
final Size size = renderEditable.size;
|
final Size size = renderEditable.size;
|
||||||
final Matrix4 transform = renderEditable.getTransformTo(null);
|
final Matrix4 transform = renderEditable.getTransformTo(null);
|
||||||
_textInputConnection!.setEditableSizeAndTransform(size, transform);
|
_textInputConnection!.setEditableSizeAndTransform(size, transform);
|
||||||
_updateSelectionRects();
|
|
||||||
SchedulerBinding.instance!
|
SchedulerBinding.instance!
|
||||||
.addPostFrameCallback((Duration _) => _updateSizeAndTransform());
|
.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
|
/// Returns `false` if a toolbar couldn't be shown, such as when the toolbar
|
||||||
/// is already shown, or when no text selection currently exists.
|
/// is already shown, or when no text selection currently exists.
|
||||||
@override
|
|
||||||
bool showToolbar() {
|
bool showToolbar() {
|
||||||
// Web is using native dom elements to enable clipboard functionality of the
|
// Web is using native dom elements to enable clipboard functionality of the
|
||||||
// toolbar: copy, paste, select, cut. It might also provide additional
|
// 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
|
@override
|
||||||
String get autofillId => 'EditableText-$hashCode';
|
String get autofillId => 'EditableText-$hashCode';
|
||||||
|
|
||||||
|
@ -3107,62 +2991,53 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||||
onCopy: _semanticsOnCopy(controls),
|
onCopy: _semanticsOnCopy(controls),
|
||||||
onCut: _semanticsOnCut(controls),
|
onCut: _semanticsOnCut(controls),
|
||||||
onPaste: _semanticsOnPaste(controls),
|
onPaste: _semanticsOnPaste(controls),
|
||||||
child: _ScribbleFocusable(
|
child: _Editable(
|
||||||
focusNode: widget.focusNode,
|
key: _editableKey,
|
||||||
editableKey: _editableKey,
|
startHandleLayerLink: _startHandleLayerLink,
|
||||||
enabled: widget.scribbleEnabled,
|
endHandleLayerLink: _endHandleLayerLink,
|
||||||
updateSelectionRects: () {
|
inlineSpan: buildTextSpan(),
|
||||||
_openInputConnection();
|
value: _value,
|
||||||
_updateSelectionRects(force: true);
|
cursorColor: _cursorColor,
|
||||||
},
|
backgroundCursorColor: widget.backgroundCursorColor,
|
||||||
child: _Editable(
|
showCursor: EditableText.debugDeterministicCursor
|
||||||
key: _editableKey,
|
? ValueNotifier<bool>(widget.showCursor)
|
||||||
startHandleLayerLink: _startHandleLayerLink,
|
: _cursorVisibilityNotifier,
|
||||||
endHandleLayerLink: _endHandleLayerLink,
|
forceLine: widget.forceLine,
|
||||||
inlineSpan: buildTextSpan(),
|
readOnly: widget.readOnly,
|
||||||
value: _value,
|
hasFocus: _hasFocus,
|
||||||
cursorColor: _cursorColor,
|
maxLines: widget.maxLines,
|
||||||
backgroundCursorColor: widget.backgroundCursorColor,
|
minLines: widget.minLines,
|
||||||
showCursor: EditableText.debugDeterministicCursor
|
expands: widget.expands,
|
||||||
? ValueNotifier<bool>(widget.showCursor)
|
strutStyle: widget.strutStyle,
|
||||||
: _cursorVisibilityNotifier,
|
selectionColor: widget.selectionColor,
|
||||||
forceLine: widget.forceLine,
|
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
||||||
readOnly: widget.readOnly,
|
textAlign: widget.textAlign,
|
||||||
hasFocus: _hasFocus,
|
textDirection: _textDirection,
|
||||||
maxLines: widget.maxLines,
|
locale: widget.locale,
|
||||||
minLines: widget.minLines,
|
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
|
||||||
expands: widget.expands,
|
textWidthBasis: widget.textWidthBasis,
|
||||||
strutStyle: widget.strutStyle,
|
obscuringCharacter: widget.obscuringCharacter,
|
||||||
selectionColor: widget.selectionColor,
|
obscureText: widget.obscureText,
|
||||||
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
autocorrect: widget.autocorrect,
|
||||||
textAlign: widget.textAlign,
|
smartDashesType: widget.smartDashesType,
|
||||||
textDirection: _textDirection,
|
smartQuotesType: widget.smartQuotesType,
|
||||||
locale: widget.locale,
|
enableSuggestions: widget.enableSuggestions,
|
||||||
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
|
offset: offset,
|
||||||
textWidthBasis: widget.textWidthBasis,
|
onCaretChanged: _handleCaretChanged,
|
||||||
obscuringCharacter: widget.obscuringCharacter,
|
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||||
obscureText: widget.obscureText,
|
cursorWidth: widget.cursorWidth,
|
||||||
autocorrect: widget.autocorrect,
|
cursorHeight: widget.cursorHeight,
|
||||||
smartDashesType: widget.smartDashesType,
|
cursorRadius: widget.cursorRadius,
|
||||||
smartQuotesType: widget.smartQuotesType,
|
cursorOffset: widget.cursorOffset ?? Offset.zero,
|
||||||
enableSuggestions: widget.enableSuggestions,
|
selectionHeightStyle: widget.selectionHeightStyle,
|
||||||
offset: offset,
|
selectionWidthStyle: widget.selectionWidthStyle,
|
||||||
onCaretChanged: _handleCaretChanged,
|
paintCursorAboveText: widget.paintCursorAboveText,
|
||||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
enableInteractiveSelection: widget.enableInteractiveSelection,
|
||||||
cursorWidth: widget.cursorWidth,
|
textSelectionDelegate: this,
|
||||||
cursorHeight: widget.cursorHeight,
|
devicePixelRatio: _devicePixelRatio,
|
||||||
cursorRadius: widget.cursorRadius,
|
promptRectRange: _currentPromptRectRange,
|
||||||
cursorOffset: widget.cursorOffset ?? Offset.zero,
|
promptRectColor: widget.autocorrectionTextRectColor,
|
||||||
selectionHeightStyle: widget.selectionHeightStyle,
|
clipBehavior: widget.clipBehavior,
|
||||||
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<EditableText> with AutomaticKeepAliveClien
|
||||||
}
|
}
|
||||||
return TextSpan(style: widget.style, text: text);
|
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.
|
// Read only mode should not paint text composing.
|
||||||
return widget.controller.buildTextSpan(
|
return widget.controller.buildTextSpan(
|
||||||
context: context,
|
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)
|
/// An interface for retriving the logical text boundary (left-closed-right-open)
|
||||||
/// at a given location in a document.
|
/// at a given location in a document.
|
||||||
///
|
///
|
||||||
|
|
|
@ -2368,7 +2368,7 @@ void main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderEditable renderEditable = tester.renderObject<RenderEditable>(
|
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(
|
List<TextSelectionPoint> lastCharEndpoint = renderEditable.getEndpointsForSelection(
|
||||||
|
@ -3166,7 +3166,7 @@ void main() {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
tester.renderObject<RenderEditable>(
|
tester.renderObject<RenderEditable>(
|
||||||
find.byElementPredicate((Element element) => element.renderObject is RenderEditable).last,
|
find.byElementPredicate((Element element) => element.renderObject is RenderEditable),
|
||||||
).text!.style!.color,
|
).text!.style!.color,
|
||||||
isSameColorAs(CupertinoColors.white),
|
isSameColorAs(CupertinoColors.white),
|
||||||
);
|
);
|
||||||
|
|
|
@ -9255,38 +9255,6 @@ void main() {
|
||||||
expect(right.opacity.value, equals(1.0));
|
expect(right.opacity.value, equals(1.0));
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, 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 {
|
testWidgets('Tap shows handles but not toolbar', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController(
|
final TextEditingController controller = TextEditingController(
|
||||||
text: 'abc def ghi',
|
text: 'abc def ghi',
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
@ -143,21 +141,6 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void autofill(TextEditingValue newEditingValue) => updateEditingValue(newEditingValue);
|
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 {
|
class FakeAutofillScope with AutofillScopeMixin implements AutofillScope {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:convert' show jsonDecode;
|
import 'dart:convert' show jsonDecode;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
@ -115,20 +114,5 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
|
||||||
latestMethodCall = 'showAutocorrectionPromptRect';
|
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);
|
TextInputConfiguration get configuration => const TextInputConfiguration(enableDeltaModel: true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
|
|
||||||
import 'dart:convert' show jsonDecode;
|
import 'dart:convert' show jsonDecode;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
@ -497,148 +496,6 @@ void main() {
|
||||||
|
|
||||||
expect(client.latestMethodCall, 'showAutocorrectionPromptRect');
|
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 {
|
test('TextEditingValue.isComposingRangeValid', () async {
|
||||||
|
@ -710,20 +567,5 @@ class FakeTextInputClient implements TextInputClient {
|
||||||
latestMethodCall = 'showAutocorrectionPromptRect';
|
latestMethodCall = 'showAutocorrectionPromptRect';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void showToolbar() {
|
|
||||||
latestMethodCall = 'showToolbar';
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInputConfiguration get configuration => const TextInputConfiguration();
|
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.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:convert' show utf8;
|
import 'dart:convert' show utf8;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 {
|
testWidgets('Sends "updateConfig" when read-only flag is flipped', (WidgetTester tester) async {
|
||||||
bool readOnly = true;
|
bool readOnly = true;
|
||||||
late StateSetter setState;
|
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 {
|
testWidgets('text styling info is sent on show keyboard', (WidgetTester tester) async {
|
||||||
final List<MethodCall> log = <MethodCall>[];
|
final List<MethodCall> log = <MethodCall>[];
|
||||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async {
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async {
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:ui' show Rect, Offset;
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -273,84 +271,4 @@ class TestTextInput {
|
||||||
(ByteData? data) { /* response from framework is discarded */ },
|
(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