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> { ...@@ -868,10 +868,6 @@ class _DayPickerState extends State<_DayPicker> {
/// List of [FocusNode]s, one for each day of the month. /// List of [FocusNode]s, one for each day of the month.
late List<FocusNode> _dayFocusNodes; 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 @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -897,9 +893,6 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -897,9 +893,6 @@ class _DayPickerState extends State<_DayPicker> {
for (final FocusNode node in _dayFocusNodes) { for (final FocusNode node in _dayFocusNodes) {
node.dispose(); node.dispose();
} }
for (final MaterialStatesController controller in _statesControllers.values) {
controller.dispose();
}
super.dispose(); super.dispose();
} }
...@@ -937,7 +930,6 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -937,7 +930,6 @@ class _DayPickerState extends State<_DayPicker> {
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context); final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final DatePickerThemeData defaults = DatePickerTheme.defaults(context); final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle; final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
final int year = widget.displayedMonth.year; final int year = widget.displayedMonth.year;
final int month = widget.displayedMonth.month; final int month = widget.displayedMonth.month;
...@@ -945,18 +937,6 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -945,18 +937,6 @@ class _DayPickerState extends State<_DayPicker> {
final int daysInMonth = DateUtils.getDaysInMonth(year, month); final int daysInMonth = DateUtils.getDaysInMonth(year, month);
final int dayOffset = DateUtils.firstDayOffset(year, month, localizations); 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); final List<Widget> dayItems = _dayHeaders(weekdayStyle, localizations);
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
// a leap year. // a leap year.
...@@ -973,22 +953,95 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -973,22 +953,95 @@ class _DayPickerState extends State<_DayPicker> {
(widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild)); (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild); final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild); final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
dayItems.add(
_Day(
dayToBuild,
key: ValueKey<DateTime>(dayToBuild),
isDisabled: isDisabled,
isSelectedDay: isSelectedDay,
isToday: isToday,
onChanged: widget.onChanged,
focusNode: _dayFocusNodes[day - 1],
),
);
}
}
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: _monthPickerHorizontalPadding,
),
child: GridView.custom(
physics: const ClampingScrollPhysics(),
gridDelegate: _dayPickerGridDelegate,
childrenDelegate: SliverChildListDelegate(
dayItems,
addRepaintBoundaries: false,
),
),
);
}
}
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>{ final Set<MaterialState> states = <MaterialState>{
if (isDisabled) MaterialState.disabled, if (widget.isDisabled) MaterialState.disabled,
if (isSelectedDay) MaterialState.selected, if (widget.isSelectedDay) MaterialState.selected,
}; };
final MaterialStatesController statesController = _statesControllers.putIfAbsent(day, () => MaterialStatesController()); _statesController.value = states;
statesController.value = states;
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states); final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states); final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => widget.isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>( final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)), (Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
); );
final BoxDecoration decoration = isToday final BoxDecoration decoration = widget.isToday
? BoxDecoration( ? BoxDecoration(
color: dayBackgroundColor, color: dayBackgroundColor,
border: Border.fromBorderSide( border: Border.fromBorderSide(
...@@ -1005,20 +1058,20 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -1005,20 +1058,20 @@ class _DayPickerState extends State<_DayPicker> {
Widget dayWidget = Container( Widget dayWidget = Container(
decoration: decoration, decoration: decoration,
child: Center( child: Center(
child: Text(localizations.formatDecimal(day), style: dayStyle?.apply(color: dayForegroundColor)), child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)),
), ),
); );
if (isDisabled) { if (widget.isDisabled) {
dayWidget = ExcludeSemantics( dayWidget = ExcludeSemantics(
child: dayWidget, child: dayWidget,
); );
} else { } else {
dayWidget = InkResponse( dayWidget = InkResponse(
focusNode: _dayFocusNodes[day - 1], focusNode: widget.focusNode,
onTap: () => widget.onChanged(dayToBuild), onTap: () => widget.onChanged(widget.day),
radius: _dayPickerRowHeight / 2 + 4, radius: _dayPickerRowHeight / 2 + 4,
statesController: statesController, statesController: _statesController,
overlayColor: dayOverlayColor, overlayColor: dayOverlayColor,
child: Semantics( child: Semantics(
// We want the day of month to be spoken first irrespective of the // We want the day of month to be spoken first irrespective of the
...@@ -1027,33 +1080,23 @@ class _DayPickerState extends State<_DayPicker> { ...@@ -1027,33 +1080,23 @@ class _DayPickerState extends State<_DayPicker> {
// day of month before the rest of the date, as they are looking // 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 // for the day of month. To do that we prepend day of month to the
// formatted full date. // formatted full date.
label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix', label: '${localizations.formatDecimal(widget.day.day)}, ${localizations.formatFullDate(widget.day)}$semanticLabelSuffix',
// Set button to true to make the date selectable. // Set button to true to make the date selectable.
button: true, button: true,
selected: isSelectedDay, selected: widget.isSelectedDay,
excludeSemantics: true, excludeSemantics: true,
child: dayWidget, child: dayWidget,
), ),
); );
} }
dayItems.add(dayWidget); return dayWidget;
}
} }
return Padding( @override
padding: const EdgeInsets.symmetric( void dispose() {
horizontal: _monthPickerHorizontalPadding, _statesController.dispose();
), super.dispose();
child: GridView.custom(
physics: const ClampingScrollPhysics(),
gridDelegate: _dayPickerGridDelegate,
childrenDelegate: SliverChildListDelegate(
dayItems,
addRepaintBoundaries: false,
),
),
);
} }
} }
......
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