mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit b571abfbfd
.
This commit is contained in:
parent
8a9ddade9a
commit
009fa69f3c
@ -37,7 +37,6 @@ export 'src/services/raw_keyboard_macos.dart';
|
||||
export 'src/services/raw_keyboard_web.dart';
|
||||
export 'src/services/raw_keyboard_windows.dart';
|
||||
export 'src/services/restoration.dart';
|
||||
export 'src/services/scribble.dart';
|
||||
export 'src/services/service_extensions.dart';
|
||||
export 'src/services/spell_check.dart';
|
||||
export 'src/services/system_channels.dart';
|
||||
|
@ -15,9 +15,9 @@ import 'binary_messenger.dart';
|
||||
import 'hardware_keyboard.dart';
|
||||
import 'message_codec.dart';
|
||||
import 'restoration.dart';
|
||||
import 'scribble.dart';
|
||||
import 'service_extensions.dart';
|
||||
import 'system_channels.dart';
|
||||
import 'text_input.dart';
|
||||
|
||||
export 'dart:ui' show ChannelBuffers, RootIsolateToken;
|
||||
|
||||
@ -43,7 +43,7 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
||||
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
|
||||
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
||||
SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
|
||||
Scribble.ensureInitialized();
|
||||
TextInput.ensureInitialized();
|
||||
readInitialLifecycleStateFromNativeWindow();
|
||||
}
|
||||
|
||||
@ -326,6 +326,7 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
||||
void setSystemUiChangeCallback(SystemUiChangeCallback? callback) {
|
||||
_systemUiChangeCallback = callback;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Signature for listening to changes in the [SystemUiMode].
|
||||
|
@ -1,243 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// 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/foundation.dart';
|
||||
|
||||
import 'message_codec.dart';
|
||||
import 'platform_channel.dart';
|
||||
import 'system_channels.dart';
|
||||
|
||||
/// An interface into system-level handwriting text input.
|
||||
///
|
||||
/// This is typically used by implemeting the methods in [ScribbleClient] in a
|
||||
/// class, usually a [State], and setting an instance of it to [client]. The
|
||||
/// relevant methods on [ScribbleClient] will be called in response to method
|
||||
/// channel calls on [SystemChannels.scribble].
|
||||
///
|
||||
/// Currently, handwriting input is supported in the iOS embedder with the Apple
|
||||
/// Pencil.
|
||||
///
|
||||
/// [EditableText] uses this class via [ScribbleClient] to automatically support
|
||||
/// handwriting input when [EditableText.scribbleEnabled] is set to true.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SystemChannels.scribble], which is the [MethodChannel] used by this
|
||||
/// class, and which has a list of the methods that this class handles.
|
||||
class Scribble {
|
||||
Scribble._() {
|
||||
_channel.setMethodCallHandler(_handleScribbleInvocation);
|
||||
}
|
||||
|
||||
/// Ensure that a [Scribble] instance has been set up so that the platform
|
||||
/// can handle messages on the scribble method channel.
|
||||
static void ensureInitialized() {
|
||||
_instance; // ignore: unnecessary_statements
|
||||
}
|
||||
|
||||
/// Set the [MethodChannel] used to communicate with the system's text input
|
||||
/// control.
|
||||
///
|
||||
/// This is only meant for testing within the Flutter SDK. Changing this
|
||||
/// will break the ability to do handwriting input. This has no effect if
|
||||
/// asserts are disabled.
|
||||
@visibleForTesting
|
||||
static void setChannel(MethodChannel newChannel) {
|
||||
assert(() {
|
||||
_instance._channel = newChannel..setMethodCallHandler(_instance._handleScribbleInvocation);
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
static final Scribble _instance = Scribble._();
|
||||
|
||||
/// Set the given [ScribbleClient] as the single active client.
|
||||
///
|
||||
/// This is usually based on the [ScribbleClient] receiving focus.
|
||||
static set client(ScribbleClient? client) {
|
||||
_instance._client = client;
|
||||
}
|
||||
|
||||
/// Return the current active [ScribbleClient], or null if none.
|
||||
static ScribbleClient? get client => _instance._client;
|
||||
|
||||
ScribbleClient? _client;
|
||||
|
||||
MethodChannel _channel = SystemChannels.scribble;
|
||||
|
||||
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 => Scribble._instance._scribbleClients;
|
||||
|
||||
/// Returns true if a scribble interaction is currently happening.
|
||||
static bool get scribbleInProgress => _instance._scribbleInProgress;
|
||||
|
||||
Future<dynamic> _handleScribbleInvocation(MethodCall methodCall) async {
|
||||
final String method = methodCall.method;
|
||||
if (method == 'Scribble.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 == 'Scribble.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 == 'Scribble.scribbleInteractionBegan') {
|
||||
_scribbleInProgress = true;
|
||||
return;
|
||||
} else if (method == 'Scribble.scribbleInteractionFinished') {
|
||||
_scribbleInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// The methods below are only valid when a client exists, i.e. when a field
|
||||
// is focused.
|
||||
final ScribbleClient? client = _client;
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<dynamic> args = methodCall.arguments as List<dynamic>;
|
||||
switch (method) {
|
||||
case 'Scribble.showToolbar':
|
||||
client.showToolbar();
|
||||
break;
|
||||
case 'Scribble.insertTextPlaceholder':
|
||||
client.insertTextPlaceholder(Size((args[1] as num).toDouble(), (args[2] as num).toDouble()));
|
||||
break;
|
||||
case 'Scribble.removeTextPlaceholder':
|
||||
client.removeTextPlaceholder();
|
||||
break;
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
_instance._scribbleClients[elementIdentifier] = scribbleClient;
|
||||
}
|
||||
|
||||
/// Unregisters a [ScribbleClient] with [elementIdentifier].
|
||||
static void unregisterScribbleElement(String elementIdentifier) {
|
||||
_instance._scribbleClients.remove(elementIdentifier);
|
||||
}
|
||||
|
||||
List<SelectionRect> _cachedSelectionRects = <SelectionRect>[];
|
||||
|
||||
/// 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.
|
||||
static void setSelectionRects(List<SelectionRect> selectionRects) {
|
||||
if (!listEquals(_instance._cachedSelectionRects, selectionRects)) {
|
||||
_instance._cachedSelectionRects = selectionRects;
|
||||
_instance._channel.invokeMethod<void>(
|
||||
'Scribble.setSelectionRects',
|
||||
selectionRects.map((SelectionRect rect) {
|
||||
return <num>[rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position];
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface to interact with the engine for handwriting text input.
|
||||
///
|
||||
/// This is currently only used to handle
|
||||
/// [UIIndirectScribbleInteraction](https://developer.apple.com/documentation/uikit/uiindirectscribbleinteraction),
|
||||
/// which is responsible for manually receiving handwritten text input in UIKit.
|
||||
/// The Flutter engine uses this to receive handwriting input on Flutter text
|
||||
/// input fields.
|
||||
mixin 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.
|
||||
///
|
||||
/// The [Offset] indicates the location where the focus event happened, which
|
||||
/// is typically where the cursor should be placed.
|
||||
void onScribbleFocus(Offset offset);
|
||||
|
||||
/// Tests whether the [ScribbleClient] overlaps the given rectangle bounds,
|
||||
/// where the rectangle bounds are in global coordinates.
|
||||
bool isInScribbleRect(Rect rect);
|
||||
|
||||
/// The current bounds of the [ScribbleClient].
|
||||
Rect get bounds;
|
||||
|
||||
/// Requests that the client show the editing toolbar.
|
||||
///
|
||||
/// This is used when the platform changes the selection during scribble
|
||||
/// input.
|
||||
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();
|
||||
}
|
||||
|
||||
/// 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 => Object.hash(position, bounds);
|
||||
|
||||
@override
|
||||
String toString() => 'SelectionRect($position, $bounds)';
|
||||
}
|
@ -222,38 +222,6 @@ class SystemChannels {
|
||||
JSONMethodCodec(),
|
||||
);
|
||||
|
||||
/// A JSON [MethodChannel] for handling handwriting input.
|
||||
///
|
||||
/// This method channel is used by iPadOS 14's Scribble feature where writing
|
||||
/// with an Apple Pencil on top of a text field inserts text into the field.
|
||||
///
|
||||
/// The following methods are defined for this channel:
|
||||
///
|
||||
/// * `Scribble.focusElement`: Indicates that focus is requested at the given
|
||||
/// [Offset].
|
||||
///
|
||||
/// * `Scribble.requestElementsInRect`: Returns a List of identifiers and
|
||||
/// bounds for the [ScribbleClient]s that lie within the given Rect.
|
||||
///
|
||||
/// * `Scribble.scribbleInteractionBegan`: Indicates that handwriting input
|
||||
/// has started.
|
||||
///
|
||||
/// * `Scribble.scribbleInteractionFinished`: Indicates that handwriting input
|
||||
/// has ended.
|
||||
///
|
||||
/// * `Scribble.showToolbar`: Requests that the toolbar be shown, such as
|
||||
/// when selection is changed by handwriting.
|
||||
///
|
||||
/// * `Scribble.insertTextPlaceholder`: Requests that visual writing space is
|
||||
/// reserved.
|
||||
///
|
||||
/// * `Scribble.removeTextPlaceholder`: Requests that any placeholder writing
|
||||
/// space is removed.
|
||||
static const MethodChannel scribble = OptionalMethodChannel(
|
||||
'flutter/scribble',
|
||||
JSONMethodCodec(),
|
||||
);
|
||||
|
||||
/// A [MethodChannel] for handling spell check for text input.
|
||||
///
|
||||
/// This channel exposes the spell check framework for supported platforms.
|
||||
|
@ -1162,12 +1162,84 @@ mixin TextInputClient {
|
||||
/// * [TextInputControl.show], a method to show the new input control.
|
||||
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {}
|
||||
|
||||
/// 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() {}
|
||||
|
||||
/// Performs the specified MacOS-specific selector from the
|
||||
/// `NSStandardKeyBindingResponding` protocol or user-specified selector
|
||||
/// from `DefaultKeyBinding.Dict`.
|
||||
void performSelector(String selectorName) {}
|
||||
}
|
||||
|
||||
/// 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 => Object.hash(position, bounds);
|
||||
|
||||
@override
|
||||
String toString() => 'SelectionRect($position, $bounds)';
|
||||
}
|
||||
|
||||
/// An interface to receive granular information from [TextInput].
|
||||
///
|
||||
/// See also:
|
||||
@ -1227,6 +1299,7 @@ class TextInputConnection {
|
||||
Matrix4? _cachedTransform;
|
||||
Rect? _cachedRect;
|
||||
Rect? _cachedCaretRect;
|
||||
List<SelectionRect> _cachedSelectionRects = <SelectionRect>[];
|
||||
|
||||
static int _nextId = 1;
|
||||
final int _id;
|
||||
@ -1249,6 +1322,12 @@ 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);
|
||||
@ -1329,6 +1408,17 @@ class TextInputConnection {
|
||||
TextInput._instance._setCaretRect(validRect);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Send text styling information.
|
||||
///
|
||||
/// This information is used by the Flutter Web Engine to change the style
|
||||
@ -1586,10 +1676,6 @@ class TextInput {
|
||||
|
||||
/// Ensure that a [TextInput] instance has been set up so that the platform
|
||||
/// can handle messages on the text input method channel.
|
||||
@Deprecated(
|
||||
'Use Scribble.ensureInitialized instead. '
|
||||
'This feature was deprecated after v3.1.0-9.0.pre.'
|
||||
)
|
||||
static void ensureInitialized() {
|
||||
_instance; // ignore: unnecessary_statements
|
||||
}
|
||||
@ -1652,6 +1738,16 @@ 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> _loudlyHandleTextInputInvocation(MethodCall call) async {
|
||||
try {
|
||||
return await _handleTextInputInvocation(call);
|
||||
@ -1668,8 +1764,33 @@ class TextInput {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1773,6 +1894,15 @@ 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();
|
||||
}
|
||||
@ -1856,6 +1986,12 @@ class TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
void _setSelectionRects(List<SelectionRect> selectionRects) {
|
||||
for (final TextInputControl control in _inputControls) {
|
||||
control.setSelectionRects(selectionRects);
|
||||
}
|
||||
}
|
||||
|
||||
void _setStyle({
|
||||
required String? fontFamily,
|
||||
required double? fontSize,
|
||||
@ -1955,6 +2091,20 @@ class TextInput {
|
||||
control.finishAutofillContext(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for implementing text input controls that receive text editing
|
||||
@ -2038,6 +2188,12 @@ mixin TextInputControl {
|
||||
/// changes.
|
||||
void setCaretRect(Rect rect) {}
|
||||
|
||||
/// Informs the text input control about selection area changes.
|
||||
///
|
||||
/// This method is called when the attached input client's selection area
|
||||
/// changes.
|
||||
void setSelectionRects(List<SelectionRect> selectionRects) {}
|
||||
|
||||
/// Informs the text input control about text style changes.
|
||||
///
|
||||
/// This method is called on the when the attached input client's text style
|
||||
@ -2160,6 +2316,17 @@ class _PlatformTextInputControl with TextInputControl {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void setSelectionRects(List<SelectionRect> selectionRects) {
|
||||
_channel.invokeMethod<void>(
|
||||
'TextInput.setSelectionRects',
|
||||
selectionRects.map((SelectionRect rect) {
|
||||
return <num>[rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position];
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void setStyle({
|
||||
required String? fontFamily,
|
||||
|
@ -2558,12 +2558,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,
|
||||
Scribble.scribbleInProgress
|
||||
? SelectionChangedCause.scribble
|
||||
: SelectionChangedCause.keyboard,
|
||||
);
|
||||
_handleSelectionChanged(value.selection, (_textInputConnection?.scribbleInProgress ?? false) ? SelectionChangedCause.scribble : SelectionChangedCause.keyboard);
|
||||
} else {
|
||||
// Only hide the toolbar overlay, the selection handle's visibility will be handled
|
||||
// by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673
|
||||
@ -3522,7 +3517,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
}
|
||||
graphemeStart = graphemeEnd;
|
||||
}
|
||||
Scribble.setSelectionRects(rects);
|
||||
_textInputConnection!.setSelectionRects(rects);
|
||||
}
|
||||
|
||||
void _updateSizeAndTransform() {
|
||||
@ -3533,7 +3528,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
_updateSelectionRects();
|
||||
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateSizeAndTransform());
|
||||
} else if (_placeholderLocation != -1) {
|
||||
_removeTextPlaceholder();
|
||||
removeTextPlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3626,6 +3621,7 @@ 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
|
||||
@ -3696,6 +3692,39 @@ 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
|
||||
void performSelector(String selectorName) {
|
||||
final Intent? intent = intentForMacOSSelector(selectorName);
|
||||
@ -3978,35 +4007,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
return Actions.invoke(context, intent);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
void _onPlaceholderLocationChanged(int location) {
|
||||
setState(() {
|
||||
_placeholderLocation = location;
|
||||
});
|
||||
}
|
||||
|
||||
void _onScribbleFocus(Offset offset) {
|
||||
widget.focusNode.requestFocus();
|
||||
renderEditable.selectPositionAt(from: offset, cause: SelectionChangedCause.scribble);
|
||||
_openInputConnection();
|
||||
_updateSelectionRects(force: true);
|
||||
}
|
||||
|
||||
void _removeTextPlaceholder() {
|
||||
if (!widget.scribbleEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_placeholderLocation = -1;
|
||||
});
|
||||
}
|
||||
|
||||
/// The default behavior used if [onTapOutside] is null.
|
||||
///
|
||||
@ -4121,12 +4121,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
onPaste: _semanticsOnPaste(controls),
|
||||
child: _ScribbleFocusable(
|
||||
focusNode: widget.focusNode,
|
||||
editableKey: _editableKey,
|
||||
enabled: widget.scribbleEnabled,
|
||||
onPlaceholderLocationChanged: _onPlaceholderLocationChanged,
|
||||
onScribbleFocus: _onScribbleFocus,
|
||||
onShowToolbar: showToolbar,
|
||||
readOnly: widget.readOnly,
|
||||
value: _value,
|
||||
updateSelectionRects: () {
|
||||
_openInputConnection();
|
||||
_updateSelectionRects(force: true);
|
||||
},
|
||||
child: _Editable(
|
||||
key: _editableKey,
|
||||
startHandleLayerLink: _startHandleLayerLink,
|
||||
@ -4441,13 +4441,6 @@ class _Editable extends MultiChildRenderObjectWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that that takes a placeholder location as an int offset into some
|
||||
/// text.
|
||||
typedef _PlaceholderLocationCallback = void Function(int location);
|
||||
|
||||
/// A function that takes the Offset at which focus is requested.
|
||||
typedef _ScribbleFocusCallback = void Function(Offset offset);
|
||||
|
||||
@immutable
|
||||
class _ScribbleCacheKey {
|
||||
const _ScribbleCacheKey({
|
||||
@ -4488,88 +4481,55 @@ class _ScribbleCacheKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that provides the ability to receive handwriting input from
|
||||
/// [Scribble].
|
||||
class _ScribbleFocusable extends StatefulWidget {
|
||||
const _ScribbleFocusable({
|
||||
required this.child,
|
||||
required this.enabled,
|
||||
required this.focusNode,
|
||||
required this.onPlaceholderLocationChanged,
|
||||
required this.onScribbleFocus,
|
||||
required this.onShowToolbar,
|
||||
required this.readOnly,
|
||||
required this.value,
|
||||
required this.editableKey,
|
||||
required this.updateSelectionRects,
|
||||
required this.enabled,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final bool enabled;
|
||||
final FocusNode focusNode;
|
||||
final _PlaceholderLocationCallback onPlaceholderLocationChanged;
|
||||
final _ScribbleFocusCallback onScribbleFocus;
|
||||
final VoidCallback onShowToolbar;
|
||||
final bool readOnly;
|
||||
final TextEditingValue value;
|
||||
final GlobalKey editableKey;
|
||||
final VoidCallback updateSelectionRects;
|
||||
final bool enabled;
|
||||
|
||||
@override
|
||||
_ScribbleFocusableState createState() => _ScribbleFocusableState();
|
||||
}
|
||||
|
||||
class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleClient {
|
||||
class _ScribbleFocusableState extends State<_ScribbleFocusable> implements ScribbleClient {
|
||||
_ScribbleFocusableState(): _elementIdentifier = (_nextElementIdentifier++).toString();
|
||||
|
||||
void _onFocusChange() {
|
||||
_updateClient(widget.focusNode.hasFocus);
|
||||
}
|
||||
|
||||
void _updateClient(bool hasFocus) {
|
||||
if (hasFocus) {
|
||||
if (Scribble.client != this) {
|
||||
Scribble.client = this;
|
||||
}
|
||||
} else if (Scribble.client == this) {
|
||||
Scribble.client = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateClient(widget.focusNode.hasFocus);
|
||||
widget.focusNode.addListener(_onFocusChange);
|
||||
if (widget.enabled) {
|
||||
Scribble.registerScribbleElement(elementIdentifier, this);
|
||||
TextInput.registerScribbleElement(elementIdentifier, this);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_ScribbleFocusable oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.focusNode != widget.focusNode) {
|
||||
oldWidget.focusNode.removeListener(_onFocusChange);
|
||||
widget.focusNode.addListener(_onFocusChange);
|
||||
_updateClient(widget.focusNode.hasFocus);
|
||||
}
|
||||
if (!oldWidget.enabled && widget.enabled) {
|
||||
Scribble.registerScribbleElement(elementIdentifier, this);
|
||||
TextInput.registerScribbleElement(elementIdentifier, this);
|
||||
}
|
||||
|
||||
if (oldWidget.enabled && !widget.enabled) {
|
||||
Scribble.unregisterScribbleElement(elementIdentifier);
|
||||
TextInput.unregisterScribbleElement(elementIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
Scribble.unregisterScribbleElement(elementIdentifier);
|
||||
widget.focusNode.removeListener(_onFocusChange);
|
||||
if (Scribble.client == this) {
|
||||
Scribble.client = null;
|
||||
}
|
||||
TextInput.unregisterScribbleElement(elementIdentifier);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Start ScribbleClient.
|
||||
RenderEditable? get renderEditable => widget.editableKey.currentContext?.findRenderObject() as RenderEditable?;
|
||||
|
||||
static int _nextElementIdentifier = 1;
|
||||
final String _elementIdentifier;
|
||||
@ -4579,38 +4539,15 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
|
||||
|
||||
@override
|
||||
void onScribbleFocus(Offset offset) {
|
||||
return widget.onScribbleFocus(offset);
|
||||
}
|
||||
|
||||
@override
|
||||
void insertTextPlaceholder(Size size) {
|
||||
if (!widget.enabled || !widget.value.selection.isValid || widget.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.onPlaceholderLocationChanged(
|
||||
widget.value.text.length - widget.value.selection.end,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeTextPlaceholder() {
|
||||
if (!widget.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.onPlaceholderLocationChanged(-1);
|
||||
}
|
||||
|
||||
@override
|
||||
void showToolbar() {
|
||||
widget.onShowToolbar();
|
||||
widget.focusNode.requestFocus();
|
||||
renderEditable?.selectPositionAt(from: offset, cause: SelectionChangedCause.scribble);
|
||||
widget.updateSelectionRects();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isInScribbleRect(Rect rect) {
|
||||
final Rect calculatedBounds = bounds;
|
||||
if (widget.readOnly) {
|
||||
if (renderEditable?.readOnly ?? false) {
|
||||
return false;
|
||||
}
|
||||
if (calculatedBounds == Rect.zero) {
|
||||
@ -4622,8 +4559,7 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
|
||||
final Rect intersection = calculatedBounds.intersect(rect);
|
||||
final HitTestResult result = HitTestResult();
|
||||
WidgetsBinding.instance.hitTest(result, intersection.center);
|
||||
final RenderObject? renderObject = context.findRenderObject();
|
||||
return result.path.any((HitTestEntry entry) => entry.target == renderObject);
|
||||
return result.path.any((HitTestEntry entry) => entry.target == renderEditable);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -4636,8 +4572,6 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
|
||||
return MatrixUtils.transformRect(transform, Rect.fromLTWH(0, 0, box.size.width, box.size.height));
|
||||
}
|
||||
|
||||
// End ScribbleClient.
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
|
@ -147,6 +147,21 @@ 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';
|
||||
}
|
||||
|
||||
@override
|
||||
void performSelector(String selectorName) {
|
||||
latestMethodCall = 'performSelector';
|
||||
|
@ -106,4 +106,11 @@ void main() {
|
||||
await rootBundle.loadString('test_asset2');
|
||||
expect(flutterAssetsCallCount, 4);
|
||||
});
|
||||
|
||||
test('initInstances sets a default method call handler for SystemChannels.textInput', () async {
|
||||
final ByteData message = const JSONMessageCodec().encodeMessage(<String, dynamic>{'method': 'TextInput.requestElementsInRect', 'args': null})!;
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/textinput', message, (ByteData? data) {
|
||||
expect(data, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -271,6 +271,21 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
|
||||
latestMethodCall = 'showAutocorrectionPromptRect';
|
||||
}
|
||||
|
||||
@override
|
||||
void insertTextPlaceholder(Size size) {
|
||||
latestMethodCall = 'insertTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void removeTextPlaceholder() {
|
||||
latestMethodCall = 'removeTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void showToolbar() {
|
||||
latestMethodCall = 'showToolbar';
|
||||
}
|
||||
|
||||
@override
|
||||
void performSelector(String selectorName) {
|
||||
latestMethodCall = 'performSelector';
|
||||
|
@ -1,213 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'text_input_utils.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('ScribbleClient showToolbar method is called', () async {
|
||||
final FakeScribbleElement targetElement = FakeScribbleElement(elementIdentifier: 'target');
|
||||
Scribble.client = targetElement;
|
||||
|
||||
expect(targetElement.latestMethodCall, isEmpty);
|
||||
|
||||
// Send showToolbar message.
|
||||
final ByteData? messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[1, 0, 1],
|
||||
'method': 'Scribble.showToolbar',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
expect(targetElement.latestMethodCall, 'showToolbar');
|
||||
});
|
||||
|
||||
test('ScribbleClient removeTextPlaceholder method is called', () async {
|
||||
final FakeScribbleElement targetElement = FakeScribbleElement(elementIdentifier: 'target');
|
||||
Scribble.client = targetElement;
|
||||
|
||||
expect(targetElement.latestMethodCall, isEmpty);
|
||||
|
||||
// Send removeTextPlaceholder message.
|
||||
final ByteData? messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[1, 0, 1],
|
||||
'method': 'Scribble.removeTextPlaceholder',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
expect(targetElement.latestMethodCall, 'removeTextPlaceholder');
|
||||
});
|
||||
|
||||
test('ScribbleClient insertTextPlaceholder method is called', () async {
|
||||
final FakeScribbleElement targetElement = FakeScribbleElement(elementIdentifier: 'target');
|
||||
Scribble.client = targetElement;
|
||||
|
||||
expect(targetElement.latestMethodCall, isEmpty);
|
||||
|
||||
// Send insertTextPlaceholder message.
|
||||
final ByteData? messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[1, 0, 1],
|
||||
'method': 'Scribble.insertTextPlaceholder',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
expect(targetElement.latestMethodCall, 'insertTextPlaceholder');
|
||||
});
|
||||
|
||||
test('ScribbleClient scribbleInteractionBegan and scribbleInteractionFinished', () async {
|
||||
Scribble.ensureInitialized();
|
||||
|
||||
expect(Scribble.scribbleInProgress, isFalse);
|
||||
|
||||
// Send scribbleInteractionBegan message.
|
||||
ByteData? messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[1, 0, 1],
|
||||
'method': 'Scribble.scribbleInteractionBegan',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
expect(Scribble.scribbleInProgress, isTrue);
|
||||
|
||||
// Send scribbleInteractionFinished message.
|
||||
messageBytes =
|
||||
const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
||||
'args': <dynamic>[1, 0, 1],
|
||||
'method': 'Scribble.scribbleInteractionFinished',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
expect(Scribble.scribbleInProgress, isFalse);
|
||||
});
|
||||
|
||||
test('ScribbleClient focusElement', () async {
|
||||
final FakeScribbleElement targetElement = FakeScribbleElement(elementIdentifier: 'target');
|
||||
Scribble.registerScribbleElement(targetElement.elementIdentifier, targetElement);
|
||||
final FakeScribbleElement otherElement = FakeScribbleElement(elementIdentifier: 'other');
|
||||
Scribble.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': 'Scribble.focusElement',
|
||||
});
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
messageBytes,
|
||||
(ByteData? _) {},
|
||||
);
|
||||
|
||||
Scribble.unregisterScribbleElement(targetElement.elementIdentifier);
|
||||
Scribble.unregisterScribbleElement(otherElement.elementIdentifier);
|
||||
|
||||
expect(targetElement.latestMethodCall, 'onScribbleFocus');
|
||||
expect(otherElement.latestMethodCall, isEmpty);
|
||||
});
|
||||
|
||||
test('ScribbleClient requestElementsInRect', () async {
|
||||
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) => Scribble.registerScribbleElement(element.elementIdentifier, element);
|
||||
void unregisterElements(FakeScribbleElement element) => Scribble.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': 'Scribble.requestElementsInRect',
|
||||
});
|
||||
ByteData? responseBytes;
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
'flutter/scribble',
|
||||
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]));
|
||||
});
|
||||
}
|
||||
|
||||
class FakeScribbleClient implements ScribbleClient {
|
||||
FakeScribbleClient();
|
||||
|
||||
String latestMethodCall = '';
|
||||
|
||||
@override
|
||||
String get elementIdentifier => '';
|
||||
|
||||
@override
|
||||
void onScribbleFocus(Offset offset) {
|
||||
latestMethodCall = 'onScribbleFocus';
|
||||
}
|
||||
|
||||
@override
|
||||
bool isInScribbleRect(Rect rect) {
|
||||
latestMethodCall = 'isInScribbleRect';
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Rect get bounds => Rect.zero;
|
||||
|
||||
@override
|
||||
void showToolbar() {
|
||||
latestMethodCall = 'showToolbar';
|
||||
}
|
||||
|
||||
@override
|
||||
void insertTextPlaceholder(Size size) {
|
||||
latestMethodCall = 'insertTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void removeTextPlaceholder() {
|
||||
latestMethodCall = 'removeTextPlaceholder';
|
||||
}
|
||||
}
|
@ -610,6 +610,148 @@ 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 {
|
||||
@ -764,6 +906,12 @@ void main() {
|
||||
expect(fakeTextChannel.outgoingCalls.length, 6);
|
||||
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setEditableSizeAndTransform');
|
||||
|
||||
connection.setSelectionRects(const <SelectionRect>[SelectionRect(position: 0, bounds: Rect.zero)]);
|
||||
expectedMethodCalls.add('setSelectionRects');
|
||||
expect(control.methodCalls, expectedMethodCalls);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 7);
|
||||
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setSelectionRects');
|
||||
|
||||
connection.setStyle(
|
||||
fontFamily: null,
|
||||
fontSize: null,
|
||||
@ -773,20 +921,20 @@ void main() {
|
||||
);
|
||||
expectedMethodCalls.add('setStyle');
|
||||
expect(control.methodCalls, expectedMethodCalls);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 7);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 8);
|
||||
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setStyle');
|
||||
|
||||
connection.close();
|
||||
expectedMethodCalls.add('detach');
|
||||
expect(control.methodCalls, expectedMethodCalls);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 8);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 9);
|
||||
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.clearClient');
|
||||
|
||||
expectedMethodCalls.add('hide');
|
||||
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
|
||||
await binding.runAsync(() async {});
|
||||
await expectLater(control.methodCalls, expectedMethodCalls);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 9);
|
||||
expect(fakeTextChannel.outgoingCalls.length, 10);
|
||||
expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.hide');
|
||||
});
|
||||
|
||||
@ -850,6 +998,11 @@ class FakeTextInputClient with TextInputClient {
|
||||
latestMethodCall = 'showAutocorrectionPromptRect';
|
||||
}
|
||||
|
||||
@override
|
||||
void showToolbar() {
|
||||
latestMethodCall = 'showToolbar';
|
||||
}
|
||||
|
||||
TextInputConfiguration get configuration => const TextInputConfiguration();
|
||||
|
||||
@override
|
||||
@ -857,6 +1010,16 @@ class FakeTextInputClient with TextInputClient {
|
||||
latestMethodCall = 'didChangeInputControl';
|
||||
}
|
||||
|
||||
@override
|
||||
void insertTextPlaceholder(Size size) {
|
||||
latestMethodCall = 'insertTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void removeTextPlaceholder() {
|
||||
latestMethodCall = 'removeTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void performSelector(String selectorName) {
|
||||
latestMethodCall = 'performSelector';
|
||||
@ -915,6 +1078,11 @@ class FakeTextInputControl with TextInputControl {
|
||||
methodCalls.add('setEditableSizeAndTransform');
|
||||
}
|
||||
|
||||
@override
|
||||
void setSelectionRects(List<SelectionRect> selectionRects) {
|
||||
methodCalls.add('setSelectionRects');
|
||||
}
|
||||
|
||||
@override
|
||||
void setStyle({
|
||||
required String? fontFamily,
|
||||
|
@ -65,7 +65,7 @@ class FakeTextChannel implements MethodChannel {
|
||||
}
|
||||
}
|
||||
|
||||
class FakeScribbleElement with ScribbleClient {
|
||||
class FakeScribbleElement implements ScribbleClient {
|
||||
FakeScribbleElement({required String elementIdentifier, Rect bounds = Rect.zero})
|
||||
: _elementIdentifier = elementIdentifier,
|
||||
_bounds = bounds;
|
||||
@ -89,19 +89,4 @@ class FakeScribbleElement with ScribbleClient {
|
||||
void onScribbleFocus(Offset offset) {
|
||||
latestMethodCall = 'onScribbleFocus';
|
||||
}
|
||||
|
||||
@override
|
||||
void insertTextPlaceholder(Size size) {
|
||||
latestMethodCall = 'insertTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void removeTextPlaceholder() {
|
||||
latestMethodCall = 'removeTextPlaceholder';
|
||||
}
|
||||
|
||||
@override
|
||||
void showToolbar() {
|
||||
latestMethodCall = 'showToolbar';
|
||||
}
|
||||
}
|
||||
|
@ -2210,7 +2210,6 @@ void main() {
|
||||
final TextEditingController controller =
|
||||
TextEditingController(text: 'Lorem ipsum dolor sit amet');
|
||||
late SelectionChangedCause selectionCause;
|
||||
Scribble.ensureInitialized();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@ -2230,7 +2229,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await tester.testTextInput.scribbleFocusElement(Scribble.scribbleClients.keys.first, Offset.zero);
|
||||
await tester.testTextInput.scribbleFocusElement(TextInput.scribbleClients.keys.first, Offset.zero);
|
||||
|
||||
expect(focusNode.hasFocus, true);
|
||||
expect(selectionCause, SelectionChangedCause.scribble);
|
||||
@ -2256,7 +2255,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final List<dynamic> elementEntry = <dynamic>[Scribble.scribbleClients.keys.first, 0.0, 0.0, 800.0, 600.0];
|
||||
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));
|
||||
@ -4630,8 +4629,8 @@ void main() {
|
||||
tester.binding.window.physicalSizeTestValue = const Size(750.0, 1334.0);
|
||||
|
||||
final List<List<SelectionRect>> log = <List<SelectionRect>>[];
|
||||
SystemChannels.scribble.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
if (methodCall.method == 'Scribble.setSelectionRects') {
|
||||
SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
if (methodCall.method == 'TextInput.setSelectionRects') {
|
||||
final List<dynamic> args = methodCall.arguments as List<dynamic>;
|
||||
final List<SelectionRect> selectionRects = <SelectionRect>[];
|
||||
for (final dynamic rect in args) {
|
||||
@ -4802,76 +4801,6 @@ void main() {
|
||||
// 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('scribble client is set based on most recent focus', (WidgetTester tester) async {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
});
|
||||
|
||||
final TextEditingController controller = TextEditingController();
|
||||
controller.text = 'Text1';
|
||||
|
||||
final GlobalKey key1 = GlobalKey();
|
||||
final GlobalKey key2 = GlobalKey();
|
||||
|
||||
final FocusNode focusNode1 = FocusNode();
|
||||
final FocusNode focusNode2 = FocusNode();
|
||||
|
||||
Scribble.client = null;
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
EditableText(
|
||||
key: key1,
|
||||
controller: TextEditingController(),
|
||||
focusNode: focusNode1,
|
||||
style: Typography.material2018().black.subtitle1!,
|
||||
cursorColor: Colors.blue,
|
||||
backgroundCursorColor: Colors.grey,
|
||||
scribbleEnabled: false,
|
||||
),
|
||||
EditableText(
|
||||
key: key2,
|
||||
controller: TextEditingController(),
|
||||
focusNode: focusNode2,
|
||||
style: Typography.material2018().black.subtitle1!,
|
||||
cursorColor: Colors.blue,
|
||||
backgroundCursorColor: Colors.grey,
|
||||
scribbleEnabled: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(Scribble.client, isNull);
|
||||
|
||||
focusNode1.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(Scribble.client, isNotNull);
|
||||
final ScribbleClient client1 = Scribble.client!;
|
||||
|
||||
focusNode2.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(Scribble.client, isNot(client1));
|
||||
expect(Scribble.client, isNotNull);
|
||||
|
||||
focusNode2.unfocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(Scribble.client, isNull);
|
||||
|
||||
// On web, we should rely on the browser's implementation of Scribble.
|
||||
}, skip: kIsWeb); // [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 {
|
||||
|
@ -285,10 +285,10 @@ class TestTextInput {
|
||||
Future<void> startScribbleInteraction() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.scribble.name,
|
||||
SystemChannels.scribble.codec.encodeMethodCall(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'Scribble.scribbleInteractionBegan',
|
||||
'TextInputClient.scribbleInteractionBegan',
|
||||
<dynamic>[_client ?? -1,]
|
||||
),
|
||||
),
|
||||
@ -300,10 +300,10 @@ class TestTextInput {
|
||||
Future<void> scribbleFocusElement(String elementIdentifier, Offset offset) async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.scribble.name,
|
||||
SystemChannels.scribble.codec.encodeMethodCall(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'Scribble.focusElement',
|
||||
'TextInputClient.focusElement',
|
||||
<dynamic>[elementIdentifier, offset.dx, offset.dy]
|
||||
),
|
||||
),
|
||||
@ -316,15 +316,15 @@ class TestTextInput {
|
||||
assert(isRegistered);
|
||||
List<List<dynamic>> response = <List<dynamic>>[];
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.scribble.name,
|
||||
SystemChannels.scribble.codec.encodeMethodCall(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'Scribble.requestElementsInRect',
|
||||
'TextInputClient.requestElementsInRect',
|
||||
<dynamic>[rect.left, rect.top, rect.width, rect.height]
|
||||
),
|
||||
),
|
||||
(ByteData? data) {
|
||||
response = (SystemChannels.scribble.codec.decodeEnvelope(data!) as List<dynamic>).map((dynamic element) => element as List<dynamic>).toList();
|
||||
response = (SystemChannels.textInput.codec.decodeEnvelope(data!) as List<dynamic>).map((dynamic element) => element as List<dynamic>).toList();
|
||||
},
|
||||
);
|
||||
|
||||
@ -335,10 +335,10 @@ class TestTextInput {
|
||||
Future<void> scribbleInsertPlaceholder() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.scribble.name,
|
||||
SystemChannels.scribble.codec.encodeMethodCall(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'Scribble.insertTextPlaceholder',
|
||||
'TextInputClient.insertTextPlaceholder',
|
||||
<dynamic>[_client ?? -1, 0.0, 0.0]
|
||||
),
|
||||
),
|
||||
@ -350,10 +350,10 @@ class TestTextInput {
|
||||
Future<void> scribbleRemovePlaceholder() async {
|
||||
assert(isRegistered);
|
||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.scribble.name,
|
||||
SystemChannels.scribble.codec.encodeMethodCall(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall(
|
||||
'Scribble.removeTextPlaceholder',
|
||||
'TextInputClient.removeTextPlaceholder',
|
||||
<dynamic>[_client ?? -1]
|
||||
),
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user