Commit fade0dc0 authored by Adam Barth's avatar Adam Barth

Update to new editing.mojom

Now the keyboard is responsible for maintaining the state of the text
field.
parent 9e784f0c
......@@ -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;
}
void _delete(TextRange range) {
if (range.isCollapsed || !range.isValid) return;
text = range.textBefore(text) + range.textAfter(text);
}
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.
mojom.KeyboardClientStub createStub() {
return new mojom.KeyboardClientStub.unbound()..impl = this;
}
void commitCorrection(CorrectionData correction) {
// TODO(abarth): Not implemented.
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;
}
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;
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);
void updateEditingState(mojom.EditingState state) {
text = state.text;
selection = _getTextSelectionFromEditingState(state);
composing = new TextRange(start: state.composingBase, end: state.composingExtent);
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();
});
......
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