Created
May 7, 2022 14:58
-
-
Save joshuachestang/28e84f9570d18e142e6c82cd935e69e7 to your computer and use it in GitHub Desktop.
This code is for a calendar function where a user must choose a reservation date and time from a service provider's calendar. Similar to a Calendly.com.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:gini/components/services/buy/bottom/calendar/table_calendar.dart'; | |
import 'package:gini/miscellaneous/constants.dart'; | |
import 'package:gini/models/availability/availability.dart'; | |
import 'package:gini/models/service/service_calendar.dart'; | |
import 'package:gini/models/service/service_calendar_table.dart'; | |
import 'package:provider/provider.dart'; | |
import 'time_list.dart'; | |
import 'package:intl/intl.dart'; | |
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; | |
import 'package:timezone/data/latest.dart' as tz; | |
import 'package:timezone/timezone.dart' as tz; | |
import "package:collection/collection.dart"; | |
class BuyServiceDetailsCalendarStepper extends StatefulWidget { | |
const BuyServiceDetailsCalendarStepper({Key key, @required this.availability}) | |
: super(key: key); | |
final Availability availability; | |
@override | |
State<BuyServiceDetailsCalendarStepper> createState() => | |
_BuyServiceDetailsCalendarStepperState(); | |
} | |
class _BuyServiceDetailsCalendarStepperState | |
extends State<BuyServiceDetailsCalendarStepper> { | |
int _stepperIndex = 0; | |
@override | |
Widget build(BuildContext context) { | |
final minutes = int.tryParse( | |
Provider.of<ServiceCalendarProviderModel>(context, listen: false) | |
.finalSelectedDuration | |
.timeLength | |
.split(" ") | |
.first) ?? | |
0; | |
final callDuration = Duration(minutes: minutes); | |
final mediaQuery = MediaQuery.of(context); | |
setupAvailability(callDuration); | |
return Stepper( | |
currentStep: _stepperIndex, | |
controlsBuilder: (context, details) { | |
return Column( | |
children: [ | |
SizedBox(height: 16.0), | |
Consumer<ServiceCalendarTable>( | |
builder: (context, model, child) { | |
if (_stepperIndex == 0) { | |
return model.tempSelectedDate != null | |
? Container( | |
width: mediaQuery.size.width.clamp(250.0, 360.0), | |
height: 60.0, | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.circular(50.0), | |
gradient: linearGradient, | |
), | |
child: Material( | |
borderRadius: BorderRadius.circular(50.0), | |
color: Colors.transparent, | |
child: InkWell( | |
borderRadius: BorderRadius.circular(50.0), | |
splashColor: Color.fromRGBO(0, 0, 0, 0.15), | |
highlightColor: Color.fromRGBO(0, 0, 0, 0.15), | |
child: Center( | |
child: Text( | |
"Next", | |
style: TextStyle( | |
color: Colors.white, | |
fontSize: 18.0, | |
fontWeight: FontWeight.w700, | |
height: | |
mediaQuery.textScaleFactor * 0.89), | |
)), | |
onTap: () { | |
setState(() { | |
_stepperIndex = 1; | |
}); | |
}, | |
), | |
), | |
) | |
: SizedBox.shrink(); | |
} | |
return Container(); | |
}, | |
), | |
SizedBox(height: 16.0), | |
TextButton( | |
onPressed: () { | |
Navigator.pop(context); | |
}, | |
child: Text("Cancel"), | |
) | |
], | |
); | |
}, | |
onStepTapped: (step) { | |
final selectedDate = | |
Provider.of<ServiceCalendarTable>(context, listen: false) | |
.tempSelectedDate; | |
if (_stepperIndex != step && selectedDate != null) { | |
setState(() { | |
print(step); | |
_stepperIndex = step; | |
}); | |
} | |
}, | |
type: StepperType.horizontal, | |
steps: [ | |
Step( | |
isActive: _stepperIndex == 0 ? true : false, | |
title: Text('Choose A Date'), | |
content: Column( | |
children: [ | |
BuyServiceDetailsCalendarTable( | |
availability: widget.availability, | |
localTimeOptionsList: localTimeOptionsList, unavailableDays: unavailableDays) | |
], | |
)), | |
Step( | |
isActive: _stepperIndex == 1 ? true : false, | |
title: Text("Choose A Time"), | |
content: BuyServiceDetailsCalendarAvailableTimeList( | |
availability: widget.availability)) | |
]); | |
} | |
List<DateTime> dateOptionsList = []; | |
List<DateTime> localTimeOptionsList = []; | |
List<DateTime> unavailableDays = []; | |
void setupAvailability(callDuration) async { | |
_loadProviderAvailabilityInLocalTime(callDuration); | |
await _addUnavailableDaysToArray(localTimeOptionsList); | |
} | |
void _loadProviderAvailabilityInLocalTime(callDuration) async { | |
await _loadProviderCalendarAvailability(callDuration); | |
dateOptionsList.forEach((time) { | |
localTimeOptionsList.add(time.toLocal()); | |
}); | |
// localTimeOptionsList; | |
_addUnavailableDaysToArray(localTimeOptionsList); | |
} | |
_addUnavailableDaysToArray(List<DateTime> localTimeOptionsList) { | |
// Now time to convert all times to current user's timezone | |
// var unavailableDays = []; | |
var availability = widget.availability; | |
var maxFutureBookingDays = widget.availability.maxBookingDays; | |
final location = | |
tz.getLocation("America/Chicago"); | |
var providersAvailabilityStartingDay = tz.TZDateTime.now(location); | |
final providersMaxBookingDayAndTime = providersAvailabilityStartingDay | |
.add(Duration(days: maxFutureBookingDays)); | |
providersAvailabilityStartingDay = providersAvailabilityStartingDay.add(Duration(days: 1)); | |
while (providersAvailabilityStartingDay.isBefore(providersMaxBookingDayAndTime)) { | |
List timeSlotsForDay = []; | |
List listOfTimeSlotsForTheDay = localTimeOptionsList.where((timeSlot) => DateFormat.yMd(timeSlot) | |
.toString() | |
.contains(DateFormat.yMd(providersAvailabilityStartingDay) | |
.toString())).toList(); | |
if (listOfTimeSlotsForTheDay.isEmpty) { | |
unavailableDays.add(providersAvailabilityStartingDay); | |
} | |
// get all the localTimeOptions that have the same day | |
providersAvailabilityStartingDay = providersAvailabilityStartingDay.add(Duration(days: 1)); | |
} | |
// when there are no times available on a specific date, put that in an array | |
// localTimeOptionsList.length; | |
// localTimeOptionsList.forEach((element) { | |
// if (DateFormat.yMMM(element).toString() == "05/06/2023") { | |
// // place in array | |
// } | |
// }); | |
} | |
_loadProviderCalendarAvailability(Duration callDuration) { | |
// Step 1 - Get the provider's availability and timezone | |
var availability = widget.availability; | |
var maxFutureBookingDays = widget.availability.maxBookingDays; | |
final location = | |
tz.getLocation("America/Chicago"); | |
final providersAvailabilityStartingDay = tz.TZDateTime.now(location); | |
// create loop through maxFutureBookingDays in the future. But utilizing | |
// the provider's current day and time. | |
final providersMaxBookingDayAndTime = providersAvailabilityStartingDay | |
.add(Duration(days: maxFutureBookingDays)); | |
// put Date options in array for example 7/12/22, 7/13/22, etc. | |
// Why? When we are generating the potential booking option values, | |
// it will be based off of the provider's availability. | |
// That way, when we adjust for the current user's timezone, we can shift the values easily. | |
var day = providersAvailabilityStartingDay.day; | |
// user must book 24 hours in advance | |
var currentDay = providersAvailabilityStartingDay.add(Duration(days: 1)); | |
while (currentDay.isBefore(providersMaxBookingDayAndTime)) { | |
// print('hello'); | |
// Get the Day of the week | |
// if (providersAvailabilityStartingDay.day == "Monday") | |
// At the end of this, add one day | |
switch (currentDay.weekday) { | |
case 1: | |
switch (availability.monday.available) { | |
case true: | |
availability.monday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 2: | |
switch (availability.tuesday.available) { | |
case true: | |
availability.tuesday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 3: | |
switch (availability.wednesday.available) { | |
case true: | |
availability.wednesday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 4: | |
switch (availability.thursday.available) { | |
case true: | |
availability.thursday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 5: | |
switch (availability.friday.available) { | |
case true: | |
availability.friday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 6: | |
switch (availability.saturday.available) { | |
case true: | |
availability.saturday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
case 7: | |
switch (availability.sunday.available) { | |
case true: | |
availability.sunday.availabilityHours.forEach((timeframe) { | |
var rawStartTime = timeframe.start; | |
final rawEndTime = timeframe.end; | |
var startTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawStartTime.split(":").first), | |
int.parse(rawStartTime.split(":").last)); | |
var endTimeFormatted = tz.TZDateTime( | |
location, | |
currentDay.year, | |
currentDay.month, | |
currentDay.day, | |
int.parse(rawEndTime.split(":").first), | |
int.parse(rawEndTime.split(":").last)); | |
while (startTimeFormatted.isBefore(endTimeFormatted)) { | |
// start creating reservation slots in 30 min intervals | |
dateOptionsList.add(startTimeFormatted); | |
startTimeFormatted = startTimeFormatted.add(Duration(minutes: 30)); | |
} | |
}); | |
currentDay = currentDay.add(Duration(days: 1)); | |
break; | |
default: | |
} | |
break; | |
default: | |
} | |
} | |
// Now remove all time values that have already been booked. | |
availability.reservations.forEach((reservation) { | |
// get reservation in Provider's time | |
final reservationStart = reservation.start.toDate().toLocal(); | |
// get reservation in Provider's time | |
final reservationEnd = reservation.end.toDate().toLocal(); | |
final reservationDuration = | |
reservationEnd.difference(reservationStart).inMinutes; | |
if (reservationDuration <= 30) { | |
// For reservations 30 min or less, remove 1 time from list in 30 min intervals. | |
dateOptionsList.remove(reservationStart); | |
} | |
if (reservationDuration > 30 && reservationDuration <= 60) { | |
// For reservations 60 min or less, remove 2 times from list in 30 min intervals. | |
dateOptionsList.remove(reservationStart); | |
dateOptionsList.remove(reservationStart.add(Duration(minutes: 30))); | |
} | |
if (reservationDuration > 60 && reservationDuration <= 90) { | |
// For reservations greater than 60 min, remove 3 times from list in 30 min intervals. | |
dateOptionsList.remove(reservationStart); | |
dateOptionsList.remove(reservationStart.add(Duration(minutes: 30))); | |
dateOptionsList.remove(reservationStart.add(Duration(minutes: 60))); | |
} | |
}); | |
// return dateOptionsList; | |
// Then on day selection, grab all times available for that specific day | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment