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
c49eba6c
Unverified
Commit
c49eba6c
authored
Aug 16, 2021
by
LongCatIsLooong
Committed by
GitHub
Aug 16, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[TextInput] minor fixes (#87973)
parent
af4faf48
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
207 additions
and
35 deletions
+207
-35
text_editing.dart
packages/flutter/lib/src/services/text_editing.dart
+23
-10
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+33
-22
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+2
-3
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+33
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+116
-0
No files found.
packages/flutter/lib/src/services/text_editing.dart
View file @
c49eba6c
...
...
@@ -94,27 +94,40 @@ class TextSelection extends TextRange {
@override
String
toString
()
{
return
'
${objectRuntimeType(this, 'TextSelection')}
(baseOffset:
$baseOffset
, extentOffset:
$extentOffset
, affinity:
$affinity
, isDirectional:
$isDirectional
)'
;
final
String
typeName
=
objectRuntimeType
(
this
,
'TextSelection'
);
if
(!
isValid
)
{
return
'
$typeName
.invalid'
;
}
return
isCollapsed
?
'
$typeName
.collapsed(offset:
$baseOffset
, affinity:
$affinity
, isDirectional:
$isDirectional
)'
:
'
$typeName
(baseOffset:
$baseOffset
, extentOffset:
$extentOffset
, isDirectional:
$isDirectional
)'
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
return
other
is
TextSelection
&&
other
.
baseOffset
==
baseOffset
if
(
other
is
!
TextSelection
)
return
false
;
if
(!
isValid
)
{
return
!
other
.
isValid
;
}
return
other
.
baseOffset
==
baseOffset
&&
other
.
extentOffset
==
extentOffset
&&
other
.
affinity
==
affinity
&&
(!
isCollapsed
||
other
.
affinity
==
affinity
)
&&
other
.
isDirectional
==
isDirectional
;
}
@override
int
get
hashCode
=>
hashValues
(
baseOffset
.
hashCode
,
extentOffset
.
hashCode
,
affinity
.
hashCode
,
isDirectional
.
hashCode
,
);
int
get
hashCode
{
if
(!
isValid
)
{
return
hashValues
(-
1
.
hashCode
,
-
1
.
hashCode
,
TextAffinity
.
downstream
.
hashCode
);
}
final
int
affinityHash
=
isCollapsed
?
affinity
.
hashCode
:
TextAffinity
.
downstream
.
hashCode
;
return
hashValues
(
baseOffset
.
hashCode
,
extentOffset
.
hashCode
,
affinityHash
,
isDirectional
.
hashCode
);
}
/// Creates a new [TextSelection] based on the current selection, with the
/// provided parameters overridden.
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
c49eba6c
...
...
@@ -1535,9 +1535,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
TextInputConnection
?
_textInputConnection
;
TextSelectionOverlay
?
_selectionOverlay
;
ScrollController
?
_scrollController
;
ScrollController
?
_internalScrollController
;
ScrollController
get
_scrollController
=>
widget
.
scrollController
??
(
_internalScrollController
??=
ScrollController
());
late
AnimationController
_cursorBlinkOpacityController
;
late
final
AnimationController
_cursorBlinkOpacityController
=
AnimationController
(
vsync:
this
,
duration:
_fadeDuration
,
)..
addListener
(
_onCursorColorTick
);
final
LayerLink
_toolbarLayerLink
=
LayerLink
();
final
LayerLink
_startHandleLayerLink
=
LayerLink
();
...
...
@@ -1576,7 +1580,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// cursor position after the user has finished placing it.
static
const
Duration
_floatingCursorResetTime
=
Duration
(
milliseconds:
125
);
late
AnimationController
_floatingCursorResetController
;
late
final
AnimationController
_floatingCursorResetController
=
AnimationController
(
vsync:
this
,
)..
addListener
(
_onFloatingCursorResetTick
);
@override
bool
get
wantKeepAlive
=>
widget
.
focusNode
.
hasFocus
;
...
...
@@ -1610,12 +1616,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
_focusAttachment
=
widget
.
focusNode
.
attach
(
context
);
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
_scrollController
=
widget
.
scrollController
??
ScrollController
();
_scrollController
!.
addListener
(()
{
_selectionOverlay
?.
updateForScroll
();
});
_cursorBlinkOpacityController
=
AnimationController
(
vsync:
this
,
duration:
_fadeDuration
);
_cursorBlinkOpacityController
.
addListener
(
_onCursorColorTick
);
_floatingCursorResetController
=
AnimationController
(
vsync:
this
);
_floatingCursorResetController
.
addListener
(
_onFloatingCursorResetTick
);
_scrollController
.
addListener
(
_updateSelectionOverlayForScroll
);
_cursorVisibilityNotifier
.
value
=
widget
.
showCursor
;
}
...
...
@@ -1663,6 +1664,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
updateKeepAlive
();
}
if
(
widget
.
scrollController
!=
oldWidget
.
scrollController
)
{
(
oldWidget
.
scrollController
??
_internalScrollController
)?.
removeListener
(
_updateSelectionOverlayForScroll
);
_scrollController
.
addListener
(
_updateSelectionOverlayForScroll
);
}
if
(!
_shouldCreateInputConnection
)
{
_closeInputConnectionIfNeeded
();
}
else
if
(
oldWidget
.
readOnly
&&
_hasFocus
)
{
...
...
@@ -1696,14 +1702,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
dispose
()
{
_internalScrollController
?.
dispose
();
_currentAutofillScope
?.
unregister
(
autofillId
);
widget
.
controller
.
removeListener
(
_didChangeTextEditingValue
);
_cursorBlinkOpacityController
.
removeListener
(
_onCursorColorTick
);
_floatingCursorResetController
.
removeListener
(
_onFloatingCursorResetTick
);
_floatingCursorResetController
.
dispose
();
_closeInputConnectionIfNeeded
();
assert
(!
_hasInputConnection
);
_stopCursorTimer
();
assert
(
_cursorTimer
==
null
);
_cursorTimer
?.
cancel
();
_cursorTimer
=
null
;
_cursorBlinkOpacityController
.
dispose
();
_selectionOverlay
?.
dispose
();
_selectionOverlay
=
null
;
_focusAttachment
!.
detach
();
...
...
@@ -2009,8 +2016,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// `renderEditable.preferredLineHeight`, before the target scroll offset is
// calculated.
RevealedOffset
_getOffsetToRevealCaret
(
Rect
rect
)
{
if
(!
_scrollController
!
.
position
.
allowImplicitScrolling
)
return
RevealedOffset
(
offset:
_scrollController
!
.
offset
,
rect:
rect
);
if
(!
_scrollController
.
position
.
allowImplicitScrolling
)
return
RevealedOffset
(
offset:
_scrollController
.
offset
,
rect:
rect
);
final
Size
editableSize
=
renderEditable
.
size
;
final
double
additionalOffset
;
...
...
@@ -2042,13 +2049,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// No overscrolling when encountering tall fonts/scripts that extend past
// the ascent.
final
double
targetOffset
=
(
additionalOffset
+
_scrollController
!
.
offset
)
final
double
targetOffset
=
(
additionalOffset
+
_scrollController
.
offset
)
.
clamp
(
_scrollController
!
.
position
.
minScrollExtent
,
_scrollController
!
.
position
.
maxScrollExtent
,
_scrollController
.
position
.
minScrollExtent
,
_scrollController
.
position
.
maxScrollExtent
,
);
final
double
offsetDelta
=
_scrollController
!
.
offset
-
targetOffset
;
final
double
offsetDelta
=
_scrollController
.
offset
-
targetOffset
;
return
RevealedOffset
(
rect:
rect
.
shift
(
unitOffset
*
offsetDelta
),
offset:
targetOffset
);
}
...
...
@@ -2152,6 +2159,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
void
_updateSelectionOverlayForScroll
()
{
_selectionOverlay
?.
updateForScroll
();
}
@pragma
(
'vm:notify-debugger-on-exception'
)
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
?
cause
)
{
// We return early if the selection is not valid. This can happen when the
...
...
@@ -2229,7 +2240,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_showCaretOnScreenScheduled
=
true
;
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
_
)
{
_showCaretOnScreenScheduled
=
false
;
if
(
_currentCaretRect
==
null
||
!
_scrollController
!
.
hasClients
)
{
if
(
_currentCaretRect
==
null
||
!
_scrollController
.
hasClients
)
{
return
;
}
...
...
@@ -2262,7 +2273,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
RevealedOffset
targetOffset
=
_getOffsetToRevealCaret
(
_currentCaretRect
!);
_scrollController
!
.
animateTo
(
_scrollController
.
animateTo
(
targetOffset
.
offset
,
duration:
_caretAnimationDuration
,
curve:
_caretAnimationCurve
,
...
...
@@ -2543,7 +2554,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
Rect
localRect
=
renderEditable
.
getLocalRectForCaret
(
position
);
final
RevealedOffset
targetOffset
=
_getOffsetToRevealCaret
(
localRect
);
_scrollController
!
.
jumpTo
(
targetOffset
.
offset
);
_scrollController
.
jumpTo
(
targetOffset
.
offset
);
renderEditable
.
showOnScreen
(
rect:
targetOffset
.
rect
);
}
...
...
packages/flutter/test/material/text_field_test.dart
View file @
c49eba6c
...
...
@@ -3969,9 +3969,8 @@ void main() {
error
.
toStringDeep
(),
equalsIgnoringHashCodes
(
'FlutterError
\n
'
' invalid text selection: TextSelection(baseOffset: 10,
\n
'
' extentOffset: 10, affinity: TextAffinity.downstream,
\n
'
' isDirectional: false)
\n
'
,
' invalid text selection: TextSelection.collapsed(offset: 10,
\n
'
' affinity: TextAffinity.downstream, isDirectional: false)
\n
'
,
),
);
}
...
...
packages/flutter/test/services/text_input_test.dart
View file @
c49eba6c
...
...
@@ -12,6 +12,39 @@ import 'package:flutter_test/flutter_test.dart';
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
group
(
'TextSelection'
,
()
{
test
(
'The invalid selection is a singleton'
,
()
{
const
TextSelection
invalidSelection1
=
TextSelection
(
baseOffset:
-
1
,
extentOffset:
0
,
affinity:
TextAffinity
.
downstream
,
isDirectional:
true
,
);
const
TextSelection
invalidSelection2
=
TextSelection
(
baseOffset:
123
,
extentOffset:
-
1
,
affinity:
TextAffinity
.
upstream
,
isDirectional:
false
,
);
expect
(
invalidSelection1
,
invalidSelection2
);
expect
(
invalidSelection1
.
hashCode
,
invalidSelection2
.
hashCode
);
});
test
(
'TextAffinity does not affect equivalence when the selection is not collapsed'
,
()
{
const
TextSelection
selection1
=
TextSelection
(
baseOffset:
1
,
extentOffset:
2
,
affinity:
TextAffinity
.
downstream
,
);
const
TextSelection
selection2
=
TextSelection
(
baseOffset:
1
,
extentOffset:
2
,
affinity:
TextAffinity
.
upstream
,
);
expect
(
selection1
,
selection2
);
expect
(
selection1
.
hashCode
,
selection2
.
hashCode
);
});
});
group
(
'TextInput message channels'
,
()
{
late
FakeTextChannel
fakeTextChannel
;
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
c49eba6c
...
...
@@ -5717,6 +5717,81 @@ void main() {
expect
(
scrollController
.
offset
,
0
);
});
testWidgets
(
'can change scroll controller'
,
(
WidgetTester
tester
)
async
{
final
_TestScrollController
scrollController1
=
_TestScrollController
();
final
_TestScrollController
scrollController2
=
_TestScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
controller:
TextEditingController
(
text:
'A'
*
1000
),
maxLines:
1
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scrollController:
scrollController1
,
),
),
);
expect
(
scrollController1
.
attached
,
isTrue
);
expect
(
scrollController2
.
attached
,
isFalse
);
// Change scrollController to controller 2.
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
controller:
TextEditingController
(
text:
'A'
*
1000
),
maxLines:
1
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scrollController:
scrollController2
,
),
),
);
expect
(
scrollController1
.
attached
,
isFalse
);
expect
(
scrollController2
.
attached
,
isTrue
);
// Changing scrollController to null.
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
controller:
TextEditingController
(
text:
'A'
*
1000
),
maxLines:
1
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
),
),
);
expect
(
scrollController1
.
attached
,
isFalse
);
expect
(
scrollController2
.
attached
,
isFalse
);
// Change scrollController to back controller 2.
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
controller:
TextEditingController
(
text:
'A'
*
1000
),
maxLines:
1
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scrollController:
scrollController2
,
),
),
);
expect
(
scrollController1
.
attached
,
isFalse
);
expect
(
scrollController2
.
attached
,
isTrue
);
});
testWidgets
(
'getLocalRectForCaret does not throw when it sees an infinite point'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -8110,6 +8185,43 @@ void main() {
// On web, using keyboard for selection is handled by the browser.
},
skip:
kIsWeb
);
// [intended]
testWidgets
(
'EditableText does not leak animation controllers'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
autofocus:
true
,
controller:
TextEditingController
(
text:
'A'
),
maxLines:
1
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
cursorOpacityAnimates:
true
,
),
),
);
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Start
,
offset:
Offset
.
zero
));
// Start the cursor blink opacity animation controller.
// _kCursorBlinkWaitForStart
await
tester
.
pump
(
const
Duration
(
milliseconds:
150
));
// _kCursorBlinkHalfPeriod
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Start the floating cursor reset animation controller.
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
End
,
offset:
Offset
.
zero
));
expect
(
tester
.
binding
.
transientCallbackCount
,
2
);
await
tester
.
pumpWidget
(
const
SizedBox
());
expect
(
tester
.
hasRunningAnimations
,
isFalse
);
});
testWidgets
(
'Selection will be scrolled into view with SelectionChangedCause'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
EditableTextState
>
key
=
GlobalKey
<
EditableTextState
>();
final
String
text
=
List
<
int
>.
generate
(
64
,
(
int
index
)
=>
index
).
join
(
'
\n
'
);
...
...
@@ -8439,3 +8551,7 @@ class _MyMoveSelectionRightTextAction extends TextEditingAction<Intent> {
onInvoke
();
}
}
class
_TestScrollController
extends
ScrollController
{
bool
get
attached
=>
hasListeners
;
}
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