Skip to content

Commit ee9aef0

Browse files
authored
_DayPicker should build days using separate stetefull widget _Day. (flutter#134607)
Fixes flutter#134323
1 parent b2f3404 commit ee9aef0

File tree

1 file changed

+125
-82
lines changed

1 file changed

+125
-82
lines changed

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

Lines changed: 125 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -868,10 +868,6 @@ class _DayPickerState extends State<_DayPicker> {
868868
/// List of [FocusNode]s, one for each day of the month.
869869
late List<FocusNode> _dayFocusNodes;
870870

871-
// TODO(polina-c): a cleaner solution is to create separate statefull widget for a day.
872-
// https://github.com/flutter/flutter/issues/134323
873-
final Map<int, MaterialStatesController> _statesControllers = <int, MaterialStatesController>{};
874-
875871
@override
876872
void initState() {
877873
super.initState();
@@ -897,9 +893,6 @@ class _DayPickerState extends State<_DayPicker> {
897893
for (final FocusNode node in _dayFocusNodes) {
898894
node.dispose();
899895
}
900-
for (final MaterialStatesController controller in _statesControllers.values) {
901-
controller.dispose();
902-
}
903896
super.dispose();
904897
}
905898

@@ -937,26 +930,13 @@ class _DayPickerState extends State<_DayPicker> {
937930
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
938931
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
939932
final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
940-
final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
941933

942934
final int year = widget.displayedMonth.year;
943935
final int month = widget.displayedMonth.month;
944936

945937
final int daysInMonth = DateUtils.getDaysInMonth(year, month);
946938
final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
947939

948-
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
949-
return getProperty(datePickerTheme) ?? getProperty(defaults);
950-
}
951-
952-
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
953-
return effectiveValue(
954-
(DatePickerThemeData? theme) {
955-
return getProperty(theme)?.resolve(states);
956-
},
957-
);
958-
}
959-
960940
final List<Widget> dayItems = _dayHeaders(weekdayStyle, localizations);
961941
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
962942
// a leap year.
@@ -973,71 +953,18 @@ class _DayPickerState extends State<_DayPicker> {
973953
(widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
974954
final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
975955
final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
976-
final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
977-
978-
final Set<MaterialState> states = <MaterialState>{
979-
if (isDisabled) MaterialState.disabled,
980-
if (isSelectedDay) MaterialState.selected,
981-
};
982-
983-
final MaterialStatesController statesController = _statesControllers.putIfAbsent(day, () => MaterialStatesController());
984-
statesController.value = states;
985956

986-
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
987-
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
988-
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
989-
(Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
990-
);
991-
final BoxDecoration decoration = isToday
992-
? BoxDecoration(
993-
color: dayBackgroundColor,
994-
border: Border.fromBorderSide(
995-
(datePickerTheme.todayBorder ?? defaults.todayBorder!)
996-
.copyWith(color: dayForegroundColor)
997-
),
998-
shape: BoxShape.circle,
999-
)
1000-
: BoxDecoration(
1001-
color: dayBackgroundColor,
1002-
shape: BoxShape.circle,
1003-
);
1004-
1005-
Widget dayWidget = Container(
1006-
decoration: decoration,
1007-
child: Center(
1008-
child: Text(localizations.formatDecimal(day), style: dayStyle?.apply(color: dayForegroundColor)),
957+
dayItems.add(
958+
_Day(
959+
dayToBuild,
960+
key: ValueKey<DateTime>(dayToBuild),
961+
isDisabled: isDisabled,
962+
isSelectedDay: isSelectedDay,
963+
isToday: isToday,
964+
onChanged: widget.onChanged,
965+
focusNode: _dayFocusNodes[day - 1],
1009966
),
1010967
);
1011-
1012-
if (isDisabled) {
1013-
dayWidget = ExcludeSemantics(
1014-
child: dayWidget,
1015-
);
1016-
} else {
1017-
dayWidget = InkResponse(
1018-
focusNode: _dayFocusNodes[day - 1],
1019-
onTap: () => widget.onChanged(dayToBuild),
1020-
radius: _dayPickerRowHeight / 2 + 4,
1021-
statesController: statesController,
1022-
overlayColor: dayOverlayColor,
1023-
child: Semantics(
1024-
// We want the day of month to be spoken first irrespective of the
1025-
// locale-specific preferences or TextDirection. This is because
1026-
// an accessibility user is more likely to be interested in the
1027-
// day of month before the rest of the date, as they are looking
1028-
// for the day of month. To do that we prepend day of month to the
1029-
// formatted full date.
1030-
label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix',
1031-
// Set button to true to make the date selectable.
1032-
button: true,
1033-
selected: isSelectedDay,
1034-
excludeSemantics: true,
1035-
child: dayWidget,
1036-
),
1037-
);
1038-
}
1039-
1040-
dayItems.add(dayWidget);
1041968
}
1042969
}
1043970

@@ -1057,6 +984,122 @@ class _DayPickerState extends State<_DayPicker> {
1057984
}
1058985
}
1059986

987+
class _Day extends StatefulWidget {
988+
const _Day(
989+
this.day, {
990+
super.key,
991+
required this.isDisabled,
992+
required this.isSelectedDay,
993+
required this.isToday,
994+
required this.onChanged,
995+
required this.focusNode,
996+
});
997+
998+
final DateTime day;
999+
final bool isDisabled;
1000+
final bool isSelectedDay;
1001+
final bool isToday;
1002+
final ValueChanged<DateTime> onChanged;
1003+
final FocusNode? focusNode;
1004+
1005+
@override
1006+
State<_Day> createState() => _DayState();
1007+
}
1008+
1009+
class _DayState extends State<_Day> {
1010+
final MaterialStatesController _statesController = MaterialStatesController();
1011+
1012+
@override
1013+
Widget build(BuildContext context) {
1014+
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
1015+
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
1016+
final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
1017+
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
1018+
return getProperty(datePickerTheme) ?? getProperty(defaults);
1019+
}
1020+
1021+
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
1022+
return effectiveValue(
1023+
(DatePickerThemeData? theme) {
1024+
return getProperty(theme)?.resolve(states);
1025+
},
1026+
);
1027+
}
1028+
1029+
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
1030+
final String semanticLabelSuffix = widget.isToday ? ', ${localizations.currentDateLabel}' : '';
1031+
1032+
final Set<MaterialState> states = <MaterialState>{
1033+
if (widget.isDisabled) MaterialState.disabled,
1034+
if (widget.isSelectedDay) MaterialState.selected,
1035+
};
1036+
1037+
_statesController.value = states;
1038+
1039+
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
1040+
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
1041+
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
1042+
(Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
1043+
);
1044+
final BoxDecoration decoration = widget.isToday
1045+
? BoxDecoration(
1046+
color: dayBackgroundColor,
1047+
border: Border.fromBorderSide(
1048+
(datePickerTheme.todayBorder ?? defaults.todayBorder!)
1049+
.copyWith(color: dayForegroundColor)
1050+
),
1051+
shape: BoxShape.circle,
1052+
)
1053+
: BoxDecoration(
1054+
color: dayBackgroundColor,
1055+
shape: BoxShape.circle,
1056+
);
1057+
1058+
Widget dayWidget = Container(
1059+
decoration: decoration,
1060+
child: Center(
1061+
child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)),
1062+
),
1063+
);
1064+
1065+
if (widget.isDisabled) {
1066+
dayWidget = ExcludeSemantics(
1067+
child: dayWidget,
1068+
);
1069+
} else {
1070+
dayWidget = InkResponse(
1071+
focusNode: widget.focusNode,
1072+
onTap: () => widget.onChanged(widget.day),
1073+
radius: _dayPickerRowHeight / 2 + 4,
1074+
statesController: _statesController,
1075+
overlayColor: dayOverlayColor,
1076+
child: Semantics(
1077+
// We want the day of month to be spoken first irrespective of the
1078+
// locale-specific preferences or TextDirection. This is because
1079+
// an accessibility user is more likely to be interested in the
1080+
// day of month before the rest of the date, as they are looking
1081+
// for the day of month. To do that we prepend day of month to the
1082+
// formatted full date.
1083+
label: '${localizations.formatDecimal(widget.day.day)}, ${localizations.formatFullDate(widget.day)}$semanticLabelSuffix',
1084+
// Set button to true to make the date selectable.
1085+
button: true,
1086+
selected: widget.isSelectedDay,
1087+
excludeSemantics: true,
1088+
child: dayWidget,
1089+
),
1090+
);
1091+
}
1092+
1093+
return dayWidget;
1094+
}
1095+
1096+
@override
1097+
void dispose() {
1098+
_statesController.dispose();
1099+
super.dispose();
1100+
}
1101+
}
1102+
10601103
class _DayPickerGridDelegate extends SliverGridDelegate {
10611104
const _DayPickerGridDelegate();
10621105

0 commit comments

Comments
 (0)