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
b73722b1
Unverified
Commit
b73722b1
authored
Jan 15, 2021
by
J-P Nurmi
Committed by
GitHub
Jan 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[text_input] prepare for custom text input sources (#72803)
parent
58301211
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
354 additions
and
185 deletions
+354
-185
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+293
-183
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+1
-2
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+60
-0
No files found.
packages/flutter/lib/src/services/text_input.dart
View file @
b73722b1
...
@@ -860,21 +860,22 @@ abstract class TextInputClient {
...
@@ -860,21 +860,22 @@ abstract class TextInputClient {
/// An interface for interacting with a text input control.
/// An interface for interacting with a text input control.
///
///
/// [TextInputConnection] communicates with the platform text input plugin
/// over the [SystemChannels.textInput] method channel. See [SystemChannels.textInput]
/// for more details about the method channel messages.
///
/// See also:
/// See also:
///
///
/// * [TextInput.attach], a method used to establish a [TextInputConnection]
/// * [TextInput.attach], a method used to establish a [TextInputConnection]
/// between the system's text input and a [TextInputClient].
/// between the system's text input and a [TextInputClient].
/// * [EditableText], a [TextInputClient] that connects to and interacts with
/// * [EditableText], a [TextInputClient] that connects to and interacts with
/// the system's text input using a [TextInputConnection].
/// the system's text input using a [TextInputConnection].
class
TextInputConnection
{
abstract
class
TextInputConnection
{
TextInputConnection
.
_
(
this
.
_client
)
/// Creates a connection for a [TextInputClient].
TextInputConnection
(
this
.
_client
)
:
assert
(
_client
!=
null
),
:
assert
(
_client
!=
null
),
_id
=
_nextId
++;
_id
=
_nextId
++;
Size
?
_cachedSize
;
Matrix4
?
_cachedTransform
;
Rect
?
_cachedRect
;
static
int
_nextId
=
1
;
static
int
_nextId
=
1
;
final
int
_id
;
final
int
_id
;
...
@@ -897,10 +898,18 @@ class TextInputConnection {
...
@@ -897,10 +898,18 @@ class TextInputConnection {
bool
get
attached
=>
TextInput
.
_instance
.
_currentConnection
==
this
;
bool
get
attached
=>
TextInput
.
_instance
.
_currentConnection
==
this
;
/// Requests that the text input control become visible.
/// Requests that the text input control become visible.
void
show
()
{
void
show
();
assert
(
attached
);
TextInput
.
_instance
.
_show
();
/// Requests that the text input control is hidden.
}
///
/// This method is called by the framework when the text input control should
/// hide.
///
/// See also:
///
/// * [TextInput.detach], a method to stop interacting with the text
/// input control.
void
hide
();
/// Requests the system autofill UI to appear.
/// Requests the system autofill UI to appear.
///
///
...
@@ -910,24 +919,23 @@ class TextInputConnection {
...
@@ -910,24 +919,23 @@ class TextInputConnection {
/// See also:
/// See also:
///
///
/// * [EditableText], a [TextInputClient] that calls this method when focused.
/// * [EditableText], a [TextInputClient] that calls this method when focused.
void
requestAutofill
()
{
void
requestAutofill
();
assert
(
attached
);
TextInput
.
_instance
.
_requestAutofill
();
/// This method actually notifies the embedding of the client. It is utilized
}
/// by [TextInput.attach] and for the `TextInputClient.requestExistingInputState`
/// method.
void
setClient
(
TextInputConfiguration
configuration
);
/// Clears the embedding of the client.
void
clearClient
();
/// Requests that the text input control update itself according to the new
/// Requests that the text input control update itself according to the new
/// [TextInputConfiguration].
/// [TextInputConfiguration].
void
updateConfig
(
TextInputConfiguration
configuration
)
{
void
updateConfig
(
TextInputConfiguration
configuration
);
assert
(
attached
);
TextInput
.
_instance
.
_updateConfig
(
configuration
);
}
/// Requests that the text input control change its internal state to match
/// Requests that the text input control change its internal state to match
/// the given state.
/// the given state.
void
setEditingState
(
TextEditingValue
value
)
{
void
setEditingState
(
TextEditingValue
value
);
assert
(
attached
);
TextInput
.
_instance
.
_setEditingState
(
value
);
}
/// Send the size and transform of the editable text to engine.
/// Send the size and transform of the editable text to engine.
///
///
...
@@ -938,11 +946,126 @@ class TextInputConnection {
...
@@ -938,11 +946,126 @@ class TextInputConnection {
///
///
/// 2. [transform]: a matrix that maps the local paint coordinate system
/// 2. [transform]: a matrix that maps the local paint coordinate system
/// to the [PipelineOwner.rootNode].
/// to the [PipelineOwner.rootNode].
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
);
/// Send the smallest rect that covers the text in the client that's currently
/// being composed.
///
/// The given `rect` can not be null. If any of the 4 coordinates of the given
/// [Rect] is not finite, a [Rect] of size (-1, -1) will be sent instead.
///
/// The information is currently only used on iOS, for positioning the IME bar.
void
setComposingRect
(
Rect
rect
);
/// Send text styling information.
///
/// This information is used by the Flutter Web Engine to change the style
/// of the hidden native input's content. Hence, the content size will match
/// to the size of the editable widget's content.
void
setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
});
/// Stop interacting with the text input control.
///
/// After calling this method, the text input control might disappear if no
/// other client attaches to it within this animation frame.
@Deprecated
(
'Use TextInput.detach instead. '
'This feature was deprecated after v1.26.0-1.0.pre.'
)
void
close
()
{
TextInput
.
detach
(
_client
);
assert
(!
attached
);
}
/// Platform sent a notification informing the connection is closed.
///
/// [TextInputConnection] should clean current client connection.
@Deprecated
(
'Use TextInput.reset instead. '
'This feature was deprecated after v1.26.0-1.0.pre.'
)
void
connectionClosedReceived
()
{
TextInput
.
reset
();
assert
(!
attached
);
}
}
// A MethodChannel-based TextInputConnection implementation.
class
_TextInputChannelConnection
extends
TextInputConnection
{
_TextInputChannelConnection
(
TextInputClient
client
,
this
.
_channel
)
:
super
(
client
);
Size
?
_cachedSize
;
Matrix4
?
_cachedTransform
;
Rect
?
_cachedRect
;
final
MethodChannel
_channel
;
@override
void
show
()
{
assert
(
attached
);
_channel
.
invokeMethod
<
void
>(
'TextInput.show'
);
}
@override
void
hide
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.hide'
);
}
@override
void
requestAutofill
()
{
assert
(
attached
);
_channel
.
invokeMethod
<
void
>(
'TextInput.requestAutofill'
);
}
@override
void
setClient
(
TextInputConfiguration
configuration
)
{
assert
(
_client
!=
null
);
assert
(
configuration
!=
null
);
_channel
.
invokeMethod
<
void
>(
'TextInput.setClient'
,
<
dynamic
>[
_id
,
configuration
.
toJson
()],
);
}
@override
void
clearClient
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.clearClient'
);
}
@override
void
updateConfig
(
TextInputConfiguration
configuration
)
{
assert
(
attached
);
assert
(
configuration
!=
null
);
_channel
.
invokeMethod
<
void
>(
'TextInput.updateConfig'
,
configuration
.
toJson
(),
);
}
@override
void
setEditingState
(
TextEditingValue
value
)
{
assert
(
attached
);
assert
(
value
!=
null
);
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditingState'
,
value
.
toJSON
(),
);
}
@override
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
if
(
editableBoxSize
!=
_cachedSize
||
transform
!=
_cachedTransform
)
{
if
(
editableBoxSize
!=
_cachedSize
||
transform
!=
_cachedTransform
)
{
_cachedSize
=
editableBoxSize
;
_cachedSize
=
editableBoxSize
;
_cachedTransform
=
transform
;
_cachedTransform
=
transform
;
TextInput
.
_instance
.
_setEditableSizeAndTransform
(
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditableSizeAndTransform'
,
<
String
,
dynamic
>{
<
String
,
dynamic
>{
'width'
:
editableBoxSize
.
width
,
'width'
:
editableBoxSize
.
width
,
'height'
:
editableBoxSize
.
height
,
'height'
:
editableBoxSize
.
height
,
...
@@ -952,20 +1075,15 @@ class TextInputConnection {
...
@@ -952,20 +1075,15 @@ class TextInputConnection {
}
}
}
}
/// Send the smallest rect that covers the text in the client that's currently
@override
/// being composed.
///
/// The given `rect` can not be null. If any of the 4 coordinates of the given
/// [Rect] is not finite, a [Rect] of size (-1, -1) will be sent instead.
///
/// The information is currently only used on iOS, for positioning the IME bar.
void
setComposingRect
(
Rect
rect
)
{
void
setComposingRect
(
Rect
rect
)
{
assert
(
rect
!=
null
);
assert
(
rect
!=
null
);
if
(
rect
==
_cachedRect
)
if
(
rect
==
_cachedRect
)
return
;
return
;
_cachedRect
=
rect
;
_cachedRect
=
rect
;
final
Rect
validRect
=
rect
.
isFinite
?
rect
:
Offset
.
zero
&
const
Size
(-
1
,
-
1
);
final
Rect
validRect
=
rect
.
isFinite
?
rect
:
Offset
.
zero
&
const
Size
(-
1
,
-
1
);
TextInput
.
_instance
.
_setComposingTextRect
(
_channel
.
invokeMethod
<
void
>(
'TextInput.setMarkedTextRect'
,
<
String
,
dynamic
>{
<
String
,
dynamic
>{
'width'
:
validRect
.
width
,
'width'
:
validRect
.
width
,
'height'
:
validRect
.
height
,
'height'
:
validRect
.
height
,
...
@@ -975,11 +1093,7 @@ class TextInputConnection {
...
@@ -975,11 +1093,7 @@ class TextInputConnection {
);
);
}
}
/// Send text styling information.
@override
///
/// This information is used by the Flutter Web Engine to change the style
/// of the hidden native input's content. Hence, the content size will match
/// to the size of the editable widget's content.
void
setStyle
({
void
setStyle
({
required
String
?
fontFamily
,
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
double
?
fontSize
,
...
@@ -988,8 +1102,8 @@ class TextInputConnection {
...
@@ -988,8 +1102,8 @@ class TextInputConnection {
required
TextAlign
textAlign
,
required
TextAlign
textAlign
,
})
{
})
{
assert
(
attached
);
assert
(
attached
);
_channel
.
invokeMethod
<
void
>(
TextInput
.
_instance
.
_setStyle
(
'TextInput.setStyle'
,
<
String
,
dynamic
>{
<
String
,
dynamic
>{
'fontFamily'
:
fontFamily
,
'fontFamily'
:
fontFamily
,
'fontSize'
:
fontSize
,
'fontSize'
:
fontSize
,
...
@@ -1000,23 +1114,68 @@ class TextInputConnection {
...
@@ -1000,23 +1114,68 @@ class TextInputConnection {
);
);
}
}
/// Stop interacting with the text input control.
Future
<
dynamic
>
_handleTextInputInvocation
(
MethodCall
methodCall
)
async
{
///
final
String
method
=
methodCall
.
method
;
/// After calling this method, the text input control might disappear if no
/// other client attaches to it within this animation frame.
// The requestExistingInputState request needs to be handled regardless of
void
close
()
{
// the client ID, as long as we have a _currentConnection.
if
(
attached
)
{
if
(
method
==
'TextInputClient.requestExistingInputState'
)
{
TextInput
.
_instance
.
_clearClient
();
assert
(
_client
!=
null
);
setClient
(
TextInput
.
_instance
.
_currentConfiguration
);
final
TextEditingValue
?
editingValue
=
_client
.
currentTextEditingValue
;
if
(
editingValue
!=
null
)
{
setEditingState
(
editingValue
);
}
return
;
}
}
assert
(!
attached
);
}
/// Platform sent a notification informing the connection is closed.
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
///
/// [TextInputConnection] should clean current client connection.
if
(
method
==
'TextInputClient.updateEditingStateWithTag'
)
{
void
connectionClosedReceived
()
{
assert
(
_client
!=
null
);
TextInput
.
_instance
.
_currentConnection
=
null
;
final
AutofillScope
?
scope
=
_client
.
currentAutofillScope
;
assert
(!
attached
);
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
String
tag
in
editingValue
.
keys
)
{
final
TextEditingValue
textEditingValue
=
TextEditingValue
.
fromJSON
(
editingValue
[
tag
]
as
Map
<
String
,
dynamic
>,
);
scope
?.
getAutofillClient
(
tag
)?.
updateEditingValue
(
textEditingValue
);
}
return
;
}
final
int
clientId
=
args
[
0
]
as
int
;
// The incoming message was for a different client.
if
(
clientId
!=
_id
)
return
;
switch
(
method
)
{
case
'TextInputClient.updateEditingState'
:
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
break
;
case
'TextInputClient.performAction'
:
_client
.
performAction
(
_toTextInputAction
(
args
[
1
]
as
String
));
break
;
case
'TextInputClient.performPrivateCommand'
:
_client
.
performPrivateCommand
(
args
[
1
][
'action'
]
as
String
,
args
[
1
][
'data'
]
as
Map
<
String
,
dynamic
>);
break
;
case
'TextInputClient.updateFloatingCursor'
:
_client
.
updateFloatingCursor
(
_toTextPoint
(
_toTextCursorAction
(
args
[
1
]
as
String
),
args
[
2
]
as
Map
<
String
,
dynamic
>,
));
break
;
case
'TextInputClient.onConnectionClosed'
:
_client
.
connectionClosed
();
TextInput
.
reset
();
break
;
case
'TextInputClient.showAutocorrectionPromptRect'
:
_client
.
showAutocorrectionPromptRect
(
args
[
1
]
as
int
,
args
[
2
]
as
int
);
break
;
default
:
throw
MissingPluginException
();
}
}
}
}
}
...
@@ -1129,8 +1288,7 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d
...
@@ -1129,8 +1288,7 @@ RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, d
/// wants to take user input from the keyboard.
/// wants to take user input from the keyboard.
class
TextInput
{
class
TextInput
{
TextInput
.
_
()
{
TextInput
.
_
()
{
_channel
=
SystemChannels
.
textInput
;
_currentSource
.
init
();
_channel
.
setMethodCallHandler
(
_handleTextInputInvocation
);
}
}
/// Set the [MethodChannel] used to communicate with the system's text input
/// Set the [MethodChannel] used to communicate with the system's text input
...
@@ -1142,7 +1300,7 @@ class TextInput {
...
@@ -1142,7 +1300,7 @@ class TextInput {
@visibleForTesting
@visibleForTesting
static
void
setChannel
(
MethodChannel
newChannel
)
{
static
void
setChannel
(
MethodChannel
newChannel
)
{
assert
(()
{
assert
(()
{
_
instance
.
_channel
=
newChannel
..
setMethodCallHandler
(
_instance
.
_handleTextInputInvocation
);
_
TextInputSource
.
setChannel
(
newChannel
);
return
true
;
return
true
;
}());
}());
}
}
...
@@ -1183,28 +1341,23 @@ class TextInput {
...
@@ -1183,28 +1341,23 @@ class TextInput {
/// the text input control.
/// the text input control.
///
///
/// A client that no longer wishes to interact with the text input control
/// A client that no longer wishes to interact with the text input control
/// should call [TextInputConnection.close] on the returned
/// should call [TextInput.detach].
/// [TextInputConnection].
static
TextInputConnection
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
static
TextInputConnection
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
assert
(
client
!=
null
);
assert
(
client
!=
null
);
assert
(
configuration
!=
null
);
assert
(
configuration
!=
null
);
final
TextInputConnection
connection
=
TextInputConnection
.
_
(
client
);
_instance
.
_detach
();
final
TextInputConnection
connection
=
_instance
.
_currentSource
.
attach
(
client
);
_instance
.
_attach
(
connection
,
configuration
);
_instance
.
_attach
(
connection
,
configuration
);
return
connection
;
return
connection
;
}
}
/// This method actually notifies the embedding of the client. It is utilized
// This method actually notifies the embedding of the client.
/// by [attach] and by [_handleTextInputInvocation] for the
/// `TextInputClient.requestExistingInputState` method.
void
_attach
(
TextInputConnection
connection
,
TextInputConfiguration
configuration
)
{
void
_attach
(
TextInputConnection
connection
,
TextInputConfiguration
configuration
)
{
assert
(
connection
!=
null
);
assert
(
connection
!=
null
);
assert
(
connection
.
_client
!=
null
);
assert
(
connection
.
_client
!=
null
);
assert
(
configuration
!=
null
);
assert
(
configuration
!=
null
);
assert
(
_debugEnsureInputActionWorksOnPlatform
(
configuration
.
inputAction
));
assert
(
_debugEnsureInputActionWorksOnPlatform
(
configuration
.
inputAction
));
_channel
.
invokeMethod
<
void
>(
connection
.
setClient
(
configuration
);
'TextInput.setClient'
,
<
dynamic
>[
connection
.
_id
,
configuration
.
toJson
()
],
);
_currentConnection
=
connection
;
_currentConnection
=
connection
;
_currentConfiguration
=
configuration
;
_currentConfiguration
=
configuration
;
}
}
...
@@ -1231,80 +1384,13 @@ class TextInput {
...
@@ -1231,80 +1384,13 @@ class TextInput {
return
true
;
return
true
;
}
}
late
MethodChannel
_channel
;
final
_TextInputSource
_currentSource
=
_TextInputSource
();
TextInputConnection
?
_currentConnection
;
TextInputConnection
?
_currentConnection
;
late
TextInputConfiguration
_currentConfiguration
;
late
TextInputConfiguration
_currentConfiguration
;
Future
<
dynamic
>
_handleTextInputInvocation
(
MethodCall
methodCall
)
async
{
if
(
_currentConnection
==
null
)
return
;
final
String
method
=
methodCall
.
method
;
// The requestExistingInputState request needs to be handled regardless of
// the client ID, as long as we have a _currentConnection.
if
(
method
==
'TextInputClient.requestExistingInputState'
)
{
assert
(
_currentConnection
!.
_client
!=
null
);
_attach
(
_currentConnection
!,
_currentConfiguration
);
final
TextEditingValue
?
editingValue
=
_currentConnection
!.
_client
.
currentTextEditingValue
;
if
(
editingValue
!=
null
)
{
_setEditingState
(
editingValue
);
}
return
;
}
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
if
(
method
==
'TextInputClient.updateEditingStateWithTag'
)
{
final
TextInputClient
client
=
_currentConnection
!.
_client
;
assert
(
client
!=
null
);
final
AutofillScope
?
scope
=
client
.
currentAutofillScope
;
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
String
tag
in
editingValue
.
keys
)
{
final
TextEditingValue
textEditingValue
=
TextEditingValue
.
fromJSON
(
editingValue
[
tag
]
as
Map
<
String
,
dynamic
>,
);
scope
?.
getAutofillClient
(
tag
)?.
updateEditingValue
(
textEditingValue
);
}
return
;
}
final
int
client
=
args
[
0
]
as
int
;
// The incoming message was for a different client.
if
(
client
!=
_currentConnection
!.
_id
)
return
;
switch
(
method
)
{
case
'TextInputClient.updateEditingState'
:
_currentConnection
!.
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
break
;
case
'TextInputClient.performAction'
:
_currentConnection
!.
_client
.
performAction
(
_toTextInputAction
(
args
[
1
]
as
String
));
break
;
case
'TextInputClient.performPrivateCommand'
:
_currentConnection
!.
_client
.
performPrivateCommand
(
args
[
1
][
'action'
]
as
String
,
args
[
1
][
'data'
]
as
Map
<
String
,
dynamic
>);
break
;
case
'TextInputClient.updateFloatingCursor'
:
_currentConnection
!.
_client
.
updateFloatingCursor
(
_toTextPoint
(
_toTextCursorAction
(
args
[
1
]
as
String
),
args
[
2
]
as
Map
<
String
,
dynamic
>,
));
break
;
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
();
}
}
bool
_hidePending
=
false
;
bool
_hidePending
=
false
;
void
_scheduleHide
()
{
void
_scheduleHide
(
TextInputConnection
connection
)
{
if
(
_hidePending
)
if
(
_hidePending
)
return
;
return
;
_hidePending
=
true
;
_hidePending
=
true
;
...
@@ -1315,59 +1401,50 @@ class TextInput {
...
@@ -1315,59 +1401,50 @@ class TextInput {
scheduleMicrotask
(()
{
scheduleMicrotask
(()
{
_hidePending
=
false
;
_hidePending
=
false
;
if
(
_currentConnection
==
null
)
if
(
_currentConnection
==
null
)
_channel
.
invokeMethod
<
void
>(
'TextInput.hide'
);
connection
.
hide
(
);
});
});
}
}
void
_clearClient
()
{
/// Stop interacting with the text input control.
_channel
.
invokeMethod
<
void
>(
'TextInput.clearClient'
);
///
_currentConnection
=
null
;
/// A client that no longer wishes to interact with the text input control
_scheduleHide
();
/// should call this method.
}
///
/// After calling this method, the text input control might be requested to
void
_updateConfig
(
TextInputConfiguration
configuration
)
{
/// hide if no other client attaches to it within this animation frame.
assert
(
configuration
!=
null
);
///
_channel
.
invokeMethod
<
void
>(
/// See also:
'TextInput.updateConfig'
,
///
configuration
.
toJson
(),
/// * [TextInputConnection.hide], a method called when the text input control
);
/// actually should hide.
}
static
void
detach
(
TextInputClient
client
)
{
assert
(
client
!=
null
);
void
_setEditingState
(
TextEditingValue
value
)
{
if
(
client
!=
_instance
.
_currentConnection
?.
_client
)
assert
(
value
!=
null
);
return
;
_channel
.
invokeMethod
<
void
>(
_instance
.
_detach
();
'TextInput.setEditingState'
,
value
.
toJSON
(),
);
}
void
_show
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.show'
);
}
void
_requestAutofill
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.requestAutofill'
);
}
void
_setEditableSizeAndTransform
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditableSizeAndTransform'
,
args
,
);
}
}
void
_setComposingTextRect
(
Map
<
String
,
dynamic
>
args
)
{
void
_detach
()
{
_channel
.
invokeMethod
<
void
>(
if
(
_currentConnection
==
null
)
'TextInput.setMarkedTextRect'
,
return
;
args
,
_currentConnection
!.
clearClient
();
);
_scheduleHide
(
_currentConnection
!);
_currentSource
.
detach
(
_currentConnection
!.
_client
);
_currentConnection
=
null
;
}
}
void
_setStyle
(
Map
<
String
,
dynamic
>
args
)
{
/// Resets the current text input connection.
_channel
.
invokeMethod
<
void
>(
///
'TextInput.setStyle'
,
/// This function should be called to reset the current text input connection
args
,
/// in case the platform sent a notification informing the connection is
);
/// closed.
///
/// See also:
///
/// * [TextInputClient.connectionClosed], a method called to notify the
/// current text input client when the connection is closed.
static
void
reset
()
{
_instance
.
_currentConnection
=
null
;
}
}
/// Finishes the current autofill context, and potentially saves the user
/// Finishes the current autofill context, and potentially saves the user
...
@@ -1420,9 +1497,42 @@ class TextInput {
...
@@ -1420,9 +1497,42 @@ class TextInput {
/// topmost [AutofillGroup] is getting disposed.
/// topmost [AutofillGroup] is getting disposed.
static
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
static
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
assert
(
shouldSave
!=
null
);
assert
(
shouldSave
!=
null
);
TextInput
.
_instance
.
_channel
.
invokeMethod
<
void
>(
_instance
.
_currentSource
.
finishAutofillContext
(
shouldSave:
shouldSave
);
}
}
class
_TextInputSource
{
static
MethodChannel
?
_channel
;
static
void
setChannel
(
MethodChannel
newChannel
)
{
_channel
=
newChannel
..
setMethodCallHandler
(
_handleTextInputInvocation
);
}
void
init
()
{
_channel
??=
SystemChannels
.
textInput
;
_channel
!.
setMethodCallHandler
(
_handleTextInputInvocation
);
}
void
cleanup
()
{
_channel
!.
setMethodCallHandler
((
MethodCall
methodCall
)
async
{});
}
TextInputConnection
attach
(
TextInputClient
client
)
{
return
_TextInputChannelConnection
(
client
,
_channel
!);
}
void
detach
(
TextInputClient
client
)
{}
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
_channel
!.
invokeMethod
<
void
>(
'TextInput.finishAutofillContext'
,
'TextInput.finishAutofillContext'
,
shouldSave
,
shouldSave
,
);
);
}
}
static
Future
<
dynamic
>
_handleTextInputInvocation
(
MethodCall
methodCall
)
async
{
final
TextInputConnection
?
connection
=
TextInput
.
_instance
.
_currentConnection
;
if
(
connection
is
_TextInputChannelConnection
)
return
connection
.
_handleTextInputInvocation
(
methodCall
);
}
}
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
b73722b1
...
@@ -2038,7 +2038,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -2038,7 +2038,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void
_closeInputConnectionIfNeeded
()
{
void
_closeInputConnectionIfNeeded
()
{
if
(
_hasInputConnection
)
{
if
(
_hasInputConnection
)
{
_textInputConnection
!.
close
(
);
TextInput
.
detach
(
this
);
_textInputConnection
=
null
;
_textInputConnection
=
null
;
_lastKnownRemoteTextEditingValue
=
null
;
_lastKnownRemoteTextEditingValue
=
null
;
}
}
...
@@ -2056,7 +2056,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -2056,7 +2056,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
@override
void
connectionClosed
()
{
void
connectionClosed
()
{
if
(
_hasInputConnection
)
{
if
(
_hasInputConnection
)
{
_textInputConnection
!.
connectionClosedReceived
();
_textInputConnection
=
null
;
_textInputConnection
=
null
;
_lastKnownRemoteTextEditingValue
=
null
;
_lastKnownRemoteTextEditingValue
=
null
;
_finalizeEditing
(
TextInputAction
.
done
,
shouldUnfocus:
true
);
_finalizeEditing
(
TextInputAction
.
done
,
shouldUnfocus:
true
);
...
...
packages/flutter/test/services/text_input_test.dart
View file @
b73722b1
...
@@ -22,6 +22,7 @@ void main() {
...
@@ -22,6 +22,7 @@ void main() {
});
});
tearDown
(()
{
tearDown
(()
{
TextInput
.
reset
();
TextInputConnection
.
debugResetId
();
TextInputConnection
.
debugResetId
();
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
});
});
...
@@ -74,6 +75,65 @@ void main() {
...
@@ -74,6 +75,65 @@ void main() {
}),
}),
]);
]);
});
});
test
(
'text input client is requested to hide on detach'
,
()
async
{
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
TextInput
.
attach
(
client
,
client
.
configuration
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
client
.
configuration
.
toJson
()]),
]);
TextInput
.
detach
(
client
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// From original attach
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
client
.
configuration
.
toJson
()]),
// From detach
const
MethodCall
(
'TextInput.clearClient'
),
]);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
()
as
TestWidgetsFlutterBinding
;
await
binding
.
runAsync
(()
async
{});
await
expectLater
(
fakeTextChannel
.
outgoingCalls
.
length
,
3
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// From original attach
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
client
.
configuration
.
toJson
()]),
// From detach
const
MethodCall
(
'TextInput.clearClient'
),
// From hide
const
MethodCall
(
'TextInput.hide'
),
]);
});
test
(
'old client is detached when a new client is attached'
,()
{
final
FakeTextInputClient
client1
=
FakeTextInputClient
(
const
TextEditingValue
(
text:
'1'
));
final
TextInputConnection
connection1
=
TextInput
.
attach
(
client1
,
client1
.
configuration
);
expect
(
connection1
.
attached
,
isTrue
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
client1
.
configuration
.
toJson
()]),
]);
final
FakeTextInputClient
client2
=
FakeTextInputClient
(
const
TextEditingValue
(
text:
'1'
));
final
TextInputConnection
connection2
=
TextInput
.
attach
(
client2
,
client2
.
configuration
);
expect
(
connection2
.
attached
,
isTrue
);
expect
(
connection1
.
attached
,
isFalse
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// From original attach
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
client1
.
configuration
.
toJson
()]),
// From internal detach
const
MethodCall
(
'TextInput.clearClient'
),
// From second attach
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
2
,
client1
.
configuration
.
toJson
()]),
]);
});
test
(
'text input connection is reset'
,
()
async
{
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
client
.
configuration
);
expect
(
connection
.
attached
,
isTrue
);
TextInput
.
reset
();
expect
(
connection
.
attached
,
isFalse
);
});
});
});
group
(
'TextInputConfiguration'
,
()
{
group
(
'TextInputConfiguration'
,
()
{
...
...
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