Control, Shift and Arrow Key functionality for Chromebook (#20204)
* added keyboard functionatliy to android builds * Added tests * almost ready for review * ready for review * Fixes * final comments * final commit * removing raw keyboard changes * removing raw keyboard changes * removing raw keyboard changes * actual last commit * fixed the imports * a few more changes * A few more changes * a few changes * Final changes * Final changes2 * final actual commit for real * final actual commit for real2 * final actual commit for real3 * final actual commit for real4 * final * final 2 * f * f2 * fin * fin 2 * fin3 * fin4
This commit is contained in:
parent
dc5a5c18a9
commit
3900d42bfa
@ -198,6 +198,186 @@ class RenderEditable extends RenderBox {
|
|||||||
|
|
||||||
Rect _lastCaretRect;
|
Rect _lastCaretRect;
|
||||||
|
|
||||||
|
static const int _kLeftArrowCode = 21;
|
||||||
|
static const int _kRightArrowCode = 22;
|
||||||
|
static const int _kUpArrowCode = 19;
|
||||||
|
static const int _kDownArrowCode = 20;
|
||||||
|
|
||||||
|
// The extent offset of the current selection
|
||||||
|
int _extentOffset = -1;
|
||||||
|
|
||||||
|
// The base offset of the current selection
|
||||||
|
int _baseOffset = -1;
|
||||||
|
|
||||||
|
// Holds the last location the user selected in the case that he selects all
|
||||||
|
// the way to the end or beginning of the field.
|
||||||
|
int _previousCursorLocation = -1;
|
||||||
|
|
||||||
|
// Whether we should reset the location of the cursor in the case the user
|
||||||
|
// selects all the way to the end or the beginning of a field.
|
||||||
|
bool _resetCursor = false;
|
||||||
|
|
||||||
|
static const int _kShiftMask = 1; // https://developer.android.com/reference/android/view/KeyEvent.html#META_SHIFT_ON
|
||||||
|
static const int _kControlMask = 1 << 12; // https://developer.android.com/reference/android/view/KeyEvent.html#META_CTRL_ON
|
||||||
|
|
||||||
|
// TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
|
||||||
|
void _handleKeyEvent(RawKeyEvent keyEvent){
|
||||||
|
if (defaultTargetPlatform != TargetPlatform.android)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (keyEvent is RawKeyUpEvent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final RawKeyEventDataAndroid rawAndroidEvent = keyEvent.data;
|
||||||
|
final int pressedKeyCode = rawAndroidEvent.keyCode;
|
||||||
|
final int pressedKeyMetaState = rawAndroidEvent.metaState;
|
||||||
|
|
||||||
|
if (selection.isCollapsed) {
|
||||||
|
_extentOffset = selection.extentOffset;
|
||||||
|
_baseOffset = selection.baseOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current key states
|
||||||
|
final bool shift = pressedKeyMetaState & _kShiftMask > 0;
|
||||||
|
final bool ctrl = pressedKeyMetaState & _kControlMask > 0;
|
||||||
|
|
||||||
|
final bool rightArrow = pressedKeyCode == _kRightArrowCode;
|
||||||
|
final bool leftArrow = pressedKeyCode == _kLeftArrowCode;
|
||||||
|
final bool upArrow = pressedKeyCode == _kUpArrowCode;
|
||||||
|
final bool downArrow = pressedKeyCode == _kDownArrowCode;
|
||||||
|
final bool arrow = leftArrow || rightArrow || upArrow || downArrow;
|
||||||
|
|
||||||
|
// We will only move select or more the caret if an arrow is pressed
|
||||||
|
if (arrow) {
|
||||||
|
int newOffset = _extentOffset;
|
||||||
|
|
||||||
|
// Because the user can use multiple keys to change how he selects
|
||||||
|
// the new offset variable is threaded through these four functions
|
||||||
|
// and potentially changes after each one.
|
||||||
|
if (ctrl)
|
||||||
|
newOffset = _handleControl(rightArrow, leftArrow, ctrl, newOffset);
|
||||||
|
newOffset = _handleHorizontalArrows(rightArrow, leftArrow, shift, newOffset);
|
||||||
|
if (downArrow || upArrow)
|
||||||
|
newOffset = _handleVerticalArrows(upArrow, downArrow, shift, newOffset);
|
||||||
|
newOffset = _handleShift(rightArrow, leftArrow, shift, newOffset);
|
||||||
|
|
||||||
|
_extentOffset = newOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles full word traversal using control.
|
||||||
|
int _handleControl(bool rightArrow, bool leftArrow, bool ctrl, int newOffset) {
|
||||||
|
// If control is pressed, we will decide which way to look for a word
|
||||||
|
// based on which arrow is pressed.
|
||||||
|
if (leftArrow && _extentOffset > 2) {
|
||||||
|
final TextSelection textSelection = _selectWordAtOffset(new TextPosition(offset: _extentOffset - 2));
|
||||||
|
newOffset = textSelection.baseOffset + 1;
|
||||||
|
} else if (rightArrow && _extentOffset < text.text.length - 2) {
|
||||||
|
final TextSelection textSelection = _selectWordAtOffset(new TextPosition(offset: _extentOffset + 1));
|
||||||
|
newOffset = textSelection.extentOffset - 1;
|
||||||
|
}
|
||||||
|
return newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _handleHorizontalArrows(bool rightArrow, bool leftArrow, bool shift, int newOffset) {
|
||||||
|
// Set the new offset to be +/- 1 depending on which arrow is pressed
|
||||||
|
// If shift is down, we also want to update the previous cursor location
|
||||||
|
if (rightArrow && _extentOffset < text.text.length) {
|
||||||
|
newOffset += 1;
|
||||||
|
if (shift)
|
||||||
|
_previousCursorLocation += 1;
|
||||||
|
}
|
||||||
|
if (leftArrow && _extentOffset > 0) {
|
||||||
|
newOffset -= 1;
|
||||||
|
if (shift)
|
||||||
|
_previousCursorLocation -= 1;
|
||||||
|
}
|
||||||
|
return newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles moving the cursor vertically as well as taking care of the
|
||||||
|
// case where the user moves the cursor to the end or beginning of the text
|
||||||
|
// and then back up or down.
|
||||||
|
int _handleVerticalArrows(bool upArrow, bool downArrow, bool shift, int newOffset) {
|
||||||
|
// The caret offset gives a location in the upper left hand corner of
|
||||||
|
// the caret so the middle of the line above is a half line above that
|
||||||
|
// point and the line below is 1.5 lines below that point.
|
||||||
|
final double plh = _textPainter.preferredLineHeight;
|
||||||
|
final double verticalOffset = upArrow ? -0.5 * plh : 1.5 * plh;
|
||||||
|
|
||||||
|
final Offset caretOffset = _textPainter.getOffsetForCaret(new TextPosition(offset: _extentOffset), _caretPrototype);
|
||||||
|
final Offset caretOffsetTranslated = caretOffset.translate(0.0, verticalOffset);
|
||||||
|
final TextPosition position = _textPainter.getPositionForOffset(caretOffsetTranslated);
|
||||||
|
|
||||||
|
// To account for the possibility where the user vertically highlights
|
||||||
|
// all the way to the top or bottom of the text, we hold the previous
|
||||||
|
// cursor location. This allows us to restore to this position in the
|
||||||
|
// case that the user wants to unhighlight some text.
|
||||||
|
if (position.offset == _extentOffset) {
|
||||||
|
if (downArrow)
|
||||||
|
newOffset = text.text.length;
|
||||||
|
else if (upArrow)
|
||||||
|
newOffset = 0;
|
||||||
|
_resetCursor = shift;
|
||||||
|
} else if (_resetCursor && shift) {
|
||||||
|
newOffset = _previousCursorLocation;
|
||||||
|
_resetCursor = false;
|
||||||
|
} else {
|
||||||
|
newOffset = position.offset;
|
||||||
|
_previousCursorLocation = newOffset;
|
||||||
|
}
|
||||||
|
return newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the selection of text or removal of the selection and placing
|
||||||
|
// of the caret.
|
||||||
|
int _handleShift(bool rightArrow, bool leftArrow, bool shift, int newOffset) {
|
||||||
|
if (onSelectionChanged == null)
|
||||||
|
return newOffset;
|
||||||
|
// In the text_selection class, a TextSelection is defined such that the
|
||||||
|
// base offset is always less than the extent offset.
|
||||||
|
if (shift) {
|
||||||
|
if (_baseOffset < newOffset) {
|
||||||
|
onSelectionChanged(
|
||||||
|
new TextSelection(
|
||||||
|
baseOffset: _baseOffset,
|
||||||
|
extentOffset: newOffset
|
||||||
|
),
|
||||||
|
this,
|
||||||
|
SelectionChangedCause.keyboard,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onSelectionChanged(
|
||||||
|
new TextSelection(
|
||||||
|
baseOffset: newOffset,
|
||||||
|
extentOffset: _baseOffset
|
||||||
|
),
|
||||||
|
this,
|
||||||
|
SelectionChangedCause.keyboard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We want to put the cursor at the correct location depending on which
|
||||||
|
// arrow is used while there is a selection.
|
||||||
|
if (!selection.isCollapsed) {
|
||||||
|
if (leftArrow)
|
||||||
|
newOffset = _baseOffset < _extentOffset ? _baseOffset : _extentOffset;
|
||||||
|
else if (rightArrow)
|
||||||
|
newOffset = _baseOffset > _extentOffset ? _baseOffset : _extentOffset;
|
||||||
|
}
|
||||||
|
onSelectionChanged(
|
||||||
|
new TextSelection.fromPosition(
|
||||||
|
new TextPosition(
|
||||||
|
offset: newOffset
|
||||||
|
)
|
||||||
|
),
|
||||||
|
this,
|
||||||
|
SelectionChangedCause.keyboard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/// Marks the render object as needing to be laid out again and have its text
|
/// Marks the render object as needing to be laid out again and have its text
|
||||||
/// metrics recomputed.
|
/// metrics recomputed.
|
||||||
///
|
///
|
||||||
@ -300,11 +480,23 @@ class RenderEditable extends RenderBox {
|
|||||||
/// Whether the editable is currently focused.
|
/// Whether the editable is currently focused.
|
||||||
bool get hasFocus => _hasFocus;
|
bool get hasFocus => _hasFocus;
|
||||||
bool _hasFocus;
|
bool _hasFocus;
|
||||||
|
bool _listenerAttached = false;
|
||||||
set hasFocus(bool value) {
|
set hasFocus(bool value) {
|
||||||
assert(value != null);
|
assert(value != null);
|
||||||
if (_hasFocus == value)
|
if (_hasFocus == value)
|
||||||
return;
|
return;
|
||||||
_hasFocus = value;
|
_hasFocus = value;
|
||||||
|
if (_hasFocus) {
|
||||||
|
assert(!_listenerAttached);
|
||||||
|
RawKeyboard.instance.addListener(_handleKeyEvent);
|
||||||
|
_listenerAttached = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(_listenerAttached);
|
||||||
|
RawKeyboard.instance.removeListener(_handleKeyEvent);
|
||||||
|
_listenerAttached = false;
|
||||||
|
}
|
||||||
|
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,6 +766,8 @@ class RenderEditable extends RenderBox {
|
|||||||
void detach() {
|
void detach() {
|
||||||
_offset.removeListener(markNeedsPaint);
|
_offset.removeListener(markNeedsPaint);
|
||||||
_showCursor.removeListener(markNeedsPaint);
|
_showCursor.removeListener(markNeedsPaint);
|
||||||
|
if (_listenerAttached)
|
||||||
|
RawKeyboard.instance.removeListener(_handleKeyEvent);
|
||||||
super.detach();
|
super.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1764,6 +1764,316 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void sendFakeKeyEvent(Map<String, dynamic> data) {
|
||||||
|
BinaryMessages.handlePlatformMessage(
|
||||||
|
SystemChannels.keyEvent.name,
|
||||||
|
SystemChannels.keyEvent.codec.encodeMessage(data),
|
||||||
|
(ByteData data) { },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendKeyEventWithCode(int code, bool down, bool shiftDown, bool ctrlDown) {
|
||||||
|
|
||||||
|
int metaState = shiftDown ? 1 : 0;
|
||||||
|
if (ctrlDown)
|
||||||
|
metaState |= 1 << 12;
|
||||||
|
|
||||||
|
sendFakeKeyEvent(<String, dynamic>{
|
||||||
|
'type': down ? 'keydown' : 'keyup',
|
||||||
|
'keymap': 'android',
|
||||||
|
'keyCode' : code,
|
||||||
|
'hidUsage': 0x04,
|
||||||
|
'codePoint': 0x64,
|
||||||
|
'metaState': metaState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group('Keyboard Tests', (){
|
||||||
|
TextEditingController controller;
|
||||||
|
|
||||||
|
setUp( () {
|
||||||
|
controller = new TextEditingController();
|
||||||
|
});
|
||||||
|
|
||||||
|
MaterialApp setupWidget() {
|
||||||
|
|
||||||
|
final FocusNode focusNode = new FocusNode();
|
||||||
|
controller = new TextEditingController();
|
||||||
|
|
||||||
|
return new MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: new RawKeyboardListener(
|
||||||
|
focusNode: focusNode,
|
||||||
|
onKey: null,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
) ,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('Shift test 1', (WidgetTester tester) async{
|
||||||
|
|
||||||
|
await tester.pumpWidget(setupWidget());
|
||||||
|
const String testValue = 'a big house';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown, SHIFT_ON
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Control Shift test', (WidgetTester tester) async{
|
||||||
|
await tester.pumpWidget(setupWidget());
|
||||||
|
const String testValue = 'their big house';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(22, true, true, true); // RIGHT_ARROW keydown SHIFT_ON, CONTROL_ON
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Down and up test', (WidgetTester tester) async{
|
||||||
|
await tester.pumpWidget(setupWidget());
|
||||||
|
const String testValue = 'a big house';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 11);
|
||||||
|
|
||||||
|
sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('Down and up test 2', (WidgetTester tester) async{
|
||||||
|
await tester.pumpWidget(setupWidget());
|
||||||
|
const String testValue = 'a big house\njumped over a mouse\nOne more line yay'; // 11 \n 19
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 12);
|
||||||
|
|
||||||
|
sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 32);
|
||||||
|
|
||||||
|
sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 12);
|
||||||
|
|
||||||
|
sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
||||||
|
|
||||||
|
sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Changing positions of text fields', (WidgetTester tester) async{
|
||||||
|
|
||||||
|
final FocusNode focusNode = new FocusNode();
|
||||||
|
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
||||||
|
|
||||||
|
final TextEditingController c1 = new TextEditingController();
|
||||||
|
final TextEditingController c2 = new TextEditingController();
|
||||||
|
final Key key1 = new UniqueKey();
|
||||||
|
final Key key2 = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home:
|
||||||
|
Material(
|
||||||
|
child: new RawKeyboardListener(
|
||||||
|
focusNode: focusNode,
|
||||||
|
onKey: events.add,
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
TextField(
|
||||||
|
key: key1,
|
||||||
|
controller: c1,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
key: key2,
|
||||||
|
controller: c2,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const String testValue = 'a big house';
|
||||||
|
await tester.enterText(find.byType(TextField).first, testValue);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField).first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home:
|
||||||
|
Material(
|
||||||
|
child: new RawKeyboardListener(
|
||||||
|
focusNode: focusNode,
|
||||||
|
onKey: events.add,
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
TextField(
|
||||||
|
key: key2,
|
||||||
|
controller: c2,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
key: key1,
|
||||||
|
controller: c1,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('Changing focus test', (WidgetTester tester) async {
|
||||||
|
final FocusNode focusNode = new FocusNode();
|
||||||
|
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
||||||
|
|
||||||
|
final TextEditingController c1 = new TextEditingController();
|
||||||
|
final TextEditingController c2 = new TextEditingController();
|
||||||
|
final Key key1 = new UniqueKey();
|
||||||
|
final Key key2 = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
home:
|
||||||
|
Material(
|
||||||
|
child: new RawKeyboardListener(
|
||||||
|
focusNode: focusNode,
|
||||||
|
onKey: events.add,
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
TextField(
|
||||||
|
key: key1,
|
||||||
|
controller: c1,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
key: key2,
|
||||||
|
controller: c2,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField).first);
|
||||||
|
|
||||||
|
const String testValue = 'a big house';
|
||||||
|
await tester.enterText(find.byType(TextField).first, testValue);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 5);
|
||||||
|
expect(c2.selection.extentOffset - c2.selection.baseOffset, 0);
|
||||||
|
|
||||||
|
await tester.idle();
|
||||||
|
await tester.tap(find.byType(TextField).last);
|
||||||
|
|
||||||
|
await tester.enterText(find.byType(TextField).last, testValue);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i += 1) {
|
||||||
|
sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
|
||||||
|
expect(c2.selection.extentOffset - c2.selection.baseOffset, 5);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = new TextEditingController();
|
final TextEditingController controller = new TextEditingController();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user