Skip to content

Commit 5d2353c

Browse files
authored
CalendarDatePicker doesn't announce selected date on desktop (#143583)
fixes [Screen reader is not announcing the selected date as selected on DatePicker](flutter/flutter#143439) ### Descriptions - This fixes an issue where `CalendarDatePicker` doesn't announce selected date on desktop. - Add semantic label to describe the selected date is indeed "Selected". ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @OverRide MyHomePageState createState() => MyHomePageState(); } class MyHomePageState extends State<MyHomePage> { void _showDatePicker() async { await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime(2200), ); } @OverRide Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title, style: const TextStyle(fontFamily: 'ProductSans')), ), body: const Center( child: Text('Click the button to show date picker.'), ), floatingActionButton: FloatingActionButton( onPressed: _showDatePicker, tooltip: 'Show date picker', child: const Icon(Icons.edit_calendar), ), ); } } // import 'package:flutter/material.dart'; // void main() => runApp(const MyApp()); // class MyApp extends StatelessWidget { // const MyApp({super.key}); // @OverRide // Widget build(BuildContext context) { // return MaterialApp( // debugShowCheckedModeBanner: false, // home: Scaffold( // body: Center( // child: CalendarDatePicker( // initialDate: DateTime.now(), // firstDate: DateTime(2020), // lastDate: DateTime(2050), // onDateChanged: (date) { // print(date); // }, // ), // ), // ), // ); // } // } ``` </details> ### Before https://github.com/flutter/flutter/assets/48603081/c82e1f15-f067-4865-8a5a-1f3c0c8d91da ### After https://github.com/flutter/flutter/assets/48603081/193d9e26-df9e-4d89-97ce-265c3d564607
1 parent a41868e commit 5d2353c

File tree

85 files changed

+467
-78
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+467
-78
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,21 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
265265
setState(() {
266266
_selectedDate = value;
267267
widget.onDateChanged(_selectedDate!);
268+
switch (Theme.of(context).platform) {
269+
case TargetPlatform.linux:
270+
case TargetPlatform.macOS:
271+
case TargetPlatform.windows:
272+
final bool isToday = DateUtils.isSameDay(widget.currentDate, _selectedDate);
273+
final String semanticLabelSuffix = isToday ? ', ${_localizations.currentDateLabel}' : '';
274+
SemanticsService.announce(
275+
'${_localizations.selectedDateLabel} ${_localizations.formatFullDate(_selectedDate!)}$semanticLabelSuffix',
276+
_textDirection,
277+
);
278+
case TargetPlatform.android:
279+
case TargetPlatform.iOS:
280+
case TargetPlatform.fuchsia:
281+
break;
282+
}
268283
});
269284
}
270285

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ abstract class MaterialLocalizations {
182182
/// Label indicating that a given date is the current date.
183183
String get currentDateLabel;
184184

185+
/// The semantics label to describe the selected date in the calendar picker
186+
/// invoked using [showDatePicker].
187+
String get selectedDateLabel;
188+
185189
/// Label for the scrim rendered underneath a [BottomSheet].
186190
String get scrimLabel;
187191

@@ -1108,6 +1112,9 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
11081112
@override
11091113
String get currentDateLabel => 'Today';
11101114

1115+
@override
1116+
String get selectedDateLabel => 'Selected';
1117+
11111118
@override
11121119
String get scrimLabel => 'Scrim';
11131120

packages/flutter/test/material/calendar_date_picker_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,51 @@ void main() {
10861086
}
10871087
semantics.dispose();
10881088
});
1089+
1090+
// This is a regression test for https://github.com/flutter/flutter/issues/143439.
1091+
testWidgets('Selected date Semantics announcement on onDateChanged', (WidgetTester tester) async {
1092+
final SemanticsHandle semantics = tester.ensureSemantics();
1093+
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
1094+
final DateTime initialDate = DateTime(2016, DateTime.january, 15);
1095+
DateTime? selectedDate;
1096+
1097+
await tester.pumpWidget(calendarDatePicker(
1098+
initialDate: initialDate,
1099+
onDateChanged: (DateTime value) {
1100+
selectedDate = value;
1101+
},
1102+
));
1103+
1104+
final bool isToday = DateUtils.isSameDay(initialDate, selectedDate);
1105+
final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
1106+
1107+
// The initial date should be announced.
1108+
expect(
1109+
tester.takeAnnouncements().last.message,
1110+
'${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
1111+
);
1112+
1113+
// Select a new date.
1114+
await tester.tap(find.text('20'));
1115+
await tester.pumpAndSettle();
1116+
1117+
// The selected date should be announced.
1118+
expect(
1119+
tester.takeAnnouncements().last.message,
1120+
'${localizations.selectedDateLabel} ${localizations.formatFullDate(selectedDate!)}$semanticLabelSuffix',
1121+
);
1122+
1123+
// Select the initial date.
1124+
await tester.tap(find.text('15'));
1125+
1126+
// The initial date should be announced as selected.
1127+
expect(
1128+
tester.takeAnnouncements().first.message,
1129+
'${localizations.selectedDateLabel} ${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
1130+
);
1131+
1132+
semantics.dispose();
1133+
}, variant: TargetPlatformVariant.desktop());
10891134
});
10901135
});
10911136

packages/flutter/test/material/localizations_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ void main() {
137137
expect(localizations.currentDateLabel, isNotNull);
138138
expect(localizations.scrimLabel, isNotNull);
139139
expect(localizations.bottomSheetLabel, isNotNull);
140+
expect(localizations.selectedDateLabel, isNotNull);
140141

141142
expect(localizations.scrimOnTapHint('FOO'), contains('FOO'));
142143

0 commit comments

Comments
 (0)