After a few months of painfully working on it, I finally got the booking_booking calendar working for what I need. I still need to clean up some of the places where it says "mock" but it works. Please let me know if you could use a video of how to set this up. What the calendar does: • It blocks off appointments before 10 and after 3:30 • Creates 90 minute appointments (mine show at 10, 11:30, 1) • Prevents people from booking the current day and next day (aka today and tomorrow) • References firebase (my privateSessions collection with a status of "booked"). If it finds an appointment with the same start_time as 10, 11:30 or 1 it will show up as red. • If all 3 appointments are booked, it will disable the day so its unclickable • prevents someone from booking on thursdays and sundays. • When someone clicks on a date I save the timestamp for the date in a local state called privateDate • I have an action set up in the code. What i do is when someone clicks continue, I have them navigate to the next page where i show the time stamp (from the local state) and allow a user to add any notes. Upon hitting submit I then create a document in my privateSessions collection. • This is all just my use case but complicated enough that hopefully it helps you format for yours. Before you copy my code, heres what may be helpful to know: • the booking _calendar does not support multiple bookings at the same time or overlapping appointments. • I dont think theres any way to set availability. If there is, I havent found that yet, so I don't think you can pull your availability from Firebase. • How I've found you set availability is by setting the duration of appointments, the start time and end time of your day. The days you take appointments .... and factor in breaks. Full Code:
// Automatic FlutterFlow imports
import '../../backend/backend.dart';
import '../../flutter_flow/flutter_flow_theme.dart';
import '../../flutter_flow/flutter_flow_util.dart';
import '../widgets/index.dart'; // Imports other custom widgets
import '../actions/index.dart'; // Imports custom actions
import '../../flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
// Set your widget name, define your parameter, and then add the
// boilerplate code using the button on the right!
import 'package:booking_calendar/booking_calendar.dart';
import 'package:intl/intl.dart';
class Cal extends StatefulWidget {
const Cal({
Key? key,
this.width,
this.height,
required this.bookingPage,
}) : super(key: key);
final double? width;
final double? height;
final Future Function() bookingPage;
@override
State createState() => _CalState();
}
// Get a reference to the Firestore databases
final firestore = FirebaseFirestore.instance;
final lastDay = DateTime.now().add(Duration(days: 30 * 4));
class _CalState extends State {
final now = DateTime.now();
final timeFormat = DateFormat.jm(); // create the time format here
late BookingService mockBookingService;
@override
void initState() {
super.initState();
mockBookingService = new BookingService(
serviceName: 'Private Session',
serviceDuration: 90,
bookingEnd: DateTime(now.year, now.month, now.day, 15, 31),
bookingStart: DateTime(now.year, now.month, now.day, 10, 00));
}
Stream getBookingStream({DateTime? end, DateTime? start}) {
return firestore
.collection("privateSessions")
.where("description", isEqualTo: "Booked")
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
var data = doc.data();
return DateTimeRange(
start: data["start_time"].toDate(),
end: data["end_time"].toDate(),
);
}).toList();
});
}
Future uploadBookingMock({BookingService? newBooking}) async {
await Future.delayed(const Duration(seconds: 1));
FFAppState().update(() {
FFAppState().privateDate = newBooking!.bookingStart;
});
print('${newBooking?.toJson()} has been uploaded');
if (widget.bookingPage != null) {
widget.bookingPage?.call();
}
}
List convertStreamResultMock({required dynamic streamResult}) {
List converted = [];
for (var i = 0; i < streamResult.length; i++) {
final item = streamResult[i];
if (item != null) {
converted.add(item);
}
}
return converted;
}
List filterBookedSlots(
List bookedSlots, List allSlots) {
return allSlots.where((slot) => !bookedSlots.contains(slot)).toList();
}
Stream> getDisabledDates() async* {
final querySnapshot = await firestore
.collection("privateSessions")
.where("description", isEqualTo: "Booked")
.get();
final appointmentsPerDay = {};
for (final doc in querySnapshot.docs) {
final data = doc.data();
final startTime = data["start_time"].toDate() as DateTime;
final date = DateTime(startTime.year, startTime.month, startTime.day);
appointmentsPerDay.update(date, (value) => value + 1, ifAbsent: () => 1);
}
final disabledDates = [];
for (final entry in appointmentsPerDay.entries) {
if (entry.value >= 3) {
print("Disabled date: ${entry.key}");
disabledDates.add(entry.key);
}
}
final now = DateTime.now();
final tomorrow = DateTime(now.year, now.month, now.day + 1);
if (!disabledDates.contains(now)) {
disabledDates.add(now);
}
if (!disabledDates.contains(tomorrow)) {
disabledDates.add(tomorrow);
}
yield disabledDates;
}
@override
Widget build(BuildContext context) {
final timeFormat = DateFormat.jm();
final now = DateTime.now();
final disabledCurrentAndTomorrow = [now, now.add(Duration(days: 1))];
return StreamBuilder>(
stream: getDisabledDates(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final disabledDates = snapshot.data ?? [];
return BookingCalendar(
bookingService: mockBookingService,
convertStreamResultToDateTimeRanges: convertStreamResultMock,
getBookingStream: getBookingStream,
lastDay: lastDay,
uploadBooking: uploadBookingMock,
disabledDays: [4, 7], // 4 represents Thursday, 0 represents Sunday
bookingButtonColor: Color(0xFF4B39EF),
bookingButtonText: "Continue",
uploadingWidget: Container(),
formatDateTime: (dateTime) => timeFormat.format(dateTime),
disabledDates: disabledDates,
);
});
}
}