Unverified Commit ee9aef01 authored by Polina Cherkasova's avatar Polina Cherkasova Committed by GitHub

_DayPicker should build days using separate stetefull widget _Day. (#134607)

Fixes https://github.com/flutter/flutter/issues/134323
parent b2f3404c
......@@ -868,10 +868,6 @@ class _DayPickerState extends State<_DayPicker> {
/// List of [FocusNode]s, one for each day of the month.
late List<FocusNode> _dayFocusNodes;
// TODO(polina-c): a cleaner solution is to create separate statefull widget for a day.
// https://github.com/flutter/flutter/issues/134323
final Map<int, MaterialStatesController> _statesControllers = <int, MaterialStatesController>{};
@override
void initState() {
super.initState();
......@@ -897,9 +893,6 @@ class _DayPickerState extends State<_DayPicker> {
for (final FocusNode node in _dayFocusNodes) {
node.dispose();
}
for (final MaterialStatesController controller in _statesControllers.values) {
controller.dispose();
}
super.dispose();
}
......@@ -937,7 +930,6 @@ class _DayPickerState extends State<_DayPicker> {
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
final int year = widget.displayedMonth.year;
final int month = widget.displayedMonth.month;
......@@ -945,18 +937,6 @@ class _DayPickerState extends State<_DayPicker> {
final int daysInMonth = DateUtils.getDaysInMonth(year, month);
final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
return getProperty(datePickerTheme) ?? getProperty(defaults);
}
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
return effectiveValue(
(DatePickerThemeData? theme) {
return getProperty(theme)?.resolve(states);
},
);
}
final List<Widget> dayItems = _dayHeaders(weekdayStyle, localizations);
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
// a leap year.
......@@ -973,71 +953,18 @@ class _DayPickerState extends State<_DayPicker> {
(widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
final Set<MaterialState> states = <MaterialState>{
if (isDisabled) MaterialState.disabled,
if (isSelectedDay) MaterialState.selected,
};
final MaterialStatesController statesController = _statesControllers.putIfAbsent(day, () => MaterialStatesController());
statesController.value = states;
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
);
final BoxDecoration decoration = isToday
? BoxDecoration(
color: dayBackgroundColor,
border: Border.fromBorderSide(
(datePickerTheme.todayBorder ?? defaults.todayBorder!)
.copyWith(color: dayForegroundColor)
),
shape: BoxShape.circle,
)
: BoxDecoration(
color: dayBackgroundColor,
shape: BoxShape.circle,
);
Widget dayWidget = Container(
decoration: decoration,
child: Center(
child: Text(localizations.formatDecimal(day), style: dayStyle?.apply(color: dayForegroundColor)),
dayItems.add(
_Day(
dayToBuild,
key: ValueKey<DateTime>(dayToBuild),
isDisabled: isDisabled,
isSelectedDay: isSelectedDay,
isToday: isToday,
onChanged: widget.onChanged,
focusNode: _dayFocusNodes[day - 1],
),
);
if (isDisabled) {
dayWidget = ExcludeSemantics(
child: dayWidget,
);
} else {
dayWidget = InkResponse(
focusNode: _dayFocusNodes[day - 1],
onTap: () => widget.onChanged(dayToBuild),
radius: _dayPickerRowHeight / 2 + 4,
statesController: statesController,
overlayColor: dayOverlayColor,
child: Semantics(
// We want the day of month to be spoken first irrespective of the
// locale-specific preferences or TextDirection. This is because
// an accessibility user is more likely to be interested in the
// day of month before the rest of the date, as they are looking
// for the day of month. To do that we prepend day of month to the
// formatted full date.
label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix',
// Set button to true to make the date selectable.
button: true,
selected: isSelectedDay,
excludeSemantics: true,
child: dayWidget,
),
);
}
dayItems.add(dayWidget);
}
}
......@@ -1057,6 +984,122 @@ class _DayPickerState extends State<_DayPicker> {
}
}
class _Day extends StatefulWidget {
const _Day(
this.day, {
super.key,
required this.isDisabled,
required this.isSelectedDay,
required this.isToday,
required this.onChanged,
required this.focusNode,
});
final DateTime day;
final bool isDisabled;
final bool isSelectedDay;
final bool isToday;
final ValueChanged<DateTime> onChanged;
final FocusNode? focusNode;
@override
State<_Day> createState() => _DayState();
}
class _DayState extends State<_Day> {
final MaterialStatesController _statesController = MaterialStatesController();
@override
Widget build(BuildContext context) {
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
return getProperty(datePickerTheme) ?? getProperty(defaults);
}
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
return effectiveValue(
(DatePickerThemeData? theme) {
return getProperty(theme)?.resolve(states);
},
);
}
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final String semanticLabelSuffix = widget.isToday ? ', ${localizations.currentDateLabel}' : '';
final Set<MaterialState> states = <MaterialState>{
if (widget.isDisabled) MaterialState.disabled,
if (widget.isSelectedDay) MaterialState.selected,
};
_statesController.value = states;
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
);
final BoxDecoration decoration = widget.isToday
? BoxDecoration(
color: dayBackgroundColor,
border: Border.fromBorderSide(
(datePickerTheme.todayBorder ?? defaults.todayBorder!)
.copyWith(color: dayForegroundColor)
),
shape: BoxShape.circle,
)
: BoxDecoration(
color: dayBackgroundColor,
shape: BoxShape.circle,
);
Widget dayWidget = Container(
decoration: decoration,
child: Center(
child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)),
),
);
if (widget.isDisabled) {
dayWidget = ExcludeSemantics(
child: dayWidget,
);
} else {
dayWidget = InkResponse(
focusNode: widget.focusNode,
onTap: () => widget.onChanged(widget.day),
radius: _dayPickerRowHeight / 2 + 4,
statesController: _statesController,
overlayColor: dayOverlayColor,
child: Semantics(
// We want the day of month to be spoken first irrespective of the
// locale-specific preferences or TextDirection. This is because
// an accessibility user is more likely to be interested in the
// day of month before the rest of the date, as they are looking
// for the day of month. To do that we prepend day of month to the
// formatted full date.
label: '${localizations.formatDecimal(widget.day.day)}, ${localizations.formatFullDate(widget.day)}$semanticLabelSuffix',
// Set button to true to make the date selectable.
button: true,
selected: widget.isSelectedDay,
excludeSemantics: true,
child: dayWidget,
),
);
}
return dayWidget;
}
@override
void dispose() {
_statesController.dispose();
super.dispose();
}
}
class _DayPickerGridDelegate extends SliverGridDelegate {
const _DayPickerGridDelegate();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment