Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
053c3881
Unverified
Commit
053c3881
authored
May 07, 2020
by
Justin McCandless
Committed by
GitHub
May 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Paste shows only when content on clipboard (#54902)
parent
4be79199
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
715 additions
and
118 deletions
+715
-118
text_selection.dart
packages/flutter/lib/src/cupertino/text_selection.dart
+154
-41
text_selection.dart
packages/flutter/lib/src/material/text_selection.dart
+90
-12
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+17
-2
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+102
-4
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+18
-15
text_selection_test.dart
packages/flutter/test/cupertino/text_selection_test.dart
+71
-2
date_picker_test.dart
packages/flutter/test/material/date_picker_test.dart
+29
-2
search_test.dart
packages/flutter/test/material/search_test.dart
+30
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+100
-23
text_selection_test.dart
packages/flutter/test/material/text_selection_test.dart
+84
-5
editable_text_cursor_test.dart
packages/flutter/test/widgets/editable_text_cursor_test.dart
+7
-2
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+13
-10
No files found.
packages/flutter/lib/src/cupertino/text_selection.dart
View file @
053c3881
...
...
@@ -8,6 +8,7 @@ import 'dart:ui' as ui;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'button.dart'
;
import
'colors.dart'
;
...
...
@@ -62,6 +63,151 @@ const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle(
// Eyeballed value.
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.
///
/// Typically displays buttons for text manipulation, e.g. copying and pasting text.
...
...
@@ -312,6 +458,7 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
MediaQueryData
mediaQuery
=
MediaQuery
.
of
(
context
);
...
...
@@ -338,49 +485,15 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
?
endpoints
.
first
.
point
.
dy
-
textLineHeight
-
_kToolbarContentDistance
-
_kToolbarHeight
:
endpoints
.
last
.
point
.
dy
+
_kToolbarContentDistance
;
final
List
<
Widget
>
items
=
<
Widget
>[];
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
,
return
_CupertinoTextSelectionToolbarWrapper
(
arrowTipX:
arrowTipX
,
barTopY:
localBarTopY
+
globalEditableRegion
.
top
,
clipboardStatus:
clipboardStatus
,
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
,
child:
items
.
isEmpty
?
null
:
_CupertinoTextSelectionToolbarContent
(
isArrowPointingDown:
isArrowPointingDown
,
children:
items
,
),
);
}
...
...
packages/flutter/lib/src/material/text_selection.dart
View file @
053c3881
...
...
@@ -6,6 +6,7 @@ import 'dart:math' as math;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'debug.dart'
;
...
...
@@ -29,6 +30,7 @@ const double _kToolbarContentDistance = 8.0;
/// Manages a copy/paste text selection toolbar.
class
_TextSelectionToolbar
extends
StatefulWidget
{
const
_TextSelectionToolbar
({
this
.
clipboardStatus
,
Key
key
,
this
.
handleCut
,
this
.
handleCopy
,
...
...
@@ -37,6 +39,7 @@ class _TextSelectionToolbar extends StatefulWidget {
this
.
isAbove
,
})
:
super
(
key:
key
);
final
ClipboardStatusNotifier
clipboardStatus
;
final
VoidCallback
handleCut
;
final
VoidCallback
handleCopy
;
final
VoidCallback
handlePaste
;
...
...
@@ -50,6 +53,8 @@ class _TextSelectionToolbar extends StatefulWidget {
}
class
_TextSelectionToolbarState
extends
State
<
_TextSelectionToolbar
>
with
TickerProviderStateMixin
{
ClipboardStatusNotifier
_clipboardStatus
;
// 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
// menu items are shown.
...
...
@@ -66,33 +71,93 @@ 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
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
))
||
((
widget
.
handleCopy
==
null
)
!=
(
oldWidget
.
handleCopy
==
null
))
||
((
widget
.
handlePaste
==
null
)
!=
(
oldWidget
.
handlePaste
==
null
))
||
((
widget
.
handleSelectAll
==
null
)
!=
(
oldWidget
.
handleSelectAll
==
null
)))
{
// 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
;
_reset
();
}
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
();
}
}
super
.
didUpdateWidget
(
oldWidget
);
}
@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
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
List
<
Widget
>
items
=
<
Widget
>[
if
(
widget
.
handleCut
!=
null
)
_getItem
(
widget
.
handleCut
,
localizations
.
cutButtonLabel
),
if
(
widget
.
handleCopy
!=
null
)
_getItem
(
widget
.
handleCopy
,
localizations
.
copyButtonLabel
),
if
(
widget
.
handlePaste
!=
null
)
if
(
widget
.
handlePaste
!=
null
&&
_clipboardStatus
.
value
==
ClipboardStatus
.
pasteable
)
_getItem
(
widget
.
handlePaste
,
localizations
.
pasteButtonLabel
),
if
(
widget
.
handleSelectAll
!=
null
)
_getItem
(
widget
.
handleSelectAll
,
localizations
.
selectAllButtonLabel
),
...
...
@@ -103,7 +168,6 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke
return
const
SizedBox
(
width:
0.0
,
height:
0.0
);
}
return
_TextSelectionToolbarContainer
(
key:
_containerKey
,
overflowOpen:
_overflowOpen
,
...
...
@@ -527,6 +591,18 @@ class _TextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRender
}
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
...
...
@@ -630,6 +706,7 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
Offset
selectionMidpoint
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
assert
(
debugCheckHasMaterialLocalizations
(
context
));
...
...
@@ -665,8 +742,9 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
fitsAbove
,
),
child:
_TextSelectionToolbar
(
clipboardStatus:
clipboardStatus
,
handleCut:
canCut
(
delegate
)
?
()
=>
handleCut
(
delegate
)
:
null
,
handleCopy:
canCopy
(
delegate
)
?
()
=>
handleCopy
(
delegate
)
:
null
,
handleCopy:
canCopy
(
delegate
)
?
()
=>
handleCopy
(
delegate
,
clipboardStatus
)
:
null
,
handlePaste:
canPaste
(
delegate
)
?
()
=>
handlePaste
(
delegate
)
:
null
,
handleSelectAll:
canSelectAll
(
delegate
)
?
()
=>
handleSelectAll
(
delegate
)
:
null
,
isAbove:
fitsAbove
,
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
053c3881
...
...
@@ -1144,6 +1144,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
bool
_targetCursorVisibility
=
false
;
final
ValueNotifier
<
bool
>
_cursorVisibilityNotifier
=
ValueNotifier
<
bool
>(
true
);
final
GlobalKey
_editableKey
=
GlobalKey
();
final
ClipboardStatusNotifier
_clipboardStatus
=
ClipboardStatusNotifier
();
TextInputConnection
_textInputConnection
;
TextSelectionOverlay
_selectionOverlay
;
...
...
@@ -1190,11 +1191,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
bool
get
selectAllEnabled
=>
widget
.
toolbarOptions
.
selectAll
;
void
_onChangedClipboardStatus
()
{
setState
(()
{
// Inform the widget that the value of clipboardStatus has changed.
});
}
// State lifecycle:
@override
void
initState
()
{
super
.
initState
();
_clipboardStatus
.
addListener
(
_onChangedClipboardStatus
);
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
_focusAttachment
=
widget
.
focusNode
.
attach
(
context
);
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
...
...
@@ -1268,6 +1276,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
);
}
}
if
(
widget
.
selectionEnabled
&&
pasteEnabled
&&
widget
.
selectionControls
?.
canPaste
(
this
)
==
true
)
{
_clipboardStatus
.
update
();
}
}
@override
...
...
@@ -1284,6 +1295,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay
=
null
;
_focusAttachment
.
detach
();
widget
.
focusNode
.
removeListener
(
_handleFocusChanged
);
WidgetsBinding
.
instance
.
removeObserver
(
this
);
_clipboardStatus
.
removeListener
(
_onChangedClipboardStatus
);
_clipboardStatus
.
dispose
();
super
.
dispose
();
}
...
...
@@ -1610,6 +1624,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
widget
.
selectionControls
!=
null
)
{
_selectionOverlay
=
TextSelectionOverlay
(
clipboardStatus:
_clipboardStatus
,
context:
context
,
value:
_value
,
debugRequiredFor:
widget
,
...
...
@@ -1980,7 +1995,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
VoidCallback
_semanticsOnCopy
(
TextSelectionControls
controls
)
{
return
widget
.
selectionEnabled
&&
copyEnabled
&&
_hasFocus
&&
controls
?.
canCopy
(
this
)
==
true
?
()
=>
controls
.
handleCopy
(
this
)
?
()
=>
controls
.
handleCopy
(
this
,
_clipboardStatus
)
:
null
;
}
...
...
@@ -1991,7 +2006,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
VoidCallback
_semanticsOnPaste
(
TextSelectionControls
controls
)
{
return
widget
.
selectionEnabled
&&
pasteEnabled
&&
_hasFocus
&&
controls
?.
canPaste
(
this
)
==
tru
e
return
widget
.
selectionEnabled
&&
pasteEnabled
&&
_hasFocus
&&
controls
?.
canPaste
(
this
)
==
true
&&
_clipboardStatus
.
value
==
ClipboardStatus
.
pasteabl
e
?
()
=>
controls
.
handlePaste
(
this
)
:
null
;
}
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
053c3881
...
...
@@ -12,6 +12,7 @@ import 'package:flutter/scheduler.dart';
import
'package:flutter/services.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
import
'constants.dart'
;
import
'container.dart'
;
import
'editable_text.dart'
;
...
...
@@ -137,6 +138,7 @@ abstract class TextSelectionControls {
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
);
/// Returns the size of the selection handle.
...
...
@@ -165,13 +167,16 @@ abstract class TextSelectionControls {
return
delegate
.
copyEnabled
&&
!
delegate
.
textEditingValue
.
selection
.
isCollapsed
;
}
/// Whether the
current [Clipboard] content can be pasted into the text field
///
managed by the given `delegate`
.
/// Whether the
text field managed by the given `delegate` supports pasting
///
from the clipboard
.
///
/// Subclasses can use this to decide if they should expose the paste
/// 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
)
{
// TODO(goderbauer): return false when clipboard is empty, https://github.com/flutter/flutter/issues/11254
return
delegate
.
pasteEnabled
;
}
...
...
@@ -213,11 +218,12 @@ abstract class TextSelectionControls {
///
/// This is called by subclasses when their copy affordance is activated by
/// the user.
void
handleCopy
(
TextSelectionDelegate
delegate
)
{
void
handleCopy
(
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
)
{
final
TextEditingValue
value
=
delegate
.
textEditingValue
;
Clipboard
.
setData
(
ClipboardData
(
text:
value
.
selection
.
textInside
(
value
.
text
),
));
clipboardStatus
?.
update
();
delegate
.
textEditingValue
=
TextEditingValue
(
text:
value
.
text
,
selection:
TextSelection
.
collapsed
(
offset:
value
.
selection
.
end
),
...
...
@@ -294,6 +300,7 @@ class TextSelectionOverlay {
this
.
selectionDelegate
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
onSelectionHandleTapped
,
this
.
clipboardStatus
,
})
:
assert
(
value
!=
null
),
assert
(
context
!=
null
),
assert
(
handlesVisible
!=
null
),
...
...
@@ -365,6 +372,13 @@ class TextSelectionOverlay {
/// {@endtemplate}
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.
static
const
Duration
fadeDuration
=
Duration
(
milliseconds:
150
);
...
...
@@ -576,6 +590,7 @@ class TextSelectionOverlay {
midpoint
,
endpoints
,
selectionDelegate
,
clipboardStatus
,
),
),
);
...
...
@@ -1486,3 +1501,86 @@ 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
,
}
packages/flutter/test/cupertino/text_field_test.dart
View file @
053c3881
...
...
@@ -171,8 +171,11 @@ void main() {
Offset
textOffsetToPosition
(
WidgetTester
tester
,
int
offset
)
=>
textOffsetToBottomLeftPosition
(
tester
,
offset
)
+
const
Offset
(
0
,
-
2
);
setUp
(()
{
setUp
(()
async
{
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
(
...
...
@@ -1545,7 +1548,7 @@ void main() {
await
tester
.
tapAt
(
textOffsetToPosition
(
tester
,
index
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
textOffsetToPosition
(
tester
,
index
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
...
...
@@ -1585,7 +1588,7 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Second tap selects the word around the cursor.
expect
(
...
...
@@ -1621,7 +1624,7 @@ void main() {
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
// Hold the press.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
)
);
await
tester
.
pump
AndSettle
(
);
expect
(
controller
.
selection
,
...
...
@@ -1760,7 +1763,7 @@ void main() {
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
// Hold the press.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
)
);
await
tester
.
pump
AndSettle
(
);
// The obscured text is treated as one word, should select all
expect
(
...
...
@@ -1846,7 +1849,7 @@ void main() {
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Collapsed cursor for iOS long press.
expect
(
...
...
@@ -1946,7 +1949,7 @@ void main() {
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// The selection isn't affected by the gesture lift.
expect
(
...
...
@@ -2021,7 +2024,7 @@ void main() {
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// The selection isn't affected by the gesture lift.
expect
(
...
...
@@ -2076,7 +2079,7 @@ void main() {
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
100.0
,
5.0
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Plain collapsed selection at the exact tap position.
expect
(
...
...
@@ -2118,7 +2121,7 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Double tap selection.
expect
(
...
...
@@ -2155,7 +2158,7 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
)
);
await
tester
.
pump
AndSettle
(
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
...
...
@@ -2171,7 +2174,7 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
100.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
)
);
await
tester
.
pump
AndSettle
(
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
...
...
@@ -2186,7 +2189,7 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
)
);
await
tester
.
pump
AndSettle
(
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
...
...
@@ -2230,7 +2233,7 @@ void main() {
);
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Shows toolbar.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
});
...
...
@@ -3842,7 +3845,7 @@ void main() {
// Long press shows the selection menu.
await
tester
.
longPressAt
(
textOffsetToPosition
(
tester
,
0
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
},
);
...
...
packages/flutter/test/cupertino/text_selection_test.dart
View file @
053c3881
...
...
@@ -8,9 +8,26 @@ import 'package:flutter/cupertino.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
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
>
{
const
_LongCupertinoLocalizationsDelegate
();
...
...
@@ -49,6 +66,9 @@ class _LongCupertinoLocalizations extends DefaultCupertinoLocalizations {
const
_LongCupertinoLocalizations
longLocalizations
=
_LongCupertinoLocalizations
();
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
// Returns true iff the button is visually enabled.
bool
appearsEnabled
(
WidgetTester
tester
,
String
text
)
{
...
...
@@ -154,6 +174,55 @@ 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)'
,
()
{
testWidgets
(
'All menu items show when they fit.'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
);
...
...
@@ -181,7 +250,7 @@ void main() {
// Long press on an empty space to show the selection menu.
await
tester
.
longPressAt
(
textOffsetToPosition
(
tester
,
4
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'Cut'
),
findsNothing
);
expect
(
find
.
text
(
'Copy'
),
findsNothing
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
...
...
@@ -405,7 +474,7 @@ void main() {
// Long press on an empty space to show the selection menu, with only the
// paste button visible.
await
tester
.
longPressAt
(
textOffsetToPosition
(
tester
,
4
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
longLocalizations
.
cutButtonLabel
),
findsNothing
);
expect
(
find
.
text
(
longLocalizations
.
copyButtonLabel
),
findsNothing
);
expect
(
find
.
text
(
longLocalizations
.
pasteButtonLabel
),
findsOneWidget
);
...
...
packages/flutter/test/material/date_picker_test.dart
View file @
053c3881
...
...
@@ -3,12 +3,31 @@
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.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
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
DateTime
firstDate
;
DateTime
lastDate
;
...
...
@@ -35,7 +54,7 @@ void main() {
return
tester
.
widget
<
TextField
>(
find
.
byType
(
TextField
));
}
setUp
(()
{
setUp
(()
async
{
firstDate
=
DateTime
(
2001
,
DateTime
.
january
,
1
);
lastDate
=
DateTime
(
2031
,
DateTime
.
december
,
31
);
initialDate
=
DateTime
(
2016
,
DateTime
.
january
,
15
);
...
...
@@ -51,6 +70,15 @@ void main() {
fieldHintText
=
null
;
fieldLabelText
=
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
{
...
...
@@ -1018,7 +1046,6 @@ void main() {
semantics
.
dispose
();
});
});
group
(
'Screen configurations'
,
()
{
...
...
packages/flutter/test/material/search_test.dart
View file @
053c3881
...
...
@@ -9,7 +9,37 @@ import 'package:flutter_test/flutter_test.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
(
)
{
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
{
final
_TestSearchDelegate
delegate
=
_TestSearchDelegate
();
final
List
<
String
>
selectedResults
=
<
String
>[];
...
...
packages/flutter/test/material/text_field_test.dart
View file @
053c3881
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/text_selection_test.dart
View file @
053c3881
...
...
@@ -5,10 +5,35 @@
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
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
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
setUp
(()
async
{
await
Clipboard
.
setData
(
const
ClipboardData
(
text:
'clipboard data'
));
});
group
(
'canSelectAll'
,
()
{
Widget
createEditableText
({
Key
key
,
...
...
@@ -104,7 +129,7 @@ void main() {
expect
(
endpoints
.
length
,
1
);
final
Offset
handlePos
=
endpoints
[
0
].
point
+
const
Offset
(
0.0
,
1.0
);
await
tester
.
tapAt
(
handlePos
,
pointer:
7
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'CUT'
),
findsNothing
);
expect
(
find
.
text
(
'COPY'
),
findsNothing
);
expect
(
find
.
text
(
'PASTE'
),
findsOneWidget
);
...
...
@@ -235,7 +260,7 @@ void main() {
// Long press to show the menu.
final
Offset
textOffset
=
textOffsetToPosition
(
tester
,
1
);
await
tester
.
longPressAt
(
textOffset
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// The last two buttons are missing, and a more button is shown.
expect
(
find
.
text
(
'CUT'
),
findsOneWidget
);
...
...
@@ -301,7 +326,7 @@ void main() {
// Long press to show the menu.
final
Offset
textOffset
=
textOffsetToPosition
(
tester
,
1
);
await
tester
.
longPressAt
(
textOffset
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// The last button is missing, and a more button is shown.
expect
(
find
.
text
(
'CUT'
),
findsOneWidget
);
...
...
@@ -413,7 +438,7 @@ void main() {
// Long press to show the menu.
await
tester
.
longPressAt
(
textOffsetToPosition
(
tester
,
1
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// The last button is missing, and a more button is shown.
expect
(
find
.
text
(
'CUT'
),
findsOneWidget
);
...
...
@@ -488,7 +513,7 @@ void main() {
expect
(
endpoints
.
length
,
1
);
final
Offset
handlePos
=
endpoints
[
0
].
point
+
const
Offset
(
0.0
,
1.0
);
await
tester
.
tapAt
(
handlePos
,
pointer:
7
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'CUT'
),
findsNothing
);
expect
(
find
.
text
(
'COPY'
),
findsNothing
);
expect
(
find
.
text
(
'PASTE'
),
findsOneWidget
);
...
...
@@ -556,4 +581,58 @@ 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
);
}
packages/flutter/test/widgets/editable_text_cursor_test.dart
View file @
053c3881
...
...
@@ -19,6 +19,12 @@ const TextStyle textStyle = TextStyle();
const
Color
cursorColor
=
Color
.
fromARGB
(
0xFF
,
0xFF
,
0x00
,
0x00
);
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
{
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
...
...
@@ -39,7 +45,6 @@ void main() {
expect
(
editableText
.
cursorRadius
.
x
,
2.0
);
});
testWidgets
(
'cursor layout has correct width'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
EditableTextState
>
editableTextKey
=
GlobalKey
<
EditableTextState
>();
...
...
@@ -132,7 +137,7 @@ void main() {
final
Finder
textFinder
=
find
.
byKey
(
editableTextKey
);
await
tester
.
longPress
(
textFinder
);
tester
.
state
<
EditableTextState
>(
textFinder
).
showToolbar
();
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
await
tester
.
tap
(
find
.
text
(
'PASTE'
));
await
tester
.
pump
();
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
053c3881
...
...
@@ -48,9 +48,12 @@ void main() {
final
MockClipboard
mockClipboard
=
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
setUp
(()
{
setUp
(()
async
{
debugResetSemanticsIdCounter
();
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
(()
{
...
...
@@ -961,7 +964,7 @@ void main() {
// Can't show the toolbar when there's no focus.
expect
(
state
.
showToolbar
(),
false
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'PASTE'
),
findsNothing
);
// Can show the toolbar when focused even though there's no text.
...
...
@@ -971,7 +974,7 @@ void main() {
);
await
tester
.
pump
();
expect
(
state
.
showToolbar
(),
true
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'PASTE'
),
findsOneWidget
);
// Hide the menu again.
...
...
@@ -983,7 +986,7 @@ void main() {
controller
.
text
=
'blah'
;
await
tester
.
pump
();
expect
(
state
.
showToolbar
(),
true
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'PASTE'
),
findsOneWidget
);
},
skip:
isBrowser
);
...
...
@@ -1023,7 +1026,7 @@ void main() {
// Should be able to show the toolbar.
expect
(
state
.
showToolbar
(),
true
);
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
expect
(
find
.
text
(
'PASTE'
),
findsOneWidget
);
});
...
...
@@ -1192,7 +1195,7 @@ void main() {
final
Finder
textFinder
=
find
.
byType
(
EditableText
);
await
tester
.
longPress
(
textFinder
);
tester
.
state
<
EditableTextState
>(
textFinder
).
showToolbar
();
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
await
tester
.
tap
(
find
.
text
(
'PASTE'
));
await
tester
.
pump
();
...
...
@@ -2412,12 +2415,13 @@ void main() {
controls
=
MockTextSelectionControls
();
when
(
controls
.
buildHandle
(
any
,
any
,
any
)).
thenReturn
(
Container
());
when
(
controls
.
buildToolbar
(
any
,
any
,
any
,
any
,
any
,
any
))
when
(
controls
.
buildToolbar
(
any
,
any
,
any
,
any
,
any
,
any
,
any
))
.
thenReturn
(
Container
());
});
testWidgets
(
'are exposed'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
addTearDown
(
semantics
.
dispose
);
when
(
controls
.
canCopy
(
any
)).
thenReturn
(
false
);
when
(
controls
.
canCut
(
any
)).
thenReturn
(
false
);
...
...
@@ -2457,6 +2461,7 @@ void main() {
when
(
controls
.
canCopy
(
any
)).
thenReturn
(
false
);
when
(
controls
.
canPaste
(
any
)).
thenReturn
(
true
);
await
_buildApp
(
controls
,
tester
);
await
tester
.
pumpAndSettle
();
expect
(
semantics
,
includesNodeWith
(
...
...
@@ -2504,8 +2509,6 @@ void main() {
],
),
);
semantics
.
dispose
();
});
testWidgets
(
'can copy/cut/paste with a11y'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -2564,7 +2567,7 @@ void main() {
);
owner
.
performAction
(
expectedNodeId
,
SemanticsAction
.
copy
);
verify
(
controls
.
handleCopy
(
any
)).
called
(
1
);
verify
(
controls
.
handleCopy
(
any
,
any
)).
called
(
1
);
owner
.
performAction
(
expectedNodeId
,
SemanticsAction
.
cut
);
verify
(
controls
.
handleCut
(
any
)).
called
(
1
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment