Commit c08ea4eb authored by Adam Barth's avatar Adam Barth Committed by GitHub

Switch to TextInputPlugin (#6392)

This patch removes uses of keyboard.mojom in favor of the
TextInputPlugin.
parent 4f8da89d
......@@ -88,7 +88,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
new Input(
hintText: 'Where can we reach you?',
labelText: 'Phone Number',
keyboardType: KeyboardType.phone,
keyboardType: TextInputType.phone,
formField: new FormField<String>(
setter: (String val) { person.phoneNumber = val; },
validator: _validatePhoneNumber
......
......@@ -24,7 +24,6 @@ export 'src/services/image_decoder.dart';
export 'src/services/image_provider.dart';
export 'src/services/image_resolution.dart';
export 'src/services/image_stream.dart';
export 'src/services/keyboard.dart';
export 'src/services/path_provider.dart';
export 'src/services/platform_messages.dart';
export 'src/services/raw_keyboard.dart';
......
......@@ -14,7 +14,7 @@ import 'material.dart';
import 'text_selection.dart';
import 'theme.dart';
export 'package:flutter_services/editing.dart' show KeyboardType;
export 'package:flutter/services.dart' show TextInputType;
/// A material design text input field.
///
......@@ -44,7 +44,7 @@ class Input extends StatefulWidget {
Input({
Key key,
this.value,
this.keyboardType: KeyboardType.text,
this.keyboardType: TextInputType.text,
this.icon,
this.labelText,
this.hintText,
......@@ -68,7 +68,7 @@ class Input extends StatefulWidget {
final InputValue value;
/// The type of keyboard to use for editing the text.
final KeyboardType keyboardType;
final TextInputType keyboardType;
/// An icon to show adjacent to the input field.
///
......
// Copyright 2015 The Chromium 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:async';
import 'package:flutter_services/editing.dart' as mojom;
import 'shell.dart';
export 'package:flutter_services/editing.dart' show KeyboardType;
/// An interface to the system's keyboard.
///
/// Most clients will want to use the [keyboard] singleton instance.
class Keyboard {
/// Creates a keyboard that wraps the given keyboard service.
///
/// This constructor is exposed for use by tests. Most non-test clients should
/// use the [keyboard] singleton instance.
Keyboard(this.service);
/// The underlying keyboard service.
///
/// It is rare to interact with the keyboard service directly. Instead, most
/// clients should interact with the service by first calling [attach] and
/// then using the returned [KeyboardHandle].
final mojom.Keyboard service;
KeyboardHandle _currentHandle;
bool _hidePending = false;
/// Begin interacting with the keyboard.
///
/// Calling this function helps multiple clients coordinate about which one is
/// currently interacting with the keyboard. The returned [KeyboardHandle]
/// provides interfaces for actually interacting with the keyboard.
KeyboardHandle attach(mojom.KeyboardClientStub stub, mojom.KeyboardConfiguration configuration) {
assert(stub != null);
_currentHandle?.release();
assert(_currentHandle == null);
_currentHandle = new KeyboardHandle._(this);
service.setClient(stub, configuration);
return _currentHandle;
}
void _scheduleHide() {
if (_hidePending)
return;
_hidePending = true;
// Schedule a deferred task that hides the keyboard. If someone else shows
// the keyboard during this update cycle, then the task will do nothing.
scheduleMicrotask(() {
_hidePending = false;
if (_currentHandle == null) {
service.hide();
}
});
}
}
/// An object that represents a session with the keyboard.
///
/// Keyboard handles are created by the [Keyboard.attach] function. When first
/// created, the keyboard handle is attached to the keyboard as the currently
/// active client. A keyboard handle can become detached either by an explicit
/// calle to [release] or because another client attaches themselves.
class KeyboardHandle {
KeyboardHandle._(Keyboard keyboard) : _keyboard = keyboard, _attached = true;
final Keyboard _keyboard;
/// Whether this handle is currently attached to the keyboard.
///
/// If another client calls [Keyboard.attach], this handle will be marked as
/// no longer attached to the keyboard.
bool get attached => _attached;
bool _attached;
/// Request that the keyboard become visible (if necesssary).
void show() {
assert(_attached);
assert(_keyboard._currentHandle == this);
_keyboard.service.show();
}
/// Disclaim interest in the keyboard.
///
/// After calling this function, [attached] will be `false` and the keyboard
/// will disappear (if possible).
void release() {
if (_attached) {
assert(_keyboard._currentHandle == this);
_attached = false;
_keyboard.service.clearClient();
_keyboard._currentHandle = null;
_keyboard._scheduleHide();
}
assert(_keyboard._currentHandle != this);
}
/// Changes the keyboard's state.
///
/// The given `state` is uploaded to the keyboard, overwriting whatever state
/// the keyboard had previously.
///
/// Interacting with the keyboard is inherently racy because there are two
/// asynchronous writers: code that wishes to change keyboard state
/// spontaneously (e.g., because the user pasted some text) and the user
/// themselves. For this reason, state set by this function might overwrite
/// state that user has modified in the keyboard that has not yet been
/// reported via the keyboard client.
///
/// Should be called only if `attached` is `true`.
void setEditingState(mojom.EditingState state) {
assert(_attached);
assert(_keyboard._currentHandle == this);
_keyboard.service.setEditingState(state);
}
}
mojom.KeyboardProxy _initKeyboardProxy() {
return shell.connectToViewAssociatedService(mojom.Keyboard.connectToService);
}
final mojom.KeyboardProxy _keyboardProxy = _initKeyboardProxy();
/// A singleton for managing interactions with the keyboard.
///
/// You can begin a session with the keyboard by calling the [Keyboard.attach]
/// method on this object.
final Keyboard keyboard = new Keyboard(_keyboardProxy);
......@@ -190,22 +190,17 @@ class TextInputConnection {
final TextInputClient _client;
/// Whether this connection is currently interacting with the text input control.
bool get attached => _attached;
bool _attached = true;
bool get _isAttachedAndCurrent {
return _attached && _clientHandler._currentConnection == this;
}
bool get attached => _clientHandler._currentConnection == this;
/// Requests that the text input control become visible.
void show() {
assert(_isAttachedAndCurrent);
assert(attached);
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.show');
}
/// Requests that the text input control change its internal state to match the given state.
void setEditingState(TextEditingState state) {
assert(_isAttachedAndCurrent);
assert(attached);
PlatformMessages.invokeMethod(
_kChannelName,
'TextInput.setEditingState',
......@@ -218,15 +213,13 @@ class TextInputConnection {
/// After calling this method, the text input control might disappear if no
/// other client attaches to it within this animation frame.
void close() {
if (_attached) {
assert(_clientHandler._currentConnection == this);
_attached = false;
if (attached) {
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.clearClient');
_clientHandler
.._currentConnection = null
.._scheduleHide();
}
assert(_clientHandler._currentConnection != this);
assert(!attached);
}
}
......
......@@ -7,7 +7,6 @@ import 'dart:async';
import 'package:flutter/rendering.dart' show RenderEditable, SelectionChangedHandler, RenderEditablePaintOffsetNeededCallback;
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:flutter_services/editing.dart' as mojom;
import 'basic.dart';
import 'focus.dart';
......@@ -18,73 +17,37 @@ import 'scrollable.dart';
import 'text_selection.dart';
export 'package:flutter/painting.dart' show TextSelection;
export 'package:flutter_services/editing.dart' show KeyboardType;
export 'package:flutter/services.dart' show TextInputType;
const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500);
TextSelection _getTextSelectionFromEditingState(mojom.EditingState state) {
TextSelection _getTextSelectionFromEditingState(TextEditingState state) {
return new TextSelection(
baseOffset: state.selectionBase,
extentOffset: state.selectionExtent,
affinity: TextAffinity.values[state.selectionAffinity.mojoEnumValue],
isDirectional: state.selectionIsDirectional
affinity: state.selectionAffinity,
isDirectional: state.selectionIsDirectional,
);
}
class _KeyboardClientImpl implements mojom.KeyboardClient {
_KeyboardClientImpl({
this.inputValue,
this.onUpdated,
this.onSubmitted
}) {
assert(inputValue != null);
assert(onUpdated != null);
assert(onSubmitted != null);
}
InputValue inputValue;
/// Called whenever the text changes.
final VoidCallback onUpdated;
/// Called whenever the user indicates they are done editing the string.
final VoidCallback onSubmitted;
/// A keyboard client stub that can be attached to a keyboard service.
mojom.KeyboardClientStub createStub() {
return new mojom.KeyboardClientStub.unbound()..impl = this;
}
mojom.EditingState get editingState {
return new mojom.EditingState()
..text = inputValue.text
..selectionBase = inputValue.selection.baseOffset
..selectionExtent = inputValue.selection.extentOffset
..selectionAffinity = mojom.TextAffinity.values[inputValue.selection.affinity.index]
..selectionIsDirectional = inputValue.selection.isDirectional
..composingBase = inputValue.composing.start
..composingExtent = inputValue.composing.end;
}
@override
void updateEditingState(mojom.EditingState state) {
inputValue = new InputValue(
text: state.text,
selection: _getTextSelectionFromEditingState(state),
composing: new TextRange(start: state.composingBase, end: state.composingExtent)
);
onUpdated();
}
void clearComposing() {
inputValue = inputValue.copyWith(composing: TextRange.empty);
}
InputValue _getInputValueFromEditingState(TextEditingState state) {
return new InputValue(
text: state.text,
selection: _getTextSelectionFromEditingState(state),
composing: new TextRange(start: state.composingBase, end: state.composingExtent),
);
}
@override
void submit(mojom.SubmitAction action) {
clearComposing();
onSubmitted();
}
TextEditingState _getTextEditingStateFromInputValue(InputValue value) {
return new TextEditingState(
text: value.text,
selectionBase: value.selection.baseOffset,
selectionExtent: value.selection.extentOffset,
selectionAffinity: value.selection.affinity,
selectionIsDirectional: value.selection.isDirectional,
composingBase: value.composing.start,
composingExtent: value.composing.end,
);
}
/// Configuration information for an input field.
......@@ -92,6 +55,9 @@ class _KeyboardClientImpl implements mojom.KeyboardClient {
/// An [InputValue] contains the text for the input field as well as the
/// selection extent and the composing range.
class InputValue {
// TODO(abarth): This class is really the same as TextEditingState.
// We should merge them into one object.
/// Creates configuration information for an input field
///
/// The selection and composing range must be within the text.
......@@ -226,7 +192,7 @@ class RawInput extends Scrollable {
final TargetPlatform platform;
/// The type of keyboard to use for editing the text.
final KeyboardType keyboardType;
final TextInputType keyboardType;
/// Called when the text being edited changes.
final ValueChanged<InputValue> onChanged;
......@@ -239,12 +205,12 @@ class RawInput extends Scrollable {
}
/// State for a [RawInput].
class RawInputState extends ScrollableState<RawInput> {
class RawInputState extends ScrollableState<RawInput> implements TextInputClient {
Timer _cursorTimer;
bool _showCursor = false;
_KeyboardClientImpl _keyboardClient;
KeyboardHandle _keyboardHandle;
InputValue _currentValue;
TextInputConnection _textInputConnection;
TextSelectionOverlay _selectionOverlay;
@override
......@@ -256,23 +222,19 @@ class RawInputState extends ScrollableState<RawInput> {
@override
void initState() {
super.initState();
_keyboardClient = new _KeyboardClientImpl(
inputValue: config.value,
onUpdated: _handleTextUpdated,
onSubmitted: _handleTextSubmitted
);
_currentValue = config.value;
}
@override
void didUpdateConfig(RawInput oldConfig) {
if (_keyboardClient.inputValue != config.value) {
_keyboardClient.inputValue = config.value;
if (_currentValue != config.value) {
_currentValue = config.value;
if (_isAttachedToKeyboard)
_keyboardHandle.setEditingState(_keyboardClient.editingState);
_textInputConnection.setEditingState(_getTextEditingStateFromInputValue(_currentValue));
}
}
bool get _isAttachedToKeyboard => _keyboardHandle != null && _keyboardHandle.attached;
bool get _isAttachedToKeyboard => _textInputConnection != null && _textInputConnection.attached;
bool get _isMultiline => config.maxLines > 1;
......@@ -315,20 +277,25 @@ class RawInputState extends ScrollableState<RawInput> {
void _attachOrDetachKeyboard(bool focused) {
if (focused && !_isAttachedToKeyboard) {
_keyboardHandle = keyboard.attach(_keyboardClient.createStub(),
new mojom.KeyboardConfiguration()
..type = config.keyboardType);
_keyboardHandle.setEditingState(_keyboardClient.editingState);
_keyboardHandle.show();
_textInputConnection = TextInput.attach(
this, new TextInputConfiguration(inputType: config.keyboardType))
..setEditingState(_getTextEditingStateFromInputValue(_currentValue))
..show();
} else if (!focused) {
if (_isAttachedToKeyboard) {
_keyboardHandle.release();
_keyboardHandle = null;
_textInputConnection.close();
_textInputConnection = null;
}
_keyboardClient.clearComposing();
_clearComposing();
}
}
void _clearComposing() {
// TODO(abarth): We should call config.onChanged to notify our parent of
// this change in our composing range.
_currentValue = _currentValue.copyWith(composing: TextRange.empty);
}
/// Express interest in interacting with the keyboard.
///
/// If this control is already attached to the keyboard, this function will
......@@ -338,25 +305,29 @@ class RawInputState extends ScrollableState<RawInput> {
/// keyboard become visible.
void requestKeyboard() {
if (_isAttachedToKeyboard) {
_keyboardHandle.show();
_textInputConnection.show();
} else {
Focus.moveTo(config.focusKey);
}
}
void _handleTextUpdated() {
@override
void updateEditingState(TextEditingState state) {
_currentValue = _getInputValueFromEditingState(state);
if (config.onChanged != null)
config.onChanged(_keyboardClient.inputValue);
if (_keyboardClient.inputValue.text != config.value.text) {
config.onChanged(_currentValue);
if (_currentValue.text != config.value.text) {
_selectionOverlay?.hide();
_selectionOverlay = null;
}
}
void _handleTextSubmitted() {
@override
void performAction(TextInputAction action) {
_clearComposing();
Focus.clear(context);
if (config.onSubmitted != null)
config.onSubmitted(_keyboardClient.inputValue);
config.onSubmitted(_currentValue);
}
void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, bool longPress) {
......@@ -364,7 +335,7 @@ class RawInputState extends ScrollableState<RawInput> {
// EditableWidget, not just changes triggered by user gestures.
requestKeyboard();
InputValue newInput = new InputValue(text: _keyboardClient.inputValue.text, selection: selection);
InputValue newInput = _currentValue.copyWith(selection: selection, composing: TextRange.empty);
if (config.onChanged != null)
config.onChanged(newInput);
......@@ -425,8 +396,10 @@ class RawInputState extends ScrollableState<RawInput> {
@override
void dispose() {
if (_isAttachedToKeyboard)
_keyboardHandle.release();
if (_isAttachedToKeyboard) {
_textInputConnection.close();
_textInputConnection = null;
}
if (_cursorTimer != null)
_stopCursorTimer();
_selectionOverlay?.dispose();
......@@ -464,7 +437,7 @@ class RawInputState extends ScrollableState<RawInput> {
return new ClipRect(
child: new _Editable(
value: _keyboardClient.inputValue,
value: _currentValue,
style: config.style,
cursorColor: config.cursorColor,
showCursor: _showCursor,
......
......@@ -4,47 +4,14 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_services/editing.dart' as mojom;
import 'package:meta/meta.dart';
class MockKeyboard extends mojom.KeyboardProxy {
MockKeyboard() : super.unbound();
mojom.KeyboardClient client;
mojom.EditingState currentState;
@override
void setClient(@checked mojom.KeyboardClientStub client, mojom.KeyboardConfiguration configuraiton) {
this.client = client.impl;
}
@override
void show() {}
@override
void hide() {}
@override
void setEditingState(mojom.EditingState state) {
currentState = state;
}
}
import 'mock_text_input.dart';
void main() {
MockKeyboard mockKeyboard = new MockKeyboard();
setUpAll(() {
serviceMocker.registerMockService(mockKeyboard);
});
MockTextInput mockTextInput = new MockTextInput()..register();
void enterText(String testValue) {
// Simulate entry of text through the keyboard.
expect(mockKeyboard.client, isNotNull);
mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = testValue
..composingBase = 0
..composingExtent = testValue.length);
void enterText(String text) {
mockTextInput.enterText(text);
}
testWidgets('Setter callback is called', (WidgetTester tester) async {
......@@ -70,6 +37,7 @@ void main() {
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.idle();
// pump'ing is unnecessary because callback happens regardless of frames
expect(fieldValue, equals(testValue));
}
......@@ -101,6 +69,7 @@ void main() {
Future<Null> checkErrorText(String testValue) async {
enterText(testValue);
await tester.idle();
await tester.pump();
// Check for a new Text widget with our error text.
expect(find.text(errorText(testValue)), findsOneWidget);
......@@ -151,6 +120,7 @@ void main() {
Future<Null> checkErrorText(String testValue) async {
enterText(testValue);
await tester.idle();
await tester.pump();
expect(fieldValue, equals(testValue));
......@@ -186,8 +156,8 @@ void main() {
await tester.pumpWidget(builder());
// initial value should be loaded into keyboard editing state
expect(mockKeyboard.currentState, isNotNull);
expect(mockKeyboard.currentState.text, equals(initialValue));
expect(mockTextInput.editingState, isNotNull);
expect(mockTextInput.editingState['text'], equals(initialValue));
// initial value should also be visible in the raw input line
RawInputState editableText = tester.state(find.byType(RawInput));
......@@ -196,6 +166,7 @@ void main() {
// sanity check, make sure we can still edit the text and everything updates
expect(currentValue, isNull);
enterText('world');
await tester.idle();
expect(currentValue, equals('world'));
await tester.pump();
expect(editableText.config.value.text, equals('world'));
......
......@@ -8,28 +8,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_services/editing.dart' as mojom;
import 'package:meta/meta.dart';
class MockKeyboard extends mojom.KeyboardProxy {
MockKeyboard() : super.unbound();
mojom.KeyboardClient client;
@override
void setClient(@checked mojom.KeyboardClientStub client, mojom.KeyboardConfiguration configuraiton) {
this.client = client.impl;
}
@override
void show() {}
@override
void hide() {}
@override
void setEditingState(mojom.EditingState state) {}
}
import 'mock_text_input.dart';
class MockClipboard {
Object _clipboardData = <String, dynamic>{
......@@ -50,9 +30,7 @@ class MockClipboard {
}
void main() {
MockKeyboard mockKeyboard = new MockKeyboard();
serviceMocker.registerMockService(mockKeyboard);
MockTextInput mockTextInput = new MockTextInput()..register();
MockClipboard mockClipboard = new MockClipboard();
PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage);
......@@ -64,13 +42,12 @@ void main() {
kThreeLines +
'Fourth line won\'t display and ends at abcdef ghi. ';
void enterText(String testValue) {
// Simulate entry of text through the keyboard.
expect(mockKeyboard.client, isNotNull);
mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = testValue
..composingBase = 0
..composingExtent = testValue.length);
void updateEditingState(TextEditingState state) {
mockTextInput.updateEditingState(state);
}
void enterText(String text) {
mockTextInput.enterText(text);
}
// Returns the first RenderEditable.
......@@ -123,13 +100,14 @@ void main() {
RenderBox inputBox = findInputBox();
Size emptyInputSize = inputBox.size;
Future<Null> checkText(String testValue) {
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.idle();
// Check that the onChanged event handler fired.
expect(inputValue.text, equals(testValue));
return tester.pumpWidget(builder());
return await tester.pumpWidget(builder());
}
await checkText(' ');
......@@ -177,10 +155,11 @@ void main() {
await checkCursorToggle();
// Try the test again with a nonempty EditableText.
mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = 'X'
..selectionBase = 1
..selectionExtent = 1);
updateEditingState(new TextEditingState(
text: 'X',
selectionBase: 1,
selectionExtent: 1,
));
await checkCursorToggle();
});
......@@ -202,10 +181,11 @@ void main() {
await tester.pumpWidget(builder());
const String testValue = 'ABC';
mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = testValue
..selectionBase = testValue.length
..selectionExtent = testValue.length);
updateEditingState(new TextEditingState(
text: testValue,
selectionBase: testValue.length,
selectionExtent: testValue.length,
));
await tester.pump();
});
......@@ -238,6 +218,7 @@ void main() {
String testValue = 'abc def ghi';
enterText(testValue);
await tester.idle();
expect(inputValue.text, testValue);
await tester.pumpWidget(builder());
......@@ -284,6 +265,7 @@ void main() {
String testValue = 'abc def ghi';
enterText(testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -358,6 +340,7 @@ void main() {
String testValue = 'abc def ghi';
enterText(testValue);
await tester.idle();
await tester.pumpWidget(builder());
// Tap the selection handle to bring up the "paste / select all" menu.
......@@ -422,6 +405,7 @@ void main() {
String testValue = 'abc def ghi';
enterText(testValue);
await tester.idle();
await tester.pumpWidget(builder());
// Tap the selection handle to bring up the "paste / select all" menu.
......@@ -475,11 +459,13 @@ void main() {
Size emptyInputSize = inputBox.size;
enterText('No wrapping here.');
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, equals(emptyInputSize));
enterText(kThreeLines);
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, greaterThan(emptyInputSize));
......@@ -488,12 +474,14 @@ void main() {
// An extra line won't increase the size because we max at 3.
enterText(kFourLines);
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, threeLineInputSize);
// But now it will.
enterText(kFourLines);
await tester.idle();
await tester.pumpWidget(builder(4));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, greaterThan(threeLineInputSize));
......@@ -530,6 +518,7 @@ void main() {
String testValue = kThreeLines;
String cutValue = 'First line of stuff keeps going until abcdef ghijk. ';
enterText(testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -618,6 +607,7 @@ void main() {
await tester.pumpWidget(builder());
enterText(kFourLines);
await tester.idle();
await tester.pumpWidget(builder());
......
// Copyright 2015 The Chromium 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:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/services.dart';
const String _kTextInputClientChannel = 'flutter/textinputclient';
class MockTextInput {
void register() {
PlatformMessages.setMockJSONMessageHandler('flutter/textinput', handleJSONMessage);
}
int client = 0;
Map<String, dynamic> editingState;
Future<dynamic> handleJSONMessage(dynamic message) async {
final String method = message['method'];
final List<dynamic> args= message['args'];
switch (method) {
case 'TextInput.setClient':
client = args[0];
break;
case 'TextInput.setEditingState':
editingState = args[0];
break;
}
}
void updateEditingState(TextEditingState state) {
expect(client, isNonZero);
String message = JSON.encode(<String, dynamic>{
'method': 'TextInputClient.updateEditingState',
'args': <dynamic>[client, state.toJSON()],
});
Uint8List encoded = UTF8.encoder.convert(message);
PlatformMessages.handlePlatformMessage(
_kTextInputClientChannel, encoded.buffer.asByteData(), (_) {});
}
void enterText(String text) {
updateEditingState(new TextEditingState(
text: text,
composingBase: 0,
composingExtent: text.length,
));
}
}
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