mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Incorporating Link Semantics (#41327)
This commit is contained in:
parent
14c1c211d5
commit
1ec44a0c36
@ -1 +1 @@
|
||||
c635d70c726699cc79c0f3d900e0d0a3ea65efe2
|
||||
a29385d9fe81761f2cedba7d9d4aa4709db04d83
|
||||
|
@ -831,6 +831,9 @@ class RenderCustomPaint extends RenderProxyBox {
|
||||
if (properties.button != null) {
|
||||
config.isButton = properties.button;
|
||||
}
|
||||
if (properties.link != null) {
|
||||
config.isLink = properties.link;
|
||||
}
|
||||
if (properties.textField != null) {
|
||||
config.isTextField = properties.textField;
|
||||
}
|
||||
|
@ -887,6 +887,7 @@ class RenderParagraph extends RenderBox
|
||||
if (info.recognizer is TapGestureRecognizer) {
|
||||
final TapGestureRecognizer recognizer = info.recognizer;
|
||||
configuration.onTap = recognizer.onTap;
|
||||
configuration.isLink = true;
|
||||
} else if (info.recognizer is LongPressGestureRecognizer) {
|
||||
final LongPressGestureRecognizer recognizer = info.recognizer;
|
||||
configuration.onLongPress = recognizer.onLongPress;
|
||||
|
@ -3485,6 +3485,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
bool toggled,
|
||||
bool selected,
|
||||
bool button,
|
||||
bool link,
|
||||
bool header,
|
||||
bool textField,
|
||||
bool readOnly,
|
||||
@ -3536,6 +3537,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
_toggled = toggled,
|
||||
_selected = selected,
|
||||
_button = button,
|
||||
_link = link,
|
||||
_header = header,
|
||||
_textField = textField,
|
||||
_readOnly = readOnly,
|
||||
@ -3678,6 +3680,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.isLink] semantic to the given value.
|
||||
bool get link => _link;
|
||||
bool _link;
|
||||
set link(bool value) {
|
||||
if (link == value)
|
||||
return;
|
||||
_link = value;
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value.
|
||||
bool get header => _header;
|
||||
bool _header;
|
||||
@ -4360,6 +4372,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
config.isSelected = selected;
|
||||
if (button != null)
|
||||
config.isButton = button;
|
||||
if (link != null)
|
||||
config.isLink = link;
|
||||
if (header != null)
|
||||
config.isHeader = header;
|
||||
if (textField != null)
|
||||
|
@ -591,6 +591,7 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
this.selected,
|
||||
this.toggled,
|
||||
this.button,
|
||||
this.link,
|
||||
this.header,
|
||||
this.textField,
|
||||
this.readOnly,
|
||||
@ -670,6 +671,13 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
/// is focused.
|
||||
final bool button;
|
||||
|
||||
/// If non-null, indicates that this subtree represents a link.
|
||||
///
|
||||
/// iOS's VoiceOver provides users with a unique hint when a link is focused.
|
||||
/// Android's Talkback will announce a link hint the same way it does a
|
||||
/// button.
|
||||
final bool link;
|
||||
|
||||
/// If non-null, indicates that this subtree represents a header.
|
||||
///
|
||||
/// A header divides into sections. For example, an address book application
|
||||
@ -3615,6 +3623,12 @@ class SemanticsConfiguration {
|
||||
_setFlag(SemanticsFlag.isButton, value);
|
||||
}
|
||||
|
||||
/// Whether the owning [RenderObject] is a link (true) or not (false).
|
||||
bool get isLink => _hasFlag(SemanticsFlag.isLink);
|
||||
set isLink(bool value) {
|
||||
_setFlag(SemanticsFlag.isLink, value);
|
||||
}
|
||||
|
||||
/// Whether the owning [RenderObject] is a header (true) or not (false).
|
||||
bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
|
||||
set isHeader(bool value) {
|
||||
|
@ -6184,6 +6184,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
bool selected,
|
||||
bool toggled,
|
||||
bool button,
|
||||
bool link,
|
||||
bool header,
|
||||
bool textField,
|
||||
bool readOnly,
|
||||
@ -6237,6 +6238,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
toggled: toggled,
|
||||
selected: selected,
|
||||
button: button,
|
||||
link: link,
|
||||
header: header,
|
||||
textField: textField,
|
||||
readOnly: readOnly,
|
||||
@ -6349,6 +6351,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
toggled: properties.toggled,
|
||||
selected: properties.selected,
|
||||
button: properties.button,
|
||||
link: properties.link,
|
||||
header: properties.header,
|
||||
textField: properties.textField,
|
||||
readOnly: properties.readOnly,
|
||||
@ -6418,6 +6421,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
..toggled = properties.toggled
|
||||
..selected = properties.selected
|
||||
..button = properties.button
|
||||
..link = properties.link
|
||||
..header = properties.header
|
||||
..textField = properties.textField
|
||||
..readOnly = properties.readOnly
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../flutter_test_alternative.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:ui' as ui show TextBox;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -573,6 +573,7 @@ void main() {
|
||||
|
||||
expect(config.isSemanticBoundary, isFalse);
|
||||
expect(config.isButton, isFalse);
|
||||
expect(config.isLink, isFalse);
|
||||
expect(config.isMergingSemanticsOfDescendants, isFalse);
|
||||
expect(config.isEnabled, null);
|
||||
expect(config.isChecked, null);
|
||||
@ -596,6 +597,7 @@ void main() {
|
||||
|
||||
config.isSemanticBoundary = true;
|
||||
config.isButton = true;
|
||||
config.isLink = true;
|
||||
config.isMergingSemanticsOfDescendants = true;
|
||||
config.isEnabled = true;
|
||||
config.isChecked = true;
|
||||
@ -632,6 +634,7 @@ void main() {
|
||||
|
||||
expect(config.isSemanticBoundary, isTrue);
|
||||
expect(config.isButton, isTrue);
|
||||
expect(config.isLink, isTrue);
|
||||
expect(config.isMergingSemanticsOfDescendants, isTrue);
|
||||
expect(config.isEnabled, isTrue);
|
||||
expect(config.isChecked, isTrue);
|
||||
|
@ -413,6 +413,7 @@ void _defineTests() {
|
||||
selected: true,
|
||||
hidden: true,
|
||||
button: true,
|
||||
link: true,
|
||||
textField: true,
|
||||
readOnly: true,
|
||||
focused: true,
|
||||
@ -461,6 +462,7 @@ void _defineTests() {
|
||||
selected: true,
|
||||
hidden: true,
|
||||
button: true,
|
||||
link: true,
|
||||
textField: true,
|
||||
readOnly: true,
|
||||
focused: true,
|
||||
|
@ -473,6 +473,7 @@ void main() {
|
||||
checked: true,
|
||||
selected: true,
|
||||
button: true,
|
||||
link: true,
|
||||
textField: true,
|
||||
readOnly: true,
|
||||
focused: true,
|
||||
|
@ -232,6 +232,7 @@ void main() {
|
||||
TestSemantics(
|
||||
label: 'Clickable',
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
@ -281,9 +282,8 @@ void main() {
|
||||
TestSemantics(
|
||||
label: 'world',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
),
|
||||
TestSemantics(
|
||||
label: ' this is a cat-astrophe',
|
||||
@ -338,6 +338,7 @@ void main() {
|
||||
label: 'world',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -389,9 +390,8 @@ void main() {
|
||||
TestSemantics(
|
||||
label: 'world',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
),
|
||||
TestSemantics(
|
||||
label: ' this is a regrettable event',
|
||||
@ -454,9 +454,8 @@ void main() {
|
||||
rect: const Rect.fromLTRB(150.0, -4.0, 200.0, 18.0),
|
||||
label: 'RIS',
|
||||
textDirection: TextDirection.rtl, // in the last string we switched to RTL using RLE.
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
),
|
||||
TestSemantics(
|
||||
rect: const Rect.fromLTRB(192.0, -4.0, 424.0, 18.0),
|
||||
@ -491,6 +490,46 @@ void main() {
|
||||
semantics.dispose();
|
||||
}, skip: true); // TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved.
|
||||
|
||||
testWidgets('TapGesture recognizers contribute link semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
|
||||
await tester.pumpWidget(
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'click me',
|
||||
recognizer: TapGestureRecognizer()..onTap = () { },
|
||||
),
|
||||
],
|
||||
style: textStyle,
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
);
|
||||
final TestSemantics expectedSemantics = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics.rootChild(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
label: 'click me',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink]
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(semantics, hasSemantics(
|
||||
expectedSemantics,
|
||||
ignoreTransform: true,
|
||||
ignoreId: true,
|
||||
ignoreRect: true,
|
||||
));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('inline widgets generate semantic nodes', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
|
||||
@ -534,9 +573,8 @@ void main() {
|
||||
TestSemantics(
|
||||
label: 'pebble',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
),
|
||||
TestSemantics(
|
||||
label: ' in the ',
|
||||
@ -612,9 +650,8 @@ void main() {
|
||||
TestSemantics(
|
||||
label: 'pebble',
|
||||
textDirection: TextDirection.ltr,
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isLink],
|
||||
rect: const Rect.fromLTRB(52.0, 48.0, 228.0, 84.0),
|
||||
),
|
||||
TestSemantics(
|
||||
|
@ -440,6 +440,7 @@ Matcher matchesSemantics({
|
||||
bool isChecked = false,
|
||||
bool isSelected = false,
|
||||
bool isButton = false,
|
||||
bool isLink = false,
|
||||
bool isFocused = false,
|
||||
bool isTextField = false,
|
||||
bool isReadOnly = false,
|
||||
@ -489,6 +490,7 @@ Matcher matchesSemantics({
|
||||
if (isChecked) SemanticsFlag.isChecked,
|
||||
if (isSelected) SemanticsFlag.isSelected,
|
||||
if (isButton) SemanticsFlag.isButton,
|
||||
if (isLink) SemanticsFlag.isLink,
|
||||
if (isTextField) SemanticsFlag.isTextField,
|
||||
if (isReadOnly) SemanticsFlag.isReadOnly,
|
||||
if (isFocused) SemanticsFlag.isFocused,
|
||||
|
@ -412,6 +412,7 @@ void main() {
|
||||
namesRoute: true,
|
||||
header: true,
|
||||
button: true,
|
||||
link: true,
|
||||
onTap: () { },
|
||||
onLongPress: () { },
|
||||
label: 'foo',
|
||||
@ -439,6 +440,7 @@ void main() {
|
||||
hasTapAction: true,
|
||||
hasLongPressAction: true,
|
||||
isButton: true,
|
||||
isLink: true,
|
||||
isHeader: true,
|
||||
namesRoute: true,
|
||||
onTapHint: 'scan',
|
||||
@ -460,6 +462,7 @@ void main() {
|
||||
hasTapAction: true,
|
||||
hasLongPressAction: true,
|
||||
isButton: true,
|
||||
isLink: true,
|
||||
isHeader: true,
|
||||
namesRoute: true,
|
||||
onTapHint: 'scan',
|
||||
@ -481,6 +484,7 @@ void main() {
|
||||
hasTapAction: true,
|
||||
hasLongPressAction: true,
|
||||
isButton: true,
|
||||
isLink: true,
|
||||
isHeader: true,
|
||||
namesRoute: true,
|
||||
onTapHint: 'scans',
|
||||
@ -544,6 +548,7 @@ void main() {
|
||||
isChecked: true,
|
||||
isSelected: true,
|
||||
isButton: true,
|
||||
isLink: true,
|
||||
isTextField: true,
|
||||
isReadOnly: true,
|
||||
hasEnabledState: true,
|
||||
|
Loading…
Reference in New Issue
Block a user