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
0f8c0da0
Unverified
Commit
0f8c0da0
authored
5 years ago
by
LongCatIsLooong
Committed by
GitHub
5 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
iOS UITextInput autocorrection prompt (#45354)
parent
e54e301d
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 @
0f8c0da0
...
...
@@ -876,6 +876,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
(
...
...
@@ -902,7 +904,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
,
...
...
@@ -917,6 +919,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorOpacityAnimates:
true
,
cursorOffset:
cursorOffset
,
paintCursorAboveText:
true
,
autocorrectionTextRectColor:
selectionColor
,
backgroundCursorColor:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
inactiveGray
,
context
),
scrollPadding:
widget
.
scrollPadding
,
keyboardAppearance:
keyboardAppearance
,
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/material/text_field.dart
View file @
0f8c0da0
...
...
@@ -935,6 +935,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
)
{
...
...
@@ -947,6 +948,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
:
...
...
@@ -1006,6 +1008,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
dragStartBehavior:
widget
.
dragStartBehavior
,
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
autocorrectionTextRectColor:
autocorrectionTextRectColor
,
),
);
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/rendering/editable.dart
View file @
0f8c0da0
...
...
@@ -210,6 +210,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
double
devicePixelRatio
=
1.0
,
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.'
),
...
...
@@ -266,10 +268,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.
...
...
@@ -1113,6 +1118,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
...
...
@@ -1912,6 +1951,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
(
TextBox
box
in
boxes
)
{
canvas
.
drawRect
(
box
.
toRect
().
shift
(
effectiveOffset
),
_promptRectPaint
);
}
}
void
_paintContents
(
PaintingContext
context
,
Offset
offset
)
{
assert
(
_textLayoutLastMaxWidth
==
constraints
.
maxWidth
&&
_textLayoutLastMinWidth
==
constraints
.
minWidth
,
...
...
@@ -1934,6 +1991,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
)
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/services/text_input.dart
View file @
0f8c0da0
...
...
@@ -753,6 +753,12 @@ abstract class TextInputClient {
/// Updates the floating cursor position and state.
void
updateFloatingCursor
(
RawFloatingCursorPoint
point
);
/// 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.
...
...
@@ -1076,6 +1082,9 @@ class TextInput {
case
'TextInputClient.onConnectionClosed'
:
_currentConnection
.
_client
.
connectionClosed
();
break
;
case
'TextInputClient.showAutocorrectionPromptRect'
:
_currentConnection
.
_client
.
showAutocorrectionPromptRect
(
args
[
1
],
args
[
2
]);
break
;
default
:
throw
MissingPluginException
();
}
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
0f8c0da0
...
...
@@ -395,6 +395,7 @@ class EditableText extends StatefulWidget {
this
.
enableInteractiveSelection
=
true
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autocorrectionTextRectColor
,
this
.
toolbarOptions
=
const
ToolbarOptions
(
copy:
true
,
cut:
true
,
...
...
@@ -634,6 +635,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.
///
...
...
@@ -737,6 +750,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.
...
...
@@ -1212,6 +1229,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
;
...
...
@@ -1734,6 +1752,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
();
}
...
...
@@ -1812,6 +1831,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
)
...
...
@@ -1890,6 +1919,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
enableInteractiveSelection:
widget
.
enableInteractiveSelection
,
textSelectionDelegate:
this
,
devicePixelRatio:
_devicePixelRatio
,
promptRectRange:
_currentPromptRectRange
,
promptRectColor:
widget
.
autocorrectionTextRectColor
,
),
),
);
...
...
@@ -1958,6 +1989,8 @@ class _Editable extends LeafRenderObjectWidget {
this
.
textSelectionDelegate
,
this
.
paintCursorAboveText
,
this
.
devicePixelRatio
,
this
.
promptRectRange
,
this
.
promptRectColor
,
})
:
assert
(
textDirection
!=
null
),
assert
(
rendererIgnoresPointer
!=
null
),
super
(
key:
key
);
...
...
@@ -1998,6 +2031,8 @@ class _Editable extends LeafRenderObjectWidget {
final
TextSelectionDelegate
textSelectionDelegate
;
final
double
devicePixelRatio
;
final
bool
paintCursorAboveText
;
final
TextRange
promptRectRange
;
final
Color
promptRectColor
;
@override
RenderEditable
createRenderObject
(
BuildContext
context
)
{
...
...
@@ -2034,6 +2069,8 @@ class _Editable extends LeafRenderObjectWidget {
enableInteractiveSelection:
enableInteractiveSelection
,
textSelectionDelegate:
textSelectionDelegate
,
devicePixelRatio:
devicePixelRatio
,
promptRectRange:
promptRectRange
,
promptRectColor:
promptRectColor
,
);
}
...
...
@@ -2069,6 +2106,8 @@ class _Editable extends LeafRenderObjectWidget {
..
cursorOffset
=
cursorOffset
..
textSelectionDelegate
=
textSelectionDelegate
..
devicePixelRatio
=
devicePixelRatio
..
paintCursorAboveText
=
paintCursorAboveText
;
..
paintCursorAboveText
=
paintCursorAboveText
..
promptRectColor
=
promptRectColor
..
setPromptRectRange
(
promptRectRange
);
}
}
This diff is collapsed.
Click to expand it.
packages/flutter/test/rendering/editable_test.dart
View file @
0f8c0da0
...
...
@@ -546,6 +546,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
();
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/test/services/text_input_test.dart
View file @
0f8c0da0
...
...
@@ -72,6 +72,10 @@ void main() {
});
group
(
'TextInputConfiguration'
,
()
{
tearDown
(()
{
TextInputConnection
.
debugResetId
();
});
test
(
'sets expected defaults'
,
()
{
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
expect
(
configuration
.
inputType
,
TextInputType
.
text
);
...
...
@@ -172,6 +176,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
();
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'
);
});
});
}
...
...
@@ -198,6 +224,11 @@ class FakeTextInputClient implements TextInputClient {
latestMethodCall
=
'connectionClosed'
;
}
@override
void
showAutocorrectionPromptRect
(
int
start
,
int
end
)
{
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
}
...
...
This diff is collapsed.
Click to expand it.
packages/flutter/test/widgets/editable_text_test.dart
View file @
0f8c0da0
...
...
@@ -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'
;
...
...
@@ -1338,6 +1339,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
];
return
paint
.
color
==
rectColor
;
}));
}
else
{
return
paints
..
everything
(((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawRect
)
return
true
;
final
Paint
paint
=
arguments
[
1
];
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
(
platform:
TargetPlatform
.
android
).
black
.
subhead
,
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'
);
...
...
This diff is collapsed.
Click to expand it.
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