From fade0dc097c9193e36df80c615aeef3bed4d1d12 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 2 Feb 2016 08:45:25 -0800 Subject: [PATCH] Update to new editing.mojom Now the keyboard is responsible for maintaining the state of the text field. --- packages/flutter/lib/src/material/input.dart | 17 +-- .../flutter/lib/src/services/keyboard.dart | 41 +++---- .../flutter/lib/src/widgets/editable.dart | 107 +++++------------- packages/flutter/test/widget/input_test.dart | 67 ++++------- 4 files changed, 75 insertions(+), 157 deletions(-) diff --git a/packages/flutter/lib/src/material/input.dart b/packages/flutter/lib/src/material/input.dart index e1092dbb92..1597ef4059 100644 --- a/packages/flutter/lib/src/material/input.dart +++ b/packages/flutter/lib/src/material/input.dart @@ -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 { 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 { 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 { void _handleSelectionChanged(TextSelection selection) { if (_isAttachedToKeyboard) { - _keyboardHandle.setSelection(selection.start, selection.end); + _editableString.setSelection(selection); + _keyboardHandle.setEditingState(_editableString.editingState); } else { _editableString.setSelection(selection); _requestKeyboard(); diff --git a/packages/flutter/lib/src/services/keyboard.dart b/packages/flutter/lib/src/services/keyboard.dart index 8e58165ef4..43751f8253 100644 --- a/packages/flutter/lib/src/services/keyboard.dart +++ b/packages/flutter/lib/src/services/keyboard.dart @@ -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); diff --git a/packages/flutter/lib/src/widgets/editable.dart b/packages/flutter/lib/src/widgets/editable.dart index 361db24cd6..3eb9e8e6df 100644 --- a/packages/flutter/lib/src/widgets/editable.dart +++ b/packages/flutter/lib/src/widgets/editable.dart @@ -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; diff --git a/packages/flutter/test/widget/input_test.dart b/packages/flutter/test/widget/input_test.dart index 904025dde9..a885ca2595 100644 --- a/packages/flutter/test/widget/input_test.dart +++ b/packages/flutter/test/widget/input_test.dart @@ -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(); });