WIP added time picker

This commit is contained in:
Thomas Klein Langenhorst 2022-08-31 08:53:34 +02:00
parent 2e7532497f
commit e61a839996
11 changed files with 465 additions and 144 deletions

View file

@ -90,13 +90,11 @@ class MyHomePage extends StatelessWidget {
// ],
// ),
// ),
markedDates: [DateTime(2022, 8, 26)],
child: Container(
margin: const EdgeInsets.only(
top: 195,
),
child: Container(),
),
disabledDates: [DateTime(2022, 8, 26), DateTime(2022, 8, 27)],
markedDates: [DateTime(2022, 8, 28), DateTime(2022, 8, 29)],
disabledTimes: const [TimeOfDay(hour: 12, minute: 0)],
pickTime: true,
child: Container(),
),
);
}

View file

@ -1,3 +1,4 @@
library flutter_date_time_picker;
export 'src/date_time_picker.dart';
export 'src/enums/date_box_shape.dart';

View file

@ -1,4 +1,7 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_date_time_picker/src/enums/date_box_shape.dart';
import 'package:flutter_date_time_picker/src/utils/date_time_picker_controller.dart';
import 'package:flutter_date_time_picker/src/widgets/month_date_time_picker.dart/month_date_time_picker_sheet.dart';
import 'package:flutter_date_time_picker/src/widgets/week_date_time_picker/week_date_time_picker_sheet.dart';
@ -108,22 +111,40 @@ import 'package:intl/date_symbol_data_local.dart';
/// ),
///```
class DateTimePicker extends StatefulWidget {
const DateTimePicker({
DateTimePicker({
required this.child,
this.weekDateBoxSize = 12,
this.monthDateBoxSize = 45,
this.header,
this.onTapDay,
this.highlightToday = true,
bool? use24HourFormat,
this.pickTime = false,
this.dateBoxSize,
this.dateBoxShape = DateBoxShape.roundedRectangle,
this.initialDate,
this.markedDates,
Key? key,
}) : super(key: key);
this.disabledDates,
this.disabledTimes,
super.key,
}) {
alwaysUse24HourFormat = use24HourFormat ?? useTimeFormatBasedOnLocale();
}
final Widget child;
final Widget? header;
final Function(DateTime)? onTapDay;
final bool highlightToday;
late final bool alwaysUse24HourFormat;
final bool pickTime;
final double? dateBoxSize;
final DateBoxShape dateBoxShape;
final double? weekDateBoxSize;
final double? monthDateBoxSize;
final DateTime? initialDate;
final List<DateTime>? markedDates;
final List<DateTime>? disabledDates;
final List<TimeOfDay>? disabledTimes;
@override
State<StatefulWidget> createState() => _DateTimePickerState();
@ -139,8 +160,13 @@ class _DateTimePickerState extends State<DateTimePicker> {
_dateTimePickerController = DateTimePickerController(
highlightToday: widget.highlightToday,
alwaysUse24HourFormat: widget.alwaysUse24HourFormat,
pickTime: widget.pickTime,
dateBoxShape: widget.dateBoxShape,
header: widget.header,
markedDates: widget.markedDates,
disabledDates: widget.disabledDates,
disabledTimes: widget.disabledTimes,
onTapDayCallBack: widget.onTapDay,
browsingDate: widget.initialDate ?? DateTime.now(),
selectedDate: widget.initialDate ?? DateTime.now(),
@ -204,10 +230,13 @@ class _DateTimePickerState extends State<DateTimePicker> {
? WeekDateTimePickerSheet(
dateTimePickerController:
_dateTimePickerController,
weekDateBoxSize: widget.weekDateBoxSize ?? 12,
)
: MonthDateTimePickerSheet(
dateTimePickerController:
_dateTimePickerController,
monthDateBoxSize:
widget.monthDateBoxSize ?? 45,
),
),
),
@ -222,3 +251,24 @@ class _DateTimePickerState extends State<DateTimePicker> {
);
}
}
bool useTimeFormatBasedOnLocale() {
// Get LocaleName of current platform and split language- and countryCode in 2 List values.
var deviceLocale = Platform.localeName.split('_');
// Make LocaleName of current platform in a Locale Object
Locale defaultLocale = Locale.fromSubtags(
languageCode: deviceLocale[0],
countryCode: deviceLocale[1],
);
// Determine Country.
switch (defaultLocale.countryCode) {
case 'NL':
return true;
case 'US':
return false;
default:
return true;
}
}

View file

