Skip to content

Commit 68ce1ae

Browse files
authored
Reland "Use semantics label for backbutton and closebutton for Android" (#115776)
* Reland "Use semantics label for backbutton and closebutton for Android" This reverts commit 20a78ed. * change to default target platform
1 parent 7673108 commit 68ce1ae

File tree

4 files changed

+134
-12
lines changed

4 files changed

+134
-12
lines changed

packages/flutter/lib/src/material/back_button.dart

+42-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
56
import 'package:flutter/widgets.dart';
67

78
import 'debug.dart';
@@ -27,22 +28,39 @@ class BackButtonIcon extends StatelessWidget {
2728
/// the current platform (as obtained from the [Theme]).
2829
const BackButtonIcon({ super.key });
2930

30-
/// Returns the appropriate "back" icon for the given `platform`.
31-
static IconData _getIconData(TargetPlatform platform) {
32-
switch (platform) {
31+
@override
32+
Widget build(BuildContext context) {
33+
final String? semanticsLabel;
34+
final IconData data;
35+
switch (Theme.of(context).platform) {
3336
case TargetPlatform.android:
3437
case TargetPlatform.fuchsia:
3538
case TargetPlatform.linux:
3639
case TargetPlatform.windows:
37-
return Icons.arrow_back;
40+
data = Icons.arrow_back;
41+
break;
3842
case TargetPlatform.iOS:
3943
case TargetPlatform.macOS:
40-
return Icons.arrow_back_ios;
44+
data = Icons.arrow_back_ios;
45+
break;
46+
}
47+
// This can't use the platform from Theme because it is the Android OS that
48+
// expects the duplicated tooltip and label.
49+
switch (defaultTargetPlatform) {
50+
case TargetPlatform.android:
51+
semanticsLabel = MaterialLocalizations.of(context).backButtonTooltip;
52+
break;
53+
case TargetPlatform.fuchsia:
54+
case TargetPlatform.linux:
55+
case TargetPlatform.windows:
56+
case TargetPlatform.iOS:
57+
case TargetPlatform.macOS:
58+
semanticsLabel = null;
59+
break;
4160
}
42-
}
4361

44-
@override
45-
Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));
62+
return Icon(data, semanticLabel: semanticsLabel);
63+
}
4664
}
4765

4866
/// A Material Design back button.
@@ -149,8 +167,23 @@ class CloseButton extends StatelessWidget {
149167
@override
150168
Widget build(BuildContext context) {
151169
assert(debugCheckHasMaterialLocalizations(context));
170+
final String? semanticsLabel;
171+
// This can't use the platform from Theme because it is the Android OS that
172+
// expects the duplicated tooltip and label.
173+
switch (defaultTargetPlatform) {
174+
case TargetPlatform.android:
175+
semanticsLabel = MaterialLocalizations.of(context).closeButtonTooltip;
176+
break;
177+
case TargetPlatform.fuchsia:
178+
case TargetPlatform.linux:
179+
case TargetPlatform.windows:
180+
case TargetPlatform.iOS:
181+
case TargetPlatform.macOS:
182+
semanticsLabel = null;
183+
break;
184+
}
152185
return IconButton(
153-
icon: const Icon(Icons.close),
186+
icon: Icon(Icons.close, semanticLabel: semanticsLabel),
154187
color: color,
155188
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
156189
onPressed: () {

packages/flutter/lib/src/widgets/semantics_debugger.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,14 @@ class _SemanticsDebuggerPainter extends CustomPainter {
288288

289289
assert(data.attributedLabel != null);
290290
final String message;
291+
// Android will avoid pronouncing duplicating tooltip and label.
292+
// Therefore, having two identical strings is the same as having a single
293+
// string.
294+
final bool shouldIgnoreDuplicatedLabel = defaultTargetPlatform == TargetPlatform.android && data.attributedLabel.string == data.tooltip;
291295
final String tooltipAndLabel = <String>[
292296
if (data.tooltip.isNotEmpty)
293297
data.tooltip,
294-
if (data.attributedLabel.string.isNotEmpty)
298+
if (data.attributedLabel.string.isNotEmpty && !shouldIgnoreDuplicatedLabel)
295299
data.attributedLabel.string,
296300
].join('\n');
297301
if (tooltipAndLabel.isEmpty) {

packages/flutter/test/material/back_button_test.dart

+59-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
56
import 'package:flutter/material.dart';
67
import 'package:flutter_test/flutter_test.dart';
78

@@ -152,17 +153,73 @@ void main() {
152153
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
153154

154155
await tester.pumpAndSettle();
155-
156+
final String? expectedLabel;
157+
switch(defaultTargetPlatform) {
158+
case TargetPlatform.android:
159+
expectedLabel = 'Back';
160+
break;
161+
case TargetPlatform.fuchsia:
162+
case TargetPlatform.iOS:
163+
case TargetPlatform.linux:
164+
case TargetPlatform.macOS:
165+
case TargetPlatform.windows:
166+
expectedLabel = null;
167+
}
156168
expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics(
157169
tooltip: 'Back',
170+
label: expectedLabel,
158171
isButton: true,
159172
hasEnabledState: true,
160173
isEnabled: true,
161174
hasTapAction: true,
162175
isFocusable: true,
163176
));
164177
handle.dispose();
165-
});
178+
}, variant: TargetPlatformVariant.all());
179+
180+
testWidgets('CloseButton semantics', (WidgetTester tester) async {
181+
final SemanticsHandle handle = tester.ensureSemantics();
182+
await tester.pumpWidget(
183+
MaterialApp(
184+
home: const Material(child: Text('Home')),
185+
routes: <String, WidgetBuilder>{
186+
'/next': (BuildContext context) {
187+
return const Material(
188+
child: Center(
189+
child: CloseButton(),
190+
),
191+
);
192+
},
193+
},
194+
),
195+
);
196+
197+
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
198+
199+
await tester.pumpAndSettle();
200+
final String? expectedLabel;
201+
switch(defaultTargetPlatform) {
202+
case TargetPlatform.android:
203+
expectedLabel = 'Close';
204+
break;
205+
case TargetPlatform.fuchsia:
206+
case TargetPlatform.iOS:
207+
case TargetPlatform.linux:
208+
case TargetPlatform.macOS:
209+
case TargetPlatform.windows:
210+
expectedLabel = null;
211+
}
212+
expect(tester.getSemantics(find.byType(CloseButton)), matchesSemantics(
213+
tooltip: 'Close',
214+
label: expectedLabel,
215+
isButton: true,
216+
hasEnabledState: true,
217+
isEnabled: true,
218+
hasTapAction: true,
219+
isFocusable: true,
220+
));
221+
handle.dispose();
222+
}, variant: TargetPlatformVariant.all());
166223

167224
testWidgets('CloseButton color', (WidgetTester tester) async {
168225
await tester.pumpWidget(

packages/flutter/test/widgets/semantics_debugger_test.dart

+28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart';
56
import 'package:flutter/material.dart';
67
import 'package:flutter_test/flutter_test.dart';
78

@@ -438,6 +439,33 @@ void main() {
438439
);
439440
});
440441

442+
testWidgets('SemanticsDebugger ignores duplicated label and tooltip for Android', (WidgetTester tester) async {
443+
final Key child = UniqueKey();
444+
final Key debugger = UniqueKey();
445+
final bool isPlatformAndroid = defaultTargetPlatform == TargetPlatform.android;
446+
await tester.pumpWidget(
447+
Directionality(
448+
textDirection: TextDirection.ltr,
449+
child: SemanticsDebugger(
450+
key: debugger,
451+
child: Material(
452+
child: Semantics(
453+
container: true,
454+
key: child,
455+
label: 'text',
456+
tooltip: 'text',
457+
),
458+
),
459+
),
460+
),
461+
);
462+
463+
expect(
464+
_getMessageShownInSemanticsDebugger(widgetKey: child, debuggerKey: debugger, tester: tester),
465+
isPlatformAndroid ? 'text' : 'text\ntext',
466+
);
467+
}, variant: TargetPlatformVariant.all());
468+
441469
testWidgets('SemanticsDebugger textfield', (WidgetTester tester) async {
442470
final UniqueKey textField = UniqueKey();
443471
final UniqueKey debugger = UniqueKey();

0 commit comments

Comments
 (0)