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
28e0f089
Unverified
Commit
28e0f089
authored
Oct 24, 2022
by
J-P Nurmi
Committed by
GitHub
Oct 24, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "[text_input] introduce TextInputControl" (#113758)
parent
a25c86c4
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
902 additions
and
83 deletions
+902
-83
AUTHORS
AUTHORS
+1
-0
text_input_control.0.dart
...les/api/lib/services/text_input/text_input_control.0.dart
+167
-0
text_input_control.0_test.dart
...i/test/services/text_input/text_input_control.0_test.dart
+40
-0
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+421
-83
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+8
-0
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+5
-0
delta_text_input_test.dart
packages/flutter/test/services/delta_text_input_test.dart
+5
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+255
-0
No files found.
AUTHORS
View file @
28e0f089
...
...
@@ -77,6 +77,7 @@ Hidenori Matsubayashi <Hidenori.Matsubayashi@sony.com>
Perqin Xie <perqinxie@gmail.com>
Seongyun Kim <helloworld@cau.ac.kr>
Ludwik Trammer <ludwik@gmail.com>
J-P Nurmi <jpnurmi@gmail.com>
Marian Triebe <m.triebe@live.de>
Alexis Rouillard <contact@arouillard.fr>
Mirko Mucaria <skogsfrae@gmail.com>
...
...
examples/api/lib/services/text_input/text_input_control.0.dart
0 → 100644
View file @
28e0f089
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for TextInputControl
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
=>
runApp
(
const
MyApp
());
class
MyApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
home:
MyStatefulWidget
(),
);
}
}
class
MyStatefulWidget
extends
StatefulWidget
{
const
MyStatefulWidget
({
super
.
key
});
@override
MyStatefulWidgetState
createState
()
=>
MyStatefulWidgetState
();
}
class
MyStatefulWidgetState
extends
State
<
MyStatefulWidget
>
{
final
TextEditingController
_controller
=
TextEditingController
();
final
FocusNode
_focusNode
=
FocusNode
();
@override
void
dispose
()
{
super
.
dispose
();
_controller
.
dispose
();
_focusNode
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
body:
Center
(
child:
TextField
(
autofocus:
true
,
controller:
_controller
,
focusNode:
_focusNode
,
decoration:
InputDecoration
(
suffix:
IconButton
(
icon:
const
Icon
(
Icons
.
clear
),
tooltip:
'Clear and unfocus'
,
onPressed:
()
{
_controller
.
clear
();
_focusNode
.
unfocus
();
},
),
),
),
),
bottomSheet:
const
MyVirtualKeyboard
(),
);
}
}
class
MyVirtualKeyboard
extends
StatefulWidget
{
const
MyVirtualKeyboard
({
super
.
key
});
@override
MyVirtualKeyboardState
createState
()
=>
MyVirtualKeyboardState
();
}
class
MyVirtualKeyboardState
extends
State
<
MyVirtualKeyboard
>
{
final
MyTextInputControl
_inputControl
=
MyTextInputControl
();
@override
void
initState
()
{
super
.
initState
();
_inputControl
.
register
();
}
@override
void
dispose
()
{
super
.
dispose
();
_inputControl
.
unregister
();
}
void
_handleKeyPress
(
String
key
)
{
_inputControl
.
processUserInput
(
key
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
ValueListenableBuilder
<
bool
>(
valueListenable:
_inputControl
.
visible
,
builder:
(
_
,
bool
visible
,
__
)
{
return
Visibility
(
visible:
visible
,
child:
FocusScope
(
canRequestFocus:
false
,
child:
TextFieldTapRegion
(
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
for
(
final
String
key
in
<
String
>[
'A'
,
'B'
,
'C'
])
ElevatedButton
(
child:
Text
(
key
),
onPressed:
()
=>
_handleKeyPress
(
key
),
),
],
),
),
),
);
},
);
}
}
class
MyTextInputControl
with
TextInputControl
{
TextEditingValue
_editingState
=
TextEditingValue
.
empty
;
final
ValueNotifier
<
bool
>
_visible
=
ValueNotifier
<
bool
>(
false
);
/// The input control's visibility state for updating the visual presentation.
ValueListenable
<
bool
>
get
visible
=>
_visible
;
/// Register the input control.
void
register
()
=>
TextInput
.
setInputControl
(
this
);
/// Restore the original platform input control.
void
unregister
()
=>
TextInput
.
restorePlatformInputControl
();
@override
void
show
()
=>
_visible
.
value
=
true
;
@override
void
hide
()
=>
_visible
.
value
=
false
;
@override
void
setEditingState
(
TextEditingValue
value
)
=>
_editingState
=
value
;
/// Process user input.
///
/// Updates the internal editing state by inserting the input text,
/// and by replacing the current selection if any.
void
processUserInput
(
String
input
)
{
_editingState
=
_editingState
.
copyWith
(
text:
_insertText
(
input
),
selection:
_replaceSelection
(
input
),
);
// Request the attached client to update accordingly.
TextInput
.
updateEditingValue
(
_editingState
);
}
String
_insertText
(
String
input
)
{
final
String
text
=
_editingState
.
text
;
final
TextSelection
selection
=
_editingState
.
selection
;
return
text
.
replaceRange
(
selection
.
start
,
selection
.
end
,
input
);
}
TextSelection
_replaceSelection
(
String
input
)
{
final
TextSelection
selection
=
_editingState
.
selection
;
return
TextSelection
.
collapsed
(
offset:
selection
.
start
+
input
.
length
);
}
}
examples/api/test/services/text_input/text_input_control.0_test.dart
0 → 100644
View file @
28e0f089
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_api_samples/services/text_input/text_input_control.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Enter text using the VKB'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
MyApp
());
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'A'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'A'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'B'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'AB'
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'C'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'ACB'
),
findsOneWidget
);
});
}
packages/flutter/lib/src/services/text_input.dart
View file @
28e0f089
...
...
@@ -1150,6 +1150,18 @@ mixin TextInputClient {
/// [TextInputClient] should cleanup its connection and finalize editing.
void
connectionClosed
();
/// The framework calls this method to notify that the text input control has
/// been changed.
///
/// The [TextInputClient] may switch to the new text input control by hiding
/// the old and showing the new input control.
///
/// See also:
///
/// * [TextInputControl.hide], a method to hide the old input control.
/// * [TextInputControl.show], a method to show the new input control.
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{}
/// Requests that the client show the editing toolbar, for example when the
/// platform changes the selection through a non-flutter method such as
/// scribble.
...
...
@@ -1362,13 +1374,7 @@ class TextInputConnection {
if
(
editableBoxSize
!=
_cachedSize
||
transform
!=
_cachedTransform
)
{
_cachedSize
=
editableBoxSize
;
_cachedTransform
=
transform
;
TextInput
.
_instance
.
_setEditableSizeAndTransform
(
<
String
,
dynamic
>{
'width'
:
editableBoxSize
.
width
,
'height'
:
editableBoxSize
.
height
,
'transform'
:
transform
.
storage
,
},
);
TextInput
.
_instance
.
_setEditableSizeAndTransform
(
editableBoxSize
,
transform
);
}
}
...
...
@@ -1387,14 +1393,7 @@ class TextInputConnection {
}
_cachedRect
=
rect
;
final
Rect
validRect
=
rect
.
isFinite
?
rect
:
Offset
.
zero
&
const
Size
(-
1
,
-
1
);
TextInput
.
_instance
.
_setComposingTextRect
(
<
String
,
dynamic
>{
'width'
:
validRect
.
width
,
'height'
:
validRect
.
height
,
'x'
:
validRect
.
left
,
'y'
:
validRect
.
top
,
},
);
TextInput
.
_instance
.
_setComposingTextRect
(
validRect
);
}
/// Sends the coordinates of caret rect. This is used on macOS for positioning
...
...
@@ -1406,14 +1405,7 @@ class TextInputConnection {
}
_cachedCaretRect
=
rect
;
final
Rect
validRect
=
rect
.
isFinite
?
rect
:
Offset
.
zero
&
const
Size
(-
1
,
-
1
);
TextInput
.
_instance
.
_setCaretRect
(
<
String
,
dynamic
>{
'width'
:
validRect
.
width
,
'height'
:
validRect
.
height
,
'x'
:
validRect
.
left
,
'y'
:
validRect
.
top
,
},
);
TextInput
.
_instance
.
_setCaretRect
(
validRect
);
}
/// Send the bounding boxes of the current selected glyphs in the client to
...
...
@@ -1423,9 +1415,7 @@ class TextInputConnection {
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
if
(!
listEquals
(
_cachedSelectionRects
,
selectionRects
))
{
_cachedSelectionRects
=
selectionRects
;
TextInput
.
_instance
.
_setSelectionRects
(
selectionRects
.
map
((
SelectionRect
rect
)
{
return
<
num
>[
rect
.
bounds
.
left
,
rect
.
bounds
.
top
,
rect
.
bounds
.
width
,
rect
.
bounds
.
height
,
rect
.
position
];
}).
toList
());
TextInput
.
_instance
.
_setSelectionRects
(
selectionRects
);
}
}
...
...
@@ -1444,13 +1434,11 @@ class TextInputConnection {
assert
(
attached
);
TextInput
.
_instance
.
_setStyle
(
<
String
,
dynamic
>{
'fontFamily'
:
fontFamily
,
'fontSize'
:
fontSize
,
'fontWeightIndex'
:
fontWeight
?.
index
,
'textAlignIndex'
:
textAlign
.
index
,
'textDirectionIndex'
:
textDirection
.
index
,
},
fontFamily:
fontFamily
,
fontSize:
fontSize
,
fontWeight:
fontWeight
,
textDirection:
textDirection
,
textAlign:
textAlign
,
);
}
...
...
@@ -1603,6 +1591,63 @@ class TextInput {
static
final
TextInput
_instance
=
TextInput
.
_
();
static
void
_addInputControl
(
TextInputControl
control
)
{
if
(
control
!=
_PlatformTextInputControl
.
instance
)
{
_instance
.
_inputControls
.
add
(
control
);
}
}
static
void
_removeInputControl
(
TextInputControl
control
)
{
if
(
control
!=
_PlatformTextInputControl
.
instance
)
{
_instance
.
_inputControls
.
remove
(
control
);
}
}
/// Sets the current text input control.
///
/// The current text input control receives text input state changes and visual
/// text input control requests, such as showing and hiding the input control,
/// from the framework.
///
/// Setting the current text input control as `null` removes the visual text
/// input control.
///
/// See also:
///
/// * [TextInputControl], an interface for implementing text input controls.
/// * [TextInput.restorePlatformInputControl], a method to restore the default
/// platform text input control.
static
void
setInputControl
(
TextInputControl
?
newControl
)
{
final
TextInputControl
?
oldControl
=
_instance
.
_currentControl
;
if
(
newControl
==
oldControl
)
{
return
;
}
if
(
newControl
!=
null
)
{
_addInputControl
(
newControl
);
}
if
(
oldControl
!=
null
)
{
_removeInputControl
(
oldControl
);
}
_instance
.
_currentControl
=
newControl
;
final
TextInputClient
?
client
=
_instance
.
_currentConnection
?.
_client
;
client
?.
didChangeInputControl
(
oldControl
,
newControl
);
}
/// Restores the default platform text input control.
///
/// See also:
///
/// * [TextInput.setInputControl], a method to set a custom input
/// control, or to remove the visual input control.
static
void
restorePlatformInputControl
()
{
setInputControl
(
_PlatformTextInputControl
.
instance
);
}
TextInputControl
?
_currentControl
=
_PlatformTextInputControl
.
instance
;
final
Set
<
TextInputControl
>
_inputControls
=
<
TextInputControl
>{
_PlatformTextInputControl
.
instance
,
};
static
const
List
<
TextInputAction
>
_androidSupportedInputActions
=
<
TextInputAction
>[
TextInputAction
.
none
,
TextInputAction
.
unspecified
,
...
...
@@ -1661,15 +1706,9 @@ class TextInput {
assert
(
connection
.
_client
!=
null
);
assert
(
configuration
!=
null
);
assert
(
_debugEnsureInputActionWorksOnPlatform
(
configuration
.
inputAction
));
_channel
.
invokeMethod
<
void
>(
'TextInput.setClient'
,
<
Object
>[
connection
.
_id
,
configuration
.
toJson
(),
],
);
_currentConnection
=
connection
;
_currentConfiguration
=
configuration
;
_setClient
(
connection
.
_client
,
configuration
);
}
static
bool
_debugEnsureInputActionWorksOnPlatform
(
TextInputAction
inputAction
)
{
...
...
@@ -1811,7 +1850,8 @@ class TextInput {
switch
(
method
)
{
case
'TextInputClient.updateEditingState'
:
_currentConnection
!.
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
final
TextEditingValue
value
=
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>);
TextInput
.
_instance
.
_updateEditingValue
(
value
,
exclude:
_PlatformTextInputControl
.
instance
);
break
;
case
'TextInputClient.updateEditingStateWithDeltas'
:
assert
(
_currentConnection
!.
_client
is
DeltaTextInputClient
,
'You must be using a DeltaTextInputClient if TextInputConfiguration.enableDeltaModel is set to true'
);
...
...
@@ -1882,74 +1922,119 @@ class TextInput {
scheduleMicrotask
(()
{
_hidePending
=
false
;
if
(
_currentConnection
==
null
)
{
_
channel
.
invokeMethod
<
void
>(
'TextInput.hide'
);
_
hide
(
);
}
});
}
void
_setClient
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
attach
(
client
,
configuration
);
}
}
void
_clearClient
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.clearClient'
);
final
TextInputClient
client
=
_currentConnection
!.
_client
;
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
detach
(
client
);
}
_currentConnection
=
null
;
_scheduleHide
();
}
void
_updateConfig
(
TextInputConfiguration
configuration
)
{
assert
(
configuration
!=
null
);
_channel
.
invokeMethod
<
void
>(
'TextInput.updateConfig'
,
configuration
.
toJson
(),
);
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
updateConfig
(
configuration
);
}
}
void
_setEditingState
(
TextEditingValue
value
)
{
assert
(
value
!=
null
);
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditingState'
,
value
.
toJSON
(),
);
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setEditingState
(
value
);
}
}
void
_show
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.show'
);
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
show
();
}
}
void
_requestAutofill
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.requestAutofill'
);
void
_hide
()
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
hide
();
}
}
void
_setEditableSizeAndTransform
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditableSizeAndTransform'
,
args
,
);
void
_setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setEditableSizeAndTransform
(
editableBoxSize
,
transform
);
}
}
void
_setComposingTextRect
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setMarkedTextRect'
,
args
,
);
void
_setComposingTextRect
(
Rect
rect
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setComposingRect
(
rect
);
}
}
void
_setCaretRect
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setCaretRect'
,
args
,
);
void
_setCaretRect
(
Rect
rect
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setCaretRect
(
rect
);
}
}
void
_setSelectionRects
(
List
<
List
<
num
>>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setSelectionRects'
,
args
,
);
void
_setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setSelectionRects
(
selectionRects
);
}
}
void
_setStyle
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setStyle'
,
args
,
);
void
_setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
})
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setStyle
(
fontFamily:
fontFamily
,
fontSize:
fontSize
,
fontWeight:
fontWeight
,
textDirection:
textDirection
,
textAlign:
textAlign
,
);
}
}
void
_requestAutofill
()
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
requestAutofill
();
}
}
void
_updateEditingValue
(
TextEditingValue
value
,
{
TextInputControl
?
exclude
})
{
if
(
_currentConnection
==
null
)
{
return
;
}
for
(
final
TextInputControl
control
in
_instance
.
_inputControls
)
{
if
(
control
!=
exclude
)
{
control
.
setEditingState
(
value
);
}
}
_instance
.
_currentConnection
!.
_client
.
updateEditingValue
(
value
);
}
/// Updates the editing value of the attached input client.
///
/// This method should be called by the text input control implementation to
/// send editing value updates to the attached input client.
static
void
updateEditingValue
(
TextEditingValue
value
)
{
_instance
.
_updateEditingValue
(
value
,
exclude:
_instance
.
_currentControl
);
}
/// Finishes the current autofill context, and potentially saves the user
...
...
@@ -2002,10 +2087,9 @@ class TextInput {
/// topmost [AutofillGroup] is getting disposed.
static
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
assert
(
shouldSave
!=
null
);
TextInput
.
_instance
.
_channel
.
invokeMethod
<
void
>(
'TextInput.finishAutofillContext'
,
shouldSave
,
);
for
(
final
TextInputControl
control
in
TextInput
.
_instance
.
_inputControls
)
{
control
.
finishAutofillContext
(
shouldSave:
shouldSave
);
}
}
/// Registers a [ScribbleClient] with [elementIdentifier] that can be focused
...
...
@@ -2022,3 +2106,257 @@ class TextInput {
TextInput
.
_instance
.
_scribbleClients
.
remove
(
elementIdentifier
);
}
}
/// An interface for implementing text input controls that receive text editing
/// state changes and visual input control requests.
///
/// Editing state changes and input control requests are sent by the framework
/// when the editing state of the attached text input client changes, or it
/// requests the input control to be shown or hidden, for example.
///
/// The input control can be installed with [TextInput.setInputControl], and the
/// default platform text input control can be restored with
/// [TextInput.restorePlatformInputControl].
///
/// The [TextInputControl] class must be extended. [TextInputControl]
/// implementations should call [TextInput.updateEditingValue] to send user
/// input to the attached input client.
///
/// {@tool dartpad}
/// This example illustrates a basic [TextInputControl] implementation.
///
/// ** See code in examples/api/lib/services/text_input/text_input_control.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [TextInput.setInputControl], a method to install a custom text input control.
/// * [TextInput.restorePlatformInputControl], a method to restore the default
/// platform text input control.
/// * [TextInput.updateEditingValue], a method to send user input to
/// the framework.
mixin
TextInputControl
{
/// Requests the text input control to attach to the given input client.
///
/// This method is called when a text input client is attached. The input
/// control should update its configuration to match the client's configuration.
void
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{}
/// Requests the text input control to detach from the given input client.
///
/// This method is called when a text input client is detached. The input
/// control should release any resources allocated for the client.
void
detach
(
TextInputClient
client
)
{}
/// Requests that the text input control is shown.
///
/// This method is called when the input control should become visible.
void
show
()
{}
/// Requests that the text input control is hidden.
///
/// This method is called when the input control should hide.
void
hide
()
{}
/// Informs the text input control about input configuration changes.
///
/// This method is called when the configuration of the attached input client
/// has changed.
void
updateConfig
(
TextInputConfiguration
configuration
)
{}
/// Informs the text input control about editing state changes.
///
/// This method is called when the editing state of the attached input client
/// has changed.
void
setEditingState
(
TextEditingValue
value
)
{}
/// Informs the text input control about client position changes.
///
/// This method is called on when the input control should position itself in
/// relation to the attached input client.
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{}
/// Informs the text input control about composing area changes.
///
/// This method is called when the attached input client's composing area
/// changes.
void
setComposingRect
(
Rect
rect
)
{}
/// Informs the text input control about caret area changes.
///
/// This method is called when the attached input client's caret area
/// changes.
void
setCaretRect
(
Rect
rect
)
{}
/// Informs the text input control about selection area changes.
///
/// This method is called when the attached input client's selection area
/// changes.
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{}
/// Informs the text input control about text style changes.
///
/// This method is called on the when the attached input client's text style
/// changes.
void
setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
})
{}
/// Requests autofill from the text input control.
///
/// This method is called when the autofill UI should appear.
void
requestAutofill
()
{}
/// Requests that the autofill context is finalized.
///
/// See also:
///
/// * [TextInput.finishAutofillContext]
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{}
}
/// Provides access to the platform text input control.
class
_PlatformTextInputControl
with
TextInputControl
{
_PlatformTextInputControl
.
_
();
/// The shared instance of [_PlatformTextInputControl].
static
final
_PlatformTextInputControl
instance
=
_PlatformTextInputControl
.
_
();
MethodChannel
get
_channel
=>
TextInput
.
_instance
.
_channel
;
Map
<
String
,
dynamic
>
_configurationToJson
(
TextInputConfiguration
configuration
)
{
final
Map
<
String
,
dynamic
>
json
=
configuration
.
toJson
();
if
(
TextInput
.
_instance
.
_currentControl
!=
_PlatformTextInputControl
.
instance
)
{
json
[
'inputType'
]
=
TextInputType
.
none
.
toJson
();
}
return
json
;
}
@override
void
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setClient'
,
<
Object
>[
TextInput
.
_instance
.
_currentConnection
!.
_id
,
_configurationToJson
(
configuration
),
],
);
}
@override
void
detach
(
TextInputClient
client
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.clearClient'
);
}
@override
void
updateConfig
(
TextInputConfiguration
configuration
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.updateConfig'
,
_configurationToJson
(
configuration
),
);
}
@override
void
setEditingState
(
TextEditingValue
value
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditingState'
,
value
.
toJSON
(),
);
}
@override
void
show
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.show'
);
}
@override
void
hide
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.hide'
);
}
@override
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditableSizeAndTransform'
,
<
String
,
dynamic
>{
'width'
:
editableBoxSize
.
width
,
'height'
:
editableBoxSize
.
height
,
'transform'
:
transform
.
storage
,
},
);
}
@override
void
setComposingRect
(
Rect
rect
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setMarkedTextRect'
,
<
String
,
dynamic
>{
'width'
:
rect
.
width
,
'height'
:
rect
.
height
,
'x'
:
rect
.
left
,
'y'
:
rect
.
top
,
},
);
}
@override
void
setCaretRect
(
Rect
rect
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setCaretRect'
,
<
String
,
dynamic
>{
'width'
:
rect
.
width
,
'height'
:
rect
.
height
,
'x'
:
rect
.
left
,
'y'
:
rect
.
top
,
},
);
}
@override
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setSelectionRects'
,
selectionRects
.
map
((
SelectionRect
rect
)
{
return
<
num
>[
rect
.
bounds
.
left
,
rect
.
bounds
.
top
,
rect
.
bounds
.
width
,
rect
.
bounds
.
height
,
rect
.
position
];
}).
toList
(),
);
}
@override
void
setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
})
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setStyle'
,
<
String
,
dynamic
>{
'fontFamily'
:
fontFamily
,
'fontSize'
:
fontSize
,
'fontWeightIndex'
:
fontWeight
?.
index
,
'textAlignIndex'
:
textAlign
.
index
,
'textDirectionIndex'
:
textDirection
.
index
,
},
);
}
@override
void
requestAutofill
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.requestAutofill'
);
}
@override
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
_channel
.
invokeMethod
<
void
>(
'TextInput.finishAutofillContext'
,
shouldSave
,
);
}
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
28e0f089
...
...
@@ -2675,6 +2675,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
if
(
_hasFocus
&&
_hasInputConnection
)
{
oldControl
?.
hide
();
newControl
?.
show
();
}
}
@override
void
connectionClosed
()
{
if
(
_hasInputConnection
)
{
...
...
packages/flutter/test/services/autofill_test.dart
View file @
28e0f089
...
...
@@ -139,6 +139,11 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
updateEditingValue
(
newEditingValue
);
...
...
packages/flutter/test/services/delta_text_input_test.dart
View file @
28e0f089
...
...
@@ -292,4 +292,9 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
(
enableDeltaModel:
true
);
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
}
packages/flutter/test/services/text_input_test.dart
View file @
28e0f089
...
...
@@ -780,6 +780,178 @@ void main() {
isTrue
,
);
});
group
(
'TextInputControl'
,
()
{
late
FakeTextChannel
fakeTextChannel
;
setUp
(()
{
fakeTextChannel
=
FakeTextChannel
((
MethodCall
call
)
async
{});
TextInput
.
setChannel
(
fakeTextChannel
);
});
tearDown
(()
{
TextInput
.
restorePlatformInputControl
();
TextInputConnection
.
debugResetId
();
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
});
test
(
'gets attached and detached'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
final
List
<
String
>
expectedMethodCalls
=
<
String
>[
'attach'
];
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
});
test
(
'receives text input state changes'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
control
.
methodCalls
.
clear
();
final
List
<
String
>
expectedMethodCalls
=
<
String
>[];
connection
.
updateConfig
(
const
TextInputConfiguration
());
expectedMethodCalls
.
add
(
'updateConfig'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
setEditingState
(
TextEditingValue
.
empty
);
expectedMethodCalls
.
add
(
'setEditingState'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
});
test
(
'does not interfere with platform text input'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
fakeTextChannel
.
outgoingCalls
.
clear
();
fakeTextChannel
.
incoming
!(
MethodCall
(
'TextInputClient.updateEditingState'
,
<
dynamic
>[
1
,
TextEditingValue
.
empty
.
toJSON
()]));
expect
(
client
.
latestMethodCall
,
'updateEditingValue'
);
expect
(
control
.
methodCalls
,
<
String
>[
'attach'
,
'setEditingState'
]);
expect
(
fakeTextChannel
.
outgoingCalls
,
isEmpty
);
});
test
(
'both input controls receive requests'
,
()
async
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
const
TextInputConfiguration
textConfig
=
TextInputConfiguration
();
const
TextInputConfiguration
numberConfig
=
TextInputConfiguration
(
inputType:
TextInputType
.
number
);
const
TextInputConfiguration
noneConfig
=
TextInputConfiguration
(
inputType:
TextInputType
.
none
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
textConfig
);
final
List
<
String
>
expectedMethodCalls
=
<
String
>[
'attach'
];
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
control
.
inputType
,
TextInputType
.
text
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
noneConfig
.
toJson
()]),
]);
connection
.
show
();
expectedMethodCalls
.
add
(
'show'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
2
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.show'
);
connection
.
updateConfig
(
numberConfig
);
expectedMethodCalls
.
add
(
'updateConfig'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
control
.
inputType
,
TextInputType
.
number
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
3
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
noneConfig
.
toJson
()]),
const
MethodCall
(
'TextInput.show'
),
MethodCall
(
'TextInput.updateConfig'
,
noneConfig
.
toJson
()),
]);
connection
.
setComposingRect
(
Rect
.
zero
);
expectedMethodCalls
.
add
(
'setComposingRect'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
4
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setMarkedTextRect'
);
connection
.
setCaretRect
(
Rect
.
zero
);
expectedMethodCalls
.
add
(
'setCaretRect'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
5
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setCaretRect'
);
connection
.
setEditableSizeAndTransform
(
Size
.
zero
,
Matrix4
.
identity
());
expectedMethodCalls
.
add
(
'setEditableSizeAndTransform'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
6
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setEditableSizeAndTransform'
);
connection
.
setSelectionRects
(
const
<
SelectionRect
>[
SelectionRect
(
position:
0
,
bounds:
Rect
.
zero
)]);
expectedMethodCalls
.
add
(
'setSelectionRects'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
7
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setSelectionRects'
);
connection
.
setStyle
(
fontFamily:
null
,
fontSize:
null
,
fontWeight:
null
,
textDirection:
TextDirection
.
ltr
,
textAlign:
TextAlign
.
left
,
);
expectedMethodCalls
.
add
(
'setStyle'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
8
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setStyle'
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
9
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.clearClient'
);
expectedMethodCalls
.
add
(
'hide'
);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
await
binding
.
runAsync
(()
async
{});
await
expectLater
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
10
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.hide'
);
});
test
(
'notifies changes to the attached client'
,
()
async
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
TextInput
.
setInputControl
(
null
);
expect
(
client
.
latestMethodCall
,
'didChangeInputControl'
);
connection
.
show
();
expect
(
client
.
latestMethodCall
,
'didChangeInputControl'
);
});
});
}
class
FakeTextInputClient
with
TextInputClient
{
...
...
@@ -833,6 +1005,11 @@ class FakeTextInputClient with TextInputClient {
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
...
...
@@ -849,3 +1026,81 @@ class FakeTextInputClient with TextInputClient {
performedSelectors
.
add
(
selectorName
);
}
}
class
FakeTextInputControl
with
TextInputControl
{
final
List
<
String
>
methodCalls
=
<
String
>[];
late
TextInputType
inputType
;
@override
void
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
methodCalls
.
add
(
'attach'
);
inputType
=
configuration
.
inputType
;
}
@override
void
detach
(
TextInputClient
client
)
{
methodCalls
.
add
(
'detach'
);
}
@override
void
setEditingState
(
TextEditingValue
value
)
{
methodCalls
.
add
(
'setEditingState'
);
}
@override
void
updateConfig
(
TextInputConfiguration
configuration
)
{
methodCalls
.
add
(
'updateConfig'
);
inputType
=
configuration
.
inputType
;
}
@override
void
show
()
{
methodCalls
.
add
(
'show'
);
}
@override
void
hide
()
{
methodCalls
.
add
(
'hide'
);
}
@override
void
setComposingRect
(
Rect
rect
)
{
methodCalls
.
add
(
'setComposingRect'
);
}
@override
void
setCaretRect
(
Rect
rect
)
{
methodCalls
.
add
(
'setCaretRect'
);
}
@override
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
methodCalls
.
add
(
'setEditableSizeAndTransform'
);
}
@override
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
methodCalls
.
add
(
'setSelectionRects'
);
}
@override
void
setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
})
{
methodCalls
.
add
(
'setStyle'
);
}
@override
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
methodCalls
.
add
(
'finishAutofillContext'
);
}
@override
void
requestAutofill
()
{
methodCalls
.
add
(
'requestAutofill'
);
}
}
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