Unverified Commit 9184dfba authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Support value announcement for increase/decrease (#12741)

* Support value announcment for increase/decrease

* ++

* review comments about docs
parent e1174eb0
......@@ -3177,6 +3177,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool button,
String label,
String value,
String increasedValue,
String decreasedValue,
String hint,
TextDirection textDirection,
VoidCallback onTap,
......@@ -3195,6 +3197,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_button = button,
_label = label,
_value = value,
_increasedValue = increasedValue,
_decreasedValue = decreasedValue,
_hint = hint,
_textDirection = textDirection,
_onTap = onTap,
......@@ -3283,7 +3287,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get label => _label;
String _label;
set label(String value) {
......@@ -3296,7 +3300,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// If non-null, sets the [SemanticsNode.value] semantic to the given value.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get value => _value;
String _value;
set value(String value) {
......@@ -3307,9 +3311,37 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
}
/// If non-null, sets the [SemanticsNode.increasedValue] semantic to the given
/// value.
///
/// The reading direction is given by [textDirection].
String get increasedValue => _increasedValue;
String _increasedValue;
set increasedValue(String value) {
if (_increasedValue == value)
return;
final bool hadValue = _increasedValue != null;
_increasedValue = value;
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
}
/// If non-null, sets the [SemanticsNode.decreasedValue] semantic to the given
/// value.
///
/// The reading direction is given by [textDirection].
String get decreasedValue => _decreasedValue;
String _decreasedValue;
set decreasedValue(String value) {
if (_decreasedValue == value)
return;
final bool hadValue = _decreasedValue != null;
_decreasedValue = value;
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
}
/// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get hint => _hint;
String _hint;
set hint(String value) {
......@@ -3322,7 +3354,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
///
/// This must not be null if [label], [hint], or [value] is not null.
/// This must not be null if [label], [hint], [value], [increasedValue], or
/// [decreasedValue] are not null.
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
......@@ -3499,6 +3532,15 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
assert(
onIncrease == null || (value == null) == (increasedValue == null),
'If "onIncrease" is set either both "value" and "increasedValue" or neither have to be set.',
);
assert(
onDecrease == null || (value == null) == (decreasedValue == null),
'If "onDecrease" is set either both "value" and "decreasedValue" or neither have to be set.',
);
config.isSemanticBoundary = container;
config.explicitChildNodes = explicitChildNodes;
......@@ -3512,6 +3554,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.label = label;
if (value != null)
config.value = value;
if (increasedValue != null)
config.increasedValue = increasedValue;
if (decreasedValue != null)
config.decreasedValue = decreasedValue;
if (hint != null)
config.hint = hint;
if (textDirection != null)
......
......@@ -76,7 +76,9 @@ class SemanticsData extends Diagnosticable {
@required this.flags,
@required this.actions,
@required this.label,
@required this.increasedValue,
@required this.value,
@required this.decreasedValue,
@required this.hint,
@required this.textDirection,
@required this.rect,
......@@ -85,7 +87,15 @@ class SemanticsData extends Diagnosticable {
}) : assert(flags != null),
assert(actions != null),
assert(label != null),
assert(value != null),
assert(decreasedValue != null),
assert(increasedValue != null),
assert(hint != null),
assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
assert(value == '' || textDirection != null, 'A SemanticsData object with value "$value" had a null textDirection.'),
assert(hint == '' || textDirection != null, 'A SemanticsData object with hint "$hint" had a null textDirection.'),
assert(decreasedValue == '' || textDirection != null, 'A SemanticsData object with decreasedValue "$decreasedValue" had a null textDirection.'),
assert(increasedValue == '' || textDirection != null, 'A SemanticsData object with increasedValue "$increasedValue" had a null textDirection.'),
assert(rect != null);
/// A bit field of [SemanticsFlags] that apply to this node.
......@@ -96,20 +106,33 @@ class SemanticsData extends Diagnosticable {
/// A textual description of this node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
final String label;
/// A textual description for the current value of the node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
final String value;
/// The value that [value] will become after performing a
/// [SemanticsAction.increase] action.
///
/// The reading direction is given by [textDirection].
final String increasedValue;
/// The value that [value] will become after performing a
/// [SemanticsAction.decrease] action.
///
/// The reading direction is given by [textDirection].
final String decreasedValue;
/// A brief description of the result of performing an action on this node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
final String hint;
/// The reading direction for the text in [label], [value], and [hint].
/// The reading direction for the text in [label], [value], [hint],
/// [increasedValue], and [decreasedValue].
final TextDirection textDirection;
/// The bounding box for this node in its coordinate system.
......@@ -153,6 +176,10 @@ class SemanticsData extends Diagnosticable {
}
properties.add(new IterableProperty<String>('flags', flagSummary, ifEmpty: null));
properties.add(new StringProperty('label', label, defaultValue: ''));
properties.add(new StringProperty('value', value, defaultValue: ''));
properties.add(new StringProperty('increasedValue', increasedValue, defaultValue: ''));
properties.add(new StringProperty('decreasedValue', decreasedValue, defaultValue: ''));
properties.add(new StringProperty('hint', hint, defaultValue: ''));
properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
}
......@@ -495,7 +522,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
return _label != config.label ||
_hint != config.hint ||
_decreasedValue != config.decreasedValue ||
_value != config.value ||
_increasedValue != config.increasedValue ||
_flags != config._flags ||
_textDirection != config.textDirection ||
_actionsAsBits != config._actionsAsBits ||
......@@ -523,23 +552,44 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// A textual description of this node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get label => _label;
String _label = _kEmptyConfig.label;
/// A textual description for the current value of the node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get value => _value;
String _value = _kEmptyConfig.value;
/// The value that [value] will have after a [SemanticsAction.decrease] action
/// has been performed.
///
/// This property is only valid if the [SemanticsAction.decrease] action is
/// available on this node.
///
/// The reading direction is given by [textDirection].
String get decreasedValue => _decreasedValue;
String _decreasedValue = _kEmptyConfig.decreasedValue;
/// The value that [value] will have after a [SemanticsAction.increase] action
/// has been performed.
///
/// This property is only valid if the [SemanticsAction.increase] action is
/// available on this node.
///
/// The reading direction is given by [textDirection].
String get increasedValue => _increasedValue;
String _increasedValue = _kEmptyConfig.increasedValue;
/// A brief description of the result of performing an action on this node.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get hint => _hint;
String _hint = _kEmptyConfig.hint;
/// The reading direction for [label], [value], and [hint].
/// The reading direction for [label], [value], [hint], [increasedValue], and
/// [decreasedValue].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection = _kEmptyConfig.textDirection;
......@@ -556,7 +606,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_markDirty();
_label = config.label;
_decreasedValue = config.decreasedValue;
_value = config.value;
_increasedValue = config.increasedValue;
_hint = config.hint;
_flags = config._flags;
_textDirection = config.textDirection;
......@@ -564,6 +616,15 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_actionsAsBits = config._actionsAsBits;
_mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
assert(
!_canPerformAction(SemanticsAction.increase) || (_value == '') == (_increasedValue == ''),
'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "increasedValue" or neither',
);
assert(
!_canPerformAction(SemanticsAction.decrease) || (_value == '') == (_decreasedValue == ''),
'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "decreasedValue" or neither',
);
}
......@@ -578,6 +639,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
String label = _label;
String hint = _hint;
String value = _value;
String increasedValue = _increasedValue;
String decreasedValue = _decreasedValue;
TextDirection textDirection = _textDirection;
Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
......@@ -589,6 +652,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
textDirection ??= node._textDirection;
if (value == '' || value == null)
value = node._value;
if (increasedValue == '' || increasedValue == null)
increasedValue = node._increasedValue;
if (decreasedValue == '' || decreasedValue == null)
decreasedValue = node._decreasedValue;
if (node.tags != null) {
mergedTags ??= new Set<SemanticsTag>();
mergedTags.addAll(node.tags);
......@@ -614,6 +681,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
actions: actions,
label: label,
value: value,
increasedValue: increasedValue,
decreasedValue: decreasedValue,
hint: hint,
textDirection: textDirection,
rect: rect,
......@@ -648,6 +717,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
rect: data.rect,
label: data.label,
value: data.value,
decreasedValue: data.decreasedValue,
increasedValue: data.increasedValue,
hint: data.hint,
textDirection: data.textDirection,
transform: data.transform?.storage ?? _kIdentityTransform,
......@@ -707,6 +778,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties.add(new FlagProperty('isButton', value: _hasFlag(SemanticsFlags.isButton), ifTrue: 'button'));
properties.add(new StringProperty('label', _label, defaultValue: ''));
properties.add(new StringProperty('value', _value, defaultValue: ''));
properties.add(new StringProperty('increasedValue', _increasedValue, defaultValue: ''));
properties.add(new StringProperty('decreasedValue', _decreasedValue, defaultValue: ''));
properties.add(new StringProperty('hint', _hint, defaultValue: ''));
properties.add(new EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
}
......@@ -1018,6 +1091,7 @@ class SemanticsConfiguration {
///
/// Whenever the user performs `action` the provided `handler` is called.
void addAction(SemanticsAction action, VoidCallback handler) {
assert(handler != null);
_actions[action] = handler;
_actionsAsBits |= action.index;
_hasBeenAnnotated = true;
......@@ -1050,10 +1124,11 @@ class SemanticsConfiguration {
/// [value] and [hint] in the following order: [value], [label], [hint].
/// The concatenated value is then used as the `Text` description.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get label => _label;
String _label = '';
set label(String label) {
assert(label != null);
_label = label;
_hasBeenAnnotated = true;
}
......@@ -1065,14 +1140,51 @@ class SemanticsConfiguration {
/// [label] and [hint] in the following order: [value], [label], [hint].
/// The concatenated value is then used as the `Text` description.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
///
/// See also:
/// * [decreasedValue], describes what [value] will be after performing
/// [SemanticsAction.decrease]
/// * [increasedValue], describes what [value] will be after performing
/// [SemanticsAction.increase]
String get value => _value;
String _value = '';
set value(String value) {
assert(value != null);
_value = value;
_hasBeenAnnotated = true;
}
/// The value that [value] will have after performing a
/// [SemanticsAction.decrease] action.
///
/// This must be set if a handler for [SemanticsAction.decrease] is provided
/// and [value] is set.
///
/// The reading direction is given by [textDirection].
String get decreasedValue => _decreasedValue;
String _decreasedValue = '';
set decreasedValue(String decreasedValue) {
assert(decreasedValue != null);
_decreasedValue = decreasedValue;
_hasBeenAnnotated = true;
}
/// The value that [value] will have after performing a
/// [SemanticsAction.increase] action.
///
/// This must be set if a handler for [SemanticsAction.increase] is provided
/// and [value] is set.
///
/// The reading direction is given by [textDirection].
String get increasedValue => _increasedValue;
String _increasedValue = '';
set increasedValue(String increasedValue) {
assert(increasedValue != null);
_increasedValue = increasedValue;
_hasBeenAnnotated = true;
}
/// A brief description of the result of performing an action on this node.
///
/// On iOS this is used for the `accessibilityHint` property defined in the
......@@ -1080,15 +1192,17 @@ class SemanticsConfiguration {
/// [label] and [value] in the following order: [value], [label], [hint].
/// The concatenated value is then used as the `Text` description.
///
/// The text's reading direction is given by [textDirection].
/// The reading direction is given by [textDirection].
String get hint => _hint;
String _hint = '';
set hint(String hint) {
assert(hint != null);
_hint = hint;
_hasBeenAnnotated = true;
}
/// The reading direction for the text in [label], [value], and [hint].
/// The reading direction for the text in [label], [value], [hint],
/// [increasedValue], and [decreasedValue].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection textDirection) {
......@@ -1182,8 +1296,12 @@ class SemanticsConfiguration {
otherString: other._label,
otherTextDirection: other.textDirection,
);
if (_decreasedValue == '' || _decreasedValue == null)
_decreasedValue = other._decreasedValue;
if (_value == '' || _value == null)
_value = other._value;
if (_increasedValue == '' || _increasedValue == null)
_increasedValue = other._increasedValue;
_hint = _concatStrings(
thisString: _hint,
thisTextDirection: textDirection,
......@@ -1202,7 +1320,9 @@ class SemanticsConfiguration {
.._hasBeenAnnotated = _hasBeenAnnotated
.._textDirection = _textDirection
.._label = _label
.._increasedValue = _increasedValue
.._value = _value
.._decreasedValue = _decreasedValue
.._hint = _hint
.._flags = _flags
.._actionsAsBits = _actionsAsBits
......
......@@ -4454,6 +4454,8 @@ class Semantics extends SingleChildRenderObjectWidget {
this.button,
this.label,
this.value,
this.increasedValue,
this.decreasedValue,
this.hint,
this.textDirection,
this.onTap,
......@@ -4528,6 +4530,30 @@ class Semantics extends SingleChildRenderObjectWidget {
/// in TalkBack and VoiceOver.
final String value;
/// The value that [value] will become after a [SemanticsAction.increase]
/// action has been performed on this widget.
///
/// If a value is provided, [onIncrease] must also be set and there must
/// either be an ambient [Directionality] or an explicit [textDirection]
/// must be provided.
///
/// See also:
/// * [SemanticsConfiguration.increasedValue] for a description of how this
/// is exposed in TalkBack and VoiceOver.
final String increasedValue;
/// The value that [value] will become after a [SemanticsAction.decrease]
/// action has been performed on this widget.
///
/// If a value is provided, [onDecrease] must also be set and there must
/// either be an ambient [Directionality] or an explicit [textDirection]
/// must be provided.
///
/// See also:
/// * [SemanticsConfiguration.decreasedValue] for a description of how this
/// is exposed in TalkBack and VoiceOver.
final String decreasedValue;
/// Provides a brief textual description of the result of an action performed
/// on the widget.
///
......@@ -4539,7 +4565,8 @@ class Semantics extends SingleChildRenderObjectWidget {
/// in TalkBack and VoiceOver.
final String hint;
/// The reading direction of the [label], [value], and [hint].
/// The reading direction of the [label], [value], [hint], [increasedValue],
/// and [decreasedValue].
///
/// Defaults to the ambient [Directionality].
final TextDirection textDirection;
......@@ -4625,6 +4652,9 @@ class Semantics extends SingleChildRenderObjectWidget {
/// This is a request to increase the value represented by the widget. For
/// example, this action might be recognized by a slider control.
///
/// If a [value] is set, [increasedValue] must also be provided and
/// [onIncrease] must ensure that [value] will be set to [increasedValue].
///
/// VoiceOver users on iOS can trigger this action by swiping up with one
/// finger. TalkBack users on Android can trigger this action by pressing the
/// volume up button.
......@@ -4635,6 +4665,9 @@ class Semantics extends SingleChildRenderObjectWidget {
/// This is a request to decrease the value represented by the widget. For
/// example, this action might be recognized by a slider control.
///
/// If a [value] is set, [decreasedValue] must also be provided and
/// [onDecrease] must ensure that [value] will be set to [decreasedValue].
///
/// VoiceOver users on iOS can trigger this action by swiping down with one
/// finger. TalkBack users on Android can trigger this action by pressing the
/// volume down button.
......@@ -4650,6 +4683,8 @@ class Semantics extends SingleChildRenderObjectWidget {
button: button,
label: label,
value: value,
increasedValue: increasedValue,
decreasedValue: decreasedValue,
hint: hint,
textDirection: _getTextDirection(context),
onTap: onTap,
......@@ -4672,6 +4707,8 @@ class Semantics extends SingleChildRenderObjectWidget {
..selected = selected
..label = label
..value = value
..increasedValue = increasedValue
..decreasedValue = decreasedValue
..hint = hint
..textDirection = _getTextDirection(context)
..onTap = onTap
......
......@@ -198,7 +198,7 @@ void main() {
expect(
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isButton: false, label: "", value: "", hint: "", textDirection: null)\n',
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isButton: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n',
);
final SemanticsConfiguration config = new SemanticsConfiguration()
......
......@@ -513,4 +513,36 @@ void main() {
semantics.dispose();
});
testWidgets('Increased/decreased values are annotated', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
container: true,
value: '10s',
increasedValue: '11s',
decreasedValue: '9s',
onIncrease: () => () {},
onDecrease: () => () {},
),
),
);
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
actions: SemanticsAction.increase.index | SemanticsAction.decrease.index,
textDirection: TextDirection.ltr,
value: '10s',
increasedValue: '11s',
decreasedValue: '9s',
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
}
......@@ -36,6 +36,8 @@ class TestSemantics {
this.actions: 0,
this.label: '',
this.value: '',
this.increasedValue: '',
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.rect,
......@@ -45,6 +47,8 @@ class TestSemantics {
}) : assert(flags != null),
assert(label != null),
assert(value != null),
assert(increasedValue != null),
assert(decreasedValue != null),
assert(hint != null),
assert(children != null),
tags = tags?.toSet() ?? new Set<SemanticsTag>();
......@@ -56,6 +60,8 @@ class TestSemantics {
this.actions: 0,
this.label: '',
this.value: '',
this.increasedValue: '',
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.transform,
......@@ -64,6 +70,8 @@ class TestSemantics {
}) : id = 0,
assert(flags != null),
assert(label != null),
assert(increasedValue != null),
assert(decreasedValue != null),
assert(value != null),
assert(hint != null),
rect = TestSemantics.rootRect,
......@@ -86,6 +94,8 @@ class TestSemantics {
this.label: '',
this.hint: '',
this.value: '',
this.increasedValue: '',
this.decreasedValue: '',
this.textDirection,
this.rect,
Matrix4 transform,
......@@ -94,6 +104,8 @@ class TestSemantics {
}) : assert(flags != null),
assert(label != null),
assert(value != null),
assert(increasedValue != null),
assert(decreasedValue != null),
assert(hint != null),
transform = _applyRootChildScale(transform),
assert(children != null),
......@@ -117,6 +129,14 @@ class TestSemantics {
/// A textual description for the value of this node.
final String value;
/// What [value] will become after [SemanticsAction.increase] has been
/// performed.
final String increasedValue;
/// What [value] will become after [SemanticsAction.decrease] has been
/// performed.
final String decreasedValue;
/// A brief textual description of the result of the action that can be
/// performed on this node.
final String hint;
......@@ -190,11 +210,15 @@ class TestSemantics {
return fail('expected node id $id to have label "$label" but found label "${nodeData.label}".');
if (value != nodeData.value)
return fail('expected node id $id to have value "$value" but found value "${nodeData.value}".');
if (increasedValue != nodeData.increasedValue)
return fail('expected node id $id to have increasedValue "$increasedValue" but found value "${nodeData.increasedValue}".');
if (decreasedValue != nodeData.decreasedValue)
return fail('expected node id $id to have decreasedValue "$decreasedValue" but found value "${nodeData.decreasedValue}".');
if (hint != nodeData.hint)
return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".');
if (textDirection != null && textDirection != nodeData.textDirection)
return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".');
if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '') && nodeData.textDirection == null)
if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null)
return fail('expected node id $id, which has a label, value, or hint, to have a textDirection, but it did not.');
if (!ignoreRect && rect != nodeData.rect)
return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment