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
4af5fe2c
Unverified
Commit
4af5fe2c
authored
Apr 07, 2020
by
LongCatIsLooong
Committed by
GitHub
Apr 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "iOS UITextInput autocorrection prompt (#45354)" (#54119)
parent
e39cafb8
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
266 additions
and
3 deletions
+266
-3
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+4
-1
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+3
-0
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+60
-1
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+9
-0
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+40
-1
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+38
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+31
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+81
-0
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
4af5fe2c
...
...
@@ -897,6 +897,8 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
color:
enabled
?
decorationColor
:
(
decorationColor
??
disabledColor
),
);
final
Color
selectionColor
=
CupertinoTheme
.
of
(
context
).
primaryColor
.
withOpacity
(
0.2
);
final
Widget
paddedEditable
=
Padding
(
padding:
widget
.
padding
,
child:
RepaintBoundary
(
...
...
@@ -923,7 +925,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
maxLines:
widget
.
maxLines
,
minLines:
widget
.
minLines
,
expands:
widget
.
expands
,
selectionColor:
CupertinoTheme
.
of
(
context
).
primaryColor
.
withOpacity
(
0.2
)
,
selectionColor:
selectionColor
,
selectionControls:
widget
.
selectionEnabled
?
cupertinoTextSelectionControls
:
null
,
onChanged:
widget
.
onChanged
,
...
...
@@ -938,6 +940,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorOpacityAnimates:
true
,
cursorOffset:
cursorOffset
,
paintCursorAboveText:
true
,
autocorrectionTextRectColor:
selectionColor
,
backgroundCursorColor:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
inactiveGray
,
context
),
selectionHeightStyle:
widget
.
selectionHeightStyle
,
selectionWidthStyle:
widget
.
selectionWidthStyle
,
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
4af5fe2c
...
...
@@ -972,6 +972,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
bool
cursorOpacityAnimates
;
Offset
cursorOffset
;
Color
cursorColor
=
widget
.
cursorColor
;
Color
autocorrectionTextRectColor
;
Radius
cursorRadius
=
widget
.
cursorRadius
;
switch
(
themeData
.
platform
)
{
...
...
@@ -984,6 +985,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
cursorColor
??=
CupertinoTheme
.
of
(
context
).
primaryColor
;
cursorRadius
??=
const
Radius
.
circular
(
2.0
);
cursorOffset
=
Offset
(
iOSHorizontalOffset
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
autocorrectionTextRectColor
=
themeData
.
textSelectionColor
;
break
;
case
TargetPlatform
.
android
:
...
...
@@ -1047,6 +1049,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
dragStartBehavior:
widget
.
dragStartBehavior
,
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
autocorrectionTextRectColor:
autocorrectionTextRectColor
,
),
);
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
4af5fe2c
...
...
@@ -212,6 +212,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
ui
.
BoxWidthStyle
selectionWidthStyle
=
ui
.
BoxWidthStyle
.
tight
,
bool
enableInteractiveSelection
,
EdgeInsets
floatingCursorAddedMargin
=
const
EdgeInsets
.
fromLTRB
(
4
,
4
,
4
,
5
),
TextRange
promptRectRange
,
Color
promptRectColor
,
@required
this
.
textSelectionDelegate
,
})
:
assert
(
textAlign
!=
null
),
assert
(
textDirection
!=
null
,
'RenderEditable created without a textDirection.'
),
...
...
@@ -272,10 +274,13 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_endHandleLayerLink
=
endHandleLayerLink
,
_obscureText
=
obscureText
,
_readOnly
=
readOnly
,
_forceLine
=
forceLine
{
_forceLine
=
forceLine
,
_promptRectRange
=
promptRectRange
{
assert
(
_showCursor
!=
null
);
assert
(!
_showCursor
.
value
||
cursorColor
!=
null
);
this
.
hasFocus
=
hasFocus
??
false
;
if
(
promptRectColor
!=
null
)
_promptRectPaint
.
color
=
promptRectColor
;
}
/// Character used to obscure text if [obscureText] is true.
...
...
@@ -1156,6 +1161,40 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return
enableInteractiveSelection
??
!
obscureText
;
}
/// The color used to paint the prompt rectangle.
///
/// The prompt rectangle will only be requested on non-web iOS applications.
Color
get
promptRectColor
=>
_promptRectPaint
.
color
;
set
promptRectColor
(
Color
newValue
)
{
// Painter.color can not be null.
if
(
newValue
==
null
)
{
setPromptRectRange
(
null
);
return
;
}
if
(
promptRectColor
==
newValue
)
return
;
_promptRectPaint
.
color
=
newValue
;
if
(
_promptRectRange
!=
null
)
markNeedsPaint
();
}
TextRange
_promptRectRange
;
/// Dismisses the currently displayed prompt rectangle and displays a new prompt rectangle
/// over [newRange] in the given color [promptRectColor].
///
/// The prompt rectangle will only be requested on non-web iOS applications.
///
/// When set to null, the currently displayed prompt rectangle (if any) will be dismissed.
void
setPromptRectRange
(
TextRange
newRange
)
{
if
(
_promptRectRange
==
newRange
)
return
;
_promptRectRange
=
newRange
;
markNeedsPaint
();
}
/// The maximum amount the text is allowed to scroll.
///
/// This value is only valid after layout and can change as additional
...
...
@@ -1960,6 +1999,24 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
canvas
.
drawRect
(
box
.
toRect
().
shift
(
effectiveOffset
),
paint
);
}
final
Paint
_promptRectPaint
=
Paint
();
void
_paintPromptRectIfNeeded
(
Canvas
canvas
,
Offset
effectiveOffset
)
{
if
(
_promptRectRange
==
null
||
promptRectColor
==
null
)
{
return
;
}
final
List
<
TextBox
>
boxes
=
_textPainter
.
getBoxesForSelection
(
TextSelection
(
baseOffset:
_promptRectRange
.
start
,
extentOffset:
_promptRectRange
.
end
,
),
);
for
(
final
TextBox
box
in
boxes
)
{
canvas
.
drawRect
(
box
.
toRect
().
shift
(
effectiveOffset
),
_promptRectPaint
);
}
}
void
_paintContents
(
PaintingContext
context
,
Offset
offset
)
{
assert
(
_textLayoutLastMaxWidth
==
constraints
.
maxWidth
&&
_textLayoutLastMinWidth
==
constraints
.
minWidth
,
...
...
@@ -1982,6 +2039,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_paintSelection
(
context
.
canvas
,
effectiveOffset
);
}
_paintPromptRectIfNeeded
(
context
.
canvas
,
effectiveOffset
);
// On iOS, the cursor is painted over the text, on Android, it's painted
// under it.
if
(
paintCursorAboveText
)
...
...
packages/flutter/lib/src/services/text_input.dart
View file @
4af5fe2c
...
...
@@ -757,6 +757,12 @@ abstract class TextInputClient {
/// The current state of the [TextEditingValue] held by this client.
TextEditingValue
get
currentTextEditingValue
;
/// Requests that this client display a prompt rectangle for the given text range,
/// to indicate the range of text that will be changed by a pending autocorrection.
///
/// This method will only be called on iOS.
void
showAutocorrectionPromptRect
(
int
start
,
int
end
);
/// Platform notified framework of closed connection.
///
/// [TextInputClient] should cleanup its connection and finalize editing.
...
...
@@ -1079,6 +1085,9 @@ class TextInput {
case
'TextInputClient.onConnectionClosed'
:
_currentConnection
.
_client
.
connectionClosed
();
break
;
case
'TextInputClient.showAutocorrectionPromptRect'
:
_currentConnection
.
_client
.
showAutocorrectionPromptRect
(
args
[
1
]
as
int
,
args
[
2
]
as
int
);
break
;
default
:
throw
MissingPluginException
();
}
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
4af5fe2c
...
...
@@ -402,6 +402,7 @@ class EditableText extends StatefulWidget {
this
.
enableInteractiveSelection
=
true
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autocorrectionTextRectColor
,
this
.
toolbarOptions
=
const
ToolbarOptions
(
copy:
true
,
cut:
true
,
...
...
@@ -643,6 +644,18 @@ class EditableText extends StatefulWidget {
/// Cannot be null.
final
Color
cursorColor
;
/// The color to use when painting the autocorrection Rect.
///
/// For [CupertinoTextField]s, the value is set to the ambient
/// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the
/// value is null on non-iOS platforms and the same color used in [CupertinoTextField]
/// on iOS.
///
/// Currently the autocorrection Rect only appears on iOS.
///
/// Defaults to null, which disables autocorrection Rect painting.
final
Color
autocorrectionTextRectColor
;
/// The color to use when painting the background cursor aligned with the text
/// while rendering the floating cursor.
///
...
...
@@ -746,6 +759,10 @@ class EditableText extends StatefulWidget {
final
bool
autofocus
;
/// The color to use when painting the selection.
///
/// For [CupertinoTextField]s, the value is set to the ambient
/// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the
/// value is set to the ambient [ThemeData.textSelectionColor].
final
Color
selectionColor
;
/// Optional delegate for building the text selection handles and toolbar.
...
...
@@ -1252,6 +1269,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
value
.
text
!=
_value
.
text
)
{
hideToolbar
();
_showCaretOnScreen
();
_currentPromptRectRange
=
null
;
if
(
widget
.
obscureText
&&
value
.
text
.
length
==
_value
.
text
.
length
+
1
)
{
_obscureShowCharTicksPending
=
_kObscureShowLatestCharCursorTicks
;
_obscureLatestCharIndex
=
_value
.
selection
.
baseOffset
;
...
...
@@ -1805,6 +1823,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
WidgetsBinding
.
instance
.
removeObserver
(
this
);
// Clear the selection and composition state if this widget lost focus.
_value
=
TextEditingValue
(
text:
_value
.
text
);
_currentPromptRectRange
=
null
;
}
updateKeepAlive
();
}
...
...
@@ -1883,6 +1902,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
// null if no promptRect should be shown.
TextRange
_currentPromptRectRange
;
@override
void
showAutocorrectionPromptRect
(
int
start
,
int
end
)
{
setState
(()
{
_currentPromptRectRange
=
TextRange
(
start:
start
,
end:
end
);
});
}
VoidCallback
_semanticsOnCopy
(
TextSelectionControls
controls
)
{
return
widget
.
selectionEnabled
&&
copyEnabled
&&
_hasFocus
&&
controls
?.
canCopy
(
this
)
==
true
?
()
=>
controls
.
handleCopy
(
this
)
...
...
@@ -1963,6 +1992,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
enableInteractiveSelection:
widget
.
enableInteractiveSelection
,
textSelectionDelegate:
this
,
devicePixelRatio:
_devicePixelRatio
,
promptRectRange:
_currentPromptRectRange
,
promptRectColor:
widget
.
autocorrectionTextRectColor
,
),
),
);
...
...
@@ -2033,6 +2064,8 @@ class _Editable extends LeafRenderObjectWidget {
this
.
enableInteractiveSelection
=
true
,
this
.
textSelectionDelegate
,
this
.
devicePixelRatio
,
this
.
promptRectRange
,
this
.
promptRectColor
,
})
:
assert
(
textDirection
!=
null
),
assert
(
rendererIgnoresPointer
!=
null
),
super
(
key:
key
);
...
...
@@ -2075,6 +2108,8 @@ class _Editable extends LeafRenderObjectWidget {
final
bool
enableInteractiveSelection
;
final
TextSelectionDelegate
textSelectionDelegate
;
final
double
devicePixelRatio
;
final
TextRange
promptRectRange
;
final
Color
promptRectColor
;
@override
RenderEditable
createRenderObject
(
BuildContext
context
)
{
...
...
@@ -2113,6 +2148,8 @@ class _Editable extends LeafRenderObjectWidget {
enableInteractiveSelection:
enableInteractiveSelection
,
textSelectionDelegate:
textSelectionDelegate
,
devicePixelRatio:
devicePixelRatio
,
promptRectRange:
promptRectRange
,
promptRectColor:
promptRectColor
,
);
}
...
...
@@ -2150,7 +2187,9 @@ class _Editable extends LeafRenderObjectWidget {
..
selectionWidthStyle
=
selectionWidthStyle
..
textSelectionDelegate
=
textSelectionDelegate
..
devicePixelRatio
=
devicePixelRatio
..
paintCursorAboveText
=
paintCursorAboveText
;
..
paintCursorAboveText
=
paintCursorAboveText
..
promptRectColor
=
promptRectColor
..
setPromptRectRange
(
promptRectRange
);
}
}
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
4af5fe2c
...
...
@@ -598,6 +598,44 @@ void main() {
expect
(
selectionChangedCount
,
1
);
},
skip:
isBrowser
);
test
(
'promptRect disappears when promptRectColor is set to null'
,
()
{
const
Color
promptRectColor
=
Color
(
0x12345678
);
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
();
final
RenderEditable
editable
=
RenderEditable
(
text:
const
TextSpan
(
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
),
text:
'ABCDEFG'
,
),
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
textAlign:
TextAlign
.
start
,
textDirection:
TextDirection
.
ltr
,
locale:
const
Locale
(
'en'
,
'US'
),
offset:
ViewportOffset
.
fixed
(
10.0
),
textSelectionDelegate:
delegate
,
selection:
const
TextSelection
.
collapsed
(
offset:
0
),
promptRectColor:
promptRectColor
,
promptRectRange:
const
TextRange
(
start:
0
,
end:
1
),
);
editable
.
layout
(
BoxConstraints
.
loose
(
const
Size
(
1000.0
,
1000.0
)));
expect
(
(
Canvas
canvas
)
=>
editable
.
paint
(
TestRecordingPaintingContext
(
canvas
),
Offset
.
zero
),
paints
..
rect
(
color:
promptRectColor
),
);
editable
.
promptRectColor
=
null
;
editable
.
layout
(
BoxConstraints
.
loose
(
const
Size
(
1000.0
,
1000.0
)));
pumpFrame
();
expect
(
editable
.
promptRectColor
,
promptRectColor
);
expect
(
(
Canvas
canvas
)
=>
editable
.
paint
(
TestRecordingPaintingContext
(
canvas
),
Offset
.
zero
),
isNot
(
paints
..
rect
(
color:
promptRectColor
)),
);
});
test
(
'editable hasFocus correctly initialized'
,
()
{
// Regression test for https://github.com/flutter/flutter/issues/21640
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
();
...
...
packages/flutter/test/services/text_input_test.dart
View file @
4af5fe2c
...
...
@@ -63,6 +63,10 @@ void main() {
});
group
(
'TextInputConfiguration'
,
()
{
tearDown
(()
{
TextInputConnection
.
debugResetId
();
});
test
(
'sets expected defaults'
,
()
{
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
expect
(
configuration
.
inputType
,
TextInputType
.
text
);
...
...
@@ -163,6 +167,28 @@ void main() {
expect
(
client
.
latestMethodCall
,
'connectionClosed'
);
});
test
(
'TextInputClient showAutocorrectionPromptRect method is called'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
null
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
TextInput
.
attach
(
client
,
configuration
);
expect
(
client
.
latestMethodCall
,
isEmpty
);
// Send onConnectionClosed message.
final
ByteData
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'TextInputClient.showAutocorrectionPromptRect'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
_
)
{},
);
expect
(
client
.
latestMethodCall
,
'showAutocorrectionPromptRect'
);
});
});
}
...
...
@@ -194,6 +220,11 @@ class FakeTextInputClient implements TextInputClient {
latestMethodCall
=
'connectionClosed'
;
}
@override
void
showAutocorrectionPromptRect
(
int
start
,
int
end
)
{
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
}
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
4af5fe2c
...
...
@@ -13,6 +13,7 @@ import 'package:flutter/services.dart';
import
'package:mockito/mockito.dart'
;
import
'package:flutter/foundation.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'editable_text_utils.dart'
;
import
'semantics_tester.dart'
;
...
...
@@ -1423,6 +1424,86 @@ void main() {
assert
(!
onEditingCompleteCalled
);
});
testWidgets
(
'iOS autocorrection rectangle should appear on demand'
'and dismiss when the text changes or when focus is lost'
,
(
WidgetTester
tester
)
async
{
const
Color
rectColor
=
Color
(
0xFFFF0000
);
void
verifyAutocorrectionRectVisibility
({
bool
expectVisible
})
{
PaintPattern
evaluate
()
{
if
(
expectVisible
)
{
return
paints
..
something
(((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawRect
)
return
false
;
final
Paint
paint
=
arguments
[
1
]
as
Paint
;
return
paint
.
color
==
rectColor
;
}));
}
else
{
return
paints
..
everything
(((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawRect
)
return
true
;
final
Paint
paint
=
arguments
[
1
]
as
Paint
;
if
(
paint
.
color
!=
rectColor
)
return
true
;
throw
'Expected: autocorrection rect not visible, found:
${arguments[0]}
'
;
}));
}
}
expect
(
findRenderEditable
(
tester
),
evaluate
());
}
final
FocusNode
focusNode
=
FocusNode
();
final
TextEditingController
controller
=
TextEditingController
(
text:
'ABCDEFG'
);
final
Widget
widget
=
MaterialApp
(
home:
EditableText
(
backgroundCursorColor:
Colors
.
grey
,
controller:
controller
,
focusNode:
focusNode
,
style:
Typography
.
material2018
(
platform:
TargetPlatform
.
android
).
black
.
subtitle1
,
cursorColor:
Colors
.
blue
,
autocorrect:
true
,
autocorrectionTextRectColor:
rectColor
,
showCursor:
false
,
onEditingComplete:
()
{
},
),
);
await
tester
.
pumpWidget
(
widget
);
await
tester
.
tap
(
find
.
byType
(
EditableText
));
await
tester
.
pump
();
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
assert
(
focusNode
.
hasFocus
);
// The prompt rect should be invisible initially.
verifyAutocorrectionRectVisibility
(
expectVisible:
false
);
state
.
showAutocorrectionPromptRect
(
0
,
1
);
await
tester
.
pump
();
// Show prompt rect when told to.
verifyAutocorrectionRectVisibility
(
expectVisible:
true
);
// Text changed, prompt rect goes away.
controller
.
text
=
'12345'
;
await
tester
.
pump
();
verifyAutocorrectionRectVisibility
(
expectVisible:
false
);
state
.
showAutocorrectionPromptRect
(
0
,
1
);
await
tester
.
pump
();
verifyAutocorrectionRectVisibility
(
expectVisible:
true
);
// Unfocus, prompt rect should go away.
focusNode
.
unfocus
();
await
tester
.
pump
();
verifyAutocorrectionRectVisibility
(
expectVisible:
false
);
});
testWidgets
(
'Changing controller updates EditableText'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller1
=
TextEditingController
(
text:
'Wibble'
);
...
...
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