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
d2d17abe
Unverified
Commit
d2d17abe
authored
Jul 20, 2018
by
Jonah Williams
Committed by
GitHub
Jul 20, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for custom semantics actions to Android and iOS. (#18882)
parent
924c206c
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
401 additions
and
57 deletions
+401
-57
leave_behind_demo.dart
.../flutter_gallery/lib/demo/material/leave_behind_demo.dart
+113
-51
proxy_box.dart
packages/flutter/lib/src/rendering/proxy_box.dart
+22
-0
semantics.dart
packages/flutter/lib/src/semantics/semantics.dart
+190
-5
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+5
-1
custom_semantics_action_test.dart
.../flutter/test/semantics/custom_semantics_action_test.dart
+25
-0
semantics_test.dart
packages/flutter/test/semantics/semantics_test.dart
+46
-0
No files found.
examples/flutter_gallery/lib/demo/material/leave_behind_demo.dart
View file @
d2d17abe
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
import
'package:collection/collection.dart'
show
lowerBound
;
import
'package:collection/collection.dart'
show
lowerBound
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/semantics.dart'
;
enum
LeaveBehindDemoAction
{
enum
LeaveBehindDemoAction
{
reset
,
reset
,
...
@@ -85,52 +86,55 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
...
@@ -85,52 +86,55 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
});
});
}
}
Widget
buildItem
(
LeaveBehindItem
item
)
{
void
_handleArchive
(
LeaveBehindItem
item
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
setState
(()
{
return
new
Dismissible
(
leaveBehindItems
.
remove
(
item
);
key:
new
ObjectKey
(
item
),
});
direction:
_dismissDirection
,
_scaffoldKey
.
currentState
.
showSnackBar
(
new
SnackBar
(
onDismissed:
(
DismissDirection
direction
)
{
content:
new
Text
(
'You archived item
${item.index}
'
),
setState
(()
{
action:
new
SnackBarAction
(
leaveBehindItems
.
remove
(
item
);
label:
'UNDO'
,
});
onPressed:
()
{
handleUndo
(
item
);
}
final
String
action
=
(
direction
==
DismissDirection
.
endToStart
)
?
'archived'
:
'deleted'
;
_scaffoldKey
.
currentState
.
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
'You
$action
item
${item.index}
'
),
action:
new
SnackBarAction
(
label:
'UNDO'
,
onPressed:
()
{
handleUndo
(
item
);
}
)
));
},
background:
new
Container
(
color:
theme
.
primaryColor
,
child:
const
ListTile
(
leading:
const
Icon
(
Icons
.
delete
,
color:
Colors
.
white
,
size:
36.0
)
)
),
secondaryBackground:
new
Container
(
color:
theme
.
primaryColor
,
child:
const
ListTile
(
trailing:
const
Icon
(
Icons
.
archive
,
color:
Colors
.
white
,
size:
36.0
)
)
),
child:
new
Container
(
decoration:
new
BoxDecoration
(
color:
theme
.
canvasColor
,
border:
new
Border
(
bottom:
new
BorderSide
(
color:
theme
.
dividerColor
))
),
child:
new
ListTile
(
title:
new
Text
(
item
.
name
),
subtitle:
new
Text
(
'
${item.subject}
\n
${item.body}
'
),
isThreeLine:
true
)
)
)
);
));
}
void
_handleDelete
(
LeaveBehindItem
item
)
{
setState
(()
{
leaveBehindItems
.
remove
(
item
);
});
_scaffoldKey
.
currentState
.
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
'You deleted item
${item.index}
'
),
action:
new
SnackBarAction
(
label:
'UNDO'
,
onPressed:
()
{
handleUndo
(
item
);
}
)
));
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
Widget
body
;
if
(
leaveBehindItems
.
isEmpty
)
{
body
=
new
Center
(
child:
new
RaisedButton
(
onPressed:
()
=>
handleDemoAction
(
LeaveBehindDemoAction
.
reset
),
child:
const
Text
(
'Reset the list'
),
),
);
}
else
{
body
=
new
ListView
(
children:
leaveBehindItems
.
map
((
LeaveBehindItem
item
)
{
return
new
_LeaveBehindListItem
(
item:
item
,
onArchive:
_handleArchive
,
onDelete:
_handleDelete
,
dismissDirection:
_dismissDirection
,
);
}).
toList
()
);
}
return
new
Scaffold
(
return
new
Scaffold
(
key:
_scaffoldKey
,
key:
_scaffoldKey
,
appBar:
new
AppBar
(
appBar:
new
AppBar
(
...
@@ -163,16 +167,74 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
...
@@ -163,16 +167,74 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
)
)
]
]
),
),
body:
leaveBehindItems
.
isEmpty
body:
body
,
?
new
Center
(
);
child:
new
RaisedButton
(
}
onPressed:
()
=>
handleDemoAction
(
LeaveBehindDemoAction
.
reset
),
}
child:
const
Text
(
'Reset the list'
),
),
class
_LeaveBehindListItem
extends
StatelessWidget
{
)
const
_LeaveBehindListItem
({
:
new
ListView
(
Key
key
,
children:
leaveBehindItems
.
map
(
buildItem
).
toList
()
@required
this
.
item
,
),
@required
this
.
onArchive
,
@required
this
.
onDelete
,
@required
this
.
dismissDirection
,
})
:
super
(
key:
key
);
final
LeaveBehindItem
item
;
final
DismissDirection
dismissDirection
;
final
void
Function
(
LeaveBehindItem
)
onArchive
;
final
void
Function
(
LeaveBehindItem
)
onDelete
;
void
_handleArchive
()
{
onArchive
(
item
);
}
void
_handleDelete
()
{
onDelete
(
item
);
}
@override
Widget
build
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
return
new
Semantics
(
customSemanticsActions:
<
CustomSemanticsAction
,
VoidCallback
>{
const
CustomSemanticsAction
(
label:
'Archive'
):
_handleArchive
,
const
CustomSemanticsAction
(
label:
'Delete'
):
_handleDelete
,
},
child:
new
Dismissible
(
key:
new
ObjectKey
(
item
),
direction:
dismissDirection
,
onDismissed:
(
DismissDirection
direction
)
{
if
(
direction
==
DismissDirection
.
endToStart
)
_handleArchive
();
else
_handleDelete
();
},
background:
new
Container
(
color:
theme
.
primaryColor
,
child:
const
ListTile
(
leading:
const
Icon
(
Icons
.
delete
,
color:
Colors
.
white
,
size:
36.0
)
)
),
secondaryBackground:
new
Container
(
color:
theme
.
primaryColor
,
child:
const
ListTile
(
trailing:
const
Icon
(
Icons
.
archive
,
color:
Colors
.
white
,
size:
36.0
)
)
),
child:
new
Container
(
decoration:
new
BoxDecoration
(
color:
theme
.
canvasColor
,
border:
new
Border
(
bottom:
new
BorderSide
(
color:
theme
.
dividerColor
))
),
child:
new
ListTile
(
title:
new
Text
(
item
.
name
),
subtitle:
new
Text
(
'
${item.subject}
\n
${item.body}
'
),
isThreeLine:
true
),
),
),
);
);
}
}
}
}
packages/flutter/lib/src/rendering/proxy_box.dart
View file @
d2d17abe
...
@@ -3159,6 +3159,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
...
@@ -3159,6 +3159,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
SetSelectionHandler
onSetSelection
,
SetSelectionHandler
onSetSelection
,
VoidCallback
onDidGainAccessibilityFocus
,
VoidCallback
onDidGainAccessibilityFocus
,
VoidCallback
onDidLoseAccessibilityFocus
,
VoidCallback
onDidLoseAccessibilityFocus
,
Map
<
CustomSemanticsAction
,
VoidCallback
>
customSemanticsActions
,
})
:
assert
(
container
!=
null
),
})
:
assert
(
container
!=
null
),
_container
=
container
,
_container
=
container
,
_explicitChildNodes
=
explicitChildNodes
,
_explicitChildNodes
=
explicitChildNodes
,
...
@@ -3197,6 +3198,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
...
@@ -3197,6 +3198,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_onSetSelection
=
onSetSelection
,
_onSetSelection
=
onSetSelection
,
_onDidGainAccessibilityFocus
=
onDidGainAccessibilityFocus
,
_onDidGainAccessibilityFocus
=
onDidGainAccessibilityFocus
,
_onDidLoseAccessibilityFocus
=
onDidLoseAccessibilityFocus
,
_onDidLoseAccessibilityFocus
=
onDidLoseAccessibilityFocus
,
_customSemanticsActions
=
customSemanticsActions
,
super
(
child
);
super
(
child
);
/// If 'container' is true, this [RenderObject] will introduce a new
/// If 'container' is true, this [RenderObject] will introduce a new
...
@@ -3779,6 +3781,24 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
...
@@ -3779,6 +3781,24 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate
();
markNeedsSemanticsUpdate
();
}
}
/// The handlers and supported [CustomSemanticsAction]s for this node.
///
/// These handlers are called whenever the user performs the associated
/// custom accessibility action from a special platform menu. Providing any
/// custom actions here also adds [SemanticsAction.customAction] to the node.
///
/// See also:
///
/// * [CustomSemanticsAction], for an explaination of custom actions.
Map
<
CustomSemanticsAction
,
VoidCallback
>
get
customSemanticsActions
=>
_customSemanticsActions
;
Map
<
CustomSemanticsAction
,
VoidCallback
>
_customSemanticsActions
;
set
customSemanticsActions
(
Map
<
CustomSemanticsAction
,
VoidCallback
>
value
)
{
if
(
_customSemanticsActions
==
value
)
return
;
_customSemanticsActions
=
value
;
markNeedsSemanticsUpdate
();
}
@override
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
super
.
describeSemanticsConfiguration
(
config
);
...
@@ -3860,6 +3880,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
...
@@ -3860,6 +3880,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config
.
onDidGainAccessibilityFocus
=
_performDidGainAccessibilityFocus
;
config
.
onDidGainAccessibilityFocus
=
_performDidGainAccessibilityFocus
;
if
(
onDidLoseAccessibilityFocus
!=
null
)
if
(
onDidLoseAccessibilityFocus
!=
null
)
config
.
onDidLoseAccessibilityFocus
=
_performDidLoseAccessibilityFocus
;
config
.
onDidLoseAccessibilityFocus
=
_performDidLoseAccessibilityFocus
;
if
(
customSemanticsActions
!=
null
)
config
.
customSemanticsActions
=
_customSemanticsActions
;
}
}
void
_performTap
()
{
void
_performTap
()
{
...
...
packages/flutter/lib/src/semantics/semantics.dart
View file @
d2d17abe
...
@@ -69,6 +69,76 @@ class SemanticsTag {
...
@@ -69,6 +69,76 @@ class SemanticsTag {
String
toString
()
=>
'
$runtimeType
(
$name
)'
;
String
toString
()
=>
'
$runtimeType
(
$name
)'
;
}
}
/// An identifier of a custom semantics action.
///
/// Custom semantics actions can be provided to make complex user
/// interactions more accessible. For instance, if an application has a
/// drag-and-drop list that requires the user to press and hold an item
/// to move it, users interacting with the application using a hardware
/// switch may have difficulty. This can be made accessible by creating custom
/// actions and pairing them with handlers that move a list item up or down in
/// the list.
///
/// In Android, these actions are presented in the local context menu. In iOS,
/// these are presented in the radial context menu.
///
/// Localization and text direction do not automatically apply to the provided
/// label.
///
/// Instances of this class should either be instantiated with const or
/// new instances cached in static fields.
///
/// See also:
///
/// * [SemanticsProperties], where the handler for a custom action is provided.
@immutable
class
CustomSemanticsAction
{
/// Creates a new [CustomSemanticsAction].
///
/// The [label] must not be null or the empty string.
const
CustomSemanticsAction
({
@required
this
.
label
})
:
assert
(
label
!=
null
),
assert
(
label
!=
''
);
/// The user readable name of this custom accessibility action.
final
String
label
;
@override
int
get
hashCode
=>
label
.
hashCode
;
@override
bool
operator
==(
dynamic
other
)
{
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
final
CustomSemanticsAction
typedOther
=
other
;
return
typedOther
.
label
==
label
;
}
// Logic to assign a unique id to each custom action without requiring
// user specification.
static
int
_nextId
=
0
;
static
final
Map
<
int
,
CustomSemanticsAction
>
_actions
=
<
int
,
CustomSemanticsAction
>{};
static
final
Map
<
CustomSemanticsAction
,
int
>
_ids
=
<
CustomSemanticsAction
,
int
>{};
/// Get the identifier for a given `action`.
@visibleForTesting
static
int
getIdentifier
(
CustomSemanticsAction
action
)
{
int
result
=
_ids
[
action
];
if
(
result
==
null
)
{
result
=
_nextId
++;
_ids
[
action
]
=
result
;
_actions
[
result
]
=
action
;
}
return
result
;
}
/// Get the `action` for a given identifier.
@visibleForTesting
static
CustomSemanticsAction
getAction
(
int
id
)
{
return
_actions
[
id
];
}
}
/// Summary information about a [SemanticsNode] object.
/// Summary information about a [SemanticsNode] object.
///
///
/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
...
@@ -100,6 +170,7 @@ class SemanticsData extends Diagnosticable {
...
@@ -100,6 +170,7 @@ class SemanticsData extends Diagnosticable {
@required
this
.
scrollExtentMin
,
@required
this
.
scrollExtentMin
,
this
.
tags
,
this
.
tags
,
this
.
transform
,
this
.
transform
,
this
.
customSemanticsActionIds
,
})
:
assert
(
flags
!=
null
),
})
:
assert
(
flags
!=
null
),
assert
(
actions
!=
null
),
assert
(
actions
!=
null
),
assert
(
label
!=
null
),
assert
(
label
!=
null
),
...
@@ -200,6 +271,15 @@ class SemanticsData extends Diagnosticable {
...
@@ -200,6 +271,15 @@ class SemanticsData extends Diagnosticable {
/// parent).
/// parent).
final
Matrix4
transform
;
final
Matrix4
transform
;
/// The identifiers for the custom semantics action defined for this node.
///
/// The list must be sorted in increasing order.
///
/// See also:
///
/// * [CustomSemanticsAction], for an explanation of custom actions.
final
List
<
int
>
customSemanticsActionIds
;
/// Whether [flags] contains the given flag.
/// Whether [flags] contains the given flag.
bool
hasFlag
(
SemanticsFlag
flag
)
=>
(
flags
&
flag
.
index
)
!=
0
;
bool
hasFlag
(
SemanticsFlag
flag
)
=>
(
flags
&
flag
.
index
)
!=
0
;
...
@@ -219,7 +299,11 @@ class SemanticsData extends Diagnosticable {
...
@@ -219,7 +299,11 @@ class SemanticsData extends Diagnosticable {
if
((
actions
&
action
.
index
)
!=
0
)
if
((
actions
&
action
.
index
)
!=
0
)
actionSummary
.
add
(
describeEnum
(
action
));
actionSummary
.
add
(
describeEnum
(
action
));
}
}
final
List
<
String
>
customSemanticsActionSummary
=
customSemanticsActionIds
.
map
<
String
>((
int
actionId
)
=>
CustomSemanticsAction
.
getAction
(
actionId
).
label
)
.
toList
();
properties
.
add
(
new
IterableProperty
<
String
>(
'actions'
,
actionSummary
,
ifEmpty:
null
));
properties
.
add
(
new
IterableProperty
<
String
>(
'actions'
,
actionSummary
,
ifEmpty:
null
));
properties
.
add
(
new
IterableProperty
<
String
>(
'customActions'
,
customSemanticsActionSummary
,
ifEmpty:
null
));
final
List
<
String
>
flagSummary
=
<
String
>[];
final
List
<
String
>
flagSummary
=
<
String
>[];
for
(
SemanticsFlag
flag
in
SemanticsFlag
.
values
.
values
)
{
for
(
SemanticsFlag
flag
in
SemanticsFlag
.
values
.
values
)
{
...
@@ -259,11 +343,45 @@ class SemanticsData extends Diagnosticable {
...
@@ -259,11 +343,45 @@ class SemanticsData extends Diagnosticable {
&&
typedOther
.
scrollPosition
==
scrollPosition
&&
typedOther
.
scrollPosition
==
scrollPosition
&&
typedOther
.
scrollExtentMax
==
scrollExtentMax
&&
typedOther
.
scrollExtentMax
==
scrollExtentMax
&&
typedOther
.
scrollExtentMin
==
scrollExtentMin
&&
typedOther
.
scrollExtentMin
==
scrollExtentMin
&&
typedOther
.
transform
==
transform
;
&&
typedOther
.
transform
==
transform
&&
_sortedListsEqual
(
typedOther
.
customSemanticsActionIds
,
customSemanticsActionIds
);
}
}
@override
@override
int
get
hashCode
=>
ui
.
hashValues
(
flags
,
actions
,
label
,
value
,
increasedValue
,
decreasedValue
,
hint
,
textDirection
,
rect
,
tags
,
textSelection
,
scrollPosition
,
scrollExtentMax
,
scrollExtentMin
,
transform
);
int
get
hashCode
{
return
ui
.
hashValues
(
flags
,
actions
,
label
,
value
,
increasedValue
,
decreasedValue
,
hint
,
textDirection
,
rect
,
tags
,
textSelection
,
scrollPosition
,
scrollExtentMax
,
scrollExtentMin
,
transform
,
customSemanticsActionIds
,
);
}
static
bool
_sortedListsEqual
(
List
<
int
>
left
,
List
<
int
>
right
)
{
if
(
left
==
null
&&
right
==
null
)
return
true
;
if
(
left
!=
null
&&
right
!=
null
)
{
if
(
left
.
length
!=
right
.
length
)
return
false
;
for
(
int
i
=
0
;
i
<
left
.
length
;
i
++)
if
(
left
[
i
]
!=
right
[
i
])
return
false
;
return
true
;
}
return
false
;
}
}
}
class
_SemanticsDiagnosticableNode
extends
DiagnosticableNode
<
SemanticsNode
>
{
class
_SemanticsDiagnosticableNode
extends
DiagnosticableNode
<
SemanticsNode
>
{
...
@@ -333,6 +451,7 @@ class SemanticsProperties extends DiagnosticableTree {
...
@@ -333,6 +451,7 @@ class SemanticsProperties extends DiagnosticableTree {
this
.
onSetSelection
,
this
.
onSetSelection
,
this
.
onDidGainAccessibilityFocus
,
this
.
onDidGainAccessibilityFocus
,
this
.
onDidLoseAccessibilityFocus
,
this
.
onDidLoseAccessibilityFocus
,
this
.
customSemanticsActions
,
});
});
/// If non-null, indicates that this subtree represents something that can be
/// If non-null, indicates that this subtree represents something that can be
...
@@ -696,6 +815,18 @@ class SemanticsProperties extends DiagnosticableTree {
...
@@ -696,6 +815,18 @@ class SemanticsProperties extends DiagnosticableTree {
/// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
/// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
final
VoidCallback
onDidLoseAccessibilityFocus
;
final
VoidCallback
onDidLoseAccessibilityFocus
;
/// A map from each supported [CustomSemanticsAction] to a provided handler.
///
/// The handler associated with each custom action is called whenever a
/// semantics event of type [SemanticsEvent.customEvent] is received. The
/// provided argument will be an identifier used to retrieve an instance of
/// a custom action which can then retrieve the correct handler from this map.
///
/// See also:
///
/// * [CustomSemanticsAction], for an explanation of custom actions.
final
Map
<
CustomSemanticsAction
,
VoidCallback
>
customSemanticsActions
;
@override
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
super
.
debugFillProperties
(
properties
);
...
@@ -1103,6 +1234,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1103,6 +1234,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
// TAGS, LABELS, ACTIONS
// TAGS, LABELS, ACTIONS
Map
<
SemanticsAction
,
_SemanticsActionHandler
>
_actions
=
_kEmptyConfig
.
_actions
;
Map
<
SemanticsAction
,
_SemanticsActionHandler
>
_actions
=
_kEmptyConfig
.
_actions
;
Map
<
CustomSemanticsAction
,
VoidCallback
>
_customSemanticsActions
=
_kEmptyConfig
.
_customSemanticsActions
;
int
_actionsAsBits
=
_kEmptyConfig
.
_actionsAsBits
;
int
_actionsAsBits
=
_kEmptyConfig
.
_actionsAsBits
;
...
@@ -1242,6 +1374,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1242,6 +1374,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_textDirection
=
config
.
textDirection
;
_textDirection
=
config
.
textDirection
;
_sortKey
=
config
.
sortKey
;
_sortKey
=
config
.
sortKey
;
_actions
=
new
Map
<
SemanticsAction
,
_SemanticsActionHandler
>.
from
(
config
.
_actions
);
_actions
=
new
Map
<
SemanticsAction
,
_SemanticsActionHandler
>.
from
(
config
.
_actions
);
_customSemanticsActions
=
new
Map
<
CustomSemanticsAction
,
VoidCallback
>.
from
(
config
.
_customSemanticsActions
);
_actionsAsBits
=
config
.
_actionsAsBits
;
_actionsAsBits
=
config
.
_actionsAsBits
;
_textSelection
=
config
.
_textSelection
;
_textSelection
=
config
.
_textSelection
;
_scrollPosition
=
config
.
_scrollPosition
;
_scrollPosition
=
config
.
_scrollPosition
;
...
@@ -1280,6 +1413,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1280,6 +1413,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
double
scrollPosition
=
_scrollPosition
;
double
scrollPosition
=
_scrollPosition
;
double
scrollExtentMax
=
_scrollExtentMax
;
double
scrollExtentMax
=
_scrollExtentMax
;
double
scrollExtentMin
=
_scrollExtentMin
;
double
scrollExtentMin
=
_scrollExtentMin
;
final
Set
<
int
>
customSemanticsActionIds
=
new
Set
<
int
>();
for
(
CustomSemanticsAction
action
in
_customSemanticsActions
.
keys
)
customSemanticsActionIds
.
add
(
CustomSemanticsAction
.
getIdentifier
(
action
));
if
(
mergeAllDescendantsIntoThisNode
)
{
if
(
mergeAllDescendantsIntoThisNode
)
{
_visitDescendants
((
SemanticsNode
node
)
{
_visitDescendants
((
SemanticsNode
node
)
{
...
@@ -1301,6 +1437,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1301,6 +1437,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
mergedTags
??=
new
Set
<
SemanticsTag
>();
mergedTags
??=
new
Set
<
SemanticsTag
>();
mergedTags
.
addAll
(
node
.
tags
);
mergedTags
.
addAll
(
node
.
tags
);
}
}
if
(
node
.
_customSemanticsActions
!=
null
)
{
for
(
CustomSemanticsAction
action
in
_customSemanticsActions
.
keys
)
customSemanticsActionIds
.
add
(
CustomSemanticsAction
.
getIdentifier
(
action
));
}
label
=
_concatStrings
(
label
=
_concatStrings
(
thisString:
label
,
thisString:
label
,
thisTextDirection:
textDirection
,
thisTextDirection:
textDirection
,
...
@@ -1333,6 +1473,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1333,6 +1473,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
scrollPosition:
scrollPosition
,
scrollPosition:
scrollPosition
,
scrollExtentMax:
scrollExtentMax
,
scrollExtentMax:
scrollExtentMax
,
scrollExtentMin:
scrollExtentMin
,
scrollExtentMin:
scrollExtentMin
,
customSemanticsActionIds:
customSemanticsActionIds
.
toList
()..
sort
(),
);
);
}
}
...
@@ -1341,9 +1482,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1341,9 +1482,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
}
}
static
final
Int32List
_kEmptyChildList
=
new
Int32List
(
0
);
static
final
Int32List
_kEmptyChildList
=
new
Int32List
(
0
);
static
final
Int32List
_kEmptyCustomSemanticsActionsList
=
new
Int32List
(
0
);
static
final
Float64List
_kIdentityTransform
=
_initIdentityTransform
();
static
final
Float64List
_kIdentityTransform
=
_initIdentityTransform
();
void
_addToUpdate
(
ui
.
SemanticsUpdateBuilder
builder
)
{
void
_addToUpdate
(
ui
.
SemanticsUpdateBuilder
builder
,
Set
<
int
>
customSemanticsActionIdsUpdate
)
{
assert
(
_dirty
);
assert
(
_dirty
);
final
SemanticsData
data
=
getSemanticsData
();
final
SemanticsData
data
=
getSemanticsData
();
Int32List
childrenInTraversalOrder
;
Int32List
childrenInTraversalOrder
;
...
@@ -1365,6 +1507,14 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1365,6 +1507,14 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
childrenInHitTestOrder
[
i
]
=
_children
[
childCount
-
i
-
1
].
id
;
childrenInHitTestOrder
[
i
]
=
_children
[
childCount
-
i
-
1
].
id
;
}
}
}
}
Int32List
customSemanticsActionIds
;
if
(
data
.
customSemanticsActionIds
?.
isNotEmpty
==
true
)
{
customSemanticsActionIds
=
new
Int32List
(
data
.
customSemanticsActionIds
.
length
);
for
(
int
i
=
0
;
i
<
data
.
customSemanticsActionIds
.
length
;
i
++)
{
customSemanticsActionIds
[
i
]
=
data
.
customSemanticsActionIds
[
i
];
customSemanticsActionIdsUpdate
.
add
(
data
.
customSemanticsActionIds
[
i
]);
}
}
builder
.
updateNode
(
builder
.
updateNode
(
id:
id
,
id:
id
,
flags:
data
.
flags
,
flags:
data
.
flags
,
...
@@ -1384,6 +1534,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1384,6 +1534,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
transform:
data
.
transform
?.
storage
??
_kIdentityTransform
,
transform:
data
.
transform
?.
storage
??
_kIdentityTransform
,
childrenInTraversalOrder:
childrenInTraversalOrder
,
childrenInTraversalOrder:
childrenInTraversalOrder
,
childrenInHitTestOrder:
childrenInHitTestOrder
,
childrenInHitTestOrder:
childrenInHitTestOrder
,
customAcccessibilityActions:
customSemanticsActionIds
??
_kEmptyCustomSemanticsActionsList
,
);
);
_dirty
=
false
;
_dirty
=
false
;
}
}
...
@@ -1495,7 +1646,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
...
@@ -1495,7 +1646,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties
.
add
(
new
DiagnosticsProperty
<
Rect
>(
'rect'
,
rect
,
description:
description
,
showName:
false
));
properties
.
add
(
new
DiagnosticsProperty
<
Rect
>(
'rect'
,
rect
,
description:
description
,
showName:
false
));
}
}
final
List
<
String
>
actions
=
_actions
.
keys
.
map
((
SemanticsAction
action
)
=>
describeEnum
(
action
)).
toList
()..
sort
();
final
List
<
String
>
actions
=
_actions
.
keys
.
map
((
SemanticsAction
action
)
=>
describeEnum
(
action
)).
toList
()..
sort
();
final
List
<
String
>
customSemanticsActions
=
_customSemanticsActions
.
keys
.
map
<
String
>((
CustomSemanticsAction
action
)
=>
action
.
label
)
.
toList
();
properties
.
add
(
new
IterableProperty
<
String
>(
'actions'
,
actions
,
ifEmpty:
null
));
properties
.
add
(
new
IterableProperty
<
String
>(
'actions'
,
actions
,
ifEmpty:
null
));
properties
.
add
(
new
IterableProperty
<
String
>(
'customActions'
,
customSemanticsActions
,
ifEmpty:
null
));
final
List
<
String
>
flags
=
SemanticsFlag
.
values
.
values
.
where
((
SemanticsFlag
flag
)
=>
_hasFlag
(
flag
)).
map
((
SemanticsFlag
flag
)
=>
flag
.
toString
().
substring
(
'SemanticsFlag.'
.
length
)).
toList
();
final
List
<
String
>
flags
=
SemanticsFlag
.
values
.
values
.
where
((
SemanticsFlag
flag
)
=>
_hasFlag
(
flag
)).
map
((
SemanticsFlag
flag
)
=>
flag
.
toString
().
substring
(
'SemanticsFlag.'
.
length
)).
toList
();
properties
.
add
(
new
IterableProperty
<
String
>(
'flags'
,
flags
,
ifEmpty:
null
));
properties
.
add
(
new
IterableProperty
<
String
>(
'flags'
,
flags
,
ifEmpty:
null
));
properties
.
add
(
new
FlagProperty
(
'isInvisible'
,
value:
isInvisible
,
ifTrue:
'invisible'
));
properties
.
add
(
new
FlagProperty
(
'isInvisible'
,
value:
isInvisible
,
ifTrue:
'invisible'
));
...
@@ -1876,6 +2031,7 @@ class SemanticsOwner extends ChangeNotifier {
...
@@ -1876,6 +2031,7 @@ class SemanticsOwner extends ChangeNotifier {
final
Set
<
SemanticsNode
>
_dirtyNodes
=
new
Set
<
SemanticsNode
>();
final
Set
<
SemanticsNode
>
_dirtyNodes
=
new
Set
<
SemanticsNode
>();
final
Map
<
int
,
SemanticsNode
>
_nodes
=
<
int
,
SemanticsNode
>{};
final
Map
<
int
,
SemanticsNode
>
_nodes
=
<
int
,
SemanticsNode
>{};
final
Set
<
SemanticsNode
>
_detachedNodes
=
new
Set
<
SemanticsNode
>();
final
Set
<
SemanticsNode
>
_detachedNodes
=
new
Set
<
SemanticsNode
>();
final
Map
<
int
,
CustomSemanticsAction
>
_actions
=
<
int
,
CustomSemanticsAction
>{};
/// The root node of the semantics tree, if any.
/// The root node of the semantics tree, if any.
///
///
...
@@ -1894,6 +2050,7 @@ class SemanticsOwner extends ChangeNotifier {
...
@@ -1894,6 +2050,7 @@ class SemanticsOwner extends ChangeNotifier {
void
sendSemanticsUpdate
()
{
void
sendSemanticsUpdate
()
{
if
(
_dirtyNodes
.
isEmpty
)
if
(
_dirtyNodes
.
isEmpty
)
return
;
return
;
final
Set
<
int
>
customSemanticsActionIds
=
new
Set
<
int
>();
final
List
<
SemanticsNode
>
visitedNodes
=
<
SemanticsNode
>[];
final
List
<
SemanticsNode
>
visitedNodes
=
<
SemanticsNode
>[];
while
(
_dirtyNodes
.
isNotEmpty
)
{
while
(
_dirtyNodes
.
isNotEmpty
)
{
final
List
<
SemanticsNode
>
localDirtyNodes
=
_dirtyNodes
.
where
((
SemanticsNode
node
)
=>
!
_detachedNodes
.
contains
(
node
)).
toList
();
final
List
<
SemanticsNode
>
localDirtyNodes
=
_dirtyNodes
.
where
((
SemanticsNode
node
)
=>
!
_detachedNodes
.
contains
(
node
)).
toList
();
...
@@ -1927,9 +2084,11 @@ class SemanticsOwner extends ChangeNotifier {
...
@@ -1927,9 +2084,11 @@ class SemanticsOwner extends ChangeNotifier {
// which happens e.g. when the node is no longer contributing
// which happens e.g. when the node is no longer contributing
// semantics).
// semantics).
if
(
node
.
_dirty
&&
node
.
attached
)
if
(
node
.
_dirty
&&
node
.
attached
)
node
.
_addToUpdate
(
builder
);
node
.
_addToUpdate
(
builder
,
customSemanticsActionIds
);
}
}
_dirtyNodes
.
clear
();
_dirtyNodes
.
clear
();
for
(
int
actionId
in
customSemanticsActionIds
)
builder
.
updateCustomAction
(
id:
actionId
,
label:
CustomSemanticsAction
.
getAction
(
actionId
).
label
);
ui
.
window
.
updateSemantics
(
builder
.
build
());
ui
.
window
.
updateSemantics
(
builder
.
build
());
notifyListeners
();
notifyListeners
();
}
}
...
@@ -2484,6 +2643,30 @@ class SemanticsConfiguration {
...
@@ -2484,6 +2643,30 @@ class SemanticsConfiguration {
_hasBeenAnnotated
=
true
;
_hasBeenAnnotated
=
true
;
}
}
/// The handlers for each supported [CustomSemanticsAction].
///
/// Whenever a custom accessibility action is added to a node, the action
/// [SemanticAction.customAction] is automatically added. A handler is
/// created which uses the passed argument to lookup the custom action
/// handler from this map and invoke it, if present.
Map
<
CustomSemanticsAction
,
VoidCallback
>
get
customSemanticsActions
=>
_customSemanticsActions
;
Map
<
CustomSemanticsAction
,
VoidCallback
>
_customSemanticsActions
=
<
CustomSemanticsAction
,
VoidCallback
>{};
set
customSemanticsActions
(
Map
<
CustomSemanticsAction
,
VoidCallback
>
value
)
{
_hasBeenAnnotated
=
true
;
_actionsAsBits
|=
SemanticsAction
.
customAction
.
index
;
_customSemanticsActions
=
value
;
_actions
[
SemanticsAction
.
customAction
]
=
_onCustomSemanticsAction
;
}
void
_onCustomSemanticsAction
(
dynamic
args
)
{
final
CustomSemanticsAction
action
=
CustomSemanticsAction
.
getAction
(
args
);
if
(
action
==
null
)
return
;
final
VoidCallback
callback
=
_customSemanticsActions
[
action
];
if
(
callback
!=
null
)
callback
();
}
/// A textual description of the owning [RenderObject].
/// A textual description of the owning [RenderObject].
///
///
/// On iOS this is used for the `accessibilityLabel` property defined in the
/// On iOS this is used for the `accessibilityLabel` property defined in the
...
@@ -2839,6 +3022,7 @@ class SemanticsConfiguration {
...
@@ -2839,6 +3022,7 @@ class SemanticsConfiguration {
return
;
return
;
_actions
.
addAll
(
other
.
_actions
);
_actions
.
addAll
(
other
.
_actions
);
_customSemanticsActions
.
addAll
(
other
.
_customSemanticsActions
);
_actionsAsBits
|=
other
.
_actionsAsBits
;
_actionsAsBits
|=
other
.
_actionsAsBits
;
_flags
|=
other
.
_flags
;
_flags
|=
other
.
_flags
;
_textSelection
??=
other
.
_textSelection
;
_textSelection
??=
other
.
_textSelection
;
...
@@ -2892,7 +3076,8 @@ class SemanticsConfiguration {
...
@@ -2892,7 +3076,8 @@ class SemanticsConfiguration {
..
_scrollExtentMax
=
_scrollExtentMax
..
_scrollExtentMax
=
_scrollExtentMax
..
_scrollExtentMin
=
_scrollExtentMin
..
_scrollExtentMin
=
_scrollExtentMin
..
_actionsAsBits
=
_actionsAsBits
..
_actionsAsBits
=
_actionsAsBits
..
_actions
.
addAll
(
_actions
);
..
_actions
.
addAll
(
_actions
)
..
_customSemanticsActions
.
addAll
(
_customSemanticsActions
);
}
}
}
}
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
d2d17abe
...
@@ -5089,6 +5089,7 @@ class Semantics extends SingleChildRenderObjectWidget {
...
@@ -5089,6 +5089,7 @@ class Semantics extends SingleChildRenderObjectWidget {
SetSelectionHandler
onSetSelection
,
SetSelectionHandler
onSetSelection
,
VoidCallback
onDidGainAccessibilityFocus
,
VoidCallback
onDidGainAccessibilityFocus
,
VoidCallback
onDidLoseAccessibilityFocus
,
VoidCallback
onDidLoseAccessibilityFocus
,
Map
<
CustomSemanticsAction
,
VoidCallback
>
customSemanticsActions
,
})
:
this
.
fromProperties
(
})
:
this
.
fromProperties
(
key:
key
,
key:
key
,
child:
child
,
child:
child
,
...
@@ -5129,6 +5130,7 @@ class Semantics extends SingleChildRenderObjectWidget {
...
@@ -5129,6 +5130,7 @@ class Semantics extends SingleChildRenderObjectWidget {
onMoveCursorBackwardByCharacter:
onMoveCursorBackwardByCharacter
,
onMoveCursorBackwardByCharacter:
onMoveCursorBackwardByCharacter
,
onDidGainAccessibilityFocus:
onDidGainAccessibilityFocus
,
onDidGainAccessibilityFocus:
onDidGainAccessibilityFocus
,
onDidLoseAccessibilityFocus:
onDidLoseAccessibilityFocus
,
onDidLoseAccessibilityFocus:
onDidLoseAccessibilityFocus
,
customSemanticsActions:
customSemanticsActions
,
onSetSelection:
onSetSelection
,),
onSetSelection:
onSetSelection
,),
);
);
...
@@ -5216,6 +5218,7 @@ class Semantics extends SingleChildRenderObjectWidget {
...
@@ -5216,6 +5218,7 @@ class Semantics extends SingleChildRenderObjectWidget {
onSetSelection:
properties
.
onSetSelection
,
onSetSelection:
properties
.
onSetSelection
,
onDidGainAccessibilityFocus:
properties
.
onDidGainAccessibilityFocus
,
onDidGainAccessibilityFocus:
properties
.
onDidGainAccessibilityFocus
,
onDidLoseAccessibilityFocus:
properties
.
onDidLoseAccessibilityFocus
,
onDidLoseAccessibilityFocus:
properties
.
onDidLoseAccessibilityFocus
,
customSemanticsActions:
properties
.
customSemanticsActions
,
);
);
}
}
...
@@ -5270,7 +5273,8 @@ class Semantics extends SingleChildRenderObjectWidget {
...
@@ -5270,7 +5273,8 @@ class Semantics extends SingleChildRenderObjectWidget {
..
onMoveCursorBackwardByCharacter
=
properties
.
onMoveCursorForwardByCharacter
..
onMoveCursorBackwardByCharacter
=
properties
.
onMoveCursorForwardByCharacter
..
onSetSelection
=
properties
.
onSetSelection
..
onSetSelection
=
properties
.
onSetSelection
..
onDidGainAccessibilityFocus
=
properties
.
onDidGainAccessibilityFocus
..
onDidGainAccessibilityFocus
=
properties
.
onDidGainAccessibilityFocus
..
onDidLoseAccessibilityFocus
=
properties
.
onDidLoseAccessibilityFocus
;
..
onDidLoseAccessibilityFocus
=
properties
.
onDidLoseAccessibilityFocus
..
customSemanticsActions
=
properties
.
customSemanticsActions
;
}
}
@override
@override
...
...
packages/flutter/test/semantics/custom_semantics_action_test.dart
0 → 100644
View file @
d2d17abe
import
'package:test/test.dart'
;
import
'package:flutter/semantics.dart'
;
void
main
(
)
{
group
(
CustomSemanticsAction
,
()
{
test
(
'is provided a canonical id based on the label'
,
()
{
final
CustomSemanticsAction
action1
=
new
CustomSemanticsAction
(
label:
_nonconst
(
'test'
));
final
CustomSemanticsAction
action2
=
new
CustomSemanticsAction
(
label:
_nonconst
(
'test'
));
final
CustomSemanticsAction
action3
=
new
CustomSemanticsAction
(
label:
_nonconst
(
'not test'
));
final
int
id1
=
CustomSemanticsAction
.
getIdentifier
(
action1
);
final
int
id2
=
CustomSemanticsAction
.
getIdentifier
(
action2
);
final
int
id3
=
CustomSemanticsAction
.
getIdentifier
(
action3
);
expect
(
id1
,
id2
);
expect
(
id2
,
isNot
(
id3
));
expect
(
CustomSemanticsAction
.
getAction
(
id1
),
action1
);
expect
(
CustomSemanticsAction
.
getAction
(
id2
),
action1
);
expect
(
CustomSemanticsAction
.
getAction
(
id3
),
action3
);
});
});
}
T
_nonconst
<
T
>(
T
value
)
=>
value
;
packages/flutter/test/semantics/semantics_test.dart
View file @
d2d17abe
...
@@ -337,6 +337,7 @@ void main() {
...
@@ -337,6 +337,7 @@ void main() {
' mergeAllDescendantsIntoThisNode: false
\n
'
' mergeAllDescendantsIntoThisNode: false
\n
'
' Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)
\n
'
' Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)
\n
'
' actions: []
\n
'
' actions: []
\n
'
' customActions: []
\n
'
' flags: []
\n
'
' flags: []
\n
'
' invisible
\n
'
' invisible
\n
'
' isHidden: false
\n
'
' isHidden: false
\n
'
...
@@ -404,8 +405,49 @@ void main() {
...
@@ -404,8 +405,49 @@ void main() {
);
);
});
});
test
(
'Custom actions debug properties'
,
()
{
final
SemanticsConfiguration
configuration
=
new
SemanticsConfiguration
();
const
CustomSemanticsAction
action1
=
const
CustomSemanticsAction
(
label:
'action1'
);
const
CustomSemanticsAction
action2
=
const
CustomSemanticsAction
(
label:
'action2'
);
const
CustomSemanticsAction
action3
=
const
CustomSemanticsAction
(
label:
'action3'
);
configuration
.
customSemanticsActions
=
<
CustomSemanticsAction
,
VoidCallback
>{
action1:
()
{},
action2:
()
{},
action3:
()
{},
};
final
SemanticsNode
actionNode
=
new
SemanticsNode
();
actionNode
.
updateWith
(
config:
configuration
);
expect
(
actionNode
.
toStringDeep
(
minLevel:
DiagnosticLevel
.
hidden
),
'SemanticsNode#1
\n
'
' STALE
\n
'
' owner: null
\n
'
' isMergedIntoParent: false
\n
'
' mergeAllDescendantsIntoThisNode: false
\n
'
' Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)
\n
'
' actions: customAction
\n
'
' customActions: action1, action2, action3
\n
'
' flags: []
\n
'
' invisible
\n
'
' isHidden: false
\n
'
' label: ""
\n
'
' value: ""
\n
'
' increasedValue: ""
\n
'
' decreasedValue: ""
\n
'
' hint: ""
\n
'
' textDirection: null
\n
'
' sortKey: null
\n
'
' scrollExtentMin: null
\n
'
' scrollPosition: null
\n
'
' scrollExtentMax: null
\n
'
);
});
test
(
'SemanticsConfiguration getter/setter'
,
()
{
test
(
'SemanticsConfiguration getter/setter'
,
()
{
final
SemanticsConfiguration
config
=
new
SemanticsConfiguration
();
final
SemanticsConfiguration
config
=
new
SemanticsConfiguration
();
const
CustomSemanticsAction
customAction
=
const
CustomSemanticsAction
(
label:
'test'
);
expect
(
config
.
isSemanticBoundary
,
isFalse
);
expect
(
config
.
isSemanticBoundary
,
isFalse
);
expect
(
config
.
isButton
,
isFalse
);
expect
(
config
.
isButton
,
isFalse
);
...
@@ -428,6 +470,7 @@ void main() {
...
@@ -428,6 +470,7 @@ void main() {
expect
(
config
.
onMoveCursorForwardByCharacter
,
isNull
);
expect
(
config
.
onMoveCursorForwardByCharacter
,
isNull
);
expect
(
config
.
onMoveCursorBackwardByCharacter
,
isNull
);
expect
(
config
.
onMoveCursorBackwardByCharacter
,
isNull
);
expect
(
config
.
onTap
,
isNull
);
expect
(
config
.
onTap
,
isNull
);
expect
(
config
.
customSemanticsActions
[
customAction
],
isNull
);
config
.
isSemanticBoundary
=
true
;
config
.
isSemanticBoundary
=
true
;
config
.
isButton
=
true
;
config
.
isButton
=
true
;
...
@@ -450,6 +493,7 @@ void main() {
...
@@ -450,6 +493,7 @@ void main() {
final
MoveCursorHandler
onMoveCursorForwardByCharacter
=
(
bool
_
)
{
};
final
MoveCursorHandler
onMoveCursorForwardByCharacter
=
(
bool
_
)
{
};
final
MoveCursorHandler
onMoveCursorBackwardByCharacter
=
(
bool
_
)
{
};
final
MoveCursorHandler
onMoveCursorBackwardByCharacter
=
(
bool
_
)
{
};
final
VoidCallback
onTap
=
()
{
};
final
VoidCallback
onTap
=
()
{
};
final
VoidCallback
onCustomAction
=
()
{};
config
.
onShowOnScreen
=
onShowOnScreen
;
config
.
onShowOnScreen
=
onShowOnScreen
;
config
.
onScrollDown
=
onScrollDown
;
config
.
onScrollDown
=
onScrollDown
;
...
@@ -462,6 +506,7 @@ void main() {
...
@@ -462,6 +506,7 @@ void main() {
config
.
onMoveCursorForwardByCharacter
=
onMoveCursorForwardByCharacter
;
config
.
onMoveCursorForwardByCharacter
=
onMoveCursorForwardByCharacter
;
config
.
onMoveCursorBackwardByCharacter
=
onMoveCursorBackwardByCharacter
;
config
.
onMoveCursorBackwardByCharacter
=
onMoveCursorBackwardByCharacter
;
config
.
onTap
=
onTap
;
config
.
onTap
=
onTap
;
config
.
customSemanticsActions
[
customAction
]
=
onCustomAction
;
expect
(
config
.
isSemanticBoundary
,
isTrue
);
expect
(
config
.
isSemanticBoundary
,
isTrue
);
expect
(
config
.
isButton
,
isTrue
);
expect
(
config
.
isButton
,
isTrue
);
...
@@ -484,6 +529,7 @@ void main() {
...
@@ -484,6 +529,7 @@ void main() {
expect
(
config
.
onMoveCursorForwardByCharacter
,
same
(
onMoveCursorForwardByCharacter
));
expect
(
config
.
onMoveCursorForwardByCharacter
,
same
(
onMoveCursorForwardByCharacter
));
expect
(
config
.
onMoveCursorBackwardByCharacter
,
same
(
onMoveCursorBackwardByCharacter
));
expect
(
config
.
onMoveCursorBackwardByCharacter
,
same
(
onMoveCursorBackwardByCharacter
));
expect
(
config
.
onTap
,
same
(
onTap
));
expect
(
config
.
onTap
,
same
(
onTap
));
expect
(
config
.
customSemanticsActions
[
customAction
],
same
(
onCustomAction
));
});
});
}
}
...
...
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