always adds alert label for alert dialog in Android (#65973)
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 7145be7..caadb0a 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -413,9 +413,11 @@
/// The semantic label of the dialog used by accessibility frameworks to
/// announce screen transitions when the dialog is opened and closed.
///
- /// If this label is not provided, a semantic label will be inferred from the
- /// [title] if it is not null. If there is no title, the label will be taken
- /// from [MaterialLocalizations.alertDialogLabel].
+ /// In iOS, if this label is not provided, a semantic label will be inferred
+ /// from the [title] if it is not null.
+ ///
+ /// In Android, if this label is not provided, the dialog will use the
+ /// [MaterialLocalizations.alertDialogLabel] as its label.
///
/// See also:
///
@@ -455,18 +457,15 @@
final DialogTheme dialogTheme = DialogTheme.of(context);
String label = semanticLabel;
- if (title == null) {
- switch (theme.platform) {
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- label = semanticLabel;
- break;
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- case TargetPlatform.linux:
- case TargetPlatform.windows:
- label = semanticLabel ?? MaterialLocalizations.of(context)?.alertDialogLabel;
- }
+ switch (theme.platform) {
+ case TargetPlatform.iOS:
+ case TargetPlatform.macOS:
+ break;
+ case TargetPlatform.android:
+ case TargetPlatform.fuchsia:
+ case TargetPlatform.linux:
+ case TargetPlatform.windows:
+ label ??= MaterialLocalizations.of(context)?.alertDialogLabel;
}
// The paddingScaleFactor is used to adjust the padding of Dialog's
@@ -491,7 +490,7 @@
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.headline6,
child: Semantics(
child: title,
- namesRoute: true,
+ namesRoute: label == null,
container: true,
),
),
@@ -569,6 +568,8 @@
if (label != null)
dialogChild = Semantics(
+ scopesRoute: true,
+ explicitChildNodes: true,
namesRoute: true,
label: label,
child: dialogChild,
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index 2e6bbf4..b677639 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -1275,10 +1275,12 @@
));
});
- testWidgets('Dialog widget contains route semantics from title', (WidgetTester tester) async {
+ testWidgets('AlertDialog widget contains route semantics from title for iOS', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
+
await tester.pumpWidget(
MaterialApp(
+ theme: ThemeData(platform: TargetPlatform.iOS),
home: Material(
child: Builder(
builder: (BuildContext context) {
@@ -1321,6 +1323,62 @@
semantics.dispose();
});
+ testWidgets('AlertDialog widget always contains alert route semantics for android', (WidgetTester tester) async {
+ final SemanticsTester semantics = SemanticsTester(tester);
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData(platform: TargetPlatform.android),
+ home: Material(
+ child: Builder(
+ builder: (BuildContext context) {
+ return Center(
+ child: ElevatedButton(
+ child: const Text('X'),
+ onPressed: () {
+ showDialog<void>(
+ context: context,
+ builder: (BuildContext context) {
+ return const AlertDialog(
+ title: Text('Title'),
+ content: Text('Y'),
+ actions: <Widget>[],
+ );
+ },
+ );
+ },
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+
+ expect(semantics, isNot(includesNodeWith(
+ label: 'Title',
+ flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+ )));
+ expect(semantics, isNot(includesNodeWith(
+ label: 'Alert',
+ flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute],
+ )));
+
+ await tester.tap(find.text('X'));
+ await tester.pumpAndSettle();
+ // It does not use 'Title' as route semantics
+ expect(semantics, isNot(includesNodeWith(
+ label: 'Title',
+ flags: <SemanticsFlag>[SemanticsFlag.namesRoute],
+ )));
+ expect(semantics, includesNodeWith(
+ label: 'Alert',
+ flags: <SemanticsFlag>[SemanticsFlag.namesRoute, SemanticsFlag.scopesRoute],
+ ));
+
+ semantics.dispose();
+ });
+
testWidgets('Dismissible.confirmDismiss defers to an AlertDialog', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final List<int> dismissedItems = <int>[];