From a4d570f0dd29afba3481732d442dd3c9900a8efa Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 29 Jul 2020 08:26:05 -0700 Subject: [PATCH] SelectableText handles after Select All (#62072) --- .../lib/src/material/selectable_text.dart | 17 +++++ .../lib/src/widgets/text_selection.dart | 2 - .../test/widgets/selectable_text_test.dart | 73 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index ef0e67f4dad..857238af42b 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -477,27 +477,44 @@ class _SelectableTextState extends State with AutomaticKeepAlive _controller = _TextSpanEditingController( textSpan: widget.textSpan ?? TextSpan(text: widget.data) ); + _controller.addListener(_onControllerChanged); } @override void didUpdateWidget(SelectableText oldWidget) { super.didUpdateWidget(oldWidget); if (widget.data != oldWidget.data || widget.textSpan != oldWidget.textSpan) { + _controller.removeListener(_onControllerChanged); _controller = _TextSpanEditingController( textSpan: widget.textSpan ?? TextSpan(text: widget.data) ); + _controller.addListener(_onControllerChanged); } if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) { _showSelectionHandles = false; + } else { + _showSelectionHandles = true; } } @override void dispose() { _focusNode?.dispose(); + _controller.removeListener(_onControllerChanged); super.dispose(); } + void _onControllerChanged() { + final bool showSelectionHandles = !_effectiveFocusNode.hasFocus + || !_controller.selection.isCollapsed; + if (showSelectionHandles == _showSelectionHandles) { + return; + } + setState(() { + _showSelectionHandles = showSelectionHandles; + }); + } + void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) { final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); if (willShowSelectionHandles != _showSelectionHandles) { diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 73719f78245..aa67e7d1cda 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -440,8 +440,6 @@ class TextSelectionOverlay { OverlayEntry(builder: (BuildContext context) => _buildHandle(context, _TextSelectionHandlePosition.end)), ]; - - Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor).insertAll(_handles); } diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index dcbbaf54439..fab4df23136 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -3894,4 +3894,77 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); }); + + testWidgets('The handles show after pressing Select All', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Material( + child: SelectableText('abc def ghi'), + ), + ), + ); + + // Long press at 'e' in 'def'. + final Offset ePos = textOffsetToPosition(tester, 5); + await tester.longPressAt(ePos); + await tester.pumpAndSettle(); + + expect(find.text('Select all'), findsOneWidget); + expect(find.text('Copy'), findsOneWidget); + expect(find.text('Paste'), findsNothing); + expect(find.text('Cut'), findsNothing); + EditableTextState editableText = tester.state(find.byType(EditableText)); + expect(editableText.selectionOverlay.handlesAreVisible, isTrue); + expect(editableText.selectionOverlay.toolbarIsVisible, isTrue); + + await tester.tap(find.text('Select all')); + await tester.pump(); + expect(find.text('Copy'), findsOneWidget); + expect(find.text('Select all'), findsNothing); + expect(find.text('Paste'), findsNothing); + expect(find.text('Cut'), findsNothing); + editableText = tester.state(find.byType(EditableText)); + expect(editableText.selectionOverlay.handlesAreVisible, isTrue); + }, + variant: const TargetPlatformVariant({ + TargetPlatform.android, + TargetPlatform.fuchsia, + TargetPlatform.linux, + TargetPlatform.windows, + }), + ); + + testWidgets('The handles show after pressing Select All (iOS and Mac)', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Material( + child: SelectableText('abc def ghi'), + ), + ), + ); + + // Long press at 'e' in 'def'. + final Offset ePos = textOffsetToPosition(tester, 5); + await tester.longPressAt(ePos); + await tester.pumpAndSettle(); + + expect(find.text('Select All'), findsOneWidget); + expect(find.text('Copy'), findsNothing); + expect(find.text('Paste'), findsNothing); + expect(find.text('Cut'), findsNothing); + EditableTextState editableText = tester.state(find.byType(EditableText)); + expect(editableText.selectionOverlay.handlesAreVisible, isFalse); + expect(editableText.selectionOverlay.toolbarIsVisible, isTrue); + + await tester.tap(find.text('Select All')); + await tester.pumpAndSettle(); + expect(find.text('Copy'), findsOneWidget); + expect(find.text('Select All'), findsNothing); + expect(find.text('Paste'), findsNothing); + expect(find.text('Cut'), findsNothing); + editableText = tester.state(find.byType(EditableText)); + expect(editableText.selectionOverlay.handlesAreVisible, isTrue); + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + ); }