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
988bfc16
Unverified
Commit
988bfc16
authored
Nov 16, 2018
by
xster
Committed by
GitHub
Nov 16, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
iOS tap handling on CupertinoTextField (#24034)
parent
0155ee71
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
527 additions
and
17 deletions
+527
-17
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+52
-5
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+61
-11
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+1
-1
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+413
-0
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
988bfc16
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter/gestures.dart'
show
kDoubleTapTimeout
,
kDoubleTapSlop
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
...
@@ -418,6 +420,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -418,6 +420,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
FocusNode
_focusNode
;
FocusNode
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
// Is shortly after a previous single tap when not null.
Timer
_doubleTapTimer
;
Offset
_lastTapOffset
;
// True if second tap down of a double tap is detected. Used to discard
// subsequent tap up / tap hold of the same tap.
bool
_isDoubleTap
=
false
;
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
...
@@ -447,6 +456,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -447,6 +456,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
void
dispose
()
{
void
dispose
()
{
_focusNode
?.
dispose
();
_focusNode
?.
dispose
();
_controller
?.
removeListener
(
updateKeepAlive
);
_controller
?.
removeListener
(
updateKeepAlive
);
_doubleTapTimer
?.
cancel
();
super
.
dispose
();
super
.
dispose
();
}
}
...
@@ -456,17 +466,54 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -456,17 +466,54 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
RenderEditable
get
_renderEditable
=>
_editableTextKey
.
currentState
.
renderEditable
;
RenderEditable
get
_renderEditable
=>
_editableTextKey
.
currentState
.
renderEditable
;
// The down handler is force-run on success of a single tap and optimistically
// run before a long press success.
void
_handleTapDown
(
TapDownDetails
details
)
{
void
_handleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
handleTapDown
(
details
);
_renderEditable
.
handleTapDown
(
details
);
// This isn't detected as a double tap gesture in the gesture recognizer
// because it's 2 single taps, each of which may do different things depending
// on whether it's a single tap, the first tap of a double tap, the second
// tap held down, a clean double tap etc.
if
(
_doubleTapTimer
!=
null
&&
_isWithinDoubleTapTolerance
(
details
.
globalPosition
))
{
// If there was already a previous tap, the second down hold/tap is a
// double tap.
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
_doubleTapTimer
.
cancel
();
_doubleTapTimeout
();
_isDoubleTap
=
true
;
}
}
}
void
_handleTap
()
{
void
_handleTapUp
(
TapUpDetails
details
)
{
_renderEditable
.
handleTap
();
if
(!
_isDoubleTap
)
{
_renderEditable
.
selectWordEdge
(
cause:
SelectionChangedCause
.
tap
);
_lastTapOffset
=
details
.
globalPosition
;
_doubleTapTimer
=
Timer
(
kDoubleTapTimeout
,
_doubleTapTimeout
);
_requestKeyboard
();
_requestKeyboard
();
}
}
_isDoubleTap
=
false
;
}
void
_handleLongPress
()
{
void
_handleLongPress
()
{
_renderEditable
.
handleLongPress
();
if
(!
_isDoubleTap
)
{
_renderEditable
.
selectPosition
(
cause:
SelectionChangedCause
.
longPress
);
}
_isDoubleTap
=
false
;
}
void
_doubleTapTimeout
()
{
_doubleTapTimer
=
null
;
_lastTapOffset
=
null
;
}
bool
_isWithinDoubleTapTolerance
(
Offset
secondTapOffset
)
{
assert
(
secondTapOffset
!=
null
);
if
(
_lastTapOffset
==
null
)
{
return
false
;
}
final
Offset
difference
=
secondTapOffset
-
_lastTapOffset
;
return
difference
.
distance
<=
kDoubleTapSlop
;
}
}
@override
@override
...
@@ -648,7 +695,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -648,7 +695,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
child:
GestureDetector
(
child:
GestureDetector
(
behavior:
HitTestBehavior
.
translucent
,
behavior:
HitTestBehavior
.
translucent
,
onTapDown:
_handleTapDown
,
onTapDown:
_handleTapDown
,
onTap
:
_handleTa
p
,
onTap
Up:
_handleTapU
p
,
onLongPress:
_handleLongPress
,
onLongPress:
_handleLongPress
,
excludeFromSemantics:
true
,
excludeFromSemantics:
true
,
child:
_addTextDependentAttachments
(
paddedEditable
),
child:
_addTextDependentAttachments
(
paddedEditable
),
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
988bfc16
...
@@ -32,6 +32,10 @@ enum SelectionChangedCause {
...
@@ -32,6 +32,10 @@ enum SelectionChangedCause {
/// of the cursor) to change.
/// of the cursor) to change.
tap
,
tap
,
/// The user tapped twice in quick succession on the text and that caused
/// the selection (or the location of the cursor) to change.
doubleTap
,
/// The user long-pressed the text and that caused the selection (or the
/// The user long-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
/// location of the cursor) to change.
longPress
,
longPress
,
...
@@ -190,7 +194,7 @@ class RenderEditable extends RenderBox {
...
@@ -190,7 +194,7 @@ class RenderEditable extends RenderBox {
/// If true [handleEvent] does nothing and it's assumed that this
/// If true [handleEvent] does nothing and it's assumed that this
/// renderer will be notified of input gestures via [handleTapDown],
/// renderer will be notified of input gestures via [handleTapDown],
/// [handleTap], and [handleLongPress].
/// [handleTap],
[handleDoubleTap],
and [handleLongPress].
///
///
/// The default value of this property is false.
/// The default value of this property is false.
bool
ignorePointer
;
bool
ignorePointer
;
...
@@ -1081,18 +1085,23 @@ class RenderEditable extends RenderBox {
...
@@ -1081,18 +1085,23 @@ class RenderEditable extends RenderBox {
/// When [ignorePointer] is true, an ancestor widget must respond to tap
/// When [ignorePointer] is true, an ancestor widget must respond to tap
/// events by calling this method.
/// events by calling this method.
void
handleTap
()
{
void
handleTap
()
{
_layoutText
(
constraints
.
maxWidth
);
selectPosition
(
cause:
SelectionChangedCause
.
tap
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
TextSelection
.
fromPosition
(
position
),
this
,
SelectionChangedCause
.
tap
);
}
}
}
void
_handleTap
()
{
void
_handleTap
()
{
assert
(!
ignorePointer
);
assert
(!
ignorePointer
);
handleTap
();
handleTap
();
}
}
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [DoubleTapGestureRecognizer.onDoubleTap]
/// callback.
///
/// When [ignorePointer] is true, an ancestor widget must respond to double
/// tap events by calling this method.
void
handleDoubleTap
()
{
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
}
/// If [ignorePointer] is false (the default) then this method is called by
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [LongPressRecognizer.onLongPress]
/// the internal gesture recognizer's [LongPressRecognizer.onLongPress]
/// callback.
/// callback.
...
@@ -1100,16 +1109,57 @@ class RenderEditable extends RenderBox {
...
@@ -1100,16 +1109,57 @@ class RenderEditable extends RenderBox {
/// When [ignorePointer] is true, an ancestor widget must respond to long
/// When [ignorePointer] is true, an ancestor widget must respond to long
/// press events by calling this method.
/// press events by calling this method.
void
handleLongPress
()
{
void
handleLongPress
()
{
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
}
void
_handleLongPress
()
{
assert
(!
ignorePointer
);
handleLongPress
();
}
/// Move selection to the location of the last tap down.
void
selectPosition
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
_selectWordAtOffset
(
position
),
this
,
SelectionChangedCause
.
longPress
);
onSelectionChanged
(
TextSelection
.
fromPosition
(
position
),
this
,
cause
);
}
}
/// Select a word around the location of the last tap down.
void
selectWord
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
_selectWordAtOffset
(
position
),
this
,
cause
);
}
}
/// Move the selection to the beginning or end of a word.
void
selectWordEdge
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
final
TextRange
word
=
_textPainter
.
getWordBoundary
(
position
);
if
(
position
.
offset
-
word
.
start
<=
1
)
{
onSelectionChanged
(
TextSelection
.
collapsed
(
offset:
word
.
start
,
affinity:
TextAffinity
.
downstream
),
this
,
cause
,
);
}
else
{
onSelectionChanged
(
TextSelection
.
collapsed
(
offset:
word
.
end
,
affinity:
TextAffinity
.
upstream
),
this
,
cause
,
);
}
}
}
}
void
_handleLongPress
()
{
assert
(!
ignorePointer
);
handleLongPress
();
}
}
TextSelection
_selectWordAtOffset
(
TextPosition
position
)
{
TextSelection
_selectWordAtOffset
(
TextPosition
position
)
{
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
988bfc16
...
@@ -740,7 +740,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -740,7 +740,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
bool
longPress
=
cause
==
SelectionChangedCause
.
longPress
;
final
bool
longPress
=
cause
==
SelectionChangedCause
.
longPress
;
if
(
cause
!=
SelectionChangedCause
.
keyboard
&&
(
_value
.
text
.
isNotEmpty
||
longPress
))
if
(
cause
!=
SelectionChangedCause
.
keyboard
&&
(
_value
.
text
.
isNotEmpty
||
longPress
))
_selectionOverlay
.
showHandles
();
_selectionOverlay
.
showHandles
();
if
(
longPress
)
if
(
longPress
||
cause
==
SelectionChangedCause
.
doubleTap
)
_selectionOverlay
.
showToolbar
();
_selectionOverlay
.
showToolbar
();
if
(
widget
.
onSelectionChanged
!=
null
)
if
(
widget
.
onSelectionChanged
!=
null
)
widget
.
onSelectionChanged
(
selection
,
cause
);
widget
.
onSelectionChanged
(
selection
,
cause
);
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
988bfc16
...
@@ -685,4 +685,417 @@ void main() {
...
@@ -685,4 +685,417 @@ void main() {
expect
(
find
.
text
(
"j'aime la poutine"
),
findsOneWidget
);
expect
(
find
.
text
(
"j'aime la poutine"
),
findsOneWidget
);
expect
(
find
.
text
(
'field 2'
),
findsNothing
);
expect
(
find
.
text
(
'field 2'
),
findsNothing
);
});
});
testWidgets
(
'tap moves cursor to the edge of the word it tapped on'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
();
// We moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
// But don't trigger the toolbar.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
);
testWidgets
(
'slow double tap does not trigger double tap'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
();
// Plain collapsed selection.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
// No toolbar.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
);
testWidgets
(
'double tap selects word and first tap of double tap moves cursor'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
();
// Second tap selects the word around the cursor.
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
// Selected text shows 3 toolbar buttons.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
);
testWidgets
(
'double tap hold selects word'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
// Hold the press.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
// Selected text shows 3 toolbar buttons.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
await
gesture
.
up
();
await
tester
.
pump
();
// Still selected.
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
);
testWidgets
(
'tap after a double tap select is not affected'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
100.0
,
5.0
));
await
tester
.
pump
();
// Plain collapsed selection at the edge of first word. In iOS 12, the
// the first tap after a double tap ends up putting the cursor at where
// you tapped instead of the edge like every other single tap. This is
// likely a bug in iOS 12 and not present in other versions.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
// No toolbar.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
);
testWidgets
(
'long press moves cursor to the exact long press position and shows toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
();
// Collapsed cursor for iOS long press.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
upstream
),
);
// Collapsed toolbar shows 2 buttons.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
},
);
testWidgets
(
'long press tap is not a double tap'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
();
// We ended up moving the cursor to the edge of the same word and dismissed
// the toolbar.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
// Collapsed toolbar shows 2 buttons.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
);
testWidgets
(
'long tap after a double tap select is not affected'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor to the beginning of the second word.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
100.0
,
5.0
));
await
tester
.
pump
();
// Plain collapsed selection at the exact tap position.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
6
,
affinity:
TextAffinity
.
upstream
),
);
// Long press toolbar.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
},
);
testWidgets
(
'double tap after a long tap is not affected'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
8
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
();
// Double tap selection.
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
);
testWidgets
(
'double tap chains work'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
expect
(
controller
.
selection
,
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
));
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
// Double tap selecting the same word somewhere else is fine.
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
100.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
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
));
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
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
));
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
);
}
}
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