@@ -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+
10601103class _DayPickerGridDelegate extends SliverGridDelegate {
10611104 const _DayPickerGridDelegate ();
10621105
0 commit comments