Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/material_ui/lib/src/expansion_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -691,13 +691,13 @@ class _ExpansionTileState extends State<ExpansionTile> {

if (defaultTargetPlatform == TargetPlatform.android) {
return Semantics(
// Live region used to announce state changes (e.g., "expanded" or "collapsed")
// without taking focus.
// blockNode prevents this node from being part of the focus traversal.
label: semanticsHint,
// Live region announces expand/collapse without a separate focusable
// wrapper node. Nesting a liveRegion+label Semantics over the hint node
// caused TalkBack to stop twice on the same tile (flutter/flutter#190601).
liveRegion: true,
accessibilityFocusBlockType: AccessibilityFocusBlockType.blockNode,
child: Semantics(hint: semanticsHint, onTapHint: onTapHint, child: child),
hint: semanticsHint,
onTapHint: onTapHint,
child: child,
);
}
return Semantics(hint: semanticsHint, onTapHint: onTapHint, child: child);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Fixes ExpansionTile TalkBack requiring two swipes on Android by combining the live-region announcement into a single semantics node.
version: skip
36 changes: 32 additions & 4 deletions packages/material_ui/test/expansion_tile_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2243,7 +2243,7 @@ void main() {
),
);

// Initially collapsed - live region label is "Collapsed".
// Initially collapsed - live region hint is "Collapsed".

SemanticsNode liveRegionSemantics = tester.getSemantics(
find.ancestor(
Expand All @@ -2253,7 +2253,7 @@ void main() {
),
),
);
expect(liveRegionSemantics.label, localizations.expandedHint);
expect(liveRegionSemantics.hint, localizations.expandedHint);

// Tap to expand.
await tester.tap(find.text('Test Tile'));
Expand All @@ -2268,7 +2268,7 @@ void main() {
),
),
);
expect(liveRegionSemantics.label, localizations.collapsedHint);
expect(liveRegionSemantics.hint, localizations.collapsedHint);

// Tap to collapse.
await tester.tap(find.text('Test Tile'));
Expand All @@ -2283,7 +2283,35 @@ void main() {
),
),
);
expect(liveRegionSemantics.label, localizations.expandedHint);
expect(liveRegionSemantics.hint, localizations.expandedHint);

handle.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.android}));

// Regression test for https://github.com/flutter/flutter/issues/190601.
testWidgets('Android header uses a single live-region Semantics node', (
WidgetTester tester,
) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: ExpansionTile(title: Text('Filter'), children: <Widget>[Text('Child')]),
),
),
);

final Finder liveRegionSemantics = find.ancestor(
of: find.byType(ListTile),
matching: find.byWidgetPredicate(
(Widget widget) => widget is Semantics && (widget.properties.liveRegion ?? false),
),
);
expect(liveRegionSemantics, findsOneWidget);

final Semantics widget = tester.widget<Semantics>(liveRegionSemantics);
expect(widget.properties.hint, isNotNull);
expect(widget.properties.accessibilityFocusBlockType, isNull);

handle.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.android}));
Expand Down