Don't access clipboard passively on iOS (#60316)
diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 4000b61..046aa8f 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart
@@ -1522,6 +1522,25 @@ /// Check the [Clipboard] and update [value] if needed. void update() { + // iOS 14 added a notification that appears when an app accesses the + // clipboard. To avoid the notification, don't access the clipboard on iOS, + // and instead always shown the paste button, even when the clipboard is + // empty. + // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that + // won't trigger the notification. + // https://github.com/flutter/flutter/issues/60145 + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + value = ClipboardStatus.pasteable; + return; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + break; + } + Clipboard.getData(Clipboard.kTextPlain).then((ClipboardData data) { final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty ? ClipboardStatus.pasteable
diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart index 35bed86..f77d926 100644 --- a/packages/flutter/test/cupertino/text_selection_test.dart +++ b/packages/flutter/test/cupertino/text_selection_test.dart
@@ -176,7 +176,8 @@ }); }); - testWidgets('Paste only appears when clipboard has contents', (WidgetTester tester) async { + // TODO(justinmc): https://github.com/flutter/flutter/issues/60145 + testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( text: 'Atwater Peel Sherbrooke Bonaventure', ); @@ -202,8 +203,8 @@ await tester.tapAt(textOffsetToPosition(tester, index)); await tester.pumpAndSettle(); - // No Paste yet, because nothing has been copied. - expect(find.text('Paste'), findsNothing); + // Paste is showing even though clipboard is empty. + expect(find.text('Paste'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Cut'), findsOneWidget); @@ -219,7 +220,7 @@ await tester.tapAt(textOffsetToPosition(tester, index)); await tester.pumpAndSettle(); - // Paste now shows. + // Paste still shows. expect(find.text('Paste'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Cut'), findsOneWidget);
diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index 518923b..ec36761 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart
@@ -636,5 +636,57 @@ expect(find.text('Cut'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select all'), findsOneWidget); - }, skip: isBrowser); + }, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android })); + + // TODO(justinmc): https://github.com/flutter/flutter/issues/60145 + testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController( + text: 'Atwater Peel Sherbrooke Bonaventure', + ); + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Column( + children: <Widget>[ + TextField( + controller: controller, + ), + ], + ), + ), + ), + ); + + // Make sure the clipboard is empty. + await Clipboard.setData(const ClipboardData(text: '')); + + // Double tap to select the first word. + const int index = 4; + await tester.tapAt(textOffsetToPosition(tester, index)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, index)); + await tester.pumpAndSettle(); + + // Paste is showing even though clipboard is empty. + expect(find.text('Paste'), findsOneWidget); + expect(find.text('Copy'), findsOneWidget); + expect(find.text('Cut'), findsOneWidget); + + // Tap copy to add something to the clipboard and close the menu. + await tester.tapAt(tester.getCenter(find.text('Copy'))); + await tester.pumpAndSettle(); + expect(find.text('Copy'), findsNothing); + expect(find.text('Cut'), findsNothing); + + // Double tap to show the menu again. + await tester.tapAt(textOffsetToPosition(tester, index)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, index)); + await tester.pumpAndSettle(); + + // Paste still shows. + expect(find.text('Copy'), findsOneWidget); + expect(find.text('Cut'), findsOneWidget); + expect(find.text('Paste'), findsOneWidget); + }, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS })); }