Unverified Commit 61299b16 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Revert "[text_input] prepare for custom text input sources (#72803)" (#74349)

parent def31b42
...@@ -860,22 +860,21 @@ abstract class TextInputClient { ...@@ -860,22 +860,21 @@ abstract class TextInputClient {
/// An interface for interacting with a text input control. /// An interface for interacting with a text input control.
/// ///
/// [TextInputConnection] communicates with the platform text input plugin
/// over the [SystemChannels.textInput] method channel. See [SystemChannels.textInput]
/// for more details about the method channel messages.
///
/// See also: /// See also:
/// ///
/// * [TextInput.attach], a method used to establish a [TextInputConnection] /// * [TextInput.attach], a method used to establish a [TextInputConnection]
/// between the system's text input and a [TextInputClient]. /// between the system's text input and a [TextInputClient].
/// * [EditableText], a [TextInputClient] that connects to and interacts with /// * [EditableText], a [TextInputClient] that connects to and interacts with
/// the system's text input using a [TextInputConnection]. /// the system's text input using a [TextInputConnection].
abstract class TextInputConnection { class TextInputConnection {
/// Creates a connection for a [TextInputClient]. TextInputConnection._(this._client)
TextInputConnection(this._client)
: assert(_client != null), : assert(_client != null),
_id = _nextId++; _id = _nextId++;
Size? _cachedSize;
Matrix4? _cachedTransform;
Rect? _cachedRect;
static int _nextId = 1; static int _nextId = 1;
final int _id; final int _id;
...@@ -898,18 +897,10 @@ abstract class TextInputConnection { ...@@ -898,18 +897,10 @@ abstract class TextInputConnection {
bool get attached => TextInput._instance._currentConnection == this; bool get attached => TextInput._instance._currentConnection == this;
/// Requests that the text input control become visible. /// Requests that the text input control become visible.
void show(); void show() {
assert(attached);
/// Requests that the text input control is hidden. TextInput._instance._show();
/// }
/// This method is called by the framework when the text input control should
/// hide.
///
/// See also:
///
/// * [TextInput.detach], a method to stop interacting with the text
/// input control.
void hide();
/// Requests the system autofill UI to appear. /// Requests the system autofill UI to appear.
/// ///
...@@ -919,23 +910,24 @@ abstract class TextInputConnection { ...@@ -919,23 +910,24 @@ abstract class TextInputConnection {
/// See also: /// See also:
/// ///
/// * [EditableText], a [TextInputClient] that calls this method when focused. /// * [EditableText], a [TextInputClient] that calls this method when focused.
void requestAutofill(); void requestAutofill() {
assert(attached);
/// This method actually notifies the embedding of the client. It is utilized TextInput._instance._requestAutofill();
/// by [TextInput.attach] and for the `TextInputClient.requestExistingInputState` }
/// method.
void setClient(TextInputConfiguration configuration);
/// Clears the embedding of the client.
void clearClient();
/// Requests that the text input control update itself according to the new /// Requests that the text input control update itself according to the new
/// [TextInputConfiguration]. /// [TextInputConfiguration].
void updateConfig(TextInputConfiguration configuration); void updateConfig(TextInputConfiguration configuration) {
assert(attached);
TextInput._instance._updateConfig(configuration);
}
/// Requests that the text input control change its internal state to match /// Requests that the text input control change its internal state to match
/// the given state. /// the given state.
void setEditingState(TextEditingValue value); void setEditingState(TextEditingValue value) {
assert(attached);
TextInput._instance._setEditingState(value);
}
/// Send the size and transform of the editable text to engine. /// Send the size and transform of the editable text to engine.
/// ///
...@@ -946,126 +938,11 @@ abstract class TextInputConnection { ...@@ -946,126 +938,11 @@ abstract class TextInputConnection {
/// ///
/// 2. [transform]: a matrix that maps the local paint coordinate system /// 2. [transform]: a matrix that maps the local paint coordinate system
/// to the [PipelineOwner.rootNode]. /// to the [PipelineOwner.rootNode].
void setEditableSizeAndTransform(Size editableBoxSize, Matrix4 transform);
/// Send the smallest rect that covers the text in the client that's currently
/// being composed.
///
/// The given `rect` can not be null. If any of the 4 coordinates of the given
/// [Rect] is not finite, a [Rect] of size (-1, -1) will be sent instead.
///
/// The information is currently only used on iOS, for positioning the IME bar.
void setComposingRect(Rect rect);
/// Send text styling information.
///
/// This information is used by the Flutter Web Engine to change the style
/// of the hidden native input's content. Hence, the content size will match
/// to the size of the editable widget's content.
void setStyle({
required String? fontFamily,
required double? fontSize,
required FontWeight? fontWeight,
required TextDirection textDirection,
required TextAlign textAlign,
});
/// Stop interacting with the text input control.
///
/// After calling this method, the text input control might disappear if no
/// other client attaches to it within this animation frame.
@Deprecated(
'Use TextInput.detach instead. '
'This feature was deprecated after v1.26.0-1.0.pre.'
)
void close() {
TextInput.detach(_client);
assert(!attached);
}
/// Platform sent a notification informing the connection is closed.
///
/// [TextInputConnection] should clean current client connection.
@Deprecated(
'Use TextInput.reset instead. '
'This feature was deprecated after v1.26.0-1.0.pre.'
)
void connectionClosedReceived() {
TextInput.reset();
assert(!attached);
}
}
// A MethodChannel-based TextInputConnection implementation.
class _TextInputChannelConnection extends TextInputConnection {
_TextInputChannelConnection(TextInputClient client, this._channel)
: super(client);
Size? _cachedSize;
Matrix4? _cachedTransform;
Rect? _cachedRect;
final MethodChannel _channel;
@override
void show() {
assert(attached);
_channel.invokeMethod<void>('TextInput.show');
}
@override
void hide() {
_channel.invokeMethod<void>('TextInput.hide');
}
@override
void requestAutofill() {
assert(attached);
_channel.invokeMethod<void>('TextInput.requestAutofill');
}
@override
void setClient(TextInputConfiguration configuration) {
assert(_client != null);
assert(configuration != null);
_channel.invokeMethod<void>(
'TextInput.setClient',
<dynamic>[_id, configuration.toJson()],
);
}
@override
void clearClient() {
_channel.invokeMethod<void>('TextInput.clearClient');
}
@override
void updateConfig(TextInputConfiguration configuration) {
assert(attached);
assert(configuration != null);
_channel.invokeMethod<void>(
'TextInput.updateConfig',
configuration.toJson(),
);
}
@override
void setEditingState(TextEditingValue value) {
assert(attached);
assert(value != null);
_channel.invokeMethod<void>(
'TextInput.setEditingState',
value.toJSON(),
);
}
@override
void setEditableSizeAndTransform(Size editableBoxSize, Matrix4 transform) { void setEditableSizeAndTransform(Size editableBoxSize, Matrix4 transform) {
if (editableBoxSize != _cachedSize || transform != _cachedTransform) { if (editableBoxSize != _cachedSize || transform != _cachedTransform) {
_cachedSize = editableBoxSize; _cachedSize = editableBoxSize;
_cachedTransform = transform; _cachedTransform = transform;
_channel.invokeMethod<void>( TextInput._instance._setEditableSizeAndTransform(
'TextInput.setEditableSizeAndTransform',
<String, dynamic>{ <String, dynamic>{
'width': editableBoxSize.width, 'width': editableBoxSize.width,
'height': editableBoxSize.height, 'height': editableBoxSize.height,
...@@ -1075,15 +952,20 @@ class _TextInputChannelConnection extends TextInputConnection { ...@@ -1075,15 +952,20 @@ class _TextInputChannelConnection extends TextInputConnection {
} }
} }
@override /// Send the smallest rect that covers the text in the client that's currently
/// being composed.
///
/// The given `rect` can not be null. If any of the 4 coordinates of the given
/// [Rect] is not finite, a [Rect] of size (-1, -1) will be sent instead.
///
/// The information is currently only used on iOS, for positioning the IME bar.
void setComposingRect(Rect rect) { void setComposingRect(Rect rect) {
assert(rect != null); assert(rect != null);
if (rect == _cachedRect) if (rect == _cachedRect)
return; return;
_cachedRect = rect; _cachedRect = rect;
final Rect validRect = rect.isFinite ? rect : Offset.zero & const Size(-1, -1); final Rect validRect = rect.isFinite ? rect : Offset.zero & const Size(-1, -1);
_channel.invokeMethod<void>( TextInput._instance._setComposingTextRect(
'TextInput.setMarkedTextRect',
<String, dynamic>{ <String, dynamic>{
'width': validRect.width, 'width': validRect.width,
'height': validRect.height, 'height': validRect.height,
...@@ -1093,7 +975,11 @@ class _TextInputChannelConnection extends TextInputConnection { ...@@ -1093,7 +975,11 @@ class _TextInputChannelConnection extends TextInputConnection {
); );
} }
@override /// Send text styling information.
///
/// This information is used by the Flutter Web Engine to change the style
/// of the hidden native input's content. Hence, the content size will match
/// to the size of the editable widget's content.
void setStyle({ void setStyle({
required String? fontFamily, required String? fontFamily,
required double? fontSize, required double? fontSize,
...@@ -1102,8 +988,8 @@ class _TextInputChannelConnection extends TextInputConnection { ...@@ -1102,8 +988,8 @@ class _TextInputChannelConnection extends TextInputConnection {
required TextAlign textAlign, required TextAlign textAlign,
}) { }) {
assert(attached); assert(attached);
_channel.invokeMethod<void>(
'TextInput.setStyle', TextInput._instance._setStyle(
<String, dynamic>{ <String, dynamic>{
'fontFamily': fontFamily, 'fontFamily': fontFamily,
'fontSize': fontSize, 'fontSize': fontSize,
...@@ -1114,68 +1000,23 @@ class _TextInputChannelConnection extends TextInputConnection { ...@@ -1114,68 +1000,23 @@ class _TextInputChannelConnection extends TextInputConnection {
); );
} }
Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async { /// Stop interacting with the text input control.
final String method = methodCall.method; ///
/// After calling this method, the text input control might disappear if no
// The requestExistingInputState request needs to be handled regardless of /// other client attaches to it within this animation frame.
// the client ID, as long as we have a _currentConnection. void close() {
if (method == 'TextInputClient.requestExistingInputState') { if (attached) {
assert(_client != null); TextInput._instance._clearClient();
setClient(TextInput._instance._currentConfiguration);
final TextEditingValue? editingValue = _client.currentTextEditingValue;
if (editingValue != null) {
setEditingState(editingValue);
}
return;
}
final List<dynamic> args = methodCall.arguments as List<dynamic>;
if (method == 'TextInputClient.updateEditingStateWithTag') {
assert(_client != null);
final AutofillScope? scope = _client.currentAutofillScope;
final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
for (final String tag in editingValue.keys) {
final TextEditingValue textEditingValue = TextEditingValue.fromJSON(
editingValue[tag] as Map<String, dynamic>,
);
scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue);
} }
assert(!attached);
return;
} }
final int clientId = args[0] as int; /// Platform sent a notification informing the connection is closed.
// The incoming message was for a different client. ///
if (clientId != _id) /// [TextInputConnection] should clean current client connection.
return; void connectionClosedReceived() {
switch (method) { TextInput._instance._currentConnection = null;
case 'TextInputClient.updateEditingState': assert(!attached);
_client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>));
break;
case 'TextInputClient.performAction':
_client.performAction(_toTextInputAction(args[1] as String));
break;
case 'TextInputClient.performPrivateCommand':
_client.performPrivateCommand(args[1]['action'] as String,
args[1]['data'] as Map<String, dynamic>);
break;
case 'TextInputClient.updateFloatingCursor':
_client.updateFloatingCursor(_toTextPoint(
_toTextCursorAction(args[1] as String),
args[2] as Map<String, dynamic>,
));
break;
case 'TextInputClient.onConnectionClosed':
_client.connectionClosed();
TextInput.reset();
break;
case 'TextInputClient.showAutocorrectionPromptRect':
_client.showAutocorrectionPromptRect(args[1] as int, args[2] as int);
break;
default:
throw MissingPluginException();
}
} }
} }
...@@ -1288,7 +1129,8 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d ...@@ -1288,7 +1129,8 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d
/// wants to take user input from the keyboard. /// wants to take user input from the keyboard.
class TextInput { class TextInput {
TextInput._() { TextInput._() {
_currentSource.init(); _channel = SystemChannels.textInput;
_channel.setMethodCallHandler(_handleTextInputInvocation);
} }
/// Set the [MethodChannel] used to communicate with the system's text input /// Set the [MethodChannel] used to communicate with the system's text input
...@@ -1300,7 +1142,7 @@ class TextInput { ...@@ -1300,7 +1142,7 @@ class TextInput {
@visibleForTesting @visibleForTesting
static void setChannel(MethodChannel newChannel) { static void setChannel(MethodChannel newChannel) {
assert(() { assert(() {
_TextInputSource.setChannel(newChannel); _instance._channel = newChannel..setMethodCallHandler(_instance._handleTextInputInvocation);
return true; return true;
}()); }());
} }
...@@ -1341,23 +1183,28 @@ class TextInput { ...@@ -1341,23 +1183,28 @@ class TextInput {
/// the text input control. /// the text input control.
/// ///
/// A client that no longer wishes to interact with the text input control /// A client that no longer wishes to interact with the text input control
/// should call [TextInput.detach]. /// should call [TextInputConnection.close] on the returned
/// [TextInputConnection].
static TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) { static TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) {
assert(client != null); assert(client != null);
assert(configuration != null); assert(configuration != null);
_instance._detach(); final TextInputConnection connection = TextInputConnection._(client);
final TextInputConnection connection = _instance._currentSource.attach(client);
_instance._attach(connection, configuration); _instance._attach(connection, configuration);
return connection; return connection;
} }
// This method actually notifies the embedding of the client. /// This method actually notifies the embedding of the client. It is utilized
/// by [attach] and by [_handleTextInputInvocation] for the
/// `TextInputClient.requestExistingInputState` method.
void _attach(TextInputConnection connection, TextInputConfiguration configuration) { void _attach(TextInputConnection connection, TextInputConfiguration configuration) {
assert(connection != null); assert(connection != null);
assert(connection._client != null); assert(connection._client != null);
assert(configuration != null); assert(configuration != null);
assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction)); assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction));
connection.setClient(configuration); _channel.invokeMethod<void>(
'TextInput.setClient',
<dynamic>[ connection._id, configuration.toJson() ],
);
_currentConnection = connection; _currentConnection = connection;
_currentConfiguration = configuration; _currentConfiguration = configuration;
} }
...@@ -1384,13 +1231,80 @@ class TextInput { ...@@ -1384,13 +1231,80 @@ class TextInput {
return true; return true;
} }
final _TextInputSource _currentSource = _TextInputSource(); late MethodChannel _channel;
TextInputConnection? _currentConnection; TextInputConnection? _currentConnection;
late TextInputConfiguration _currentConfiguration; late TextInputConfiguration _currentConfiguration;
Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
if (_currentConnection == null)
return;
final String method = methodCall.method;
// The requestExistingInputState request needs to be handled regardless of
// the client ID, as long as we have a _currentConnection.
if (method == 'TextInputClient.requestExistingInputState') {
assert(_currentConnection!._client != null);
_attach(_currentConnection!, _currentConfiguration);
final TextEditingValue? editingValue = _currentConnection!._client.currentTextEditingValue;
if (editingValue != null) {
_setEditingState(editingValue);
}
return;
}
final List<dynamic> args = methodCall.arguments as List<dynamic>;
if (method == 'TextInputClient.updateEditingStateWithTag') {
final TextInputClient client = _currentConnection!._client;
assert(client != null);
final AutofillScope? scope = client.currentAutofillScope;
final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
for (final String tag in editingValue.keys) {
final TextEditingValue textEditingValue = TextEditingValue.fromJSON(
editingValue[tag] as Map<String, dynamic>,
);
scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue);
}
return;
}
final int client = args[0] as int;
// The incoming message was for a different client.
if (client != _currentConnection!._id)
return;
switch (method) {
case 'TextInputClient.updateEditingState':
_currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>));
break;
case 'TextInputClient.performAction':
_currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
break;
case 'TextInputClient.performPrivateCommand':
_currentConnection!._client.performPrivateCommand(
args[1]['action'] as String, args[1]['data'] as Map<String, dynamic>);
break;
case 'TextInputClient.updateFloatingCursor':
_currentConnection!._client.updateFloatingCursor(_toTextPoint(
_toTextCursorAction(args[1] as String),
args[2] as Map<String, dynamic>,
));
break;
case 'TextInputClient.onConnectionClosed':
_currentConnection!._client.connectionClosed();
break;
case 'TextInputClient.showAutocorrectionPromptRect':
_currentConnection!._client.showAutocorrectionPromptRect(args[1] as int, args[2] as int);
break;
default:
throw MissingPluginException();
}
}
bool _hidePending = false; bool _hidePending = false;
void _scheduleHide(TextInputConnection connection) { void _scheduleHide() {
if (_hidePending) if (_hidePending)
return; return;
_hidePending = true; _hidePending = true;
...@@ -1401,50 +1315,59 @@ class TextInput { ...@@ -1401,50 +1315,59 @@ class TextInput {
scheduleMicrotask(() { scheduleMicrotask(() {
_hidePending = false; _hidePending = false;
if (_currentConnection == null) if (_currentConnection == null)
connection.hide(); _channel.invokeMethod<void>('TextInput.hide');
}); });
} }
/// Stop interacting with the text input control. void _clearClient() {
/// _channel.invokeMethod<void>('TextInput.clearClient');
/// A client that no longer wishes to interact with the text input control _currentConnection = null;
/// should call this method. _scheduleHide();
///
/// After calling this method, the text input control might be requested to
/// hide if no other client attaches to it within this animation frame.
///
/// See also:
///
/// * [TextInputConnection.hide], a method called when the text input control
/// actually should hide.
static void detach(TextInputClient client) {
assert(client != null);
if (client != _instance._currentConnection?._client)
return;
_instance._detach();
} }
void _detach() { void _updateConfig(TextInputConfiguration configuration) {
if (_currentConnection == null) assert(configuration != null);
return; _channel.invokeMethod<void>(
_currentConnection!.clearClient(); 'TextInput.updateConfig',
_scheduleHide(_currentConnection!); configuration.toJson(),
_currentSource.detach(_currentConnection!._client); );
_currentConnection = null;
} }
/// Resets the current text input connection. void _setEditingState(TextEditingValue value) {
/// assert(value != null);
/// This function should be called to reset the current text input connection _channel.invokeMethod<void>(
/// in case the platform sent a notification informing the connection is 'TextInput.setEditingState',
/// closed. value.toJSON(),
/// );
/// See also: }
///
/// * [TextInputClient.connectionClosed], a method called to notify the void _show() {
/// current text input client when the connection is closed. _channel.invokeMethod<void>('TextInput.show');
static void reset() { }
_instance._currentConnection = null;
void _requestAutofill() {
_channel.invokeMethod<void>('TextInput.requestAutofill');
}
void _setEditableSizeAndTransform(Map<String, dynamic> args) {
_channel.invokeMethod<void>(
'TextInput.setEditableSizeAndTransform',
args,
);
}
void _setComposingTextRect(Map<String, dynamic> args) {
_channel.invokeMethod<void>(
'TextInput.setMarkedTextRect',
args,
);
}
void _setStyle(Map<String, dynamic> args) {
_channel.invokeMethod<void>(
'TextInput.setStyle',
args,
);
} }
/// Finishes the current autofill context, and potentially saves the user /// Finishes the current autofill context, and potentially saves the user
...@@ -1497,42 +1420,9 @@ class TextInput { ...@@ -1497,42 +1420,9 @@ class TextInput {
/// topmost [AutofillGroup] is getting disposed. /// topmost [AutofillGroup] is getting disposed.
static void finishAutofillContext({ bool shouldSave = true }) { static void finishAutofillContext({ bool shouldSave = true }) {
assert(shouldSave != null); assert(shouldSave != null);
_instance._currentSource.finishAutofillContext(shouldSave: shouldSave); TextInput._instance._channel.invokeMethod<void>(
}
}
class _TextInputSource {
static MethodChannel? _channel;
static void setChannel(MethodChannel newChannel) {
_channel = newChannel..setMethodCallHandler(_handleTextInputInvocation);
}
void init() {
_channel ??= SystemChannels.textInput;
_channel!.setMethodCallHandler(_handleTextInputInvocation);
}
void cleanup() {
_channel!.setMethodCallHandler((MethodCall methodCall) async {});
}
TextInputConnection attach(TextInputClient client) {
return _TextInputChannelConnection(client, _channel!);
}
void detach(TextInputClient client) {}
void finishAutofillContext({bool shouldSave = true}) {
_channel!.invokeMethod<void>(
'TextInput.finishAutofillContext', 'TextInput.finishAutofillContext',
shouldSave, shouldSave ,
); );
} }
static Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
final TextInputConnection? connection = TextInput._instance._currentConnection;
if (connection is _TextInputChannelConnection)
return connection._handleTextInputInvocation(methodCall);
}
} }
...@@ -2038,7 +2038,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2038,7 +2038,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void _closeInputConnectionIfNeeded() { void _closeInputConnectionIfNeeded() {
if (_hasInputConnection) { if (_hasInputConnection) {
TextInput.detach(this); _textInputConnection!.close();
_textInputConnection = null; _textInputConnection = null;
_lastKnownRemoteTextEditingValue = null; _lastKnownRemoteTextEditingValue = null;
} }
...@@ -2056,6 +2056,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2056,6 +2056,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override @override
void connectionClosed() { void connectionClosed() {
if (_hasInputConnection) { if (_hasInputConnection) {
_textInputConnection!.connectionClosedReceived();
_textInputConnection = null; _textInputConnection = null;
_lastKnownRemoteTextEditingValue = null; _lastKnownRemoteTextEditingValue = null;
_finalizeEditing(TextInputAction.done, shouldUnfocus: true); _finalizeEditing(TextInputAction.done, shouldUnfocus: true);
......
...@@ -22,7 +22,6 @@ void main() { ...@@ -22,7 +22,6 @@ void main() {
}); });
tearDown(() { tearDown(() {
TextInput.reset();
TextInputConnection.debugResetId(); TextInputConnection.debugResetId();
TextInput.setChannel(SystemChannels.textInput); TextInput.setChannel(SystemChannels.textInput);
}); });
...@@ -75,65 +74,6 @@ void main() { ...@@ -75,65 +74,6 @@ void main() {
}), }),
]); ]);
}); });
test('text input client is requested to hide on detach', () async {
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
TextInput.attach(client, client.configuration);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
]);
TextInput.detach(client);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// From original attach
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
// From detach
const MethodCall('TextInput.clearClient'),
]);
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
await binding.runAsync(() async {});
await expectLater(fakeTextChannel.outgoingCalls.length, 3);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// From original attach
MethodCall('TextInput.setClient', <dynamic>[1, client.configuration.toJson()]),
// From detach
const MethodCall('TextInput.clearClient'),
// From hide
const MethodCall('TextInput.hide'),
]);
});
test('old client is detached when a new client is attached',() {
final FakeTextInputClient client1 = FakeTextInputClient(const TextEditingValue(text: '1'));
final TextInputConnection connection1 = TextInput.attach(client1, client1.configuration);
expect(connection1.attached, isTrue);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
MethodCall('TextInput.setClient', <dynamic>[1, client1.configuration.toJson()]),
]);
final FakeTextInputClient client2 = FakeTextInputClient(const TextEditingValue(text: '1'));
final TextInputConnection connection2 = TextInput.attach(client2, client2.configuration);
expect(connection2.attached, isTrue);
expect(connection1.attached, isFalse);
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
// From original attach
MethodCall('TextInput.setClient', <dynamic>[1, client1.configuration.toJson()]),
// From internal detach
const MethodCall('TextInput.clearClient'),
// From second attach
MethodCall('TextInput.setClient', <dynamic>[2, client1.configuration.toJson()]),
]);
});
test('text input connection is reset', () async {
final FakeTextInputClient client = FakeTextInputClient(TextEditingValue.empty);
final TextInputConnection connection = TextInput.attach(client, client.configuration);
expect(connection.attached, isTrue);
TextInput.reset();
expect(connection.attached, isFalse);
});
}); });
group('TextInputConfiguration', () { group('TextInputConfiguration', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment