@@ -868,10 +868,6 @@ class _DayPickerState extends State<_DayPicker> {
868
868
/// List of [FocusNode] s, one for each day of the month.
869
869
late List <FocusNode > _dayFocusNodes;
870
870
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
-
875
871
@override
876
872
void initState () {
877
873
super .initState ();
@@ -897,9 +893,6 @@ class _DayPickerState extends State<_DayPicker> {
897
893
for (final FocusNode node in _dayFocusNodes) {
898
894
node.dispose ();
899
895
}
900
- for (final MaterialStatesController controller in _statesControllers.values) {
901
- controller.dispose ();
902
- }
903
896
super .dispose ();
904
897
}
905
898
@@ -937,26 +930,13 @@ class _DayPickerState extends State<_DayPicker> {
937
930
final DatePickerThemeData datePickerTheme = DatePickerTheme .of (context);
938
931
final DatePickerThemeData defaults = DatePickerTheme .defaults (context);
939
932
final TextStyle ? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
940
- final TextStyle ? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
941
933
942
934
final int year = widget.displayedMonth.year;
943
935
final int month = widget.displayedMonth.month;
944
936
945
937
final int daysInMonth = DateUtils .getDaysInMonth (year, month);
946
938
final int dayOffset = DateUtils .firstDayOffset (year, month, localizations);
947
939
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
-
960
940
final List <Widget > dayItems = _dayHeaders (weekdayStyle, localizations);
961
941
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
962
942
// a leap year.
@@ -973,71 +953,18 @@ class _DayPickerState extends State<_DayPicker> {
973
953
(widget.selectableDayPredicate != null && ! widget.selectableDayPredicate !(dayToBuild));
974
954
final bool isSelectedDay = DateUtils .isSameDay (widget.selectedDate, dayToBuild);
975
955
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;
985
956
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 ],
1009
966
),
1010
967
);
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);
1041
968
}
1042
969
}
1043
970
@@ -1057,6 +984,122 @@ class _DayPickerState extends State<_DayPicker> {
1057
984
}
1058
985
}
1059
986
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
+
1060
1103
class _DayPickerGridDelegate extends SliverGridDelegate {
1061
1104
const _DayPickerGridDelegate ();
1062
1105
0 commit comments