Remove deprecated DayPicker and MonthPicker (#98543)
This commit is contained in:
parent
3f52d5800a
commit
cddd5524d4
@ -56,7 +56,6 @@ export 'src/material/data_table_source.dart';
|
|||||||
export 'src/material/data_table_theme.dart';
|
export 'src/material/data_table_theme.dart';
|
||||||
export 'src/material/date.dart';
|
export 'src/material/date.dart';
|
||||||
export 'src/material/date_picker.dart';
|
export 'src/material/date_picker.dart';
|
||||||
export 'src/material/date_picker_deprecated.dart';
|
|
||||||
export 'src/material/debug.dart';
|
export 'src/material/debug.dart';
|
||||||
export 'src/material/desktop_text_selection.dart';
|
export 'src/material/desktop_text_selection.dart';
|
||||||
export 'src/material/dialog.dart';
|
export 'src/material/dialog.dart';
|
||||||
|
@ -1,609 +0,0 @@
|
|||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
import 'date.dart';
|
|
||||||
import 'icon_button.dart';
|
|
||||||
import 'icons.dart';
|
|
||||||
import 'material_localizations.dart';
|
|
||||||
import 'theme.dart';
|
|
||||||
|
|
||||||
// This is the original implementation for the Material Date Picker.
|
|
||||||
// These classes are deprecated and the whole file can be removed after
|
|
||||||
// this has been on stable for long enough for people to migrate to the new
|
|
||||||
// CalendarDatePicker (if needed, as showDatePicker has already been migrated
|
|
||||||
// and it is what most apps would have used).
|
|
||||||
|
|
||||||
const Duration _kMonthScrollDuration = Duration(milliseconds: 200);
|
|
||||||
const double _kDayPickerRowHeight = 42.0;
|
|
||||||
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
|
|
||||||
// Two extra rows: one for the day-of-week header and one for the month header.
|
|
||||||
const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
|
|
||||||
|
|
||||||
class _DayPickerGridDelegate extends SliverGridDelegate {
|
|
||||||
const _DayPickerGridDelegate();
|
|
||||||
|
|
||||||
@override
|
|
||||||
SliverGridLayout getLayout(SliverConstraints constraints) {
|
|
||||||
const int columnCount = DateTime.daysPerWeek;
|
|
||||||
final double tileWidth = constraints.crossAxisExtent / columnCount;
|
|
||||||
final double viewTileHeight = constraints.viewportMainAxisExtent / (_kMaxDayPickerRowCount + 1);
|
|
||||||
final double tileHeight = math.max(_kDayPickerRowHeight, viewTileHeight);
|
|
||||||
return SliverGridRegularTileLayout(
|
|
||||||
crossAxisCount: columnCount,
|
|
||||||
mainAxisStride: tileHeight,
|
|
||||||
crossAxisStride: tileWidth,
|
|
||||||
childMainAxisExtent: tileHeight,
|
|
||||||
childCrossAxisExtent: tileWidth,
|
|
||||||
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRelayout(_DayPickerGridDelegate oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _DayPickerGridDelegate _kDayPickerGridDelegate = _DayPickerGridDelegate();
|
|
||||||
|
|
||||||
/// Displays the days of a given month and allows choosing a day.
|
|
||||||
///
|
|
||||||
/// The days are arranged in a rectangular grid with one column for each day of
|
|
||||||
/// the week.
|
|
||||||
///
|
|
||||||
/// The day picker widget is rarely used directly. Instead, consider using
|
|
||||||
/// [showDatePicker], which creates a date picker dialog.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [showDatePicker], which shows a dialog that contains a material design
|
|
||||||
/// date picker.
|
|
||||||
/// * [showTimePicker], which shows a dialog that contains a material design
|
|
||||||
/// time picker.
|
|
||||||
///
|
|
||||||
@Deprecated(
|
|
||||||
'Use CalendarDatePicker instead. '
|
|
||||||
'This feature was deprecated after v1.26.0-18.0.pre.',
|
|
||||||
)
|
|
||||||
class DayPicker extends StatelessWidget {
|
|
||||||
/// Creates a day picker.
|
|
||||||
///
|
|
||||||
/// Rarely used directly. Instead, typically used as part of a [MonthPicker].
|
|
||||||
@Deprecated(
|
|
||||||
'Use CalendarDatePicker instead. '
|
|
||||||
'This feature was deprecated after v1.26.0-18.0.pre.',
|
|
||||||
)
|
|
||||||
DayPicker({
|
|
||||||
Key? key,
|
|
||||||
required this.selectedDate,
|
|
||||||
required this.currentDate,
|
|
||||||
required this.onChanged,
|
|
||||||
required this.firstDate,
|
|
||||||
required this.lastDate,
|
|
||||||
required this.displayedMonth,
|
|
||||||
this.selectableDayPredicate,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
}) : assert(selectedDate != null),
|
|
||||||
assert(currentDate != null),
|
|
||||||
assert(onChanged != null),
|
|
||||||
assert(displayedMonth != null),
|
|
||||||
assert(dragStartBehavior != null),
|
|
||||||
assert(!firstDate.isAfter(lastDate)),
|
|
||||||
assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// The currently selected date.
|
|
||||||
///
|
|
||||||
/// This date is highlighted in the picker.
|
|
||||||
final DateTime selectedDate;
|
|
||||||
|
|
||||||
/// The current date at the time the picker is displayed.
|
|
||||||
final DateTime currentDate;
|
|
||||||
|
|
||||||
/// Called when the user picks a day.
|
|
||||||
final ValueChanged<DateTime> onChanged;
|
|
||||||
|
|
||||||
/// The earliest date the user is permitted to pick.
|
|
||||||
final DateTime firstDate;
|
|
||||||
|
|
||||||
/// The latest date the user is permitted to pick.
|
|
||||||
final DateTime lastDate;
|
|
||||||
|
|
||||||
/// The month whose days are displayed by this picker.
|
|
||||||
final DateTime displayedMonth;
|
|
||||||
|
|
||||||
/// Optional user supplied predicate function to customize selectable days.
|
|
||||||
final SelectableDayPredicate? selectableDayPredicate;
|
|
||||||
|
|
||||||
/// Determines the way that drag start behavior is handled.
|
|
||||||
///
|
|
||||||
/// If set to [DragStartBehavior.start], the drag gesture used to scroll a
|
|
||||||
/// date picker wheel will begin at the position where the drag gesture won
|
|
||||||
/// the arena. If set to [DragStartBehavior.down] it will begin at the
|
|
||||||
/// position where a down event is first detected.
|
|
||||||
///
|
|
||||||
/// In general, setting this to [DragStartBehavior.start] will make drag
|
|
||||||
/// animation smoother and setting it to [DragStartBehavior.down] will make
|
|
||||||
/// drag behavior feel slightly more reactive.
|
|
||||||
///
|
|
||||||
/// By default, the drag start behavior is [DragStartBehavior.start].
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
|
|
||||||
/// Builds widgets showing abbreviated days of week. The first widget in the
|
|
||||||
/// returned list corresponds to the first day of week for the current locale.
|
|
||||||
///
|
|
||||||
/// Examples:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// ┌ Sunday is the first day of week in the US (en_US)
|
|
||||||
/// |
|
|
||||||
/// S M T W T F S <-- the returned list contains these widgets
|
|
||||||
/// _ _ _ _ _ 1 2
|
|
||||||
/// 3 4 5 6 7 8 9
|
|
||||||
///
|
|
||||||
/// ┌ But it's Monday in the UK (en_GB)
|
|
||||||
/// |
|
|
||||||
/// M T W T F S S <-- the returned list contains these widgets
|
|
||||||
/// _ _ _ _ 1 2 3
|
|
||||||
/// 4 5 6 7 8 9 10
|
|
||||||
/// ```
|
|
||||||
List<Widget> _getDayHeaders(TextStyle? headerStyle, MaterialLocalizations localizations) {
|
|
||||||
final List<Widget> result = <Widget>[];
|
|
||||||
for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
|
|
||||||
final String weekday = localizations.narrowWeekdays[i];
|
|
||||||
result.add(ExcludeSemantics(
|
|
||||||
child: Center(child: Text(weekday, style: headerStyle)),
|
|
||||||
));
|
|
||||||
if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not use this directly - call getDaysInMonth instead.
|
|
||||||
static const List<int> _daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
||||||
|
|
||||||
/// Returns the number of days in a month, according to the proleptic
|
|
||||||
/// Gregorian calendar.
|
|
||||||
///
|
|
||||||
/// This applies the leap year logic introduced by the Gregorian reforms of
|
|
||||||
/// 1582. It will not give valid results for dates prior to that time.
|
|
||||||
static int getDaysInMonth(int year, int month) {
|
|
||||||
if (month == DateTime.february) {
|
|
||||||
final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
|
|
||||||
if (isLeapYear)
|
|
||||||
return 29;
|
|
||||||
return 28;
|
|
||||||
}
|
|
||||||
return _daysInMonth[month - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes the offset from the first day of week that the first day of the
|
|
||||||
/// [month] falls on.
|
|
||||||
///
|
|
||||||
/// For example, September 1, 2017 falls on a Friday, which in the calendar
|
|
||||||
/// localized for United States English appears as:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// S M T W T F S
|
|
||||||
/// _ _ _ _ _ 1 2
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The offset for the first day of the months is the number of leading blanks
|
|
||||||
/// in the calendar, i.e. 5.
|
|
||||||
///
|
|
||||||
/// The same date localized for the Russian calendar has a different offset,
|
|
||||||
/// because the first day of week is Monday rather than Sunday:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// M T W T F S S
|
|
||||||
/// _ _ _ _ 1 2 3
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// So the offset is 4, rather than 5.
|
|
||||||
///
|
|
||||||
/// This code consolidates the following:
|
|
||||||
///
|
|
||||||
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1
|
|
||||||
/// falling on Monday.
|
|
||||||
/// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
|
|
||||||
/// into the [MaterialLocalizations.narrowWeekdays] list.
|
|
||||||
/// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
|
|
||||||
/// days of week, always starting with Sunday and ending with Saturday.
|
|
||||||
int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) {
|
|
||||||
// 0-based day of week, with 0 representing Monday.
|
|
||||||
final int weekdayFromMonday = DateTime(year, month).weekday - 1;
|
|
||||||
// 0-based day of week, with 0 representing Sunday.
|
|
||||||
final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex;
|
|
||||||
// firstDayOfWeekFromSunday recomputed to be Monday-based
|
|
||||||
final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
|
|
||||||
// Number of days between the first day of week appearing on the calendar,
|
|
||||||
// and the day corresponding to the 1-st of the month.
|
|
||||||
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ThemeData themeData = Theme.of(context);
|
|
||||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
|
||||||
final int year = displayedMonth.year;
|
|
||||||
final int month = displayedMonth.month;
|
|
||||||
final int daysInMonth = getDaysInMonth(year, month);
|
|
||||||
final int firstDayOffset = _computeFirstDayOffset(year, month, localizations);
|
|
||||||
final List<Widget> labels = <Widget>[
|
|
||||||
..._getDayHeaders(themeData.textTheme.caption, localizations),
|
|
||||||
];
|
|
||||||
for (int i = 0; true; i += 1) {
|
|
||||||
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
|
|
||||||
// a leap year.
|
|
||||||
final int day = i - firstDayOffset + 1;
|
|
||||||
if (day > daysInMonth)
|
|
||||||
break;
|
|
||||||
if (day < 1) {
|
|
||||||
labels.add(Container());
|
|
||||||
} else {
|
|
||||||
final DateTime dayToBuild = DateTime(year, month, day);
|
|
||||||
final bool disabled = dayToBuild.isAfter(lastDate)
|
|
||||||
|| dayToBuild.isBefore(firstDate)
|
|
||||||
|| (selectableDayPredicate != null && !selectableDayPredicate!(dayToBuild));
|
|
||||||
|
|
||||||
BoxDecoration? decoration;
|
|
||||||
TextStyle? itemStyle = themeData.textTheme.bodyText2;
|
|
||||||
|
|
||||||
final bool isSelectedDay = selectedDate.year == year && selectedDate.month == month && selectedDate.day == day;
|
|
||||||
if (isSelectedDay) {
|
|
||||||
// The selected day gets a circle background highlight, and a contrasting text color.
|
|
||||||
itemStyle = themeData.textTheme.bodyText1;
|
|
||||||
decoration = BoxDecoration(
|
|
||||||
color: themeData.colorScheme.secondary,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
);
|
|
||||||
} else if (disabled) {
|
|
||||||
itemStyle = themeData.textTheme.bodyText2!.copyWith(color: themeData.disabledColor);
|
|
||||||
} else if (currentDate.year == year && currentDate.month == month && currentDate.day == day) {
|
|
||||||
// The current day gets a different text color.
|
|
||||||
itemStyle = themeData.textTheme.bodyText1!.copyWith(color: themeData.colorScheme.secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget dayWidget = Container(
|
|
||||||
decoration: decoration,
|
|
||||||
child: Center(
|
|
||||||
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)}',
|
|
||||||
selected: isSelectedDay,
|
|
||||||
sortKey: OrdinalSortKey(day.toDouble()),
|
|
||||||
child: ExcludeSemantics(
|
|
||||||
child: Text(localizations.formatDecimal(day), style: itemStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!disabled) {
|
|
||||||
dayWidget = GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
onChanged(dayToBuild);
|
|
||||||
},
|
|
||||||
dragStartBehavior: dragStartBehavior,
|
|
||||||
child: dayWidget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
labels.add(dayWidget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
height: _kDayPickerRowHeight,
|
|
||||||
child: Center(
|
|
||||||
child: ExcludeSemantics(
|
|
||||||
child: Text(
|
|
||||||
localizations.formatMonthYear(displayedMonth),
|
|
||||||
style: themeData.textTheme.subtitle1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: GridView.custom(
|
|
||||||
gridDelegate: _kDayPickerGridDelegate,
|
|
||||||
childrenDelegate: SliverChildListDelegate(labels, addRepaintBoundaries: false),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scrollable list of months to allow picking a month.
|
|
||||||
///
|
|
||||||
/// Shows the days of each month in a rectangular grid with one column for each
|
|
||||||
/// day of the week.
|
|
||||||
///
|
|
||||||
/// The month picker widget is rarely used directly. Instead, consider using
|
|
||||||
/// [showDatePicker], which creates a date picker dialog.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [showDatePicker], which shows a dialog that contains a material design
|
|
||||||
/// date picker.
|
|
||||||
/// * [showTimePicker], which shows a dialog that contains a material design
|
|
||||||
/// time picker.
|
|
||||||
///
|
|
||||||
@Deprecated(
|
|
||||||
'Use CalendarDatePicker instead. '
|
|
||||||
'This feature was deprecated after v1.26.0-18.0.pre.',
|
|
||||||
)
|
|
||||||
class MonthPicker extends StatefulWidget {
|
|
||||||
/// Creates a month picker.
|
|
||||||
///
|
|
||||||
/// Rarely used directly. Instead, typically used as part of the dialog shown
|
|
||||||
/// by [showDatePicker].
|
|
||||||
@Deprecated(
|
|
||||||
'Use CalendarDatePicker instead. '
|
|
||||||
'This feature was deprecated after v1.26.0-18.0.pre.',
|
|
||||||
)
|
|
||||||
MonthPicker({
|
|
||||||
Key? key,
|
|
||||||
required this.selectedDate,
|
|
||||||
required this.onChanged,
|
|
||||||
required this.firstDate,
|
|
||||||
required this.lastDate,
|
|
||||||
this.selectableDayPredicate,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
}) : assert(selectedDate != null),
|
|
||||||
assert(onChanged != null),
|
|
||||||
assert(!firstDate.isAfter(lastDate)),
|
|
||||||
assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// The currently selected date.
|
|
||||||
///
|
|
||||||
/// This date is highlighted in the picker.
|
|
||||||
final DateTime selectedDate;
|
|
||||||
|
|
||||||
/// Called when the user picks a month.
|
|
||||||
final ValueChanged<DateTime> onChanged;
|
|
||||||
|
|
||||||
/// The earliest date the user is permitted to pick.
|
|
||||||
final DateTime firstDate;
|
|
||||||
|
|
||||||
/// The latest date the user is permitted to pick.
|
|
||||||
final DateTime lastDate;
|
|
||||||
|
|
||||||
/// Optional user supplied predicate function to customize selectable days.
|
|
||||||
final SelectableDayPredicate? selectableDayPredicate;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MonthPicker> createState() => _MonthPickerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin {
|
|
||||||
static final Animatable<double> _chevronOpacityTween = Tween<double>(begin: 1.0, end: 0.0)
|
|
||||||
.chain(CurveTween(curve: Curves.easeInOut));
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
// Initially display the pre-selected date.
|
|
||||||
final int monthPage = _monthDelta(widget.firstDate, widget.selectedDate);
|
|
||||||
_dayPickerController = PageController(initialPage: monthPage);
|
|
||||||
_handleMonthPageChanged(monthPage);
|
|
||||||
_updateCurrentDate();
|
|
||||||
|
|
||||||
// Setup the fade animation for chevrons
|
|
||||||
_chevronOpacityController = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 250), vsync: this,
|
|
||||||
);
|
|
||||||
_chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(MonthPicker oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.selectedDate != oldWidget.selectedDate) {
|
|
||||||
final int monthPage = _monthDelta(widget.firstDate, widget.selectedDate);
|
|
||||||
_dayPickerController = PageController(initialPage: monthPage);
|
|
||||||
_handleMonthPageChanged(monthPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialLocalizations? localizations;
|
|
||||||
TextDirection? textDirection;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
localizations = MaterialLocalizations.of(context);
|
|
||||||
textDirection = Directionality.of(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
late DateTime _todayDate;
|
|
||||||
late DateTime _currentDisplayedMonthDate;
|
|
||||||
Timer? _timer;
|
|
||||||
late PageController _dayPickerController;
|
|
||||||
late AnimationController _chevronOpacityController;
|
|
||||||
late Animation<double> _chevronOpacityAnimation;
|
|
||||||
|
|
||||||
void _updateCurrentDate() {
|
|
||||||
_todayDate = DateTime.now();
|
|
||||||
final DateTime tomorrow = DateTime(_todayDate.year, _todayDate.month, _todayDate.day + 1);
|
|
||||||
Duration timeUntilTomorrow = tomorrow.difference(_todayDate);
|
|
||||||
timeUntilTomorrow += const Duration(seconds: 1); // so we don't miss it by rounding
|
|
||||||
_timer?.cancel();
|
|
||||||
_timer = Timer(timeUntilTomorrow, () {
|
|
||||||
setState(() {
|
|
||||||
_updateCurrentDate();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _monthDelta(DateTime startDate, DateTime endDate) {
|
|
||||||
return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add months to a month truncated date.
|
|
||||||
DateTime _addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
|
|
||||||
return DateTime(monthDate.year + monthsToAdd ~/ 12, monthDate.month + monthsToAdd % 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildItems(BuildContext context, int index) {
|
|
||||||
final DateTime month = _addMonthsToMonthDate(widget.firstDate, index);
|
|
||||||
return DayPicker(
|
|
||||||
key: ValueKey<DateTime>(month),
|
|
||||||
selectedDate: widget.selectedDate,
|
|
||||||
currentDate: _todayDate,
|
|
||||||
onChanged: widget.onChanged,
|
|
||||||
firstDate: widget.firstDate,
|
|
||||||
lastDate: widget.lastDate,
|
|
||||||
displayedMonth: month,
|
|
||||||
selectableDayPredicate: widget.selectableDayPredicate,
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleNextMonth() {
|
|
||||||
if (!_isDisplayingLastMonth) {
|
|
||||||
SemanticsService.announce(localizations!.formatMonthYear(_nextMonthDate), textDirection!);
|
|
||||||
_dayPickerController.nextPage(duration: _kMonthScrollDuration, curve: Curves.ease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePreviousMonth() {
|
|
||||||
if (!_isDisplayingFirstMonth) {
|
|
||||||
SemanticsService.announce(localizations!.formatMonthYear(_previousMonthDate), textDirection!);
|
|
||||||
_dayPickerController.previousPage(duration: _kMonthScrollDuration, curve: Curves.ease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True if the earliest allowable month is displayed.
|
|
||||||
bool get _isDisplayingFirstMonth {
|
|
||||||
return !_currentDisplayedMonthDate.isAfter(DateTime(widget.firstDate.year, widget.firstDate.month));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True if the latest allowable month is displayed.
|
|
||||||
bool get _isDisplayingLastMonth {
|
|
||||||
return !_currentDisplayedMonthDate.isBefore(DateTime(widget.lastDate.year, widget.lastDate.month));
|
|
||||||
}
|
|
||||||
|
|
||||||
late DateTime _previousMonthDate;
|
|
||||||
late DateTime _nextMonthDate;
|
|
||||||
|
|
||||||
void _handleMonthPageChanged(int monthPage) {
|
|
||||||
setState(() {
|
|
||||||
_previousMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage - 1);
|
|
||||||
_currentDisplayedMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage);
|
|
||||||
_nextMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage + 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
// The month picker just adds month navigation to the day picker, so make
|
|
||||||
// it the same height as the DayPicker
|
|
||||||
height: _kMaxDayPickerHeight,
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
Semantics(
|
|
||||||
sortKey: _MonthPickerSortKey.calendar,
|
|
||||||
child: NotificationListener<ScrollStartNotification>(
|
|
||||||
onNotification: (_) {
|
|
||||||
_chevronOpacityController.forward();
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: NotificationListener<ScrollEndNotification>(
|
|
||||||
onNotification: (_) {
|
|
||||||
_chevronOpacityController.reverse();
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: PageView.builder(
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
key: ValueKey<DateTime>(widget.selectedDate),
|
|
||||||
controller: _dayPickerController,
|
|
||||||
itemCount: _monthDelta(widget.firstDate, widget.lastDate) + 1,
|
|
||||||
itemBuilder: _buildItems,
|
|
||||||
onPageChanged: _handleMonthPageChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PositionedDirectional(
|
|
||||||
top: 0.0,
|
|
||||||
start: 8.0,
|
|
||||||
child: Semantics(
|
|
||||||
sortKey: _MonthPickerSortKey.previousMonth,
|
|
||||||
child: FadeTransition(
|
|
||||||
opacity: _chevronOpacityAnimation,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.chevron_left),
|
|
||||||
tooltip: _isDisplayingFirstMonth ? null : '${localizations!.previousMonthTooltip} ${localizations!.formatMonthYear(_previousMonthDate)}',
|
|
||||||
onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PositionedDirectional(
|
|
||||||
top: 0.0,
|
|
||||||
end: 8.0,
|
|
||||||
child: Semantics(
|
|
||||||
sortKey: _MonthPickerSortKey.nextMonth,
|
|
||||||
child: FadeTransition(
|
|
||||||
opacity: _chevronOpacityAnimation,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.chevron_right),
|
|
||||||
tooltip: _isDisplayingLastMonth ? null : '${localizations!.nextMonthTooltip} ${localizations!.formatMonthYear(_nextMonthDate)}',
|
|
||||||
onPressed: _isDisplayingLastMonth ? null : _handleNextMonth,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_timer?.cancel();
|
|
||||||
_chevronOpacityController.dispose();
|
|
||||||
_dayPickerController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines semantic traversal order of the top-level widgets inside the month
|
|
||||||
// picker.
|
|
||||||
class _MonthPickerSortKey extends OrdinalSortKey {
|
|
||||||
const _MonthPickerSortKey(double order) : super(order);
|
|
||||||
|
|
||||||
static const _MonthPickerSortKey previousMonth = _MonthPickerSortKey(1.0);
|
|
||||||
static const _MonthPickerSortKey nextMonth = _MonthPickerSortKey(2.0);
|
|
||||||
static const _MonthPickerSortKey calendar = _MonthPickerSortKey(3.0);
|
|
||||||
}
|
|
@ -96,10 +96,10 @@ abstract class MaterialLocalizations {
|
|||||||
/// The tooltip for the more button on an overflowing text selection menu.
|
/// The tooltip for the more button on an overflowing text selection menu.
|
||||||
String get moreButtonTooltip;
|
String get moreButtonTooltip;
|
||||||
|
|
||||||
/// The tooltip for the [MonthPicker]'s "next month" button.
|
/// The tooltip for the [CalendarDatePicker]'s "next month" button.
|
||||||
String get nextMonthTooltip;
|
String get nextMonthTooltip;
|
||||||
|
|
||||||
/// The tooltip for the [MonthPicker]'s "previous month" button.
|
/// The tooltip for the [CalendarDatePicker]'s "previous month" button.
|
||||||
String get previousMonthTooltip;
|
String get previousMonthTooltip;
|
||||||
|
|
||||||
/// The tooltip for the [PaginatedDataTable]'s "first page" button.
|
/// The tooltip for the [PaginatedDataTable]'s "first page" button.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user