Skip to content

Commit d563498

Browse files
authored
Bring back paste button hide behavior (flutter#56689)
Unreverts flutter#54902 with fixes for a failing integration test.
1 parent 98bc176 commit d563498

File tree

13 files changed

+743
-121
lines changed

13 files changed

+743
-121
lines changed

dev/integration_tests/android_semantics_testing/test_driver/main_test.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ void main() {
101101
actions: <AndroidSemanticsAction>[
102102
AndroidSemanticsAction.clearAccessibilityFocus,
103103
AndroidSemanticsAction.click,
104-
AndroidSemanticsAction.copy,
105104
AndroidSemanticsAction.setSelection,
106105
],
107106
),
@@ -111,6 +110,33 @@ void main() {
111110
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
112111
await Future<void>.delayed(const Duration(milliseconds: 500));
113112

113+
expect(
114+
await getSemantics(normalTextField),
115+
hasAndroidSemantics(
116+
text: 'hello world',
117+
className: AndroidClassName.editText,
118+
isFocusable: true,
119+
isFocused: true,
120+
isEditable: true,
121+
isPassword: false,
122+
actions: <AndroidSemanticsAction>[
123+
AndroidSemanticsAction.clearAccessibilityFocus,
124+
AndroidSemanticsAction.click,
125+
AndroidSemanticsAction.setSelection,
126+
],
127+
),
128+
);
129+
130+
// Copy the text so that the clipboard contains something pasteable.
131+
await driver.tap(normalTextField);
132+
await Future<void>.delayed(const Duration(milliseconds: 50));
133+
await driver.tap(normalTextField);
134+
await Future<void>.delayed(const Duration(milliseconds: 500));
135+
await driver.tap(find.text('SELECT ALL'));
136+
await Future<void>.delayed(const Duration(milliseconds: 500));
137+
await driver.tap(find.text('COPY'));
138+
await Future<void>.delayed(const Duration(milliseconds: 50));
139+
114140
expect(
115141
await getSemantics(normalTextField),
116142
hasAndroidSemantics(
@@ -124,6 +150,7 @@ void main() {
124150
AndroidSemanticsAction.clearAccessibilityFocus,
125151
AndroidSemanticsAction.click,
126152
AndroidSemanticsAction.copy,
153+
AndroidSemanticsAction.nextAtMovementGranularity,
127154
AndroidSemanticsAction.setSelection,
128155
],
129156
),
@@ -166,7 +193,6 @@ void main() {
166193
actions: <AndroidSemanticsAction>[
167194
AndroidSemanticsAction.clearAccessibilityFocus,
168195
AndroidSemanticsAction.click,
169-
AndroidSemanticsAction.copy,
170196
AndroidSemanticsAction.setSelection,
171197
],
172198
),
@@ -188,7 +214,6 @@ void main() {
188214
actions: <AndroidSemanticsAction>[
189215
AndroidSemanticsAction.clearAccessibilityFocus,
190216
AndroidSemanticsAction.click,
191-
AndroidSemanticsAction.copy,
192217
AndroidSemanticsAction.setSelection,
193218
],
194219
),

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

Lines changed: 154 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:ui' as ui;
88

99
import 'package:flutter/widgets.dart';
1010
import 'package:flutter/rendering.dart';
11+
import 'package:flutter/services.dart';
1112

1213
import 'button.dart';
1314
import 'colors.dart';
@@ -62,6 +63,151 @@ const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle(
6263
// Eyeballed value.
6364
const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0);
6465

66+
// Generates the child that's passed into CupertinoTextSelectionToolbar.
67+
class _CupertinoTextSelectionToolbarWrapper extends StatefulWidget {
68+
const _CupertinoTextSelectionToolbarWrapper({
69+
Key key,
70+
this.arrowTipX,
71+
this.barTopY,
72+
this.clipboardStatus,
73+
this.handleCut,
74+
this.handleCopy,
75+
this.handlePaste,
76+
this.handleSelectAll,
77+
this.isArrowPointingDown,
78+
}) : super(key: key);
79+
80+
final double arrowTipX;
81+
final double barTopY;
82+
final ClipboardStatusNotifier clipboardStatus;
83+
final VoidCallback handleCut;
84+
final VoidCallback handleCopy;
85+
final VoidCallback handlePaste;
86+
final VoidCallback handleSelectAll;
87+
final bool isArrowPointingDown;
88+
89+
@override
90+
_CupertinoTextSelectionToolbarWrapperState createState() => _CupertinoTextSelectionToolbarWrapperState();
91+
}
92+
93+
class _CupertinoTextSelectionToolbarWrapperState extends State<_CupertinoTextSelectionToolbarWrapper> {
94+
ClipboardStatusNotifier _clipboardStatus;
95+
96+
void _onChangedClipboardStatus() {
97+
setState(() {
98+
// Inform the widget that the value of clipboardStatus has changed.
99+
});
100+
}
101+
102+
@override
103+
void initState() {
104+
super.initState();
105+
_clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier();
106+
_clipboardStatus.addListener(_onChangedClipboardStatus);
107+
_clipboardStatus.update();
108+
}
109+
110+
@override
111+
void didUpdateWidget(_CupertinoTextSelectionToolbarWrapper oldWidget) {
112+
super.didUpdateWidget(oldWidget);
113+
if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) {
114+
_clipboardStatus.removeListener(_onChangedClipboardStatus);
115+
_clipboardStatus.dispose();
116+
_clipboardStatus = widget.clipboardStatus;
117+
} else if (oldWidget.clipboardStatus != null) {
118+
if (widget.clipboardStatus == null) {
119+
_clipboardStatus = ClipboardStatusNotifier();
120+
_clipboardStatus.addListener(_onChangedClipboardStatus);
121+
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
122+
} else if (widget.clipboardStatus != oldWidget.clipboardStatus) {
123+
_clipboardStatus = widget.clipboardStatus;
124+
_clipboardStatus.addListener(_onChangedClipboardStatus);
125+
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
126+
}
127+
}
128+
if (widget.handlePaste != null) {
129+
_clipboardStatus.update();
130+
}
131+
}
132+
133+
@override
134+
void dispose() {
135+
super.dispose();
136+
// When used in an Overlay, this can be disposed after its creator has
137+
// already disposed _clipboardStatus.
138+
if (!_clipboardStatus.disposed) {
139+
_clipboardStatus.removeListener(_onChangedClipboardStatus);
140+
if (widget.clipboardStatus == null) {
141+
_clipboardStatus.dispose();
142+
}
143+
}
144+
}
145+
146+
@override
147+
Widget build(BuildContext context) {
148+
// Don't render the menu until the state of the clipboard is known.
149+
if (widget.handlePaste != null
150+
&& _clipboardStatus.value == ClipboardStatus.unknown) {
151+
return const SizedBox(width: 0.0, height: 0.0);
152+
}
153+
154+
final List<Widget> items = <Widget>[];
155+
final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
156+
final EdgeInsets arrowPadding = widget.isArrowPointingDown
157+
? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
158+
: EdgeInsets.only(top: _kToolbarArrowSize.height);
159+
final Widget onePhysicalPixelVerticalDivider =
160+
SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
161+
162+
void addToolbarButton(
163+
String text,
164+
VoidCallback onPressed,
165+
) {
166+
if (items.isNotEmpty) {
167+
items.add(onePhysicalPixelVerticalDivider);
168+
}
169+
170+
items.add(CupertinoButton(
171+
child: Text(
172+
text,
173+
overflow: TextOverflow.ellipsis,
174+
style: _kToolbarButtonFontStyle,
175+
),
176+
borderRadius: null,
177+
color: _kToolbarBackgroundColor,
178+
minSize: _kToolbarHeight,
179+
onPressed: onPressed,
180+
padding: _kToolbarButtonPadding.add(arrowPadding),
181+
pressedOpacity: 0.7,
182+
));
183+
}
184+
185+
if (widget.handleCut != null) {
186+
addToolbarButton(localizations.cutButtonLabel, widget.handleCut);
187+
}
188+
if (widget.handleCopy != null) {
189+
addToolbarButton(localizations.copyButtonLabel, widget.handleCopy);
190+
}
191+
if (widget.handlePaste != null
192+
&& _clipboardStatus.value == ClipboardStatus.pasteable) {
193+
addToolbarButton(localizations.pasteButtonLabel, widget.handlePaste);
194+
}
195+
if (widget.handleSelectAll != null) {
196+
addToolbarButton(localizations.selectAllButtonLabel, widget.handleSelectAll);
197+
}
198+
199+
return CupertinoTextSelectionToolbar._(
200+
barTopY: widget.barTopY,
201+
arrowTipX: widget.arrowTipX,
202+
isArrowPointingDown: widget.isArrowPointingDown,
203+
child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent(
204+
isArrowPointingDown: widget.isArrowPointingDown,
205+
children: items,
206+
),
207+
);
208+
}
209+
}
210+
65211
/// An iOS-style toolbar that appears in response to text selection.
66212
///
67213
/// Typically displays buttons for text manipulation, e.g. copying and pasting text.
@@ -312,6 +458,7 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
312458
Offset position,
313459
List<TextSelectionPoint> endpoints,
314460
TextSelectionDelegate delegate,
461+
ClipboardStatusNotifier clipboardStatus,
315462
) {
316463
assert(debugCheckHasMediaQuery(context));
317464
final MediaQueryData mediaQuery = MediaQuery.of(context);
@@ -338,49 +485,15 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
338485
? endpoints.first.point.dy - textLineHeight - _kToolbarContentDistance - _kToolbarHeight
339486
: endpoints.last.point.dy + _kToolbarContentDistance;
340487

341-
final List<Widget> items = <Widget>[];
342-
final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
343-
final EdgeInsets arrowPadding = isArrowPointingDown
344-
? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
345-
: EdgeInsets.only(top: _kToolbarArrowSize.height);
346-
347-
void addToolbarButtonIfNeeded(
348-
String text,
349-
bool Function(TextSelectionDelegate) predicate,
350-
void Function(TextSelectionDelegate) onPressed,
351-
) {
352-
if (!predicate(delegate)) {
353-
return;
354-
}
355-
356-
items.add(CupertinoButton(
357-
child: Text(
358-
text,
359-
overflow: TextOverflow.ellipsis,
360-
style: _kToolbarButtonFontStyle,
361-
),
362-
color: _kToolbarBackgroundColor,
363-
minSize: _kToolbarHeight,
364-
padding: _kToolbarButtonPadding.add(arrowPadding),
365-
borderRadius: null,
366-
pressedOpacity: 0.7,
367-
onPressed: () => onPressed(delegate),
368-
));
369-
}
370-
371-
addToolbarButtonIfNeeded(localizations.cutButtonLabel, canCut, handleCut);
372-
addToolbarButtonIfNeeded(localizations.copyButtonLabel, canCopy, handleCopy);
373-
addToolbarButtonIfNeeded(localizations.pasteButtonLabel, canPaste, handlePaste);
374-
addToolbarButtonIfNeeded(localizations.selectAllButtonLabel, canSelectAll, handleSelectAll);
375-
376-
return CupertinoTextSelectionToolbar._(
377-
barTopY: localBarTopY + globalEditableRegion.top,
488+
return _CupertinoTextSelectionToolbarWrapper(
378489
arrowTipX: arrowTipX,
490+
barTopY: localBarTopY + globalEditableRegion.top,
491+
clipboardStatus: clipboardStatus,
492+
handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
493+
handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null,
494+
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
495+
handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
379496
isArrowPointingDown: isArrowPointingDown,
380-
child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent(
381-
isArrowPointingDown: isArrowPointingDown,
382-
children: items,
383-
),
384497
);
385498
}
386499

0 commit comments

Comments
 (0)