_DayPicker should build days using separate stetefull widget _Day. (#134607)
Fixes https://github.com/flutter/flutter/issues/134323
This commit is contained in:
committed by
GitHub
parent
b2f3404ca0
commit
ee9aef0130
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user