Reland [a11y] Fix date picker cannot focus on the edit field (#144198)

reland https://github.com/flutter/flutter/pull/143117 


fixes: https://github.com/flutter/flutter/issues/143116
fixes: https://github.com/flutter/flutter/issues/141992

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
[Data Driven Fixes]:
https://github.com/flutter/flutter/wiki/Data-driven-Fixes
This commit is contained in:
hangyu 2024-02-29 15:16:53 -08:00 committed by GitHub
parent e41ffcb742
commit 14b914ab92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 63 deletions

View file

@ -409,6 +409,7 @@ class _DatePickerModeToggleButtonState extends State<_DatePickerModeToggleButton
label: MaterialLocalizations.of(context).selectYearSemanticsLabel, label: MaterialLocalizations.of(context).selectYearSemanticsLabel,
excludeSemantics: true, excludeSemantics: true,
button: true, button: true,
container: true,
child: SizedBox( child: SizedBox(
height: _subHeaderHeight, height: _subHeaderHeight,
child: InkWell( child: InkWell(

View file

@ -861,64 +861,76 @@ class _DatePickerHeader extends StatelessWidget {
switch (orientation) { switch (orientation) {
case Orientation.portrait: case Orientation.portrait:
return SizedBox( return Semantics(
height: _datePickerHeaderPortraitHeight, container: true,
child: Material( child: SizedBox(
color: backgroundColor, height: _datePickerHeaderPortraitHeight,
child: Padding( child: Material(
padding: const EdgeInsetsDirectional.only( color: backgroundColor,
start: 24, child: Padding(
end: 12, padding: const EdgeInsetsDirectional.only(
bottom: 12, start: 24,
), end: 12,
child: Column( bottom: 12,
crossAxisAlignment: CrossAxisAlignment.start, ),
children: <Widget>[ child: Column(
const SizedBox(height: 16), crossAxisAlignment: CrossAxisAlignment.start,
help, children: <Widget>[
const Flexible(child: SizedBox(height: 38)), const SizedBox(height: 16),
Row( help,
children: <Widget>[ const Flexible(child: SizedBox(height: 38)),
Expanded(child: title), Row(
if (entryModeButton != null) children: <Widget>[
entryModeButton!, Expanded(child: title),
], if (entryModeButton != null)
), Semantics(
], container: true,
child: entryModeButton,
),
],
),
],
),
), ),
), ),
), ),
); );
case Orientation.landscape: case Orientation.landscape:
return SizedBox( return Semantics(
width: _datePickerHeaderLandscapeWidth, container: true,
child: Material( child:SizedBox(
color: backgroundColor, width: _datePickerHeaderLandscapeWidth,
child: Column( child: Material(
crossAxisAlignment: CrossAxisAlignment.start, color: backgroundColor,
children: <Widget>[ child: Column(
const SizedBox(height: 16), crossAxisAlignment: CrossAxisAlignment.start,
Padding( children: <Widget>[
padding: const EdgeInsets.symmetric( const SizedBox(height: 16),
horizontal: _headerPaddingLandscape, Padding(
),
child: help,
),
SizedBox(height: isShort ? 16 : 56),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: _headerPaddingLandscape, horizontal: _headerPaddingLandscape,
), ),
child: title, child: help,
), ),
), SizedBox(height: isShort ? 16 : 56),
if (entryModeButton != null) Expanded(
Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(
child: entryModeButton, horizontal: _headerPaddingLandscape,
),
child: title,
),
), ),
], if (entryModeButton != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Semantics(
container: true,
child: entryModeButton,
),
),
],
),
), ),
), ),
); );

View file

@ -256,21 +256,24 @@ class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
?? theme.inputDecorationTheme.border ?? theme.inputDecorationTheme.border
?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder()); ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
return TextFormField( return Semantics(
decoration: InputDecoration( container: true,
hintText: widget.fieldHintText ?? localizations.dateHelpText, child: TextFormField(
labelText: widget.fieldLabelText ?? localizations.dateInputLabel, decoration: InputDecoration(
).applyDefaults(inputTheme hintText: widget.fieldHintText ?? localizations.dateHelpText,
.merge(datePickerTheme.inputDecorationTheme) labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
.copyWith(border: effectiveInputBorder), ).applyDefaults(inputTheme
.merge(datePickerTheme.inputDecorationTheme)
.copyWith(border: effectiveInputBorder),
),
validator: _validateDate,
keyboardType: widget.keyboardType ?? TextInputType.datetime,
onSaved: _handleSaved,
onFieldSubmitted: _handleSubmitted,
autofocus: widget.autofocus,
controller: _controller,
focusNode: widget.focusNode,
), ),
validator: _validateDate,
keyboardType: widget.keyboardType ?? TextInputType.datetime,
onSaved: _handleSaved,
onFieldSubmitted: _handleSubmitted,
autofocus: widget.autofocus,
controller: _controller,
focusNode: widget.focusNode,
); );
} }
} }

View file

@ -13,6 +13,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../widgets/clipboard_utils.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@ -1577,6 +1578,13 @@ void main() {
}); });
testWidgets('input mode', (WidgetTester tester) async { testWidgets('input mode', (WidgetTester tester) async {
// Fill the clipboard so that the Paste option is available in the text
// selection menu.
final MockClipboard mockClipboard = MockClipboard();
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null));
final SemanticsHandle semantics = tester.ensureSemantics(); final SemanticsHandle semantics = tester.ensureSemantics();
initialEntryMode = DatePickerEntryMode.input; initialEntryMode = DatePickerEntryMode.input;
@ -1596,7 +1604,22 @@ void main() {
isFocusable: true, isFocusable: true,
)); ));
// The semantics of the InputDatePickerFormField are tested in its tests. expect(tester.getSemantics(find.byType(EditableText)), matchesSemantics(
label: 'Enter Date',
isEnabled: true,
hasEnabledState: true,
isTextField: true,
isFocused: true,
value: '01/15/2016',
hasTapAction: true,
hasSetTextAction: true,
hasSetSelectionAction: true,
hasCopyAction: true,
hasCutAction: true,
hasPasteAction: true,
hasMoveCursorBackwardByCharacterAction: true,
hasMoveCursorBackwardByWordAction: true,
));
// Ok/Cancel buttons // Ok/Cancel buttons
expect(tester.getSemantics(find.text('OK')), matchesSemantics( expect(tester.getSemantics(find.text('OK')), matchesSemantics(