Unverified Commit 0786f29f authored by Mehmet Fidanboylu's avatar Mehmet Fidanboylu Committed by GitHub

Revert "Bring back paste button hide behavior (#56689)" (#56806)

This reverts commit d5634982.
parent d70d0913
...@@ -101,6 +101,7 @@ void main() { ...@@ -101,6 +101,7 @@ void main() {
actions: <AndroidSemanticsAction>[ actions: <AndroidSemanticsAction>[
AndroidSemanticsAction.clearAccessibilityFocus, AndroidSemanticsAction.clearAccessibilityFocus,
AndroidSemanticsAction.click, AndroidSemanticsAction.click,
AndroidSemanticsAction.copy,
AndroidSemanticsAction.setSelection, AndroidSemanticsAction.setSelection,
], ],
), ),
...@@ -110,33 +111,6 @@ void main() { ...@@ -110,33 +111,6 @@ void main() {
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500)); await Future<void>.delayed(const Duration(milliseconds: 500));
expect(
await getSemantics(normalTextField),
hasAndroidSemantics(
text: 'hello world',
className: AndroidClassName.editText,
isFocusable: true,
isFocused: true,
isEditable: true,
isPassword: false,
actions: <AndroidSemanticsAction>[
AndroidSemanticsAction.clearAccessibilityFocus,
AndroidSemanticsAction.click,
AndroidSemanticsAction.setSelection,
],
),
);
// Copy the text so that the clipboard contains something pasteable.
await driver.tap(normalTextField);
await Future<void>.delayed(const Duration(milliseconds: 50));
await driver.tap(normalTextField);
await Future<void>.delayed(const Duration(milliseconds: 500));
await driver.tap(find.text('SELECT ALL'));
await Future<void>.delayed(const Duration(milliseconds: 500));
await driver.tap(find.text('COPY'));
await Future<void>.delayed(const Duration(milliseconds: 50));
expect( expect(
await getSemantics(normalTextField), await getSemantics(normalTextField),
hasAndroidSemantics( hasAndroidSemantics(
...@@ -150,7 +124,6 @@ void main() { ...@@ -150,7 +124,6 @@ void main() {
AndroidSemanticsAction.clearAccessibilityFocus, AndroidSemanticsAction.clearAccessibilityFocus,
AndroidSemanticsAction.click, AndroidSemanticsAction.click,
AndroidSemanticsAction.copy, AndroidSemanticsAction.copy,
AndroidSemanticsAction.nextAtMovementGranularity,
AndroidSemanticsAction.setSelection, AndroidSemanticsAction.setSelection,
], ],
), ),
...@@ -193,6 +166,7 @@ void main() { ...@@ -193,6 +166,7 @@ void main() {
actions: <AndroidSemanticsAction>[ actions: <AndroidSemanticsAction>[
AndroidSemanticsAction.clearAccessibilityFocus, AndroidSemanticsAction.clearAccessibilityFocus,
AndroidSemanticsAction.click, AndroidSemanticsAction.click,
AndroidSemanticsAction.copy,
AndroidSemanticsAction.setSelection, AndroidSemanticsAction.setSelection,
], ],
), ),
...@@ -214,6 +188,7 @@ void main() { ...@@ -214,6 +188,7 @@ void main() {
actions: <AndroidSemanticsAction>[ actions: <AndroidSemanticsAction>[
AndroidSemanticsAction.clearAccessibilityFocus, AndroidSemanticsAction.clearAccessibilityFocus,
AndroidSemanticsAction.click, AndroidSemanticsAction.click,
AndroidSemanticsAction.copy,
AndroidSemanticsAction.setSelection, AndroidSemanticsAction.setSelection,
], ],
), ),
......
...@@ -8,7 +8,6 @@ import 'dart:ui' as ui; ...@@ -8,7 +8,6 @@ import 'dart:ui' as ui;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'button.dart'; import 'button.dart';
import 'colors.dart'; import 'colors.dart';
...@@ -63,151 +62,6 @@ const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle( ...@@ -63,151 +62,6 @@ const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle(
// Eyeballed value. // Eyeballed value.
const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0); const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0);
// Generates the child that's passed into CupertinoTextSelectionToolbar.
class _CupertinoTextSelectionToolbarWrapper extends StatefulWidget {
const _CupertinoTextSelectionToolbarWrapper({
Key key,
this.arrowTipX,
this.barTopY,
this.clipboardStatus,
this.handleCut,
this.handleCopy,
this.handlePaste,
this.handleSelectAll,
this.isArrowPointingDown,
}) : super(key: key);
final double arrowTipX;
final double barTopY;
final ClipboardStatusNotifier clipboardStatus;
final VoidCallback handleCut;
final VoidCallback handleCopy;
final VoidCallback handlePaste;
final VoidCallback handleSelectAll;
final bool isArrowPointingDown;
@override
_CupertinoTextSelectionToolbarWrapperState createState() => _CupertinoTextSelectionToolbarWrapperState();
}
class _CupertinoTextSelectionToolbarWrapperState extends State<_CupertinoTextSelectionToolbarWrapper> {
ClipboardStatusNotifier _clipboardStatus;
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
@override
void initState() {
super.initState();
_clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier();
_clipboardStatus.addListener(_onChangedClipboardStatus);
_clipboardStatus.update();
}
@override
void didUpdateWidget(_CupertinoTextSelectionToolbarWrapper oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) {
_clipboardStatus.removeListener(_onChangedClipboardStatus);
_clipboardStatus.dispose();
_clipboardStatus = widget.clipboardStatus;
} else if (oldWidget.clipboardStatus != null) {
if (widget.clipboardStatus == null) {
_clipboardStatus = ClipboardStatusNotifier();
_clipboardStatus.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
} else if (widget.clipboardStatus != oldWidget.clipboardStatus) {
_clipboardStatus = widget.clipboardStatus;
_clipboardStatus.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
}
}
if (widget.handlePaste != null) {
_clipboardStatus.update();
}
}
@override
void dispose() {
super.dispose();
// When used in an Overlay, this can be disposed after its creator has
// already disposed _clipboardStatus.
if (!_clipboardStatus.disposed) {
_clipboardStatus.removeListener(_onChangedClipboardStatus);
if (widget.clipboardStatus == null) {
_clipboardStatus.dispose();
}
}
}
@override
Widget build(BuildContext context) {
// Don't render the menu until the state of the clipboard is known.
if (widget.handlePaste != null
&& _clipboardStatus.value == ClipboardStatus.unknown) {
return const SizedBox(width: 0.0, height: 0.0);
}
final List<Widget> items = <Widget>[];
final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
final EdgeInsets arrowPadding = widget.isArrowPointingDown
? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
: EdgeInsets.only(top: _kToolbarArrowSize.height);
final Widget onePhysicalPixelVerticalDivider =
SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
void addToolbarButton(
String text,
VoidCallback onPressed,
) {
if (items.isNotEmpty) {
items.add(onePhysicalPixelVerticalDivider);
}
items.add(CupertinoButton(
child: Text(
text,
overflow: TextOverflow.ellipsis,
style: _kToolbarButtonFontStyle,
),
borderRadius: null,
color: _kToolbarBackgroundColor,
minSize: _kToolbarHeight,
onPressed: onPressed,
padding: _kToolbarButtonPadding.add(arrowPadding),
pressedOpacity: 0.7,
));
}
if (widget.handleCut != null) {
addToolbarButton(localizations.cutButtonLabel, widget.handleCut);
}
if (widget.handleCopy != null) {
addToolbarButton(localizations.copyButtonLabel, widget.handleCopy);
}
if (widget.handlePaste != null
&& _clipboardStatus.value == ClipboardStatus.pasteable) {
addToolbarButton(localizations.pasteButtonLabel, widget.handlePaste);
}
if (widget.handleSelectAll != null) {
addToolbarButton(localizations.selectAllButtonLabel, widget.handleSelectAll);
}
return CupertinoTextSelectionToolbar._(
barTopY: widget.barTopY,
arrowTipX: widget.arrowTipX,
isArrowPointingDown: widget.isArrowPointingDown,
child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent(
isArrowPointingDown: widget.isArrowPointingDown,
children: items,
),
);
}
}
/// An iOS-style toolbar that appears in response to text selection. /// An iOS-style toolbar that appears in response to text selection.
/// ///
/// Typically displays buttons for text manipulation, e.g. copying and pasting text. /// Typically displays buttons for text manipulation, e.g. copying and pasting text.
...@@ -458,7 +312,6 @@ class _CupertinoTextSelectionControls extends TextSelectionControls { ...@@ -458,7 +312,6 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
Offset position, Offset position,
List<TextSelectionPoint> endpoints, List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate, TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
) { ) {
assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMediaQuery(context));
final MediaQueryData mediaQuery = MediaQuery.of(context); final MediaQueryData mediaQuery = MediaQuery.of(context);
...@@ -485,15 +338,49 @@ class _CupertinoTextSelectionControls extends TextSelectionControls { ...@@ -485,15 +338,49 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
? endpoints.first.point.dy - textLineHeight - _kToolbarContentDistance - _kToolbarHeight ? endpoints.first.point.dy - textLineHeight - _kToolbarContentDistance - _kToolbarHeight
: endpoints.last.point.dy + _kToolbarContentDistance; : endpoints.last.point.dy + _kToolbarContentDistance;
return _CupertinoTextSelectionToolbarWrapper( final List<Widget> items = <Widget>[];
arrowTipX: arrowTipX, final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
final EdgeInsets arrowPadding = isArrowPointingDown
? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
: EdgeInsets.only(top: _kToolbarArrowSize.height);
void addToolbarButtonIfNeeded(
String text,
bool Function(TextSelectionDelegate) predicate,
void Function(TextSelectionDelegate) onPressed,
) {
if (!predicate(delegate)) {
return;
}
items.add(CupertinoButton(
child: Text(
text,
overflow: TextOverflow.ellipsis,
style: _kToolbarButtonFontStyle,
),
color: _kToolbarBackgroundColor,
minSize: _kToolbarHeight,
padding: _kToolbarButtonPadding.add(arrowPadding),
borderRadius: null,
pressedOpacity: 0.7,
onPressed: () => onPressed(delegate),
));
}
addToolbarButtonIfNeeded(localizations.cutButtonLabel, canCut, handleCut);
addToolbarButtonIfNeeded(localizations.copyButtonLabel, canCopy, handleCopy);
addToolbarButtonIfNeeded(localizations.pasteButtonLabel, canPaste, handlePaste);
addToolbarButtonIfNeeded(localizations.selectAllButtonLabel, canSelectAll, handleSelectAll);
return CupertinoTextSelectionToolbar._(
barTopY: localBarTopY + globalEditableRegion.top, barTopY: localBarTopY + globalEditableRegion.top,
clipboardStatus: clipboardStatus, arrowTipX: arrowTipX,
handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null,
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
isArrowPointingDown: isArrowPointingDown, isArrowPointingDown: isArrowPointingDown,
child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent(
isArrowPointingDown: isArrowPointingDown,
children: items,
),
); );
} }
......
...@@ -6,7 +6,6 @@ import 'dart:math' as math; ...@@ -6,7 +6,6 @@ import 'dart:math' as math;
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'debug.dart'; import 'debug.dart';
...@@ -30,7 +29,6 @@ const double _kToolbarContentDistance = 8.0; ...@@ -30,7 +29,6 @@ const double _kToolbarContentDistance = 8.0;
/// Manages a copy/paste text selection toolbar. /// Manages a copy/paste text selection toolbar.
class _TextSelectionToolbar extends StatefulWidget { class _TextSelectionToolbar extends StatefulWidget {
const _TextSelectionToolbar({ const _TextSelectionToolbar({
this.clipboardStatus,
Key key, Key key,
this.handleCut, this.handleCut,
this.handleCopy, this.handleCopy,
...@@ -39,7 +37,6 @@ class _TextSelectionToolbar extends StatefulWidget { ...@@ -39,7 +37,6 @@ class _TextSelectionToolbar extends StatefulWidget {
this.isAbove, this.isAbove,
}) : super(key: key); }) : super(key: key);
final ClipboardStatusNotifier clipboardStatus;
final VoidCallback handleCut; final VoidCallback handleCut;
final VoidCallback handleCopy; final VoidCallback handleCopy;
final VoidCallback handlePaste; final VoidCallback handlePaste;
...@@ -53,8 +50,6 @@ class _TextSelectionToolbar extends StatefulWidget { ...@@ -53,8 +50,6 @@ class _TextSelectionToolbar extends StatefulWidget {
} }
class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with TickerProviderStateMixin { class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with TickerProviderStateMixin {
ClipboardStatusNotifier _clipboardStatus;
// Whether or not the overflow menu is open. When it is closed, the menu // Whether or not the overflow menu is open. When it is closed, the menu
// items that don't overflow are shown. When it is open, only the overflowing // items that don't overflow are shown. When it is open, only the overflowing
// menu items are shown. // menu items are shown.
...@@ -71,93 +66,33 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke ...@@ -71,93 +66,33 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke
); );
} }
// Close the menu and reset layout calculations, as in when the menu has
// changed and saved values are no longer relevant. This should be called in
// setState or another context where a rebuild is happening.
void _reset() {
// Change _TextSelectionToolbarContainer's key when the menu changes in
// order to cause it to rebuild. This lets it recalculate its
// saved width for the new set of children, and it prevents AnimatedSize
// from animating the size change.
_containerKey = UniqueKey();
// If the menu items change, make sure the overflow menu is closed. This
// prevents an empty overflow menu.
_overflowOpen = false;
}
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
@override
void initState() {
super.initState();
_clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier();
_clipboardStatus.addListener(_onChangedClipboardStatus);
_clipboardStatus.update();
}
@override @override
void didUpdateWidget(_TextSelectionToolbar oldWidget) { void didUpdateWidget(_TextSelectionToolbar oldWidget) {
super.didUpdateWidget(oldWidget);
// If the children are changing, the current page should be reset.
if (((widget.handleCut == null) != (oldWidget.handleCut == null)) if (((widget.handleCut == null) != (oldWidget.handleCut == null))
|| ((widget.handleCopy == null) != (oldWidget.handleCopy == null)) || ((widget.handleCopy == null) != (oldWidget.handleCopy == null))
|| ((widget.handlePaste == null) != (oldWidget.handlePaste == null)) || ((widget.handlePaste == null) != (oldWidget.handlePaste == null))
|| ((widget.handleSelectAll == null) != (oldWidget.handleSelectAll == null))) { || ((widget.handleSelectAll == null) != (oldWidget.handleSelectAll == null))) {
_reset(); // Change _TextSelectionToolbarContainer's key when the menu changes in
} // order to cause it to rebuild. This lets it recalculate its
if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) { // saved width for the new set of children, and it prevents AnimatedSize
_clipboardStatus.removeListener(_onChangedClipboardStatus); // from animating the size change.
_clipboardStatus.dispose(); _containerKey = UniqueKey();
_clipboardStatus = widget.clipboardStatus; // If the menu items change, make sure the overflow menu is closed. This
} else if (oldWidget.clipboardStatus != null) { // prevents an empty overflow menu.
if (widget.clipboardStatus == null) { _overflowOpen = false;
_clipboardStatus = ClipboardStatusNotifier();
_clipboardStatus.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
} else if (widget.clipboardStatus != oldWidget.clipboardStatus) {
_clipboardStatus = widget.clipboardStatus;
_clipboardStatus.addListener(_onChangedClipboardStatus);
oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
}
}
if (widget.handlePaste != null) {
_clipboardStatus.update();
}
}
@override
void dispose() {
super.dispose();
// When used in an Overlay, this can be disposed after its creator has
// already disposed _clipboardStatus.
if (!_clipboardStatus.disposed) {
_clipboardStatus.removeListener(_onChangedClipboardStatus);
if (widget.clipboardStatus == null) {
_clipboardStatus.dispose();
}
} }
super.didUpdateWidget(oldWidget);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Don't render the menu until the state of the clipboard is known.
if (widget.handlePaste != null
&& _clipboardStatus.value == ClipboardStatus.unknown) {
return const SizedBox(width: 0.0, height: 0.0);
}
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final List<Widget> items = <Widget>[ final List<Widget> items = <Widget>[
if (widget.handleCut != null) if (widget.handleCut != null)
_getItem(widget.handleCut, localizations.cutButtonLabel), _getItem(widget.handleCut, localizations.cutButtonLabel),
if (widget.handleCopy != null) if (widget.handleCopy != null)
_getItem(widget.handleCopy, localizations.copyButtonLabel), _getItem(widget.handleCopy, localizations.copyButtonLabel),
if (widget.handlePaste != null if (widget.handlePaste != null)
&& _clipboardStatus.value == ClipboardStatus.pasteable)
_getItem(widget.handlePaste, localizations.pasteButtonLabel), _getItem(widget.handlePaste, localizations.pasteButtonLabel),
if (widget.handleSelectAll != null) if (widget.handleSelectAll != null)
_getItem(widget.handleSelectAll, localizations.selectAllButtonLabel), _getItem(widget.handleSelectAll, localizations.selectAllButtonLabel),
...@@ -168,6 +103,7 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke ...@@ -168,6 +103,7 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke
return const SizedBox(width: 0.0, height: 0.0); return const SizedBox(width: 0.0, height: 0.0);
} }
return _TextSelectionToolbarContainer( return _TextSelectionToolbarContainer(
key: _containerKey, key: _containerKey,
overflowOpen: _overflowOpen, overflowOpen: _overflowOpen,
...@@ -591,18 +527,6 @@ class _TextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRender ...@@ -591,18 +527,6 @@ class _TextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRender
} }
return false; return false;
} }
// Visit only the children that should be painted.
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
visitChildren((RenderObject renderObjectChild) {
final RenderBox child = renderObjectChild as RenderBox;
final ToolbarItemsParentData childParentData = child.parentData as ToolbarItemsParentData;
if (childParentData.shouldPaint) {
visitor(renderObjectChild);
}
});
}
} }
/// Centers the toolbar around the given anchor, ensuring that it remains on /// Centers the toolbar around the given anchor, ensuring that it remains on
...@@ -706,7 +630,6 @@ class _MaterialTextSelectionControls extends TextSelectionControls { ...@@ -706,7 +630,6 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
Offset selectionMidpoint, Offset selectionMidpoint,
List<TextSelectionPoint> endpoints, List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate, TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
) { ) {
assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
...@@ -742,9 +665,8 @@ class _MaterialTextSelectionControls extends TextSelectionControls { ...@@ -742,9 +665,8 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
fitsAbove, fitsAbove,
), ),
child: _TextSelectionToolbar( child: _TextSelectionToolbar(
clipboardStatus: clipboardStatus,
handleCut: canCut(delegate) ? () => handleCut(delegate) : null, handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null, handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null,
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
isAbove: fitsAbove, isAbove: fitsAbove,
......
...@@ -1144,7 +1144,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1144,7 +1144,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
bool _targetCursorVisibility = false; bool _targetCursorVisibility = false;
final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true); final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
final GlobalKey _editableKey = GlobalKey(); final GlobalKey _editableKey = GlobalKey();
final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier();
TextInputConnection _textInputConnection; TextInputConnection _textInputConnection;
TextSelectionOverlay _selectionOverlay; TextSelectionOverlay _selectionOverlay;
...@@ -1191,18 +1190,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1191,18 +1190,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override @override
bool get selectAllEnabled => widget.toolbarOptions.selectAll; bool get selectAllEnabled => widget.toolbarOptions.selectAll;
void _onChangedClipboardStatus() {
setState(() {
// Inform the widget that the value of clipboardStatus has changed.
});
}
// State lifecycle: // State lifecycle:
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_clipboardStatus.addListener(_onChangedClipboardStatus);
widget.controller.addListener(_didChangeTextEditingValue); widget.controller.addListener(_didChangeTextEditingValue);
_focusAttachment = widget.focusNode.attach(context); _focusAttachment = widget.focusNode.attach(context);
widget.focusNode.addListener(_handleFocusChanged); widget.focusNode.addListener(_handleFocusChanged);
...@@ -1276,9 +1268,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1276,9 +1268,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
); );
} }
} }
if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) {
_clipboardStatus.update();
}
} }
@override @override
...@@ -1295,9 +1284,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1295,9 +1284,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay = null; _selectionOverlay = null;
_focusAttachment.detach(); _focusAttachment.detach();
widget.focusNode.removeListener(_handleFocusChanged); widget.focusNode.removeListener(_handleFocusChanged);
WidgetsBinding.instance.removeObserver(this);
_clipboardStatus.removeListener(_onChangedClipboardStatus);
_clipboardStatus.dispose();
super.dispose(); super.dispose();
} }
...@@ -1624,7 +1610,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1624,7 +1610,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (widget.selectionControls != null) { if (widget.selectionControls != null) {
_selectionOverlay = TextSelectionOverlay( _selectionOverlay = TextSelectionOverlay(
clipboardStatus: _clipboardStatus,
context: context, context: context,
value: _value, value: _value,
debugRequiredFor: widget, debugRequiredFor: widget,
...@@ -1995,7 +1980,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1995,7 +1980,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
VoidCallback _semanticsOnCopy(TextSelectionControls controls) { VoidCallback _semanticsOnCopy(TextSelectionControls controls) {
return widget.selectionEnabled && copyEnabled && _hasFocus && controls?.canCopy(this) == true return widget.selectionEnabled && copyEnabled && _hasFocus && controls?.canCopy(this) == true
? () => controls.handleCopy(this, _clipboardStatus) ? () => controls.handleCopy(this)
: null; : null;
} }
...@@ -2006,7 +1991,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2006,7 +1991,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
VoidCallback _semanticsOnPaste(TextSelectionControls controls) { VoidCallback _semanticsOnPaste(TextSelectionControls controls) {
return widget.selectionEnabled && pasteEnabled && _hasFocus && controls?.canPaste(this) == true && _clipboardStatus.value == ClipboardStatus.pasteable return widget.selectionEnabled && pasteEnabled &&_hasFocus && controls?.canPaste(this) == true
? () => controls.handlePaste(this) ? () => controls.handlePaste(this)
: null; : null;
} }
......
...@@ -12,7 +12,6 @@ import 'package:flutter/scheduler.dart'; ...@@ -12,7 +12,6 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic.dart'; import 'basic.dart';
import 'binding.dart';
import 'constants.dart'; import 'constants.dart';
import 'container.dart'; import 'container.dart';
import 'editable_text.dart'; import 'editable_text.dart';
...@@ -138,7 +137,6 @@ abstract class TextSelectionControls { ...@@ -138,7 +137,6 @@ abstract class TextSelectionControls {
Offset position, Offset position,
List<TextSelectionPoint> endpoints, List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate, TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
); );
/// Returns the size of the selection handle. /// Returns the size of the selection handle.
...@@ -167,16 +165,13 @@ abstract class TextSelectionControls { ...@@ -167,16 +165,13 @@ abstract class TextSelectionControls {
return delegate.copyEnabled && !delegate.textEditingValue.selection.isCollapsed; return delegate.copyEnabled && !delegate.textEditingValue.selection.isCollapsed;
} }
/// Whether the text field managed by the given `delegate` supports pasting /// Whether the current [Clipboard] content can be pasted into the text field
/// from the clipboard. /// managed by the given `delegate`.
/// ///
/// Subclasses can use this to decide if they should expose the paste /// Subclasses can use this to decide if they should expose the paste
/// functionality to the user. /// functionality to the user.
///
/// This does not consider the contents of the clipboard. Subclasses may want
/// to, for example, disallow pasting when the clipboard contains an empty
/// string.
bool canPaste(TextSelectionDelegate delegate) { bool canPaste(TextSelectionDelegate delegate) {
// TODO(goderbauer): return false when clipboard is empty, https://github.com/flutter/flutter/issues/11254
return delegate.pasteEnabled; return delegate.pasteEnabled;
} }
...@@ -218,12 +213,11 @@ abstract class TextSelectionControls { ...@@ -218,12 +213,11 @@ abstract class TextSelectionControls {
/// ///
/// This is called by subclasses when their copy affordance is activated by /// This is called by subclasses when their copy affordance is activated by
/// the user. /// the user.
void handleCopy(TextSelectionDelegate delegate, ClipboardStatusNotifier clipboardStatus) { void handleCopy(TextSelectionDelegate delegate) {
final TextEditingValue value = delegate.textEditingValue; final TextEditingValue value = delegate.textEditingValue;
Clipboard.setData(ClipboardData( Clipboard.setData(ClipboardData(
text: value.selection.textInside(value.text), text: value.selection.textInside(value.text),
)); ));
clipboardStatus?.update();
delegate.textEditingValue = TextEditingValue( delegate.textEditingValue = TextEditingValue(
text: value.text, text: value.text,
selection: TextSelection.collapsed(offset: value.selection.end), selection: TextSelection.collapsed(offset: value.selection.end),
...@@ -300,7 +294,6 @@ class TextSelectionOverlay { ...@@ -300,7 +294,6 @@ class TextSelectionOverlay {
this.selectionDelegate, this.selectionDelegate,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.onSelectionHandleTapped, this.onSelectionHandleTapped,
this.clipboardStatus,
}) : assert(value != null), }) : assert(value != null),
assert(context != null), assert(context != null),
assert(handlesVisible != null), assert(handlesVisible != null),
...@@ -372,13 +365,6 @@ class TextSelectionOverlay { ...@@ -372,13 +365,6 @@ class TextSelectionOverlay {
/// {@endtemplate} /// {@endtemplate}
final VoidCallback onSelectionHandleTapped; final VoidCallback onSelectionHandleTapped;
/// Maintains the status of the clipboard for determining if its contents can
/// be pasted or not.
///
/// Useful because the actual value of the clipboard can only be checked
/// asynchronously (see [Clipboard.getData]).
final ClipboardStatusNotifier clipboardStatus;
/// Controls the fade-in and fade-out animations for the toolbar and handles. /// Controls the fade-in and fade-out animations for the toolbar and handles.
static const Duration fadeDuration = Duration(milliseconds: 150); static const Duration fadeDuration = Duration(milliseconds: 150);
...@@ -590,7 +576,6 @@ class TextSelectionOverlay { ...@@ -590,7 +576,6 @@ class TextSelectionOverlay {
midpoint, midpoint,
endpoints, endpoints,
selectionDelegate, selectionDelegate,
clipboardStatus,
), ),
), ),
); );
...@@ -1501,86 +1486,3 @@ class _TransparentTapGestureRecognizer extends TapGestureRecognizer { ...@@ -1501,86 +1486,3 @@ class _TransparentTapGestureRecognizer extends TapGestureRecognizer {
} }
} }
} }
/// A [ValueNotifier] whose [value] indicates whether the current contents of
/// the clipboard can be pasted.
///
/// The contents of the clipboard can only be read asynchronously, via
/// [Clipboard.getData], so this maintains a value that can be used
/// synchronously. Call [update] to asynchronously update value if needed.
class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with WidgetsBindingObserver {
/// Create a new ClipboardStatusNotifier.
ClipboardStatusNotifier({
ClipboardStatus value = ClipboardStatus.unknown,
}) : super(value);
bool _disposed = false;
/// True iff this instance has been disposed.
bool get disposed => _disposed;
/// Check the [Clipboard] and update [value] if needed.
void update() {
Clipboard.getData(Clipboard.kTextPlain).then((ClipboardData data) {
final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty
? ClipboardStatus.pasteable
: ClipboardStatus.notPasteable;
if (clipboardStatus == value) {
return;
}
value = clipboardStatus;
});
}
@override
void addListener(VoidCallback listener) {
if (!hasListeners) {
WidgetsBinding.instance.addObserver(this);
}
if (value == ClipboardStatus.unknown) {
update();
}
super.addListener(listener);
}
@override
void removeListener(VoidCallback listener) {
super.removeListener(listener);
if (!hasListeners) {
WidgetsBinding.instance.removeObserver(this);
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
update();
break;
case AppLifecycleState.detached:
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
// Nothing to do.
}
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
_disposed = true;
}
}
/// An enumeration of the status of the content on the user's clipboard.
enum ClipboardStatus {
/// The clipboard content can be pasted, such as a String of nonzero length.
pasteable,
/// The status of the clipboard is unknown. Since getting clipboard data is
/// asynchronous (see [Clipboard.getData]), this status often exists while
/// waiting to receive the clipboard contents for the first time.
unknown,
/// The content on the clipboard is not pasteable, such as when it is empty.
notPasteable,
}
...@@ -171,11 +171,8 @@ void main() { ...@@ -171,11 +171,8 @@ void main() {
Offset textOffsetToPosition(WidgetTester tester, int offset) => textOffsetToBottomLeftPosition(tester, offset) + const Offset(0, -2); Offset textOffsetToPosition(WidgetTester tester, int offset) => textOffsetToBottomLeftPosition(tester, offset) + const Offset(0, -2);
setUp(() async { setUp(() {
EditableText.debugDeterministicCursor = false; EditableText.debugDeterministicCursor = false;
// Fill the clipboard so that the PASTE option is available in the text
// selection menu.
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
}); });
testWidgets( testWidgets(
...@@ -1548,7 +1545,7 @@ void main() { ...@@ -1548,7 +1545,7 @@ void main() {
await tester.tapAt(textOffsetToPosition(tester, index)); await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
await tester.tapAt(textOffsetToPosition(tester, index)); await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pumpAndSettle(); await tester.pump();
expect( expect(
controller.selection, controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7), const TextSelection(baseOffset: 0, extentOffset: 7),
...@@ -1588,7 +1585,7 @@ void main() { ...@@ -1588,7 +1585,7 @@ void main() {
const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream),
); );
await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0));
await tester.pumpAndSettle(); await tester.pump();
// Second tap selects the word around the cursor. // Second tap selects the word around the cursor.
expect( expect(
...@@ -1624,7 +1621,7 @@ void main() { ...@@ -1624,7 +1621,7 @@ void main() {
final TestGesture gesture = final TestGesture gesture =
await tester.startGesture(textfieldStart + const Offset(150.0, 5.0)); await tester.startGesture(textfieldStart + const Offset(150.0, 5.0));
// Hold the press. // Hold the press.
await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 500));
expect( expect(
controller.selection, controller.selection,
...@@ -1763,7 +1760,7 @@ void main() { ...@@ -1763,7 +1760,7 @@ void main() {
final TestGesture gesture = final TestGesture gesture =
await tester.startGesture(textfieldStart + const Offset(150.0, 5.0)); await tester.startGesture(textfieldStart + const Offset(150.0, 5.0));
// Hold the press. // Hold the press.
await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 500));
// The obscured text is treated as one word, should select all // The obscured text is treated as one word, should select all
expect( expect(
...@@ -1849,7 +1846,7 @@ void main() { ...@@ -1849,7 +1846,7 @@ void main() {
final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField)); final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0));
await tester.pumpAndSettle(); await tester.pump();
// Collapsed cursor for iOS long press. // Collapsed cursor for iOS long press.
expect( expect(
...@@ -1949,7 +1946,7 @@ void main() { ...@@ -1949,7 +1946,7 @@ void main() {
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
// The selection isn't affected by the gesture lift. // The selection isn't affected by the gesture lift.
expect( expect(
...@@ -2024,7 +2021,7 @@ void main() { ...@@ -2024,7 +2021,7 @@ void main() {
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
// The selection isn't affected by the gesture lift. // The selection isn't affected by the gesture lift.
expect( expect(
...@@ -2079,7 +2076,7 @@ void main() { ...@@ -2079,7 +2076,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
await tester.longPressAt(textfieldStart + const Offset(100.0, 5.0)); await tester.longPressAt(textfieldStart + const Offset(100.0, 5.0));
await tester.pumpAndSettle(); await tester.pump();
// Plain collapsed selection at the exact tap position. // Plain collapsed selection at the exact tap position.
expect( expect(
...@@ -2121,7 +2118,7 @@ void main() { ...@@ -2121,7 +2118,7 @@ void main() {
const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream),
); );
await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0));
await tester.pumpAndSettle(); await tester.pump();
// Double tap selection. // Double tap selection.
expect( expect(
...@@ -2158,7 +2155,7 @@ void main() { ...@@ -2158,7 +2155,7 @@ void main() {
const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream),
); );
await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); await tester.tapAt(textfieldStart + const Offset(50.0, 5.0));
await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 50));
expect( expect(
controller.selection, controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7), const TextSelection(baseOffset: 0, extentOffset: 7),
...@@ -2174,7 +2171,7 @@ void main() { ...@@ -2174,7 +2171,7 @@ void main() {
const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream),
); );
await tester.tapAt(textfieldStart + const Offset(100.0, 5.0)); await tester.tapAt(textfieldStart + const Offset(100.0, 5.0));
await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 50));
expect( expect(
controller.selection, controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7), const TextSelection(baseOffset: 0, extentOffset: 7),
...@@ -2189,7 +2186,7 @@ void main() { ...@@ -2189,7 +2186,7 @@ void main() {
const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream),
); );
await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0));
await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 50));
expect( expect(
controller.selection, controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12), const TextSelection(baseOffset: 8, extentOffset: 12),
...@@ -2233,7 +2230,7 @@ void main() { ...@@ -2233,7 +2230,7 @@ void main() {
); );
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
// Shows toolbar. // Shows toolbar.
expect(find.byType(CupertinoButton), findsNWidgets(3)); expect(find.byType(CupertinoButton), findsNWidgets(3));
}); });
...@@ -3845,7 +3842,7 @@ void main() { ...@@ -3845,7 +3842,7 @@ void main() {
// Long press shows the selection menu. // Long press shows the selection menu.
await tester.longPressAt(textOffsetToPosition(tester, 0)); await tester.longPressAt(textOffsetToPosition(tester, 0));
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('Paste'), findsOneWidget); expect(find.text('Paste'), findsOneWidget);
}, },
); );
......
...@@ -8,26 +8,9 @@ import 'package:flutter/cupertino.dart'; ...@@ -8,26 +8,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../widgets/text.dart' show textOffsetToPosition; import '../widgets/text.dart' show textOffsetToPosition;
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
class _LongCupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> { class _LongCupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
const _LongCupertinoLocalizationsDelegate(); const _LongCupertinoLocalizationsDelegate();
...@@ -66,9 +49,6 @@ class _LongCupertinoLocalizations extends DefaultCupertinoLocalizations { ...@@ -66,9 +49,6 @@ class _LongCupertinoLocalizations extends DefaultCupertinoLocalizations {
const _LongCupertinoLocalizations longLocalizations = _LongCupertinoLocalizations(); const _LongCupertinoLocalizations longLocalizations = _LongCupertinoLocalizations();
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final MockClipboard mockClipboard = MockClipboard();
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
// Returns true iff the button is visually enabled. // Returns true iff the button is visually enabled.
bool appearsEnabled(WidgetTester tester, String text) { bool appearsEnabled(WidgetTester tester, String text) {
...@@ -174,55 +154,6 @@ void main() { ...@@ -174,55 +154,6 @@ void main() {
}); });
}); });
testWidgets('Paste only appears when clipboard has contents', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
await tester.pumpWidget(
CupertinoApp(
home: Column(
children: <Widget>[
CupertinoTextField(
controller: controller,
),
],
),
),
);
// Make sure the clipboard is empty to start.
await Clipboard.setData(const ClipboardData(text: ''));
// Double tap to selet 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();
// No Paste yet, because nothing has been copied.
expect(find.text('Paste'), findsNothing);
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 now shows.
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget);
}, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
group('Text selection menu overflow (iOS)', () { group('Text selection menu overflow (iOS)', () {
testWidgets('All menu items show when they fit.', (WidgetTester tester) async { testWidgets('All menu items show when they fit.', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'abc def ghi'); final TextEditingController controller = TextEditingController(text: 'abc def ghi');
...@@ -250,7 +181,7 @@ void main() { ...@@ -250,7 +181,7 @@ void main() {
// Long press on an empty space to show the selection menu. // Long press on an empty space to show the selection menu.
await tester.longPressAt(textOffsetToPosition(tester, 4)); await tester.longPressAt(textOffsetToPosition(tester, 4));
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('Cut'), findsNothing); expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing); expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsOneWidget); expect(find.text('Paste'), findsOneWidget);
...@@ -474,7 +405,7 @@ void main() { ...@@ -474,7 +405,7 @@ void main() {
// Long press on an empty space to show the selection menu, with only the // Long press on an empty space to show the selection menu, with only the
// paste button visible. // paste button visible.
await tester.longPressAt(textOffsetToPosition(tester, 4)); await tester.longPressAt(textOffsetToPosition(tester, 4));
await tester.pumpAndSettle(); await tester.pump();
expect(find.text(longLocalizations.cutButtonLabel), findsNothing); expect(find.text(longLocalizations.cutButtonLabel), findsNothing);
expect(find.text(longLocalizations.copyButtonLabel), findsNothing); expect(find.text(longLocalizations.copyButtonLabel), findsNothing);
expect(find.text(longLocalizations.pasteButtonLabel), findsOneWidget); expect(find.text(longLocalizations.pasteButtonLabel), findsOneWidget);
......
...@@ -3,31 +3,12 @@ ...@@ -3,31 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart'; import '../rendering/mock_canvas.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final MockClipboard mockClipboard = MockClipboard();
DateTime firstDate; DateTime firstDate;
DateTime lastDate; DateTime lastDate;
...@@ -54,7 +35,7 @@ void main() { ...@@ -54,7 +35,7 @@ void main() {
return tester.widget<TextField>(find.byType(TextField)); return tester.widget<TextField>(find.byType(TextField));
} }
setUp(() async { setUp(() {
firstDate = DateTime(2001, DateTime.january, 1); firstDate = DateTime(2001, DateTime.january, 1);
lastDate = DateTime(2031, DateTime.december, 31); lastDate = DateTime(2031, DateTime.december, 31);
initialDate = DateTime(2016, DateTime.january, 15); initialDate = DateTime(2016, DateTime.january, 15);
...@@ -70,15 +51,6 @@ void main() { ...@@ -70,15 +51,6 @@ void main() {
fieldHintText = null; fieldHintText = null;
fieldLabelText = null; fieldLabelText = null;
helpText = null; helpText = null;
// Fill the clipboard so that the PASTE option is available in the text
// selection menu.
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
tearDown(() {
SystemChannels.platform.setMockMethodCallHandler(null);
}); });
Future<void> prepareDatePicker(WidgetTester tester, Future<void> callback(Future<DateTime> date)) async { Future<void> prepareDatePicker(WidgetTester tester, Future<void> callback(Future<DateTime> date)) async {
...@@ -1046,6 +1018,7 @@ void main() { ...@@ -1046,6 +1018,7 @@ void main() {
semantics.dispose(); semantics.dispose();
}); });
}); });
group('Screen configurations', () { group('Screen configurations', () {
......
...@@ -9,37 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -9,37 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final MockClipboard mockClipboard = MockClipboard();
setUp(() async {
// Fill the clipboard so that the PASTE option is available in the text
// selection menu.
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
tearDown(() {
SystemChannels.platform.setMockMethodCallHandler(null);
});
testWidgets('Can open and close search', (WidgetTester tester) async { testWidgets('Can open and close search', (WidgetTester tester) async {
final _TestSearchDelegate delegate = _TestSearchDelegate(); final _TestSearchDelegate delegate = _TestSearchDelegate();
final List<String> selectedResults = <String>[]; final List<String> selectedResults = <String>[];
......
...@@ -5,35 +5,10 @@ ...@@ -5,35 +5,10 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../widgets/text.dart' show findRenderEditable, globalize, textOffsetToPosition; import '../widgets/text.dart' show findRenderEditable, globalize, textOffsetToPosition;
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final MockClipboard mockClipboard = MockClipboard();
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
setUp(() async {
await Clipboard.setData(const ClipboardData(text: 'clipboard data'));
});
group('canSelectAll', () { group('canSelectAll', () {
Widget createEditableText({ Widget createEditableText({
Key key, Key key,
...@@ -129,7 +104,7 @@ void main() { ...@@ -129,7 +104,7 @@ void main() {
expect(endpoints.length, 1); expect(endpoints.length, 1);
final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0); final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0);
await tester.tapAt(handlePos, pointer: 7); await tester.tapAt(handlePos, pointer: 7);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('CUT'), findsNothing); expect(find.text('CUT'), findsNothing);
expect(find.text('COPY'), findsNothing); expect(find.text('COPY'), findsNothing);
expect(find.text('PASTE'), findsOneWidget); expect(find.text('PASTE'), findsOneWidget);
...@@ -260,7 +235,7 @@ void main() { ...@@ -260,7 +235,7 @@ void main() {
// Long press to show the menu. // Long press to show the menu.
final Offset textOffset = textOffsetToPosition(tester, 1); final Offset textOffset = textOffsetToPosition(tester, 1);
await tester.longPressAt(textOffset); await tester.longPressAt(textOffset);
await tester.pumpAndSettle(); await tester.pump();
// The last two buttons are missing, and a more button is shown. // The last two buttons are missing, and a more button is shown.
expect(find.text('CUT'), findsOneWidget); expect(find.text('CUT'), findsOneWidget);
...@@ -326,7 +301,7 @@ void main() { ...@@ -326,7 +301,7 @@ void main() {
// Long press to show the menu. // Long press to show the menu.
final Offset textOffset = textOffsetToPosition(tester, 1); final Offset textOffset = textOffsetToPosition(tester, 1);
await tester.longPressAt(textOffset); await tester.longPressAt(textOffset);
await tester.pumpAndSettle(); await tester.pump();
// The last button is missing, and a more button is shown. // The last button is missing, and a more button is shown.
expect(find.text('CUT'), findsOneWidget); expect(find.text('CUT'), findsOneWidget);
...@@ -438,7 +413,7 @@ void main() { ...@@ -438,7 +413,7 @@ void main() {
// Long press to show the menu. // Long press to show the menu.
await tester.longPressAt(textOffsetToPosition(tester, 1)); await tester.longPressAt(textOffsetToPosition(tester, 1));
await tester.pumpAndSettle(); await tester.pump();
// The last button is missing, and a more button is shown. // The last button is missing, and a more button is shown.
expect(find.text('CUT'), findsOneWidget); expect(find.text('CUT'), findsOneWidget);
...@@ -513,7 +488,7 @@ void main() { ...@@ -513,7 +488,7 @@ void main() {
expect(endpoints.length, 1); expect(endpoints.length, 1);
final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0); final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0);
await tester.tapAt(handlePos, pointer: 7); await tester.tapAt(handlePos, pointer: 7);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('CUT'), findsNothing); expect(find.text('CUT'), findsNothing);
expect(find.text('COPY'), findsNothing); expect(find.text('COPY'), findsNothing);
expect(find.text('PASTE'), findsOneWidget); expect(find.text('PASTE'), findsOneWidget);
...@@ -581,58 +556,4 @@ void main() { ...@@ -581,58 +556,4 @@ void main() {
); );
}); });
}); });
testWidgets('Paste only appears when clipboard has contents', (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 to start.
await Clipboard.setData(const ClipboardData(text: ''));
// Double tap to selet 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();
// No Paste yet, because nothing has been copied.
expect(find.text('PASTE'), findsNothing);
expect(find.text('COPY'), findsOneWidget);
expect(find.text('CUT'), findsOneWidget);
expect(find.text('SELECT ALL'), 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);
expect(find.text('SELECT ALL'), 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 now shows.
expect(find.text('COPY'), findsOneWidget);
expect(find.text('CUT'), findsOneWidget);
expect(find.text('PASTE'), findsOneWidget);
expect(find.text('SELECT ALL'), findsOneWidget);
}, skip: isBrowser);
} }
...@@ -19,12 +19,6 @@ const TextStyle textStyle = TextStyle(); ...@@ -19,12 +19,6 @@ const TextStyle textStyle = TextStyle();
const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00);
void main() { void main() {
setUp(() async {
// Fill the clipboard so that the PASTE option is available in the text
// selection menu.
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
testWidgets('cursor has expected width and radius', (WidgetTester tester) async { testWidgets('cursor has expected width and radius', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MediaQuery(data: const MediaQueryData(devicePixelRatio: 1.0), MediaQuery(data: const MediaQueryData(devicePixelRatio: 1.0),
...@@ -45,6 +39,7 @@ void main() { ...@@ -45,6 +39,7 @@ void main() {
expect(editableText.cursorRadius.x, 2.0); expect(editableText.cursorRadius.x, 2.0);
}); });
testWidgets('cursor layout has correct width', (WidgetTester tester) async { testWidgets('cursor layout has correct width', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>(); final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
...@@ -137,7 +132,7 @@ void main() { ...@@ -137,7 +132,7 @@ void main() {
final Finder textFinder = find.byKey(editableTextKey); final Finder textFinder = find.byKey(editableTextKey);
await tester.longPress(textFinder); await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar(); tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pumpAndSettle(); await tester.pump();
await tester.tap(find.text('PASTE')); await tester.tap(find.text('PASTE'));
await tester.pump(); await tester.pump();
......
...@@ -48,12 +48,9 @@ void main() { ...@@ -48,12 +48,9 @@ void main() {
final MockClipboard mockClipboard = MockClipboard(); final MockClipboard mockClipboard = MockClipboard();
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
setUp(() async { setUp(() {
debugResetSemanticsIdCounter(); debugResetSemanticsIdCounter();
controller = TextEditingController(); controller = TextEditingController();
// Fill the clipboard so that the PASTE option is available in the text
// selection menu.
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
}); });
tearDown(() { tearDown(() {
...@@ -964,7 +961,7 @@ void main() { ...@@ -964,7 +961,7 @@ void main() {
// Can't show the toolbar when there's no focus. // Can't show the toolbar when there's no focus.
expect(state.showToolbar(), false); expect(state.showToolbar(), false);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('PASTE'), findsNothing); expect(find.text('PASTE'), findsNothing);
// Can show the toolbar when focused even though there's no text. // Can show the toolbar when focused even though there's no text.
...@@ -974,7 +971,7 @@ void main() { ...@@ -974,7 +971,7 @@ void main() {
); );
await tester.pump(); await tester.pump();
expect(state.showToolbar(), true); expect(state.showToolbar(), true);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('PASTE'), findsOneWidget); expect(find.text('PASTE'), findsOneWidget);
// Hide the menu again. // Hide the menu again.
...@@ -986,7 +983,7 @@ void main() { ...@@ -986,7 +983,7 @@ void main() {
controller.text = 'blah'; controller.text = 'blah';
await tester.pump(); await tester.pump();
expect(state.showToolbar(), true); expect(state.showToolbar(), true);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('PASTE'), findsOneWidget); expect(find.text('PASTE'), findsOneWidget);
}, skip: isBrowser); }, skip: isBrowser);
...@@ -1026,7 +1023,7 @@ void main() { ...@@ -1026,7 +1023,7 @@ void main() {
// Should be able to show the toolbar. // Should be able to show the toolbar.
expect(state.showToolbar(), true); expect(state.showToolbar(), true);
await tester.pumpAndSettle(); await tester.pump();
expect(find.text('PASTE'), findsOneWidget); expect(find.text('PASTE'), findsOneWidget);
}); });
...@@ -1195,7 +1192,7 @@ void main() { ...@@ -1195,7 +1192,7 @@ void main() {
final Finder textFinder = find.byType(EditableText); final Finder textFinder = find.byType(EditableText);
await tester.longPress(textFinder); await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar(); tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pumpAndSettle(); await tester.pump();
await tester.tap(find.text('PASTE')); await tester.tap(find.text('PASTE'));
await tester.pump(); await tester.pump();
...@@ -2415,13 +2412,12 @@ void main() { ...@@ -2415,13 +2412,12 @@ void main() {
controls = MockTextSelectionControls(); controls = MockTextSelectionControls();
when(controls.buildHandle(any, any, any)).thenReturn(Container()); when(controls.buildHandle(any, any, any)).thenReturn(Container());
when(controls.buildToolbar(any, any, any, any, any, any, any)) when(controls.buildToolbar(any, any, any, any, any, any))
.thenReturn(Container()); .thenReturn(Container());
}); });
testWidgets('are exposed', (WidgetTester tester) async { testWidgets('are exposed', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
addTearDown(semantics.dispose);
when(controls.canCopy(any)).thenReturn(false); when(controls.canCopy(any)).thenReturn(false);
when(controls.canCut(any)).thenReturn(false); when(controls.canCut(any)).thenReturn(false);
...@@ -2461,7 +2457,6 @@ void main() { ...@@ -2461,7 +2457,6 @@ void main() {
when(controls.canCopy(any)).thenReturn(false); when(controls.canCopy(any)).thenReturn(false);
when(controls.canPaste(any)).thenReturn(true); when(controls.canPaste(any)).thenReturn(true);
await _buildApp(controls, tester); await _buildApp(controls, tester);
await tester.pumpAndSettle();
expect( expect(
semantics, semantics,
includesNodeWith( includesNodeWith(
...@@ -2509,6 +2504,8 @@ void main() { ...@@ -2509,6 +2504,8 @@ void main() {
], ],
), ),
); );
semantics.dispose();
}); });
testWidgets('can copy/cut/paste with a11y', (WidgetTester tester) async { testWidgets('can copy/cut/paste with a11y', (WidgetTester tester) async {
...@@ -2567,7 +2564,7 @@ void main() { ...@@ -2567,7 +2564,7 @@ void main() {
); );
owner.performAction(expectedNodeId, SemanticsAction.copy); owner.performAction(expectedNodeId, SemanticsAction.copy);
verify(controls.handleCopy(any, any)).called(1); verify(controls.handleCopy(any)).called(1);
owner.performAction(expectedNodeId, SemanticsAction.cut); owner.performAction(expectedNodeId, SemanticsAction.cut);
verify(controls.handleCut(any)).called(1); verify(controls.handleCut(any)).called(1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment