Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience

This commit is contained in:
mohammad
2025-07-21 15:22:17 +03:00
parent d12b4c0c65
commit 5589e5b587
10 changed files with 271 additions and 218 deletions

View File

@ -10,7 +10,7 @@ part 'events_event.dart';
part 'events_state.dart'; part 'events_state.dart';
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> { class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
final EventController eventController = EventController(); EventController eventController = EventController();
final CalendarSystemService calendarService; final CalendarSystemService calendarService;
CalendarEventsBloc({ CalendarEventsBloc({
@ -20,7 +20,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
on<AddEvent>(_onAddEvent); on<AddEvent>(_onAddEvent);
on<DisposeResources>(_onDisposeResources); on<DisposeResources>(_onDisposeResources);
on<GoToWeek>(_onGoToWeek); on<GoToWeek>(_onGoToWeek);
on<ResetEvents>(_onResetEvents);
} }
Future<void> _onLoadEvents( Future<void> _onLoadEvents(
LoadEvents event, LoadEvents event,
Emitter<CalendarEventState> emit, Emitter<CalendarEventState> emit,
@ -126,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
eventController.dispose(); eventController.dispose();
return super.close(); return super.close();
} }
void _onResetEvents(
ResetEvents event,
Emitter<CalendarEventState> emit,
) {
if (calendarService is MemoryCalendarServiceWithRemoteFallback) {
(calendarService as MemoryCalendarServiceWithRemoteFallback)
.memoryService
.clear();
}
eventController.dispose();
eventController = EventController();
emit(EventsInitial());
}
} }

View File

@ -29,3 +29,10 @@ class CheckWeekHasEvents extends CalendarEventsEvent {
final DateTime weekStart; final DateTime weekStart;
const CheckWeekHasEvents(this.weekStart); const CheckWeekHasEvents(this.weekStart);
} }
class ResetEvents extends CalendarEventsEvent {
const ResetEvents();
@override
List<Object?> get props => [];
}

View File

@ -1,3 +1,4 @@
// booking_page.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calendar_view/calendar_view.dart'; import 'package:calendar_view/calendar_view.dart';
@ -26,7 +27,33 @@ class BookingPage extends StatefulWidget {
} }
class _BookingPageState extends State<BookingPage> { class _BookingPageState extends State<BookingPage> {
late final EventController _eventController; @override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
BlocProvider(create: (_) => DateSelectionBloc()),
BlocProvider(
create: (_) => CalendarEventsBloc(
calendarService: MemoryCalendarServiceWithRemoteFallback(
remoteService: RemoteCalendarService(HTTPService()),
memoryService: MemoryCalendarService(),
),
),
),
],
child: _BookingPageContent(),
);
}
}
class _BookingPageContent extends StatefulWidget {
@override
State<_BookingPageContent> createState() => _BookingPageContentState();
}
class _BookingPageContentState extends State<_BookingPageContent> {
late EventController _eventController;
@override @override
void initState() { void initState() {
@ -40,7 +67,7 @@ class _BookingPageState extends State<BookingPage> {
super.dispose(); super.dispose();
} }
void _dispatchLoadEvents(BuildContext context) { void _loadEvents(BuildContext context) {
final selectedRoom = final selectedRoom =
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace; context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
final dateState = context.read<DateSelectionBloc>().state; final dateState = context.read<DateSelectionBloc>().state;
@ -60,35 +87,25 @@ class _BookingPageState extends State<BookingPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
providers: [ listener: (context, state) {
BlocProvider(create: (_) => SelectedBookableSpaceBloc()), if (state.selectedBookableSpace != null) {
BlocProvider(create: (_) => DateSelectionBloc()), // Reset events and clear cache when room changes
BlocProvider( context.read<CalendarEventsBloc>().add(ResetEvents());
create: (_) => CalendarEventsBloc( _loadEvents(context);
calendarService: MemoryCalendarServiceWithRemoteFallback( }
remoteService: RemoteCalendarService( },
HTTPService(), child: BlocListener<DateSelectionBloc, DateSelectionState>(
), listener: (context, state) {
memoryService: MemoryCalendarService(), _loadEvents(context);
), },
)), child: BlocListener<CalendarEventsBloc, CalendarEventState>(
],
child: Builder(
builder: (context) =>
BlocListener<CalendarEventsBloc, CalendarEventState>(
listenWhen: (prev, curr) => curr is EventsLoaded,
listener: (context, state) { listener: (context, state) {
if (state is EventsLoaded) { if (state is EventsLoaded) {
_eventController.removeWhere((_) => true); _eventController.removeWhere((_) => true);
_eventController.addAll(state.events); _eventController.addAll(state.events);
} }
}, },
child: BlocListener<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: BlocListener<DateSelectionBloc, DateSelectionState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -123,8 +140,8 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
Expanded( Expanded(
child: BlocBuilder<DateSelectionBloc, child:
DateSelectionState>( BlocBuilder<DateSelectionBloc, DateSelectionState>(
builder: (context, dateState) { builder: (context, dateState) {
return CustomCalendarPage( return CustomCalendarPage(
selectedDate: dateState.selectedDate, selectedDate: dateState.selectedDate,
@ -169,8 +186,7 @@ class _BookingPageState extends State<BookingPage> {
), ),
], ],
), ),
BlocBuilder<DateSelectionBloc, BlocBuilder<DateSelectionBloc, DateSelectionState>(
DateSelectionState>(
builder: (context, state) { builder: (context, state) {
final weekStart = state.weekStart; final weekStart = state.weekStart;
final weekEnd = final weekEnd =
@ -193,6 +209,7 @@ class _BookingPageState extends State<BookingPage> {
), ),
], ],
), ),
const SizedBox(height: 20),
Expanded( Expanded(
flex: 5, flex: 5,
child: BlocBuilder<SelectedBookableSpaceBloc, child: BlocBuilder<SelectedBookableSpaceBloc,
@ -203,22 +220,16 @@ class _BookingPageState extends State<BookingPage> {
return BlocBuilder<DateSelectionBloc, return BlocBuilder<DateSelectionBloc,
DateSelectionState>( DateSelectionState>(
builder: (context, dateState) { builder: (context, dateState) {
return BlocListener<CalendarEventsBloc, return BlocBuilder<CalendarEventsBloc,
CalendarEventState>( CalendarEventState>(
listenWhen: (prev, curr) => builder: (context, eventState) {
curr is EventsLoaded, return WeeklyCalendarPage(
listener: (context, state) { key: ValueKey(
if (state is EventsLoaded) { selectedRoom?.uuid ?? 'no-room'),
_eventController
.removeWhere((_) => true);
_eventController.addAll(state.events);
}
},
child: WeeklyCalendarPage(
startTime: selectedRoom startTime: selectedRoom
?.bookableConfig.startTime, ?.bookableConfig.startTime,
endTime: selectedRoom endTime:
?.bookableConfig.endTime, selectedRoom?.bookableConfig.endTime,
weekStart: dateState.weekStart, weekStart: dateState.weekStart,
selectedDate: dateState.selectedDate, selectedDate: dateState.selectedDate,
eventController: _eventController, eventController: _eventController,
@ -226,7 +237,9 @@ class _BookingPageState extends State<BookingPage> {
.watch<DateSelectionBloc>() .watch<DateSelectionBloc>()
.state .state
.selectedDateFromSideBarCalender, .selectedDateFromSideBarCalender,
), // isLoading: eventState is EventsLoading,
);
},
); );
}, },
); );
@ -241,8 +254,6 @@ class _BookingPageState extends State<BookingPage> {
), ),
), ),
), ),
),
),
); );
} }
} }

View File

@ -76,21 +76,29 @@ class __SidebarContentState extends State<_SidebarContent> {
builder: (context, state) { builder: (context, state) {
return Column( return Column(
children: [ children: [
const _SidebarHeader(title: 'Spaces'), Padding(
Container( padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1), color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, -2), offset: const Offset(0, 4),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
), ),
],
),
child: _SidebarHeader(title: 'Spaces')),
),
Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1), color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 2), offset: const Offset(0, 4),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
), ),
@ -220,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@ -66,7 +66,16 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
); );
return CalendarDatePicker2( return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
),
),
child: CalendarDatePicker2(
config: config, config: config,
value: [_selectedDate], value: [_selectedDate],
onValueChanged: (dates) { onValueChanged: (dates) {
@ -78,6 +87,7 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
widget.onDateChanged(picked.day, picked.month, picked.year); widget.onDateChanged(picked.day, picked.month, picked.year);
} }
}, },
),
); );
} }
} }

View File

