Unverified Commit 045ee5fd authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Do not show handles when changing text selection via keyboard (#13338)

This contains a breaking API change to `SelectionChangedHandler`.

This is part of making text input accessible on Andriod (https://github.com/flutter/flutter/issues/12785).
parent 389e13f8
......@@ -331,8 +331,8 @@ class _TextFieldState extends State<TextField> {
_editableTextKey.currentState?.requestKeyboard();
}
void _onSelectionChanged(BuildContext context, bool longPress) {
if (longPress)
void _onSelectionChanged(BuildContext context, SelectionChangedCause cause) {
if (cause == SelectionChangedCause.longPress)
Feedback.forLongPress(context);
}
......@@ -365,7 +365,7 @@ class _TextFieldState extends State<TextField> {
: materialTextSelectionControls,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
onSelectionChanged: (TextSelection _, bool longPress) => _onSelectionChanged(context, longPress),
onSelectionChanged: (TextSelection _, SelectionChangedCause cause) => _onSelectionChanged(context, cause),
inputFormatters: formatters,
),
);
......
......@@ -22,7 +22,26 @@ const double _kCaretWidth = 1.0; // pixels
/// (including the cursor location).
///
/// Used by [RenderEditable.onSelectionChanged].
typedef void SelectionChangedHandler(TextSelection selection, RenderEditable renderObject, bool longPress);
typedef void SelectionChangedHandler(TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause);
/// Indicates what triggered the change in selected text (including changes to
/// the cursor location).
enum SelectionChangedCause {
/// The user tapped on the text and that caused the selection (or the location
/// of the cursor) to change.
tap,
/// The user long-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
longPress,
/// The user used the keyboard to change the selection or the location of the
/// cursor.
///
/// Keyboard-triggered selection changes may be caused by the IME as well as
/// by accessibility tools (e.g. TalkBack on Android).
keyboard,
}
/// Signature for the callback that reports when the caret location changes.
///
......@@ -523,7 +542,7 @@ class RenderEditable extends RenderBox {
_lastTapDownPosition = null;
if (onSelectionChanged != null) {
final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(globalPosition));
onSelectionChanged(new TextSelection.fromPosition(position), this, false);
onSelectionChanged(new TextSelection.fromPosition(position), this, SelectionChangedCause.tap);
}
}
......@@ -539,7 +558,7 @@ class RenderEditable extends RenderBox {
_longPressPosition = null;
if (onSelectionChanged != null) {
final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(globalPosition));
onSelectionChanged(_selectWordAtOffset(position), this, true);
onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.longPress);
}
}
......
......@@ -20,10 +20,11 @@ import 'scrollable.dart';
import 'text_selection.dart';
export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType;
export 'package:flutter/rendering.dart' show SelectionChangedCause;
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
typedef void SelectionChangedCallback(TextSelection selection, bool longPress);
typedef void SelectionChangedCallback(TextSelection selection, SelectionChangedCause cause);
const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500);
......@@ -490,7 +491,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, bool longPress) {
void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
widget.controller.selection = selection;
// This will show the keyboard for all selection changes on the
......@@ -509,12 +510,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onSelectionOverlayChanged: _handleSelectionOverlayChanged,
selectionControls: widget.selectionControls,
);
if (_value.text.isNotEmpty || longPress)
final bool longPress = cause == SelectionChangedCause.longPress;
if (cause != SelectionChangedCause.keyboard && (_value.text.isNotEmpty || longPress))
_selectionOverlay.showHandles();
if (longPress)
_selectionOverlay.showToolbar();
if (widget.onSelectionChanged != null)
widget.onSelectionChanged(selection, longPress);
widget.onSelectionChanged(selection, cause);
}
}
......@@ -566,6 +568,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@visibleForTesting
Duration get cursorBlinkInterval => _kCursorBlinkHalfPeriod;
/// The current status of the text selection handles.
@visibleForTesting
TextSelectionOverlay get selectionOverlay => _selectionOverlay;
int _obscureShowCharTicksPending = 0;
int _obscureLatestCharIndex;
......
......@@ -294,6 +294,12 @@ class TextSelectionOverlay implements TextSelectionDelegate {
_toolbar?.markNeedsBuild();
}
/// Whether the handles are currently visible.
bool get handlesAreVisible => _handles != null;
/// Whether the toolbar is currently visible.
bool get toolbarIsVisible => _toolbar != null;
/// Hides the overlay.
void hide() {
if (_handles != null) {
......
......@@ -323,4 +323,37 @@ void main() {
value: value2,
));
});
testWidgets('changing selection with keyboard does not show handles', (WidgetTester tester) async {
const String value1 = 'Hello World';
controller.text = value1;
await tester.pumpWidget(new MaterialApp(
home: new EditableText(
controller: controller,
selectionControls: materialTextSelectionControls,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
));
// Simulate selection change via tap to show handles.
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
render.onSelectionChanged(const TextSelection.collapsed(offset: 4), render, SelectionChangedCause.tap);
await tester.pumpAndSettle();
final EditableTextState textState = tester.state(find.byType(EditableText));
expect(textState.selectionOverlay.handlesAreVisible, isTrue);
expect(textState.selectionOverlay.textEditingValue.selection, const TextSelection.collapsed(offset: 4));
// Simulate selection change via keyboard and expect handles to disappear.
render.onSelectionChanged(const TextSelection.collapsed(offset: 10), render, SelectionChangedCause.keyboard);
await tester.pumpAndSettle();
expect(textState.selectionOverlay.handlesAreVisible, isFalse);
expect(textState.selectionOverlay.textEditingValue.selection, const TextSelection.collapsed(offset: 10));
});
}
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