Updated documentation, used better naming convention extension methods, refactored error dialog when choosing a disabled time

This commit is contained in:
Thomas Klein Langenhorst 2022-09-02 17:04:47 +02:00
parent 6b3f1f5d12
commit fc42f21b1b
10 changed files with 133 additions and 133 deletions

View file

@ -28,9 +28,10 @@ class MyApp extends StatelessWidget {
class DatePickerDemo extends StatelessWidget { class DatePickerDemo extends StatelessWidget {
const DatePickerDemo({Key? key}) : super(key: key); const DatePickerDemo({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DateTimePicker(dateTimePickerTheme: const DateTimePickerTheme(),); return DateTimePicker(
dateTimePickerTheme: const DateTimePickerTheme(),
);
} }
} }

View file

@ -8,52 +8,22 @@ import 'package:flutter_date_time_picker/src/widgets/week_date_time_picker/week_
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
/// A widget that displays a date picker from a sheet form the top of the screen. /// A widget that displays a date picker from a sheet form the top of the screen.
/// This sheet displays initialy displays a week of days but can be dragged down to show full months. /// This sheet displays initially displays a week but can be dragged down to show a full month.
/// Both views can be dragged sideways to show the next or previous week/month. /// Both views can be dragged sideways to show the next or previous week/month.
/// ///
/// The child will be the [Widget] that is displayed underneath the date picker in the stack.
///
///
/// [initialDate] indicates the starting date. Default is [DateTime.now()
///
/// [pickTime] is a [bool] that determines if the user is able to pick a time after picking a date.
/// true will always tirgger a [TimePickerDialog].
/// false will nvever trigger a [TimePickerDialog]. This is default.
///
/// [use24HourFormat] is a [bool] to set de clock on [TimePickerDialog] to a fixed 24 or 12-hour format.
/// By default this gets determined by the [Locale] on the device.
///
/// [dateTimePickerTheme] is used the set the global theme of the [DateTimePicker]
///
/// The header [Widget] will be displayed above the date picker in the modal sheet in a column.
///
/// [onTapDay] is a callback that provides the taped date as a [DateTime] object.
///
/// [highlightToday] is a [bool] that determines which day shall we highlighted.
/// true will always highlight the current date. This is standard.
/// false will highlight the currently selected date by either the initial date or the one chosen by the user.
/// final Widget? child;
///
/// [markedDates] contain the dates [DateTime] that will be marked in the picker by a small dot.
///
/// [disabledDates] contain the dates [DateTime] that will be disabled and cannot be interacted with whatsoever.
///
/// [disabledTimes] contain the time [TimeOfDay] that cannot be picked in the [TimePickerDialog].
///
///
/// Example: /// Example:
/// ```dart /// ```dart
/// DatePicker( /// DatePicker(
/// dateTimePickerTheme: const DateTimePickerTheme() /// dateTimePickerTheme: const DateTimePickerTheme()
/// initialDate: selectedDate, /// initialDate: selectedDate,
/// highlightToday: false, /// highlightToday: true,
/// onTapDay: (date) { /// onTapDay: (date) {
/// setState(() { /// setState(() {
/// selectedDate = date; /// selectedDate = date;
/// }); /// });
/// }, /// },
/// markedDates: [ /// markedDates: [
/// DateTime(2022, 7, 22), /// DateTime(2022, 3, 14),
/// ], /// ],
/// header: Container( /// header: Container(
/// height: 100, /// height: 100,
@ -68,7 +38,7 @@ import 'package:intl/date_symbol_data_local.dart';
/// height: 34, /// height: 34,
/// child: Center( /// child: Center(
/// child: Text( /// child: Text(
/// 'Persoonlijk', /// 'Personal calendar',
/// style: TextStyle( /// style: TextStyle(
/// fontSize: 16, /// fontSize: 16,
/// fontWeight: FontWeight.w900, /// fontWeight: FontWeight.w900,
@ -97,7 +67,7 @@ import 'package:intl/date_symbol_data_local.dart';
/// ), /// ),
/// child: const Center( /// child: const Center(
/// child: Text( /// child: Text(
/// 'Teamplanning', /// 'Work calendar',
/// style: TextStyle( /// style: TextStyle(
/// color: Colors.white, /// color: Colors.white,
/// fontSize: 16, /// fontSize: 16,
@ -113,15 +83,7 @@ import 'package:intl/date_symbol_data_local.dart';
/// margin: const EdgeInsets.only( /// margin: const EdgeInsets.only(
/// top: 195, /// top: 195,
/// ), /// ),
/// child: ShellRoster( /// child: HolidayRoster(),
/// startHour: 0,
/// endHour: 24,
/// blocks: [
/// for (Map<String, TimeOfDay> block in blocks) ...[
/// getBlock(block),
/// ],
/// ],
/// ),
/// ), /// ),
/// ), /// ),
///``` ///```
@ -131,6 +93,7 @@ class DateTimePicker extends StatefulWidget {
this.header, this.header,
this.onTapDay, this.onTapDay,
this.highlightToday = true, this.highlightToday = true,
this.wrongTimeDialog,
bool? use24HourFormat, bool? use24HourFormat,
this.pickTime = false, this.pickTime = false,
this.initialDate, this.initialDate,
@ -140,19 +103,44 @@ class DateTimePicker extends StatefulWidget {
this.child, this.child,
super.key, super.key,
}) { }) {
alwaysUse24HourFormat = use24HourFormat ?? useTimeFormatBasedOnLocale(); alwaysUse24HourFormat = use24HourFormat ?? _useTimeFormatBasedOnLocale();
} }
/// The child contained by the DatePicker.
final Widget? child; final Widget? child;
/// A [Widget] to display when the user picks a disabled time in the [TimePickerDialog]
final Widget? wrongTimeDialog;
/// Visual properties for the [DateTimePicker]
final DateTimePickerTheme dateTimePickerTheme; final DateTimePickerTheme dateTimePickerTheme;
/// Widget shown at the top of the [DateTimePicker]
final Widget? header; final Widget? header;
/// Callback that provides the date tapped on as a [DateTime] object.
final Function(DateTime)? onTapDay; final Function(DateTime)? onTapDay;
/// Whether the current day should be highlighted in the [DateTimePicker]
final bool highlightToday; final bool highlightToday;
/// a [bool] to set de clock on [TimePickerDialog] to a fixed 24 or 12-hour format.
/// By default this gets determined by the [Locale] on the device.
late final bool alwaysUse24HourFormat; late final bool alwaysUse24HourFormat;
/// [pickTime] is a [bool] that determines if the user is able to pick a time after picking a date usring the [TimePickerDialog].
final bool pickTime; final bool pickTime;
/// indicates the starting date. Default is [DateTime.now()]
final DateTime? initialDate; final DateTime? initialDate;
/// [markedDates] contain the dates [DateTime] that will be marked in the [DateTimePicker] by a small dot.
final List<DateTime>? markedDates; final List<DateTime>? markedDates;
/// a [List] of [DateTime] objects that will be disabled and cannot be interacted with whatsoever.
final List<DateTime>? disabledDates; final List<DateTime>? disabledDates;
/// a [List] of [TimeOfDay] objects that cannot be picked in the [TimePickerDialog].
final List<TimeOfDay>? disabledTimes; final List<TimeOfDay>? disabledTimes;
@override @override
@ -265,7 +253,7 @@ class _DateTimePickerState extends State<DateTimePicker> {
} }
} }
bool useTimeFormatBasedOnLocale() { bool _useTimeFormatBasedOnLocale() {
// Get LocaleName of current platform and split language- and countryCode in 2 List values. // Get LocaleName of current platform and split language- and countryCode in 2 List values.
List<String> deviceLocale = Platform.localeName.split('_'); List<String> deviceLocale = Platform.localeName.split('_');

View file

@ -1,4 +1,5 @@
/// Defines the shape of a specific date.
enum DateBoxShape { enum DateBoxShape {
circle, circle,
rectangle, rectangle,

View file

@ -1,16 +1,17 @@
extension DateTimeExtension on DateTime { extension DateTimeExtension on DateTime {
// Check if the current date is the same as the given date /// Check if the current [DateTime] is the same as the given [selectedDate]
bool sameDayAs(DateTime selectedDate) { bool equals(DateTime selectedDate) {
return selectedDate.day == day && return selectedDate.day == day &&
selectedDate.month == month && selectedDate.month == month &&
selectedDate.year == year; selectedDate.year == year;
} }
// Check if the current date is contained in the given list /// Check if the current [DateTime] contains any the given [dates]
bool dateContainedIn(List<DateTime> dates) { bool containsAny(List<DateTime> dates) {
return dates.any((element) => element.sameDayAs(this)); return dates.any((element) => element.equals(this));
} }
// Return a [List] of [DateTime] objects of the week the current [DateTime] is in.
List<DateTime> daysOfWeek() { List<DateTime> daysOfWeek() {
var startFrom = subtract(Duration(days: weekday)); var startFrom = subtract(Duration(days: weekday));
return List.generate( return List.generate(
@ -22,9 +23,11 @@ extension DateTimeExtension on DateTime {
); );
} }
/// Determine if a certain [year] of a [DateTime] object is a leap year.
bool get isLeapYear => bool get isLeapYear =>
(year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
/// Returns the amount of days in the current month of the [DateTime] object
int daysInMonth() { int daysInMonth() {
late int amountOfDays; late int amountOfDays;

View file

@ -1,11 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
extension TimeOfDayExtension on TimeOfDay { extension TimeOfDayExtension on TimeOfDay {
bool timeContainedIn(List<TimeOfDay> times) { /// Check if the current [TimeOfDay] contains any the given [times]
return times.any((element) => element.sameTimeAs(this)); bool containsAny(List<TimeOfDay> times) {
return times.any((element) => element.equals(this));
} }
bool sameTimeAs(TimeOfDay selectedTime) { /// Check if the current [TimeOfDay] is the same as the given [selectedTime]
bool equals(TimeOfDay selectedTime) {
return selectedTime.hour == hour && selectedTime.minute == minute; return selectedTime.hour == hour && selectedTime.minute == minute;
} }
} }

View file

@ -10,6 +10,7 @@ class DateTimePickerController extends ChangeNotifier {
required this.browsingDate, required this.browsingDate,
required this.selectedDate, required this.selectedDate,
this.header, this.header,
this.wrongTimeDialog,
this.markedDates, this.markedDates,
this.disabledDates, this.disabledDates,
this.disabledTimes, this.disabledTimes,
@ -23,6 +24,8 @@ class DateTimePickerController extends ChangeNotifier {
final Widget? header; final Widget? header;
final Widget? wrongTimeDialog;
final DateTimePickerTheme theme; final DateTimePickerTheme theme;
final List<DateTime>? markedDates; final List<DateTime>? markedDates;

View file

@ -82,31 +82,34 @@ class MonthDateTimePicker extends StatelessWidget {
context, dateTimePickerController); context, dateTimePickerController);
} }
if (timeOfDay != null && if (dateTimePickerController.wrongTimeDialog != null) {
timeOfDay.timeContainedIn( if (timeOfDay != null &&
dateTimePickerController.disabledTimes ?? [])) { timeOfDay.containsAny(
showDialog( dateTimePickerController.disabledTimes ?? [],
context: context, )) {
builder: (context) => AlertDialog( showDialog(
title: const Text('Verkeerde tijd gekozen'), context: context,
content: SingleChildScrollView( builder: (context) => AlertDialog(
child: ListBody( title: const Text('Verkeerde tijd gekozen'),
children: const <Widget>[ content: SingleChildScrollView(
Text( child: ListBody(
'De tijd die u wilt kiezen, is niet mogelijk, maak een andere keuze.'), children: const <Widget>[
], Text(
), 'De tijd die u wilt kiezen, is niet mogelijk, maak een andere keuze.'),
), ],
actions: <Widget>[ ),
TextButton( ),
child: const Text('OK'), actions: <Widget>[
onPressed: () { TextButton(
Navigator.pop(context); child: const Text('OK'),
}, onPressed: () {
), Navigator.pop(context);
], },
), ),
); ],
),
);
}
} }
DateTime selectedDateTime = DateTime( DateTime selectedDateTime = DateTime(
@ -170,7 +173,7 @@ class MonthDateTimePicker extends StatelessWidget {
date.year, date.year,
date.month, date.month,
index + 1 - daysToSkip, index + 1 - daysToSkip,
).sameDayAs( ).equals(
dateTimePickerController.highlightToday dateTimePickerController.highlightToday
? DateTime.now() ? DateTime.now()
: dateTimePickerController.selectedDate, : dateTimePickerController.selectedDate,
@ -217,7 +220,7 @@ class MonthDateTimePicker extends StatelessWidget {
date.year, date.year,
date.month, date.month,
index + 1 - daysToSkip, index + 1 - daysToSkip,
).dateContainedIn( ).containsAny(
dateTimePickerController.disabledDates ?? [], dateTimePickerController.disabledDates ?? [],
); );
} }
@ -227,7 +230,7 @@ class MonthDateTimePicker extends StatelessWidget {
date.year, date.year,
date.month, date.month,
index + 1 - daysToSkip, index + 1 - daysToSkip,
).sameDayAs(dateTimePickerController.selectedDate); ).equals(dateTimePickerController.selectedDate);
} }
bool shouldMark(int index, int daysToSkip) { bool shouldMark(int index, int daysToSkip) {
@ -235,7 +238,7 @@ class MonthDateTimePicker extends StatelessWidget {
date.year, date.year,
date.month, date.month,
index + 1 - daysToSkip, index + 1 - daysToSkip,
).sameDayAs( ).equals(
dateTimePickerController.highlightToday dateTimePickerController.highlightToday
? DateTime.now() ? DateTime.now()
: dateTimePickerController.selectedDate, : dateTimePickerController.selectedDate,
@ -244,7 +247,7 @@ class MonthDateTimePicker extends StatelessWidget {
date.year, date.year,
date.month, date.month,
index + 1 - daysToSkip, index + 1 - daysToSkip,
).dateContainedIn( ).containsAny(
dateTimePickerController.markedDates ?? [], dateTimePickerController.markedDates ?? [],
); );
} }

View file

@ -48,49 +48,48 @@ class WeekDateTimePicker extends StatelessWidget {
DateTime selectedDate = date.daysOfWeek()[index]; DateTime selectedDate = date.daysOfWeek()[index];
timeOfDay = const TimeOfDay(hour: 0, minute: 0);
if (dateTimePickerController.pickTime) { if (dateTimePickerController.pickTime) {
timeOfDay = await displayTimePicker( timeOfDay = await displayTimePicker(
context, dateTimePickerController); context, dateTimePickerController);
} }
if (timeOfDay != null && if (dateTimePickerController.wrongTimeDialog != null) {
timeOfDay.timeContainedIn( if (timeOfDay != null &&
dateTimePickerController.disabledTimes ?? [])) { timeOfDay.containsAny(
showDialog( dateTimePickerController.disabledTimes ?? [],
)) {
showDialog(
context: context, context: context,
builder: (context) { builder: (context) => AlertDialog(
return AlertDialog( title: const Text('Verkeerde tijd gekozen'),
title: const Text('Verkeerde tijd gekozen'), content: SingleChildScrollView(
content: SingleChildScrollView( child: ListBody(
child: ListBody( children: const <Widget>[
children: const <Widget>[ Text(
Text( 'De tijd die u wilt kiezen, is niet mogelijk, maak een andere keuze.'),
'De tijd die u wilt kiezen, is niet mogelijk, maak een andere keuze.'), ],
],
),
), ),
actions: <Widget>[ ),
TextButton( actions: <Widget>[
child: const Text('OK'), TextButton(
onPressed: () { child: const Text('OK'),
Navigator.pop(context); onPressed: () {
}, Navigator.pop(context);
), },
], ),
); ],
}); ),
} else { );
timeOfDay = const TimeOfDay( }
hour: 0,
minute: 0,
);
} }
DateTime selectedDateTime = DateTime( DateTime selectedDateTime = DateTime(
selectedDate.year, selectedDate.year,
selectedDate.month, selectedDate.month,
selectedDate.day, selectedDate.day,
timeOfDay.hour, timeOfDay!.hour,
timeOfDay.minute, timeOfDay.minute,
); );
@ -191,7 +190,7 @@ class WeekDateTimePicker extends StatelessWidget {
} }
bool shouldHighlight(int index) { bool shouldHighlight(int index) {
return date.daysOfWeek().elementAt(index).sameDayAs( return date.daysOfWeek().elementAt(index).equals(
dateTimePickerController.highlightToday dateTimePickerController.highlightToday
? DateTime.now() ? DateTime.now()
: dateTimePickerController.selectedDate, : dateTimePickerController.selectedDate,
@ -202,18 +201,18 @@ class WeekDateTimePicker extends StatelessWidget {
return date return date
.daysOfWeek() .daysOfWeek()
.elementAt(index) .elementAt(index)
.sameDayAs(dateTimePickerController.selectedDate); .equals(dateTimePickerController.selectedDate);
} }
bool isDisabled(int index) { bool isDisabled(int index) {
return date return date
.daysOfWeek() .daysOfWeek()
.elementAt(index) .elementAt(index)
.dateContainedIn(dateTimePickerController.disabledDates ?? []); .containsAny(dateTimePickerController.disabledDates ?? []);
} }
bool shouldMark(int index) { bool shouldMark(int index) {
return !date.daysOfWeek().elementAt(index).sameDayAs( return !date.daysOfWeek().elementAt(index).equals(
dateTimePickerController.highlightToday dateTimePickerController.highlightToday
? DateTime.now() ? DateTime.now()
: dateTimePickerController.selectedDate, : dateTimePickerController.selectedDate,
@ -221,7 +220,7 @@ class WeekDateTimePicker extends StatelessWidget {
date date
.daysOfWeek() .daysOfWeek()
.elementAt(index) .elementAt(index)
.dateContainedIn(dateTimePickerController.markedDates ?? []); .containsAny(dateTimePickerController.markedDates ?? []);
} }
BorderRadius _determineBorderRadius( BorderRadius _determineBorderRadius(

View file

@ -3,16 +3,16 @@ import 'package:flutter_date_time_picker/src/extensions/date_time.dart';
void main() { void main() {
group('DateTimeExtension', () { group('DateTimeExtension', () {
test('sameDayAs() should return true if the same date, if not false', () { test('equals() should return true if the same date, if not false', () {
expect(DateTime(2022, 01, 01).sameDayAs(DateTime(2022, 01, 01)), true); expect(DateTime(2022, 01, 01).equals(DateTime(2022, 01, 01)), true);
expect(DateTime(2022, 01, 01).sameDayAs(DateTime(2022, 01, 02)), false); expect(DateTime(2022, 01, 01).equals(DateTime(2022, 01, 02)), false);
}); });
test( test(
'dateContainedIn() should return a boolean if the date is found in a list of dates or not', 'containsAny() should return a boolean if the date is found in a list of dates or not',
() { () {
expect( expect(
DateTime(2022, 01, 01).dateContainedIn([ DateTime(2022, 01, 01).containsAny([
DateTime(2022, 01, 01), DateTime(2022, 01, 01),
DateTime(2022, 01, 02), DateTime(2022, 01, 02),
DateTime(2022, 01, 03) DateTime(2022, 01, 03)
@ -20,7 +20,7 @@ void main() {
true); true);
expect( expect(
DateTime(2022, 01, 01).dateContainedIn([ DateTime(2022, 01, 01).containsAny([
DateTime(2022, 01, 02), DateTime(2022, 01, 02),
DateTime(2022, 01, 03), DateTime(2022, 01, 03),
DateTime(2022, 01, 04) DateTime(2022, 01, 04)

View file

@ -4,22 +4,22 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
group('TimeOfDayExtension', () { group('TimeOfDayExtension', () {
test('sameTimeAs() should return true if the same time, if not false', () { test('equals() should return true if the same time, if not false', () {
expect( expect(
const TimeOfDay(hour: 12, minute: 0) const TimeOfDay(hour: 12, minute: 0)
.sameTimeAs(const TimeOfDay(hour: 12, minute: 0)), .equals(const TimeOfDay(hour: 12, minute: 0)),
true); true);
expect( expect(
const TimeOfDay(hour: 13, minute: 0) const TimeOfDay(hour: 13, minute: 0)
.sameTimeAs(const TimeOfDay(hour: 12, minute: 0)), .equals(const TimeOfDay(hour: 12, minute: 0)),
false); false);
}); });
test( test(
'timeContainedIn() should return a boolean if the time is found in a list of times or not', 'containsAny() should return a boolean if the time is found in a list of times or not',
() { () {
expect( expect(
const TimeOfDay(hour: 12, minute: 0).timeContainedIn(const [ const TimeOfDay(hour: 12, minute: 0).containsAny(const [
TimeOfDay(hour: 10, minute: 0), TimeOfDay(hour: 10, minute: 0),
TimeOfDay(hour: 11, minute: 0), TimeOfDay(hour: 11, minute: 0),
TimeOfDay(hour: 12, minute: 0) TimeOfDay(hour: 12, minute: 0)
@ -27,7 +27,7 @@ void main() {
true); true);
expect( expect(
const TimeOfDay(hour: 12, minute: 0).timeContainedIn(const [ const TimeOfDay(hour: 12, minute: 0).containsAny(const [
TimeOfDay(hour: 9, minute: 0), TimeOfDay(hour: 9, minute: 0),
TimeOfDay(hour: 10, minute: 0), TimeOfDay(hour: 10, minute: 0),
TimeOfDay(hour: 11, minute: 0) TimeOfDay(hour: 11, minute: 0)