Unverified Commit 1794e81f authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Keep the selection after 'Copy' pressed on iOS. (#76327)

parent df25cfe2
...@@ -6,6 +6,7 @@ import 'dart:math' as math; ...@@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'localizations.dart'; import 'localizations.dart';
import 'text_selection_toolbar.dart'; import 'text_selection_toolbar.dart';
......
...@@ -774,7 +774,11 @@ abstract class TextSelectionDelegate { ...@@ -774,7 +774,11 @@ abstract class TextSelectionDelegate {
set textEditingValue(TextEditingValue value); set textEditingValue(TextEditingValue value);
/// Hides the text selection toolbar. /// Hides the text selection toolbar.
void hideToolbar(); ///
/// By default, hideHandles is true, and the toolbar is hidden along with its
/// handles. If hideHandles is set to false, then the toolbar will be hidden
/// but the handles will remain.
void hideToolbar([bool hideHandles = true]);
/// Brings the provided [TextPosition] into the visible area of the text /// Brings the provided [TextPosition] into the visible area of the text
/// input. /// input.
......
...@@ -2505,8 +2505,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2505,8 +2505,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
@override @override
void hideToolbar() { void hideToolbar([bool hideHandles = true]) {
_selectionOverlay?.hide(); if (hideHandles) {
// Hide the handles and the toolbar.
_selectionOverlay?.hide();
} else {
// Hide only the toolbar but not the handles.
_selectionOverlay?.hideToolbar();
}
} }
/// Toggles the visibility of the toolbar. /// Toggles the visibility of the toolbar.
......
...@@ -228,12 +228,26 @@ abstract class TextSelectionControls { ...@@ -228,12 +228,26 @@ abstract class TextSelectionControls {
text: value.selection.textInside(value.text), text: value.selection.textInside(value.text),
)); ));
clipboardStatus?.update(); clipboardStatus?.update();
delegate.textEditingValue = TextEditingValue(
text: value.text,
selection: TextSelection.collapsed(offset: value.selection.end),
);
delegate.bringIntoView(delegate.textEditingValue.selection.extent); delegate.bringIntoView(delegate.textEditingValue.selection.extent);
delegate.hideToolbar();
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
// Hide the toolbar, but keep the selection and keep the handles.
delegate.hideToolbar(false);
return;
case TargetPlatform.macOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
// Collapse the selection and hide the toolbar and handles.
delegate.textEditingValue = TextEditingValue(
text: value.text,
selection: TextSelection.collapsed(offset: value.selection.end),
);
delegate.hideToolbar();
return;
}
} }
/// Paste the current clipboard selection (obtained from [Clipboard]) into /// Paste the current clipboard selection (obtained from [Clipboard]) into
......
...@@ -197,17 +197,34 @@ void main() { ...@@ -197,17 +197,34 @@ void main() {
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, index)); await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(controller.selection.isCollapsed, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 7);
// Paste is showing even though clipboard is empty. // Paste is showing even though clipboard is empty.
expect(find.text('Paste'), findsOneWidget); expect(find.text('Paste'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget); expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget); expect(find.text('Cut'), findsOneWidget);
expect(find.descendant(
of: find.byType(Overlay),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionHandleOverlay'),
), findsNWidgets(2));
// Tap copy to add something to the clipboard and close the menu. // Tap copy to add something to the clipboard and close the menu.
await tester.tapAt(tester.getCenter(find.text('Copy'))); await tester.tapAt(tester.getCenter(find.text('Copy')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The menu is gone, but the handles are visible on the existing selection.
expect(find.text('Copy'), findsNothing); expect(find.text('Copy'), findsNothing);
expect(find.text('Cut'), findsNothing); expect(find.text('Cut'), findsNothing);
expect(find.text('Paste'), findsNothing);
expect(controller.selection.isCollapsed, isFalse);
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 7);
expect(find.descendant(
of: find.byType(Overlay),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionHandleOverlay'),
), findsNWidgets(2));
// Double tap to show the menu again. // Double tap to show the menu again.
await tester.tapAt(textOffsetToPosition(tester, index)); await tester.tapAt(textOffsetToPosition(tester, index));
......
...@@ -19,7 +19,7 @@ class FakeEditableTextState with TextSelectionDelegate { ...@@ -19,7 +19,7 @@ class FakeEditableTextState with TextSelectionDelegate {
TextEditingValue textEditingValue = TextEditingValue.empty; TextEditingValue textEditingValue = TextEditingValue.empty;
@override @override
void hideToolbar() { } void hideToolbar([bool hideHandles = true]) { }
@override @override
void bringIntoView(TextPosition position) { } void bringIntoView(TextPosition position) { }
......
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