@ -0,0 +1,6 @@
enum DateBoxShape {
circle,
rectangle,
roundedRectangle
}

View file

@ -1,4 +1,4 @@
extension DatePickerUtil on DateTime {
extension DateTimeExtension on DateTime {
// Check if the current date is the same as the given date
bool sameDayAs(DateTime selectedDate) {
return selectedDate.day == day &&
@ -7,7 +7,7 @@ extension DatePickerUtil on DateTime {
}
// Check if the current date is contained in the given list
bool isContainedIn(List<DateTime> dates) {
bool dateContainedIn(List<DateTime> dates) {
return dates.any((element) => element.sameDayAs(this));
}

View file

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
extension TimeOfDayExtension on TimeOfDay {
bool timeContainedIn(List<TimeOfDay> times) {
return times.any((element) => element.sameTimeAs(this));
}
bool sameTimeAs(TimeOfDay selectedTime) {
return selectedTime.hour == hour && selectedTime.minute == minute;
}
}

View file

@ -1,10 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_date_time_picker/src/enums/date_box_shape.dart';
class DateTimePickerController extends ChangeNotifier {
DateTimePickerController({
required this.highlightToday,
required this.alwaysUse24HourFormat,
required this.pickTime,
required this.dateBoxShape,
this.header,
this.markedDates,
this.disabledDates,
this.disabledTimes,
this.onTapDayCallBack,
required this.browsingDate,
required this.selectedDate,
@ -15,10 +22,16 @@ class DateTimePickerController extends ChangeNotifier {
final PageController _pageController = PageController(initialPage: 1);
final bool highlightToday;
final bool? alwaysUse24HourFormat;
final Widget? header;
final DateBoxShape dateBoxShape;
final List<DateTime>? markedDates;
final List<DateTime>? disabledDates;
final List<TimeOfDay>? disabledTimes;
final bool pickTime;
final Function(DateTime)? onTapDayCallBack;

View file

@ -1,16 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_date_time_picker/src/enums/date_box_shape.dart';
import 'package:flutter_date_time_picker/src/extensions/date_time.dart';
import 'package:flutter_date_time_picker/src/extensions/time_of_day.dart';
import 'package:flutter_date_time_picker/src/utils/date_time_picker_controller.dart';
class MonthDateTimePicker extends StatelessWidget {
const MonthDateTimePicker({
required this.date,
required this.dateTimePickerController,
required this.monthDateBoxSize,
Key? key,
}) : super(key: key);
final DateTime date;
final DateTimePickerController dateTimePickerController;
final double monthDateBoxSize;
@override
Widget build(BuildContext context) {
@ -26,69 +30,81 @@ class MonthDateTimePicker extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 30),
child: Center(
child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 5,
crossAxisCount: 7,
children: List.generate(
DateTime(date.year, date.month).daysInMonth() + daysToSkip,
DateTime(date.year, date.month).daysInMonth() +
(daysToSkip >= 7 ? 0 : daysToSkip),
(index) {
if (index < daysToSkip) {
return Container(
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.4),
border: Border.all(color: Colors.black, width: 1.5),
),
margin:
const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
height: 45,
width: 45,
child: Center(
child: Text(
(index + 1 - daysToSkip).toString(),
style: Theme.of(context).textTheme.bodyText1!.copyWith(
color: Colors.black,
),
),
),
);
late Map<String, Color> calendarColors;
int addedIndex = index;
if (daysToSkip >= 7) {
addedIndex = index + 7;
}
if (addedIndex < daysToSkip) {
return const SizedBox.shrink();
}
calendarColors = determineColors(
context,
addedIndex,
daysToSkip,
);
return GestureDetector(
onTap: isDisabled(
addedIndex,
daysToSkip,
)
? null
: () async {
TimeOfDay? timeOfDay;
// await dateTimePickerController.getDragController().animateTo(
// 0.26,
// duration: const Duration(
// milliseconds: 350,
// ),
// curve: Curves.ease,
// );
onTap: () async {
// await dateTimePickerController.getDragController().animateTo(
// 0.26,
// duration: const Duration(
// milliseconds: 350,
// ),
// curve: Curves.ease,
// );
DateTime selectedDate = DateTime(
date.year,
date.month,
addedIndex + 1 - daysToSkip,
);
dateTimePickerController.onTapDay(DateTime(
date.year,
date.month,
index + 1 - daysToSkip,
date.hour,
date.minute,
date.second,
));
},
if (dateTimePickerController.pickTime) {
timeOfDay = await displayTimePicker(
context, dateTimePickerController);
}
if (timeOfDay != null &&
timeOfDay.timeContainedIn(
dateTimePickerController.disabledTimes ?? [])) {
} else {
timeOfDay = const TimeOfDay(hour: 0, minute: 0);
}
DateTime selectedDateTime = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
timeOfDay.hour,
timeOfDay.minute,
);
dateTimePickerController.onTapDay(selectedDateTime);
},
child: Container(
margin:
const EdgeInsets.symmetric(vertical: 5, horizontal: 5),
decoration: BoxDecoration(
color:
// isDisabled()
// ? Theme.of(context).disabledColor
// : Colors.transparent,
// isSelected(index, daysToSkip)
// ? Theme.of(context).primaryColor.withOpacity(0.2)
// : Colors.transparent,
shouldHighlight(index, daysToSkip)
? Theme.of(context).primaryColor
: Colors.transparent,
borderRadius: BorderRadius.circular(
10,
),
color: calendarColors['backgroundColor'],
borderRadius:
_determineBorderRadius(dateTimePickerController),
),
height: 45,
width: 45,
@ -96,31 +112,28 @@ class MonthDateTimePicker extends StatelessWidget {
children: [
Center(
child: Text(
(index + 1 - daysToSkip).toString(),
(addedIndex + 1 - daysToSkip).toString(),
style:
Theme.of(context).textTheme.bodyText1!.copyWith(
color:
// isDisabled()
// ? Colors.white
// : Colors.transparent,
// isSelected(index, daysToSkip)
// ? Theme.of(context).primaryColor
// : Colors.black,
shouldHighlight(index, daysToSkip)
? Colors.white
: Colors.black,
color: calendarColors['textColor'],
),
),
),
if (shouldMark(index, daysToSkip)) ...[
if (shouldMark(
addedIndex,
daysToSkip,
)) ...[
Align(
alignment: Alignment.bottomRight,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Theme.of(context).indicatorColor,
borderRadius: BorderRadius.circular(45),
child: IgnorePointer(
child: Container(
width: monthDateBoxSize / 4,
height: monthDateBoxSize / 4,
decoration: BoxDecoration(
color: Theme.of(context).indicatorColor,
borderRadius: BorderRadius.circular(
(monthDateBoxSize / 4) * 2),
),
),
),
),
@ -148,8 +161,42 @@ class MonthDateTimePicker extends StatelessWidget {
);
}
bool isDisabled() {
return true;
determineColors(context, index, daysToSkip) {
Map<String, Color> calendarColors = {
'backgroundColor': Colors.transparent,
'textColor': Colors.black
};
if (isDisabled(index, daysToSkip)) {
calendarColors = {
'backgroundColor': Theme.of(context).disabledColor,
'textColor': Colors.white
};
}
if (isSelected(index, daysToSkip)) {
calendarColors = {
'backgroundColor': Theme.of(context).primaryColor.withOpacity(0.3),
'textColor': Theme.of(context).primaryColor
};
}
if (shouldHighlight(index, daysToSkip)) {
calendarColors = {
'backgroundColor': Theme.of(context).primaryColor,
'textColor': Colors.white
};
}
return calendarColors;
}
bool isDisabled(int index, int daysToSkip) {
return DateTime(
date.year,
date.month,
index + 1 - daysToSkip,
).dateContainedIn(
dateTimePickerController.disabledDates ?? [],
);
}
bool isSelected(int index, int daysToSkip) {
@ -174,8 +221,65 @@ class MonthDateTimePicker extends StatelessWidget {
date.year,
date.month,
index + 1 - daysToSkip,
).isContainedIn(
).dateContainedIn(
dateTimePickerController.markedDates ?? [],
);
}
BorderRadius _determineBorderRadius(
DateTimePickerController dateTimePickerController) {
switch (dateTimePickerController.dateBoxShape) {
case DateBoxShape.circle:
return BorderRadius.circular(monthDateBoxSize * 2);
case DateBoxShape.rectangle:
return BorderRadius.zero;
case DateBoxShape.roundedRectangle:
return BorderRadius.circular(monthDateBoxSize / 4.5);
}
}
}
class WrongTimeDialog extends StatelessWidget {
const WrongTimeDialog({required this.dateTimePickerController, Key? key})
: super(key: key);
final DateTimePickerController dateTimePickerController;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Verkeerde tijd gekozen'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'De tijd die u wilt kiezen, is niet mogelijk, maak een andere keuze.'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
displayTimePicker(context, dateTimePickerController);
},
),
],
);
}
}
displayTimePicker(BuildContext context,
DateTimePickerController dateTimePickerController) async {
await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat:
dateTimePickerController.alwaysUse24HourFormat),
child: child!,
);
});
}

View file

@ -7,10 +7,12 @@ import 'package:intl/intl.dart';
class MonthDateTimePickerSheet extends StatelessWidget {
const MonthDateTimePickerSheet({
required this.dateTimePickerController,
required this.monthDateBoxSize,
Key? key,
}) : super(key: key);
final DateTimePickerController dateTimePickerController;
final double monthDateBoxSize;
@override
Widget build(BuildContext context) {
@ -68,10 +70,16 @@ class MonthDateTimePickerSheet extends StatelessWidget {
dateTimePickerController.browsingDate.year - 1, 12, 1)
: DateTime(dateTimePickerController.browsingDate.year,
dateTimePickerController.browsingDate.month - 1, 1),
monthDateBoxSize: monthDateBoxSize,
dateTimePickerController: dateTimePickerController),
MonthDateTimePicker(
date: dateTimePickerController.browsingDate,
dateTimePickerController: dateTimePickerController),
date: DateTime(
dateTimePickerController.browsingDate.year,
dateTimePickerController.browsingDate.month,
1,
),
dateTimePickerController: dateTimePickerController,
monthDateBoxSize: monthDateBoxSize),
MonthDateTimePicker(
date: dateTimePickerController.browsingDate.month == 12
? DateTime(
@ -82,6 +90,7 @@ class MonthDateTimePickerSheet extends StatelessWidget {
: DateTime(dateTimePickerController.browsingDate.year,
dateTimePickerController.browsingDate.month + 1, 1),
dateTimePickerController: dateTimePickerController,
monthDateBoxSize: monthDateBoxSize
),
],
),

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_date_time_picker/flutter_date_time_picker.dart';
import 'package:flutter_date_time_picker/src/extensions/date_time.dart';
import 'package:flutter_date_time_picker/src/extensions/time_of_day.dart';
import 'package:flutter_date_time_picker/src/utils/date_time_picker_controller.dart';
import 'package:intl/intl.dart';
@ -7,6 +9,7 @@ class WeekDateTimePicker extends StatelessWidget {
const WeekDateTimePicker({
required this.dateTimePickerController,
required this.date,
required this.weekDateBoxSize,
Key? key,
}) : super(key: key);
@ -14,80 +17,182 @@ class WeekDateTimePicker extends StatelessWidget {
final DateTime date;
final double weekDateBoxSize;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
date.daysOfWeek().length,
(index) => GestureDetector(
onTap: () {
dateTimePickerController.onTapDay(date.daysOfWeek()[index]);
},
child: SizedBox(
width: 40,
height: 60,
child: Column(
children: [
const Spacer(),
Text(
DateFormat.E()
.format(
date.daysOfWeek().elementAt(index),
)
.toUpperCase()[0],
style: Theme.of(context).textTheme.titleSmall,
),
const Spacer(),
Container(
height: 35,
width: 35,
decoration: BoxDecoration(
color: shouldHighlight(index)
? Theme.of(context).primaryColor
: Colors.transparent,
borderRadius: BorderRadius.circular(
10,
),
(index) {
late Map<String, Color> calendarColors;
calendarColors = determineColors(
context,
index,
);
return GestureDetector(
onTap: isDisabled(
index,
)
? null
: () async {
TimeOfDay? timeOfDay;
// await dateTimePickerController.getDragController().animateTo(
// 0.26,
// duration: const Duration(
// milliseconds: 350,
// ),
// curve: Curves.ease,
// );
DateTime selectedDate = date.daysOfWeek()[index];
if (dateTimePickerController.pickTime) {
timeOfDay = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat:
dateTimePickerController
.alwaysUse24HourFormat),
child: child!,
);
});
}
if (timeOfDay != null &&
timeOfDay.timeContainedIn(
dateTimePickerController.disabledTimes ?? [])) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Verkeerde tijd gekozen'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'De tijd die u wilt kiezen, is niet mogelijk, makke een andere keuze.'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
} else {
timeOfDay = TimeOfDay(hour: 0, minute: 0);
}
DateTime selectedDateTime = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
timeOfDay.hour,
timeOfDay.minute,
);
dateTimePickerController.onTapDay(selectedDateTime);
},
child: SizedBox(
width: 40,
height: 60,
child: Column(
children: [
const Spacer(),
Text(
DateFormat.E()
.format(
date.daysOfWeek().elementAt(index),
)
.toUpperCase()[0],
style: Theme.of(context).textTheme.titleSmall,
),
child: Stack(
children: [
Center(
child: Text(
date.daysOfWeek().elementAt(index).day.toString(),
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
color: shouldHighlight(index)
? Colors.white
: Colors.black,
),
),
),
if (shouldMark(index)) ...[
Align(
alignment: Alignment.bottomRight,
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Theme.of(context).indicatorColor,
borderRadius: BorderRadius.circular(45),
),
const Spacer(),
Container(
height: 35,
width: 35,
decoration: BoxDecoration(
color: calendarColors['backgroundColor'],
borderRadius:
_determineBorderRadius(dateTimePickerController),
),
child: Stack(
children: [
Center(
child: Text(
date.daysOfWeek().elementAt(index).day.toString(),
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: calendarColors['textColor']),
),
),
if (shouldMark(index)) ...[
Align(
alignment: Alignment.bottomRight,
child: Container(
width: weekDateBoxSize,
height: weekDateBoxSize,
decoration: BoxDecoration(
color: Theme.of(context).indicatorColor,
borderRadius:
BorderRadius.circular(weekDateBoxSize * 2),
),
),
),
],
],
],
),
),
),
const Spacer(),
],
const Spacer(),
],
),
),
),
),
);
},
),
);
}
determineColors(context, index) {
Map<String, Color> calendarColors = {
'backgroundColor': Colors.transparent,
'textColor': Colors.black
};
if (isDisabled(index)) {
calendarColors = {
'backgroundColor': Theme.of(context).disabledColor,
'textColor': Colors.white
};
}
if (isSelected(index)) {
calendarColors = {
'backgroundColor': Theme.of(context).primaryColor.withOpacity(0.3),
'textColor': Theme.of(context).primaryColor
};
}
if (shouldHighlight(index)) {
calendarColors = {
'backgroundColor': Theme.of(context).primaryColor,
'textColor': Colors.white
};
}
return calendarColors;
}
bool shouldHighlight(int index) {
return date.daysOfWeek().elementAt(index).sameDayAs(
dateTimePickerController.highlightToday
@ -103,6 +208,13 @@ class WeekDateTimePicker extends StatelessWidget {
.sameDayAs(dateTimePickerController.selectedDate);
}
bool isDisabled(int index) {
return date
.daysOfWeek()
.elementAt(index)
.dateContainedIn(dateTimePickerController.disabledDates ?? []);
}
bool shouldMark(int index) {
return !date.daysOfWeek().elementAt(index).sameDayAs(
dateTimePickerController.highlightToday
@ -112,6 +224,18 @@ class WeekDateTimePicker extends StatelessWidget {
date
.daysOfWeek()
.elementAt(index)
.isContainedIn(dateTimePickerController.markedDates ?? []);
.dateContainedIn(dateTimePickerController.markedDates ?? []);
}
BorderRadius _determineBorderRadius(
DateTimePickerController dateTimePickerController) {
switch (dateTimePickerController.dateBoxShape) {
case DateBoxShape.circle:
return BorderRadius.circular(weekDateBoxSize * 2);
case DateBoxShape.rectangle:
return BorderRadius.zero;
case DateBoxShape.roundedRectangle:
return BorderRadius.circular(weekDateBoxSize / 4.5);
}
}
}

View file

@ -7,12 +7,14 @@ import 'package:intl/intl.dart';
class WeekDateTimePickerSheet extends StatelessWidget {
const WeekDateTimePickerSheet({
required this.dateTimePickerController,
required this.weekDateBoxSize,
this.showHeader = false,
Key? key,
}) : super(key: key);
final DateTimePickerController dateTimePickerController;
final bool showHeader;
final double weekDateBoxSize;
String getDateHeader() {
List<DateTime> weekDays =
@ -75,16 +77,19 @@ class WeekDateTimePickerSheet extends StatelessWidget {
date: dateTimePickerController.browsingDate.subtract(
const Duration(days: 7),
),
weekDateBoxSize: weekDateBoxSize,
),
WeekDateTimePicker(
dateTimePickerController: dateTimePickerController,
date: dateTimePickerController.browsingDate,
weekDateBoxSize: weekDateBoxSize,
),
WeekDateTimePicker(
dateTimePickerController: dateTimePickerController,
date: dateTimePickerController.browsingDate.add(
const Duration(days: 7),
),
weekDateBoxSize: weekDateBoxSize,
),
],
),