Skip to content

Instantly share code, notes, and snippets.

@joshuachestang
Created May 7, 2022 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshuachestang/28e84f9570d18e142e6c82cd935e69e7 to your computer and use it in GitHub Desktop.
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.
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