Update to new editing.mojom
Now the keyboard is responsible for maintaining the state of the text field.
This commit is contained in:
parent
9e784f0cd3
commit
fade0dc097
@ -6,6 +6,7 @@ import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
|
||||
|
||||
import 'colors.dart';
|
||||
import 'debug.dart';
|
||||
@ -13,7 +14,7 @@ import 'icon.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
export 'package:flutter/rendering.dart' show ValueChanged;
|
||||
export 'package:flutter/services.dart' show KeyboardType;
|
||||
export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
|
||||
|
||||
/// A material design text input field.
|
||||
class Input extends StatefulComponent {
|
||||
@ -110,10 +111,11 @@ class _InputState extends State<Input> {
|
||||
|
||||
void _attachOrDetachKeyboard(bool focused) {
|
||||
if (focused && !_isAttachedToKeyboard) {
|
||||
_keyboardHandle = keyboard.show(_editableString.createStub(), config.keyboardType);
|
||||
_keyboardHandle.setText(_editableString.text);
|
||||
_keyboardHandle.setSelection(_editableString.selection.start,
|
||||
_editableString.selection.end);
|
||||
_keyboardHandle = keyboard.attach(_editableString.createStub(),
|
||||
new mojom.KeyboardConfiguration()
|
||||
..type = config.keyboardType);
|
||||
_keyboardHandle.setEditingState(_editableString.editingState);
|
||||
_keyboardHandle.show();
|
||||
} else if (!focused && _isAttachedToKeyboard) {
|
||||
_keyboardHandle.release();
|
||||
_keyboardHandle = null;
|
||||
@ -124,7 +126,7 @@ class _InputState extends State<Input> {
|
||||
void _requestKeyboard() {
|
||||
if (Focus.at(context)) {
|
||||
assert(_isAttachedToKeyboard);
|
||||
_keyboardHandle.showByRequest();
|
||||
_keyboardHandle.show();
|
||||
} else {
|
||||
Focus.moveTo(config.key);
|
||||
// we'll get told to rebuild and we'll take care of the keyboard then
|
||||
@ -149,7 +151,8 @@ class _InputState extends State<Input> {
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection) {
|
||||
if (_isAttachedToKeyboard) {
|
||||
_keyboardHandle.setSelection(selection.start, selection.end);
|
||||
_editableString.setSelection(selection);
|
||||
_keyboardHandle.setEditingState(_editableString.editingState);
|
||||
} else {
|
||||
_editableString.setSelection(selection);
|
||||
_requestKeyboard();
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:mojo_services/keyboard/keyboard.mojom.dart';
|
||||
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
|
||||
|
||||
import 'shell.dart';
|
||||
|
||||
export 'package:mojo_services/keyboard/keyboard.mojom.dart';
|
||||
export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
|
||||
|
||||
/// An interface to the system's keyboard.
|
||||
///
|
||||
@ -19,22 +19,24 @@ class Keyboard {
|
||||
// The service is exposed in case you need direct access.
|
||||
// However, as a general rule, you should be able to do
|
||||
// most of what you need using only this class.
|
||||
final KeyboardService service;
|
||||
final mojom.Keyboard service;
|
||||
|
||||
KeyboardHandle _currentHandle;
|
||||
|
||||
bool _hidePending = false;
|
||||
|
||||
KeyboardHandle show(KeyboardClientStub stub, KeyboardType keyboardType) {
|
||||
KeyboardHandle attach(mojom.KeyboardClientStub stub, mojom.KeyboardConfiguration configuration) {
|
||||
assert(stub != null);
|
||||
_currentHandle?.release();
|
||||
assert(_currentHandle == null);
|
||||
_currentHandle = new KeyboardHandle._show(this, stub, keyboardType);
|
||||
_currentHandle = new KeyboardHandle._(this);
|
||||
service.setClient(stub, configuration);
|
||||
return _currentHandle;
|
||||
}
|
||||
|
||||
void _scheduleHide() {
|
||||
if (_hidePending) return;
|
||||
if (_hidePending)
|
||||
return;
|
||||
_hidePending = true;
|
||||
|
||||
// Schedule a deferred task that hides the keyboard. If someone else shows
|
||||
@ -50,21 +52,17 @@ class Keyboard {
|
||||
}
|
||||
|
||||
class KeyboardHandle {
|
||||
|
||||
KeyboardHandle._show(Keyboard keyboard, KeyboardClientStub stub, KeyboardType keyboardType) : _keyboard = keyboard {
|
||||
_keyboard.service.show(stub, keyboardType);
|
||||
_attached = true;
|
||||
}
|
||||
KeyboardHandle._(Keyboard keyboard) : _keyboard = keyboard, _attached = true;
|
||||
|
||||
final Keyboard _keyboard;
|
||||
|
||||
bool _attached;
|
||||
bool get attached => _attached;
|
||||
|
||||
void showByRequest() {
|
||||
void show() {
|
||||
assert(_attached);
|
||||
assert(_keyboard._currentHandle == this);
|
||||
_keyboard.service.showByRequest();
|
||||
_keyboard.service.show();
|
||||
}
|
||||
|
||||
void release() {
|
||||
@ -77,25 +75,18 @@ class KeyboardHandle {
|
||||
assert(_keyboard._currentHandle != this);
|
||||
}
|
||||
|
||||
void setText(String text) {
|
||||
void setEditingState(mojom.EditingState state) {
|
||||
assert(_attached);
|
||||
assert(_keyboard._currentHandle == this);
|
||||
_keyboard.service.setText(text);
|
||||
_keyboard.service.setEditingState(state);
|
||||
}
|
||||
|
||||
void setSelection(int start, int end) {
|
||||
assert(_attached);
|
||||
assert(_keyboard._currentHandle == this);
|
||||
_keyboard.service.setSelection(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
KeyboardServiceProxy _initKeyboardProxy() {
|
||||
KeyboardServiceProxy proxy = new KeyboardServiceProxy.unbound();
|
||||
mojom.KeyboardProxy _initKeyboardProxy() {
|
||||
mojom.KeyboardProxy proxy = new mojom.KeyboardProxy.unbound();
|
||||
shell.connectToService(null, proxy);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
final KeyboardServiceProxy _keyboardProxy = _initKeyboardProxy();
|
||||
final mojom.KeyboardProxy _keyboardProxy = _initKeyboardProxy();
|
||||
final Keyboard keyboard = new Keyboard(_keyboardProxy.ptr);
|
||||
|
@ -3,9 +3,8 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:mojo_services/keyboard/keyboard.mojom.dart';
|
||||
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
@ -18,7 +17,16 @@ export 'package:flutter/painting.dart' show TextSelection;
|
||||
|
||||
const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500);
|
||||
|
||||
class _KeyboardClientImpl implements KeyboardClient {
|
||||
TextSelection _getTextSelectionFromEditingState(mojom.EditingState state) {
|
||||
return new TextSelection(
|
||||
baseOffset: state.selectionBase,
|
||||
extentOffset: state.selectionExtent,
|
||||
affinity: TextAffinity.values[state.selectionAffinity.mojoEnumValue],
|
||||
isDirectional: state.selectionIsDirectional
|
||||
);
|
||||
}
|
||||
|
||||
class _KeyboardClientImpl implements mojom.KeyboardClient {
|
||||
_KeyboardClientImpl({
|
||||
String text: '',
|
||||
TextSelection selection,
|
||||
@ -45,86 +53,29 @@ class _KeyboardClientImpl implements KeyboardClient {
|
||||
TextSelection selection;
|
||||
|
||||
/// A keyboard client stub that can be attached to a keyboard service.
|
||||
KeyboardClientStub createStub() {
|
||||
return new KeyboardClientStub.unbound()..impl = this;
|
||||
mojom.KeyboardClientStub createStub() {
|
||||
return new mojom.KeyboardClientStub.unbound()..impl = this;
|
||||
}
|
||||
|
||||
void _delete(TextRange range) {
|
||||
if (range.isCollapsed || !range.isValid) return;
|
||||
text = range.textBefore(text) + range.textAfter(text);
|
||||
mojom.EditingState get editingState {
|
||||
return new mojom.EditingState()
|
||||
..text = text
|
||||
..selectionBase = selection.baseOffset
|
||||
..selectionExtent = selection.extentOffset
|
||||
..selectionAffinity = mojom.TextAffinity.values[selection.affinity.index]
|
||||
..selectionIsDirectional = selection.isDirectional
|
||||
..composingBase = composing.start
|
||||
..composingExtent = composing.end;
|
||||
}
|
||||
|
||||
TextRange _append(String newText) {
|
||||
int start = text.length;
|
||||
text += newText;
|
||||
return new TextRange(start: start, end: start + newText.length);
|
||||
}
|
||||
|
||||
TextRange _replace(TextRange range, String newText) {
|
||||
assert(range.isValid);
|
||||
|
||||
String before = range.textBefore(text);
|
||||
String after = range.textAfter(text);
|
||||
|
||||
text = before + newText + after;
|
||||
return new TextRange(
|
||||
start: before.length, end: before.length + newText.length);
|
||||
}
|
||||
|
||||
TextRange _replaceOrAppend(TextRange range, String newText) {
|
||||
if (!range.isValid) return _append(newText);
|
||||
return _replace(range, newText);
|
||||
}
|
||||
|
||||
void commitCompletion(CompletionData completion) {
|
||||
// TODO(abarth): Not implemented.
|
||||
}
|
||||
|
||||
void commitCorrection(CorrectionData correction) {
|
||||
// TODO(abarth): Not implemented.
|
||||
}
|
||||
|
||||
void commitText(String text, int newCursorPosition) {
|
||||
// TODO(abarth): Why is |newCursorPosition| always 1?
|
||||
TextRange committedRange = _replaceOrAppend(composing, text);
|
||||
selection = new TextSelection.collapsed(offset: committedRange.end);
|
||||
composing = TextRange.empty;
|
||||
void updateEditingState(mojom.EditingState state) {
|
||||
text = state.text;
|
||||
selection = _getTextSelectionFromEditingState(state);
|
||||
composing = new TextRange(start: state.composingBase, end: state.composingExtent);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void deleteSurroundingText(int beforeLength, int afterLength) {
|
||||
TextRange beforeRange = new TextRange(
|
||||
start: selection.start - beforeLength, end: selection.start);
|
||||
int afterRangeEnd = math.min(selection.end + afterLength, text.length);
|
||||
TextRange afterRange =
|
||||
new TextRange(start: selection.end, end: afterRangeEnd);
|
||||
_delete(afterRange);
|
||||
_delete(beforeRange);
|
||||
selection = new TextSelection(
|
||||
baseOffset: math.max(selection.start - beforeLength, 0),
|
||||
extentOffset: math.max(selection.end - beforeLength, 0)
|
||||
);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setComposingRegion(int start, int end) {
|
||||
composing = new TextRange(start: start, end: end);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setComposingText(String text, int newCursorPosition) {
|
||||
// TODO(abarth): Why is |newCursorPosition| always 1?
|
||||
composing = _replaceOrAppend(composing, text);
|
||||
selection = new TextSelection.collapsed(offset: composing.end);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setSelection(int start, int end) {
|
||||
selection = new TextSelection(baseOffset: start, extentOffset: end);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void submit(SubmitAction action) {
|
||||
void submit(mojom.SubmitAction action) {
|
||||
composing = TextRange.empty;
|
||||
onSubmitted();
|
||||
}
|
||||
@ -162,10 +113,12 @@ class EditableString {
|
||||
_client.selection = selection;
|
||||
}
|
||||
|
||||
mojom.EditingState get editingState => _client.editingState;
|
||||
|
||||
/// A keyboard client stub that can be attached to a keyboard service.
|
||||
///
|
||||
/// See [Keyboard].
|
||||
KeyboardClientStub createStub() => _client.createStub();
|
||||
mojom.KeyboardClientStub createStub() => _client.createStub();
|
||||
|
||||
void didDetachKeyboard() {
|
||||
_client.composing = TextRange.empty;
|
||||
|
@ -5,31 +5,28 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mojo_services/keyboard/keyboard.mojom.dart';
|
||||
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../services/mock_services.dart';
|
||||
|
||||
class MockKeyboard implements KeyboardService {
|
||||
KeyboardClient client;
|
||||
class MockKeyboard implements mojom.Keyboard {
|
||||
mojom.KeyboardClient client;
|
||||
|
||||
void show(KeyboardClientStub client, KeyboardType type) {
|
||||
void setClient(mojom.KeyboardClientStub client, mojom.KeyboardConfiguration configuraiton) {
|
||||
this.client = client.impl;
|
||||
}
|
||||
|
||||
void showByRequest() {}
|
||||
void show() {}
|
||||
|
||||
void hide() {}
|
||||
|
||||
void setText(String text) {}
|
||||
|
||||
void setSelection(int start, int end) {}
|
||||
void setEditingState(mojom.EditingState state) {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockKeyboard mockKeyboard = new MockKeyboard();
|
||||
serviceMocker.registerMockService(KeyboardService.serviceName, mockKeyboard);
|
||||
serviceMocker.registerMockService(mojom.Keyboard.serviceName, mockKeyboard);
|
||||
|
||||
test('Editable text has consistent size', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
@ -56,7 +53,10 @@ void main() {
|
||||
void enterText(String testValue) {
|
||||
// Simulate entry of text through the keyboard.
|
||||
expect(mockKeyboard.client, isNotNull);
|
||||
mockKeyboard.client.setComposingText(testValue, testValue.length);
|
||||
mockKeyboard.client.updateEditingState(new mojom.EditingState()
|
||||
..text = testValue
|
||||
..composingBase = 0
|
||||
..composingExtent = testValue.length);
|
||||
|
||||
// Check that the onChanged event handler fired.
|
||||
expect(inputValue, equals(testValue));
|
||||
@ -109,46 +109,14 @@ void main() {
|
||||
checkCursorToggle();
|
||||
|
||||
// Try the test again with a nonempty EditableText.
|
||||
mockKeyboard.client.setComposingText('X', 1);
|
||||
mockKeyboard.client.updateEditingState(new mojom.EditingState()
|
||||
..text = 'X'
|
||||
..selectionBase = 1
|
||||
..selectionExtent = 1);
|
||||
checkCursorToggle();
|
||||
});
|
||||
});
|
||||
|
||||
test('Selection remains valid', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
GlobalKey inputKey = new GlobalKey();
|
||||
|
||||
Widget builder() {
|
||||
return new Center(
|
||||
child: new Material(
|
||||
child: new Input(
|
||||
key: inputKey,
|
||||
hintText: 'Placeholder'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
tester.pumpWidget(builder());
|
||||
|
||||
const String testValue = 'ABC';
|
||||
mockKeyboard.client.commitText(testValue, testValue.length);
|
||||
dynamic input = inputKey.currentState;
|
||||
|
||||
// Delete characters and verify that the selection follows the length
|
||||
// of the text.
|
||||
for (int i = 0; i < testValue.length; i++) {
|
||||
mockKeyboard.client.deleteSurroundingText(1, 0);
|
||||
expect(input.editableValue.selection.start, equals(testValue.length - i - 1));
|
||||
}
|
||||
|
||||
// Delete a characters when the text is empty. The selection should
|
||||
// remain at zero.
|
||||
mockKeyboard.client.deleteSurroundingText(1, 0);
|
||||
expect(input.editableValue.selection.start, equals(0));
|
||||
});
|
||||
});
|
||||
|
||||
test('hideText control test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
GlobalKey inputKey = new GlobalKey();
|
||||
@ -168,7 +136,10 @@ void main() {
|
||||
tester.pumpWidget(builder());
|
||||
|
||||
const String testValue = 'ABC';
|
||||
mockKeyboard.client.commitText(testValue, testValue.length);
|
||||
mockKeyboard.client.updateEditingState(new mojom.EditingState()
|
||||
..text = testValue
|
||||
..selectionBase = testValue.length
|
||||
..selectionExtent = testValue.length);
|
||||
|
||||
tester.pump();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user