SelectionArea on iOS should toggle the context menu when tapping on the previous selection (#132851)

https://github.com/flutter/flutter/assets/948037/210fdee4-d922-422b-a257-4ee586a3814f

Related: https://github.com/flutter/flutter/issues/129583
This commit is contained in:
Renzo Olivares 2023-08-18 16:45:16 -07:00 committed by GitHub
parent d5a162b788
commit 2ecb8866a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 1 deletions

View File

@ -341,7 +341,20 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
_gestureRecognizers[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance.onTap = _clearSelection;
instance.onTapUp = (TapUpDetails details) {
if (defaultTargetPlatform == TargetPlatform.iOS && _positionIsOnActiveSelection(globalPosition: details.globalPosition)) {
// On iOS when the tap occurs on the previous selection, instead of
// moving the selection, the context menu will be toggled.
final bool toolbarIsVisible = _selectionOverlay?.toolbarIsVisible ?? false;
if (toolbarIsVisible) {
hideToolbar(false);
} else {
_showToolbar(location: details.globalPosition);
}
} else {
_clearSelection();
}
};
instance.onSecondaryTapDown = _handleRightClickDown;
},
);

View File

@ -886,6 +886,78 @@ void main() {
await gesture.up();
});
testWidgets(
'single tap on the previous selection toggles the toolbar on iOS',
(WidgetTester tester) async {
Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{};
final UniqueKey toolbarKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: SelectableRegion(
focusNode: FocusNode(),
selectionControls: materialTextSelectionHandleControls,
contextMenuBuilder: (
BuildContext context,
SelectableRegionState selectableRegionState,
) {
buttonTypes = selectableRegionState.contextMenuButtonItems
.map((ContextMenuButtonItem buttonItem) => buttonItem.type)
.toSet();
return SizedBox.shrink(key: toolbarKey);
},
child: const Column(
children: <Widget>[
Text('How are you?'),
Text('Good, and you?'),
Text('Fine, thank you.'),
],
),
),
),
);
expect(buttonTypes.isEmpty, true);
expect(find.byKey(toolbarKey), findsNothing);
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2));
addTearDown(gesture.removePointer);
await tester.pump(const Duration(milliseconds: 500));
await gesture.up();
await tester.pumpAndSettle();
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
expect(buttonTypes, contains(ContextMenuButtonType.copy));
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
expect(find.byKey(toolbarKey), findsOneWidget);
await gesture.down(textOffsetToPosition(paragraph, 2));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
expect(buttonTypes, contains(ContextMenuButtonType.copy));
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
expect(find.byKey(toolbarKey), findsNothing);
await gesture.down(textOffsetToPosition(paragraph, 2));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
expect(buttonTypes, contains(ContextMenuButtonType.copy));
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
expect(find.byKey(toolbarKey), findsOneWidget);
// Clear selection.
await tester.tapAt(textOffsetToPosition(paragraph, 9));
await tester.pump();
expect(paragraph.selections.isEmpty, true);
expect(find.byKey(toolbarKey), findsNothing);
},
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
skip: kIsWeb, // [intended] Web uses its native context menu.
);
testWidgets(
'right-click mouse can select word at position on Apple platforms',
(WidgetTester tester) async {