Skip to content

Commit f6cd2d4

Browse files
authored
Don't access clipboard passively on iOS (flutter#60316)
1 parent d55251c commit f6cd2d4

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

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

+19
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,25 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
15221522

15231523
/// Check the [Clipboard] and update [value] if needed.
15241524
void update() {
1525+
// iOS 14 added a notification that appears when an app accesses the
1526+
// clipboard. To avoid the notification, don't access the clipboard on iOS,
1527+
// and instead always shown the paste button, even when the clipboard is
1528+
// empty.
1529+
// TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
1530+
// won't trigger the notification.
1531+
// https://github.com/flutter/flutter/issues/60145
1532+
switch (defaultTargetPlatform) {
1533+
case TargetPlatform.iOS:
1534+
value = ClipboardStatus.pasteable;
1535+
return;
1536+
case TargetPlatform.android:
1537+
case TargetPlatform.fuchsia:
1538+
case TargetPlatform.linux:
1539+
case TargetPlatform.macOS:
1540+
case TargetPlatform.windows:
1541+
break;
1542+
}
1543+
15251544
Clipboard.getData(Clipboard.kTextPlain).then((ClipboardData data) {
15261545
final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty
15271546
? ClipboardStatus.pasteable

packages/flutter/test/cupertino/text_selection_test.dart

+5-4
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ void main() {
176176
});
177177
});
178178

179-
testWidgets('Paste only appears when clipboard has contents', (WidgetTester tester) async {
179+
// TODO(justinmc): https://github.com/flutter/flutter/issues/60145
180+
testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async {
180181
final TextEditingController controller = TextEditingController(
181182
text: 'Atwater Peel Sherbrooke Bonaventure',
182183
);
@@ -202,8 +203,8 @@ void main() {
202203
await tester.tapAt(textOffsetToPosition(tester, index));
203204
await tester.pumpAndSettle();
204205

205-
// No Paste yet, because nothing has been copied.
206-
expect(find.text('Paste'), findsNothing);
206+
// Paste is showing even though clipboard is empty.
207+
expect(find.text('Paste'), findsOneWidget);
207208
expect(find.text('Copy'), findsOneWidget);
208209
expect(find.text('Cut'), findsOneWidget);
209210

@@ -219,7 +220,7 @@ void main() {
219220
await tester.tapAt(textOffsetToPosition(tester, index));
220221
await tester.pumpAndSettle();
221222

222-
// Paste now shows.
223+
// Paste still shows.
223224
expect(find.text('Paste'), findsOneWidget);
224225
expect(find.text('Copy'), findsOneWidget);
225226
expect(find.text('Cut'), findsOneWidget);

packages/flutter/test/material/text_selection_test.dart

+53-1
Original file line numberDiff line numberDiff line change
@@ -636,5 +636,57 @@ void main() {
636636
expect(find.text('Cut'), findsOneWidget);
637637
expect(find.text('Paste'), findsOneWidget);
638638
expect(find.text('Select all'), findsOneWidget);
639-
}, skip: isBrowser);
639+
}, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android }));
640+
641+
// TODO(justinmc): https://github.com/flutter/flutter/issues/60145
642+
testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async {
643+
final TextEditingController controller = TextEditingController(
644+
text: 'Atwater Peel Sherbrooke Bonaventure',
645+
);
646+
await tester.pumpWidget(
647+
MaterialApp(
648+
home: Material(
649+
child: Column(
650+
children: <Widget>[
651+
TextField(
652+
controller: controller,
653+
),
654+
],
655+
),
656+
),
657+
),
658+
);
659+
660+
// Make sure the clipboard is empty.
661+
await Clipboard.setData(const ClipboardData(text: ''));
662+
663+
// Double tap to select the first word.
664+
const int index = 4;
665+
await tester.tapAt(textOffsetToPosition(tester, index));
666+
await tester.pump(const Duration(milliseconds: 50));
667+
await tester.tapAt(textOffsetToPosition(tester, index));
668+
await tester.pumpAndSettle();
669+
670+
// Paste is showing even though clipboard is empty.
671+
expect(find.text('Paste'), findsOneWidget);
672+
expect(find.text('Copy'), findsOneWidget);
673+
expect(find.text('Cut'), findsOneWidget);
674+
675+
// Tap copy to add something to the clipboard and close the menu.
676+
await tester.tapAt(tester.getCenter(find.text('Copy')));
677+
await tester.pumpAndSettle();
678+
expect(find.text('Copy'), findsNothing);
679+
expect(find.text('Cut'), findsNothing);
680+
681+
// Double tap to show the menu again.
682+
await tester.tapAt(textOffsetToPosition(tester, index));
683+
await tester.pump(const Duration(milliseconds: 50));
684+
await tester.tapAt(textOffsetToPosition(tester, index));
685+
await tester.pumpAndSettle();
686+
687+
// Paste still shows.
688+
expect(find.text('Copy'), findsOneWidget);
689+
expect(find.text('Cut'), findsOneWidget);
690+
expect(find.text('Paste'), findsOneWidget);
691+
}, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
640692
}

0 commit comments

Comments
 (0)