Skip to content

Commit e0ea39f

Browse files
authored
Reland "Fix bug with clicking Copy or Select All within contextMenu" (flutter#122973)
Reland "Fix bug with clicking `Copy` or `Select All` within contextMenu"
1 parent e6e99e2 commit e0ea39f

File tree

2 files changed

+145
-3
lines changed

2 files changed

+145
-3
lines changed

packages/flutter/lib/src/widgets/selectable_region.dart

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
319319
/// {@macro flutter.rendering.RenderEditable.lastSecondaryTapDownPosition}
320320
Offset? lastSecondaryTapDownPosition;
321321

322+
/// The [SelectionOverlay] that is currently visible on the screen.
323+
///
324+
/// Can be null if there is no visible [SelectionOverlay].
325+
@visibleForTesting
326+
SelectionOverlay? get selectionOverlay => _selectionOverlay;
327+
322328
@override
323329
void initState() {
324330
super.initState();
@@ -988,11 +994,37 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
988994
selectionGeometry: _selectionDelegate.value,
989995
onCopy: () {
990996
_copy();
991-
hideToolbar();
997+
998+
// In Android copy should clear the selection.
999+
switch (defaultTargetPlatform) {
1000+
case TargetPlatform.android:
1001+
case TargetPlatform.fuchsia:
1002+
_clearSelection();
1003+
break;
1004+
case TargetPlatform.iOS:
1005+
hideToolbar(false);
1006+
break;
1007+
case TargetPlatform.linux:
1008+
case TargetPlatform.macOS:
1009+
case TargetPlatform.windows:
1010+
hideToolbar();
1011+
break;
1012+
}
9921013
},
9931014
onSelectAll: () {
994-
selectAll();
995-
hideToolbar();
1015+
switch (defaultTargetPlatform) {
1016+
case TargetPlatform.android:
1017+
case TargetPlatform.iOS:
1018+
case TargetPlatform.fuchsia:
1019+
selectAll(SelectionChangedCause.toolbar);
1020+
break;
1021+
case TargetPlatform.linux:
1022+
case TargetPlatform.macOS:
1023+
case TargetPlatform.windows:
1024+
selectAll();
1025+
hideToolbar();
1026+
break;
1027+
}
9961028
},
9971029
);
9981030
}

packages/flutter/test/widgets/selectable_region_test.dart

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ void main() {
926926
),
927927
),
928928
);
929+
929930
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
930931
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph2, 7)); // at the 'a'
931932
addTearDown(gesture.removePointer);
@@ -1708,6 +1709,115 @@ void main() {
17081709
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }),
17091710
);
17101711

1712+
testWidgets('the selection behavior when clicking `Copy` item in mobile platforms', (WidgetTester tester) async {
1713+
List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
1714+
await tester.pumpWidget(
1715+
MaterialApp(
1716+
home: SelectableRegion(
1717+
focusNode: FocusNode(),
1718+
selectionControls: materialTextSelectionHandleControls,
1719+
contextMenuBuilder: (
1720+
BuildContext context,
1721+
SelectableRegionState selectableRegionState,
1722+
) {
1723+
buttonItems = selectableRegionState.contextMenuButtonItems;
1724+
return const SizedBox.shrink();
1725+
},
1726+
child: const Text('How are you?'),
1727+
),
1728+
),
1729+
);
1730+
1731+
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
1732+
await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r'
1733+
await tester.pump(kLongPressTimeout);
1734+
// `are` is selected.
1735+
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
1736+
1737+
expect(buttonItems.length, 2);
1738+
expect(buttonItems[0].type, ContextMenuButtonType.copy);
1739+
1740+
// Press `Copy` item
1741+
buttonItems[0].onPressed.call();
1742+
1743+
final SelectableRegionState regionState = tester.state<SelectableRegionState>(find.byType(SelectableRegion));
1744+
1745+
// In Android copy should clear the selection.
1746+
switch(defaultTargetPlatform) {
1747+
case TargetPlatform.android:
1748+
case TargetPlatform.fuchsia:
1749+
expect(regionState.selectionOverlay, isNull);
1750+
expect(regionState.selectionOverlay?.startHandleLayerLink, isNull);
1751+
expect(regionState.selectionOverlay?.endHandleLayerLink, isNull);
1752+
break;
1753+
case TargetPlatform.iOS:
1754+
expect(regionState.selectionOverlay, isNotNull);
1755+
expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull);
1756+
expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull);
1757+
break;
1758+
case TargetPlatform.linux:
1759+
case TargetPlatform.macOS:
1760+
case TargetPlatform.windows:
1761+
expect(regionState.selectionOverlay, isNotNull);
1762+
break;
1763+
}
1764+
},
1765+
skip: kIsWeb, // [intended]
1766+
);
1767+
1768+
testWidgets('the handles do not disappear when clicking `Select all` item in mobile platforms', (WidgetTester tester) async {
1769+
List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
1770+
await tester.pumpWidget(
1771+
MaterialApp(
1772+
home: SelectableRegion(
1773+
focusNode: FocusNode(),
1774+
selectionControls: materialTextSelectionHandleControls,
1775+
contextMenuBuilder: (
1776+
BuildContext context,
1777+
SelectableRegionState selectableRegionState,
1778+
) {
1779+
buttonItems = selectableRegionState.contextMenuButtonItems;
1780+
return const SizedBox.shrink();
1781+
},
1782+
child: const Text('How are you?'),
1783+
),
1784+
),
1785+
);
1786+
1787+
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
1788+
await tester.longPressAt(textOffsetToPosition(paragraph1, 6)); // at the 'r'
1789+
await tester.pump(kLongPressTimeout);
1790+
// `are` is selected.
1791+
expect(paragraph1.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
1792+
1793+
expect(buttonItems.length, 2);
1794+
expect(buttonItems[1].type, ContextMenuButtonType.selectAll);
1795+
1796+
// Press `Select All` item
1797+
buttonItems[1].onPressed.call();
1798+
1799+
final SelectableRegionState regionState = tester.state<SelectableRegionState>(find.byType(SelectableRegion));
1800+
1801+
switch(defaultTargetPlatform) {
1802+
case TargetPlatform.android:
1803+
case TargetPlatform.iOS:
1804+
case TargetPlatform.fuchsia:
1805+
expect(regionState.selectionOverlay, isNotNull);
1806+
expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull);
1807+
expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull);
1808+
break;
1809+
case TargetPlatform.linux:
1810+
case TargetPlatform.macOS:
1811+
case TargetPlatform.windows:
1812+
// Test doesn't run these platforms.
1813+
break;
1814+
}
1815+
1816+
},
1817+
skip: kIsWeb, // [intended]
1818+
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.fuchsia }),
1819+
);
1820+
17111821
testWidgets('builds the correct button items', (WidgetTester tester) async {
17121822
Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{};
17131823
await tester.pumpWidget(

0 commit comments

Comments
 (0)