diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index ee68912c0dd..1985ecd094a 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -319,6 +319,12 @@ class SelectableRegionState extends State with TextSelectionDe /// {@macro flutter.rendering.RenderEditable.lastSecondaryTapDownPosition} Offset? lastSecondaryTapDownPosition; + /// The [SelectionOverlay] that is currently visible on the screen. + /// + /// Can be null if there is no visible [SelectionOverlay]. + @visibleForTesting + SelectionOverlay? get selectionOverlay => _selectionOverlay; + @override void initState() { super.initState(); @@ -988,11 +994,37 @@ class SelectableRegionState extends State with TextSelectionDe selectionGeometry: _selectionDelegate.value, onCopy: () { _copy(); - hideToolbar(); + + // In Android copy should clear the selection. + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + _clearSelection(); + break; + case TargetPlatform.iOS: + hideToolbar(false); + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + hideToolbar(); + break; + } }, onSelectAll: () { - selectAll(); - hideToolbar(); + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + selectAll(SelectionChangedCause.toolbar); + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + selectAll(); + hideToolbar(); + break; + } }, ); } diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index d721beb842b..035c034a567 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -926,6 +926,7 @@ void main() { ), ), ); + final RenderParagraph paragraph2 = tester.renderObject(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText))); final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph2, 7)); // at the 'a' addTearDown(gesture.removePointer); @@ -1708,6 +1709,115 @@ void main() { variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.android }), ); + testWidgets('the selection behavior when clicking `Copy` item in mobile platforms', (WidgetTester tester) async { + List buttonItems = []; + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonItems = selectableRegionState.contextMenuButtonItems; + return const SizedBox.shrink(); + }, + child: const Text('How are you?'), + ), + ), + ); + + final RenderParagraph paragraph1 = tester.renderObject(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText))); + await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r' + await tester.pump(kLongPressTimeout); + // `are` is selected. + expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7)); + + expect(buttonItems.length, 2); + expect(buttonItems[0].type, ContextMenuButtonType.copy); + + // Press `Copy` item + buttonItems[0].onPressed.call(); + + final SelectableRegionState regionState = tester.state(find.byType(SelectableRegion)); + + // In Android copy should clear the selection. + switch(defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + expect(regionState.selectionOverlay, isNull); + expect(regionState.selectionOverlay?.startHandleLayerLink, isNull); + expect(regionState.selectionOverlay?.endHandleLayerLink, isNull); + break; + case TargetPlatform.iOS: + expect(regionState.selectionOverlay, isNotNull); + expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull); + expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull); + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(regionState.selectionOverlay, isNotNull); + break; + } + }, + skip: kIsWeb, // [intended] + ); + + testWidgets('the handles do not disappear when clicking `Select all` item in mobile platforms', (WidgetTester tester) async { + List buttonItems = []; + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonItems = selectableRegionState.contextMenuButtonItems; + return const SizedBox.shrink(); + }, + child: const Text('How are you?'), + ), + ), + ); + + final RenderParagraph paragraph1 = tester.renderObject(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText))); + await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r' + await tester.pump(kLongPressTimeout); + // `are` is selected. + expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7)); + + expect(buttonItems.length, 2); + expect(buttonItems[1].type, ContextMenuButtonType.selectAll); + + // Press `Select All` item + buttonItems[1].onPressed.call(); + + final SelectableRegionState regionState = tester.state(find.byType(SelectableRegion)); + + switch(defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + expect(regionState.selectionOverlay, isNotNull); + expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull); + expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull); + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + // Test doesn't run these platforms. + break; + } + + }, + skip: kIsWeb, // [intended] + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.fuchsia }), + ); + testWidgets('builds the correct button items', (WidgetTester tester) async { Set buttonTypes = {}; await tester.pumpWidget(