@ -32,15 +32,17 @@ class EventTileWidget extends StatelessWidget {
return Expanded( return Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isEventEnded color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.1) ? ColorsManager.grayColor.withOpacity(0.1)
: ColorsManager.blue1.withOpacity(0.1), : ColorsManager.blue1.withOpacity(0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: const Border( border: Border(
left: BorderSide( left: BorderSide(
color: ColorsManager.grayColor, color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.secondaryColor,
width: 4, width: 4,
), ),
), ),

View File

@ -21,7 +21,7 @@ class RoomListItem extends StatelessWidget {
groupValue: isSelected ? room.uuid : null, groupValue: isSelected ? room.uuid : null,
visualDensity: const VisualDensity(vertical: -4), visualDensity: const VisualDensity(vertical: -4),
onChanged: (value) => onTap(), onChanged: (value) => onTap(),
activeColor: ColorsManager.primaryColor, activeColor: ColorsManager.secondaryColor,
title: Text( title: Text(
room.spaceName, room.spaceName,
maxLines: 2, maxLines: 2,

View File

@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return ColoredBox(
color: isSelectedDay
? ColorsManager.secondaryColor.withOpacity(0.1)
: Colors.transparent,
child: Column(
children: [ children: [
const SizedBox(
height: 10,
),
Text( Text(
DateFormat('EEE').format(date).toUpperCase(), DateFormat('EEE').format(date).toUpperCase(),
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 14, fontSize: 14,
color: isSelectedDay ? Colors.blue : Colors.black, color: ColorsManager.blackColor,
), ),
), ),
Text( Text(
DateFormat('d').format(date), DateFormat('d').format(date),
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 20, fontSize: 30,
color: color: ColorsManager.blackColor,
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
), ),
), ),
], ],
),
); );
} }
} }

View File

@ -57,13 +57,10 @@ class WeeklyCalendarPage extends StatelessWidget {
); );
} }
const double timeLineWidth = 90;
const double timeLineWidth = 65;
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
bool isInRange(DateTime date, DateTime start, DateTime end) { bool isInRange(DateTime date, DateTime start, DateTime end) {
!date.isBefore(start) && !date.isAfter(end); !date.isBefore(start) && !date.isAfter(end);
// remove this line and Check if the date is within the range // remove this line and Check if the date is within the range
@ -100,7 +97,6 @@ class WeeklyCalendarPage extends StatelessWidget {
width: width, width: width,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.13), color: Colors.orange.withOpacity(0.13),
borderRadius: BorderRadius.circular(8),
), ),
); );
} else if (isSelected) { } else if (isSelected) {
@ -110,7 +106,6 @@ class WeeklyCalendarPage extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:
ColorsManager.spaceColor.withOpacity(0.07), ColorsManager.spaceColor.withOpacity(0.07),
borderRadius: BorderRadius.circular(8),
), ),
); );
} }
@ -143,7 +138,7 @@ class WeeklyCalendarPage extends StatelessWidget {
heightPerMinute: 1.7, heightPerMinute: 1.7,
showLiveTimeLineInAllDays: false, showLiveTimeLineInAllDays: false,
showVerticalLines: true, showVerticalLines: true,
emulateVerticalOffsetBy: -80, emulateVerticalOffsetBy: -95,
startDay: WeekDays.monday, startDay: WeekDays.monday,
liveTimeIndicatorSettings: liveTimeIndicatorSettings:
const LiveTimeIndicatorSettings( const LiveTimeIndicatorSettings(
@ -161,7 +156,7 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
timeLineWidth: timeLineWidth, timeLineWidth: timeLineWidth,
weekPageHeaderBuilder: (start, end) => Container(), weekPageHeaderBuilder: (start, end) => Container(),
weekTitleHeight: 60, weekTitleHeight: 90,
weekNumberBuilder: (firstDayOfWeek) => Padding( weekNumberBuilder: (firstDayOfWeek) => Padding(
padding: const EdgeInsets.only(right: 15, bottom: 10), padding: const EdgeInsets.only(right: 15, bottom: 10),
child: Column( child: Column(
@ -208,8 +203,6 @@ class WeeklyCalendarPage extends StatelessWidget {
}, },
); );
} }
} }
bool isSameDay(DateTime d1, DateTime d2) { bool isSameDay(DateTime d1, DateTime d2) {

View File

@ -140,6 +140,5 @@ abstract class ApiEndpoints {
static const String saveSchedule = '/schedule/{deviceUuid}'; static const String saveSchedule = '/schedule/{deviceUuid}';
static const String getBookableSpaces = '/bookable-spaces'; static const String getBookableSpaces = '/bookable-spaces';
static const String getBookings = static const String getBookings = '/bookings?month={mm}-{yyyy}&space={space}';
'/bookings?month={mm}%2F{yyyy}&space={space}';
} }