Unverified Commit cbe0999d authored by guolinaileen's avatar guolinaileen Committed by GitHub

Introduce onAppPrivateCommand to TextField (#62712)

parent 8c795e3e
...@@ -351,6 +351,7 @@ class TextField extends StatefulWidget { ...@@ -351,6 +351,7 @@ class TextField extends StatefulWidget {
this.onChanged, this.onChanged,
this.onEditingComplete, this.onEditingComplete,
this.onSubmitted, this.onSubmitted,
this.onAppPrivateCommand,
this.inputFormatters, this.inputFormatters,
this.enabled, this.enabled,
this.cursorWidth = 2.0, this.cursorWidth = 2.0,
...@@ -632,6 +633,9 @@ class TextField extends StatefulWidget { ...@@ -632,6 +633,9 @@ class TextField extends StatefulWidget {
/// [TextInputAction.previous] for [textInputAction]. /// [TextInputAction.previous] for [textInputAction].
final ValueChanged<String> onSubmitted; final ValueChanged<String> onSubmitted;
/// {@macro flutter.widgets.editableText.onAppPrivateCommand}
final AppPrivateCommandCallback onAppPrivateCommand;
/// {@macro flutter.widgets.editableText.inputFormatters} /// {@macro flutter.widgets.editableText.inputFormatters}
final List<TextInputFormatter> inputFormatters; final List<TextInputFormatter> inputFormatters;
...@@ -1147,6 +1151,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe ...@@ -1147,6 +1151,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onEditingComplete: widget.onEditingComplete, onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted, onSubmitted: widget.onSubmitted,
onAppPrivateCommand: widget.onAppPrivateCommand,
onSelectionHandleTapped: _handleSelectionHandleTapped, onSelectionHandleTapped: _handleSelectionHandleTapped,
inputFormatters: formatters, inputFormatters: formatters,
rendererIgnoresPointer: true, rendererIgnoresPointer: true,
......
...@@ -802,6 +802,9 @@ abstract class TextInputClient { ...@@ -802,6 +802,9 @@ abstract class TextInputClient {
/// Requests that this client perform the given action. /// Requests that this client perform the given action.
void performAction(TextInputAction action); void performAction(TextInputAction action);
/// Requests that this client perform the private command.
void performPrivateCommand(String action, Map<String, dynamic> data);
/// Updates the floating cursor position and state. /// Updates the floating cursor position and state.
void updateFloatingCursor(RawFloatingCursorPoint point); void updateFloatingCursor(RawFloatingCursorPoint point);
...@@ -1157,6 +1160,10 @@ class TextInput { ...@@ -1157,6 +1160,10 @@ class TextInput {
case 'TextInputClient.performAction': case 'TextInputClient.performAction':
_currentConnection!._client.performAction(_toTextInputAction(args[1] as String)); _currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
break; break;
case 'TextInputClient.performPrivateCommand':
_currentConnection!._client.performPrivateCommand(
args[1]['action'] as String, args[1]['data'] as Map<String, dynamic>);
break;
case 'TextInputClient.updateFloatingCursor': case 'TextInputClient.updateFloatingCursor':
_currentConnection!._client.updateFloatingCursor(_toTextPoint( _currentConnection!._client.updateFloatingCursor(_toTextPoint(
_toTextCursorAction(args[1] as String), _toTextCursorAction(args[1] as String),
......
...@@ -40,6 +40,9 @@ export 'package:flutter/services.dart' show TextEditingValue, TextSelection, Tex ...@@ -40,6 +40,9 @@ export 'package:flutter/services.dart' show TextEditingValue, TextSelection, Tex
/// (including the cursor location). /// (including the cursor location).
typedef SelectionChangedCallback = void Function(TextSelection selection, SelectionChangedCause cause); typedef SelectionChangedCallback = void Function(TextSelection selection, SelectionChangedCause cause);
/// Signature for the callback that reports the app private command results.
typedef AppPrivateCommandCallback = void Function(String, Map<String, dynamic>);
// The time it takes for the cursor to fade from fully opaque to fully // The time it takes for the cursor to fade from fully opaque to fully
// transparent and vice versa. A full cursor blink, from transparent to opaque // transparent and vice versa. A full cursor blink, from transparent to opaque
// to transparent, is twice this duration. // to transparent, is twice this duration.
...@@ -413,6 +416,7 @@ class EditableText extends StatefulWidget { ...@@ -413,6 +416,7 @@ class EditableText extends StatefulWidget {
this.onChanged, this.onChanged,
this.onEditingComplete, this.onEditingComplete,
this.onSubmitted, this.onSubmitted,
this.onAppPrivateCommand,
this.onSelectionChanged, this.onSelectionChanged,
this.onSelectionHandleTapped, this.onSelectionHandleTapped,
List<TextInputFormatter> inputFormatters, List<TextInputFormatter> inputFormatters,
...@@ -1030,6 +1034,11 @@ class EditableText extends StatefulWidget { ...@@ -1030,6 +1034,11 @@ class EditableText extends StatefulWidget {
/// {@end-tool} /// {@end-tool}
final ValueChanged<String> onSubmitted; final ValueChanged<String> onSubmitted;
/// {@template flutter.widgets.editableText.onAppPrivateCommand}
/// Called when the result of an app private command is received.
/// {@endtemplate}
final AppPrivateCommandCallback onAppPrivateCommand;
/// Called when the user changes the selection of text (including the cursor /// Called when the user changes the selection of text (including the cursor
/// location). /// location).
final SelectionChangedCallback onSelectionChanged; final SelectionChangedCallback onSelectionChanged;
...@@ -1616,6 +1625,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1616,6 +1625,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
} }
@override
void performPrivateCommand(String action, Map<String, dynamic> data) {
widget.onAppPrivateCommand(action, data);
}
// The original position of the caret on FloatingCursorDragState.start. // The original position of the caret on FloatingCursorDragState.start.
Rect _startCaretRect; Rect _startCaretRect;
......
...@@ -148,6 +148,11 @@ class FakeAutofillClient implements TextInputClient, AutofillClient { ...@@ -148,6 +148,11 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
latestMethodCall = 'performAction'; latestMethodCall = 'performAction';
} }
@override
void performPrivateCommand(String action, Map<String, dynamic> data) {
latestMethodCall = 'performPrivateCommand';
}
@override @override
void updateFloatingCursor(RawFloatingCursorPoint point) { void updateFloatingCursor(RawFloatingCursorPoint point) {
latestMethodCall = 'updateFloatingCursor'; latestMethodCall = 'updateFloatingCursor';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// @dart = 2.8 // @dart = 2.8
import 'dart:convert' show utf8; import 'dart:convert' show utf8;
import 'dart:convert' show jsonDecode;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding; import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding;
...@@ -170,7 +171,7 @@ void main() { ...@@ -170,7 +171,7 @@ void main() {
expect(client.latestMethodCall, 'connectionClosed'); expect(client.latestMethodCall, 'connectionClosed');
}); });
test('TextInputClient showAutocorrectionPromptRect method is called', () async { test('TextInputClient performPrivateCommand method is called', () async {
// Assemble a TextInputConnection so we can verify its change in state. // Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null); final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration(); const TextInputConfiguration configuration = TextInputConfiguration();
...@@ -178,8 +179,149 @@ void main() { ...@@ -178,8 +179,149 @@ void main() {
expect(client.latestMethodCall, isEmpty); expect(client.latestMethodCall, isEmpty);
// Send onConnectionClosed message. // Send performPrivateCommand message.
final ByteData messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[
1,
jsonDecode(
'{"action": "actionCommand", "data": {\"input_context\" : \"abcdefg\"}}')
],
'method': 'TextInputClient.performPrivateCommand',
});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/textinput',
messageBytes,
(ByteData _) {},
);
expect(client.latestMethodCall, 'performPrivateCommand');
});
test('TextInputClient performPrivateCommand method is called with float',
() async {
// Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration();
TextInput.attach(client, configuration);
expect(client.latestMethodCall, isEmpty);
// Send performPrivateCommand message.
final ByteData messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[
1,
jsonDecode(
'{"action": "actionCommand", "data": {\"input_context\" : 0.5}}')
],
'method': 'TextInputClient.performPrivateCommand',
});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/textinput',
messageBytes,
(ByteData _) {},
);
expect(client.latestMethodCall, 'performPrivateCommand');
});
test(
'TextInputClient performPrivateCommand method is called with CharSequence array',
() async {
// Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration();
TextInput.attach(client, configuration);
expect(client.latestMethodCall, isEmpty);
// Send performPrivateCommand message.
final ByteData messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{ final ByteData messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[
1,
jsonDecode(
'{"action": "actionCommand", "data": {\"input_context\" : ["abc", "efg"]}}')
],
'method': 'TextInputClient.performPrivateCommand',
});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/textinput',
messageBytes,
(ByteData _) {},
);
expect(client.latestMethodCall, 'performPrivateCommand');
});
test(
'TextInputClient performPrivateCommand method is called with CharSequence',
() async {
// Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration();
TextInput.attach(client, configuration);
expect(client.latestMethodCall, isEmpty);
// Send performPrivateCommand message.
final ByteData messageBytes =
const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[
1,
jsonDecode(
'{"action": "actionCommand", "data": {\"input_context\" : "abc"}}')
],
'method': 'TextInputClient.performPrivateCommand',
});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/textinput',
messageBytes,
(ByteData _) {},
);
expect(client.latestMethodCall, 'performPrivateCommand');
});
test(
'TextInputClient performPrivateCommand method is called with float array',
() async {
// Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration();
TextInput.attach(client, configuration);
expect(client.latestMethodCall, isEmpty);
// Send performPrivateCommand message.
final ByteData messageBytes =
const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[
1,
jsonDecode(
'{"action": "actionCommand", "data": {\"input_context\" : [0.5, 0.8]}}')
],
'method': 'TextInputClient.performPrivateCommand',
});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/textinput',
messageBytes,
(ByteData _) {},
);
expect(client.latestMethodCall, 'performPrivateCommand');
});
test('TextInputClient showAutocorrectionPromptRect method is called',
() async {
// Assemble a TextInputConnection so we can verify its change in state.
final FakeTextInputClient client = FakeTextInputClient(null);
const TextInputConfiguration configuration = TextInputConfiguration();
TextInput.attach(client, configuration);
expect(client.latestMethodCall, isEmpty);
// Send onConnectionClosed message.
final ByteData messageBytes =
const JSONMessageCodec().encodeMessage(<String, dynamic>{
'args': <dynamic>[1, 0, 1], 'args': <dynamic>[1, 0, 1],
'method': 'TextInputClient.showAutocorrectionPromptRect', 'method': 'TextInputClient.showAutocorrectionPromptRect',
}); });
...@@ -237,6 +379,11 @@ class FakeTextInputClient implements TextInputClient { ...@@ -237,6 +379,11 @@ class FakeTextInputClient implements TextInputClient {
latestMethodCall = 'performAction'; latestMethodCall = 'performAction';
} }
@override
void performPrivateCommand(String action, Map<String, dynamic> data) {
latestMethodCall = 'performPrivateCommand';
}
@override @override
void updateEditingValue(TextEditingValue value) { void updateEditingValue(TextEditingValue value) {
latestMethodCall = 'updateEditingValue'; latestMethodCall = 'updateEditingValue';
......
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