diff --git a/assets/icons/Booking.svg b/assets/icons/Booking.svg new file mode 100644 index 0000000..f68bb61 --- /dev/null +++ b/assets/icons/Booking.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart index 0f50a8b..c23da27 100644 --- a/lib/features/auth/model/user_model.dart +++ b/lib/features/auth/model/user_model.dart @@ -22,7 +22,7 @@ class UserModel { final DateTime? appAgreementAcceptedAt; final Role? role; final Project? project; - + final int? points; UserModel({ required this.uuid, required this.email, @@ -41,6 +41,7 @@ class UserModel { required this.appAgreementAcceptedAt, required this.role, required this.project, + this.points, }); factory UserModel.fromJson(Map json) { @@ -67,33 +68,36 @@ class UserModel { role: json['role'] != null ? Role.fromJson(json['role']) : null, project: json['project'] != null ? Project.fromJson(json['project']) : null, + points: json['points'], ); } factory UserModel.fromToken(Token token) { Map tempJson = Token.decodeToken(token.accessToken); return UserModel( - uuid: tempJson['uuid'].toString(), - email: tempJson['email'], - lastName: tempJson['lastName'], - firstName: tempJson['firstName'], - profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), - phoneNumber: null, - isEmailVerified: null, - isAgreementAccepted: null, - regionUuid: null, - regionName: tempJson['region']?['regionName'], - timeZone: tempJson['timezone']?['timeZoneOffset'], - hasAcceptedWebAgreement: tempJson['hasAcceptedWebAgreement'], - webAgreementAcceptedAt: tempJson['webAgreementAcceptedAt'] != null - ? DateTime.parse(tempJson['webAgreementAcceptedAt']) - : null, - hasAcceptedAppAgreement: tempJson['hasAcceptedAppAgreement'], - appAgreementAcceptedAt: tempJson['appAgreementAcceptedAt'] != null - ? DateTime.parse(tempJson['appAgreementAcceptedAt']) - : null, - role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null, - project: null); + uuid: tempJson['uuid'].toString(), + email: tempJson['email'], + lastName: tempJson['lastName'], + firstName: tempJson['firstName'], + profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), + phoneNumber: null, + isEmailVerified: null, + isAgreementAccepted: null, + regionUuid: null, + regionName: tempJson['region']?['regionName'], + timeZone: tempJson['timezone']?['timeZoneOffset'], + hasAcceptedWebAgreement: tempJson['hasAcceptedWebAgreement'], + webAgreementAcceptedAt: tempJson['webAgreementAcceptedAt'] != null + ? DateTime.parse(tempJson['webAgreementAcceptedAt']) + : null, + hasAcceptedAppAgreement: tempJson['hasAcceptedAppAgreement'], + appAgreementAcceptedAt: tempJson['appAgreementAcceptedAt'] != null + ? DateTime.parse(tempJson['appAgreementAcceptedAt']) + : null, + role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null, + project: null, + points: tempJson['points'], + ); } static Uint8List? decodeBase64Image(String? base64String) { diff --git a/lib/features/booking_system/data/booking_dummy_source.dart b/lib/features/booking_system/data/booking_dummy_source.dart new file mode 100644 index 0000000..d44e40d --- /dev/null +++ b/lib/features/booking_system/data/booking_dummy_source.dart @@ -0,0 +1,32 @@ +import 'package:syncrow_app/features/booking_system/domain/booking_model.dart'; +import 'package:syncrow_app/features/booking_system/domain/booking_service.dart'; + +class BookingDummySource implements BookingService { + @override + Future> get() async { + await Future.delayed(Duration(seconds: 2)); + return [ + BookingModel( + uuid: 'uuid1', + roomName: 'roomName1', + date: 'wed 28th May 2025', + timeSlot: '10:30 AM - 11:30 AM', + cost: 4, + ), + BookingModel( + uuid: 'uuid2', + roomName: 'roomName2', + date: 'wed 28th May 2025', + timeSlot: '10:30 AM - 11:30 AM', + cost: 6, + ), + BookingModel( + uuid: 'uuid3', + roomName: 'roomName3', + date: 'thur 2th june 2025', + timeSlot: '10:30 AM - 1:30 PM', + cost: 10, + ) + ]; + } +} diff --git a/lib/features/booking_system/data/booking_remote_source.dart b/lib/features/booking_system/data/booking_remote_source.dart new file mode 100644 index 0000000..bb9b2f9 --- /dev/null +++ b/lib/features/booking_system/data/booking_remote_source.dart @@ -0,0 +1,35 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_app/features/booking_system/domain/booking_model.dart'; +import 'package:syncrow_app/features/booking_system/domain/booking_service.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class BookingRemoteSource implements BookingService { + final HTTPService _httpService; + BookingRemoteSource(this._httpService); + + @override + Future> get() async { + try { + return _httpService.get( + path: ApiEndpoints.upcomingBookings, + expectedResponseModel: (json) { + return BookingModel.fromJsonList(json['data']); + }, + ); + } on DioException catch (e) { + return []; + // final message = e.response?.data as Map?; + // final error = message?['error'] as Map?; + // final errorMessage = error?['error'] as String? ?? ''; + // final formattedErrorMessage = + // [_defaultErrorMessage, errorMessage].join(': '); + // throw APIException(formattedErrorMessage); + // } catch (e) { + // final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); + // throw APIException(formattedErrorMessage); + // } + // + } + } +} diff --git a/lib/features/booking_system/domain/booking_model.dart b/lib/features/booking_system/domain/booking_model.dart new file mode 100644 index 0000000..83b90e2 --- /dev/null +++ b/lib/features/booking_system/domain/booking_model.dart @@ -0,0 +1,39 @@ +class BookingModel { + final String uuid, roomName, date, timeSlot; + final int cost; + BookingModel({ + required this.uuid, + required this.roomName, + required this.date, + required this.timeSlot, + required this.cost, + }); + factory BookingModel.zero() => BookingModel( + uuid: '', + roomName: '', + date: '', + timeSlot: '', + cost: -1, + ); + factory BookingModel.fromJson(Map json) => BookingModel( + uuid: json['uuid'] as String, + roomName: json['roomName'] as String, + date: json['date'] as String, + timeSlot: json['timeSlot'] as String, + cost: json['cost'] as int, + ); + + static List fromJsonList(List jsonList) => jsonList + .map( + (bookModel) => BookingModel.fromJson(bookModel), + ) + .toList(); + + Map toJson() => { + 'uuid': uuid, + 'roomName': roomName, + 'date': date, + 'timeSlot': timeSlot, + 'cost': cost, + }; +} diff --git a/lib/features/booking_system/domain/booking_service.dart b/lib/features/booking_system/domain/booking_service.dart new file mode 100644 index 0000000..f015712 --- /dev/null +++ b/lib/features/booking_system/domain/booking_service.dart @@ -0,0 +1,5 @@ +import 'package:syncrow_app/features/booking_system/domain/booking_model.dart'; + +abstract interface class BookingService { + Future> get(); +} diff --git a/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_bloc.dart b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_bloc.dart new file mode 100644 index 0000000..d9718c5 --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_bloc.dart @@ -0,0 +1,32 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +import '../../../domain/booking_model.dart'; +import '../../../domain/booking_service.dart'; + +part 'past_bookings_event.dart'; +part 'past_bookings_state.dart'; + +class PastBookingsBloc extends Bloc { + final BookingService _bookingService; + + PastBookingsBloc(this._bookingService) : super(PastBookingsInitial()) { + on(_onGetPastBookingsEvent); + } + Future _onGetPastBookingsEvent( + GetPastBookingsEvent event, + Emitter emit, + ) async { + emit(PastBookingLoadingState()); + try { + final pastBookings = await _bookingService.get(); + emit( + PastBookingLoadedState(pastBookings: pastBookings), + ); + } catch (e) { + emit( + PastBookingErrorState(e.toString()), + ); + } + } +} diff --git a/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_event.dart b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_event.dart new file mode 100644 index 0000000..70b831d --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_event.dart @@ -0,0 +1,10 @@ +part of 'past_bookings_bloc.dart'; + +sealed class PastBookingsEvent extends Equatable { + const PastBookingsEvent(); + + @override + List get props => []; +} + +class GetPastBookingsEvent extends PastBookingsEvent {} diff --git a/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_state.dart b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_state.dart new file mode 100644 index 0000000..9604025 --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/past_bookings_bloc/past_bookings_state.dart @@ -0,0 +1,24 @@ +part of 'past_bookings_bloc.dart'; + +sealed class PastBookingsState extends Equatable { + const PastBookingsState(); + + @override + List get props => []; +} + +final class PastBookingsInitial extends PastBookingsState {} + +final class PastBookingLoadingState extends PastBookingsState {} + +final class PastBookingLoadedState extends PastBookingsState { + final List pastBookings; + const PastBookingLoadedState({ + required this.pastBookings, + }); +} + +final class PastBookingErrorState extends PastBookingsState { + final String errorMsg; + const PastBookingErrorState(this.errorMsg); +} diff --git a/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart new file mode 100644 index 0000000..093bbf2 --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/booking_system/domain/booking_model.dart'; +import 'package:syncrow_app/features/booking_system/domain/booking_service.dart'; + +part 'upcoming_bookings_event.dart'; +part 'upcoming_bookings_state.dart'; + +class UpcomingBookingsBloc + extends Bloc { + final BookingService _bookingService; + UpcomingBookingsBloc(this._bookingService) + : super(UpcomingBookingsInitial()) { + on(_onGetUpcomingBookingsEvent); + } + Future _onGetUpcomingBookingsEvent( + GetUpcomingBookingsEvent event, + Emitter emit, + ) async { + emit(UpcomingBookingLoadingState()); + try { + final upcomingBookings = await _bookingService.get(); + emit( + UpcomingBookingLoadedState(upcomingBookings: upcomingBookings), + ); + } catch (e) { + emit( + UpcomingBookingErrorState(e.toString()), + ); + } + } +} diff --git a/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_event.dart b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_event.dart new file mode 100644 index 0000000..5365cdc --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_event.dart @@ -0,0 +1,10 @@ +part of 'upcoming_bookings_bloc.dart'; + +sealed class UpcomingBookingsEvent extends Equatable { + const UpcomingBookingsEvent(); + + @override + List get props => []; +} + +class GetUpcomingBookingsEvent extends UpcomingBookingsEvent {} diff --git a/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_state.dart b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_state.dart new file mode 100644 index 0000000..3f2a09b --- /dev/null +++ b/lib/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_state.dart @@ -0,0 +1,24 @@ +part of 'upcoming_bookings_bloc.dart'; + +sealed class UpcomingBookingsState extends Equatable { + const UpcomingBookingsState(); + + @override + List get props => []; +} + +final class UpcomingBookingsInitial extends UpcomingBookingsState {} + +final class UpcomingBookingLoadingState extends UpcomingBookingsState {} + +final class UpcomingBookingLoadedState extends UpcomingBookingsState { + final List upcomingBookings; + const UpcomingBookingLoadedState({ + required this.upcomingBookings, + }); +} + +final class UpcomingBookingErrorState extends UpcomingBookingsState { + final String errorMsg; + const UpcomingBookingErrorState(this.errorMsg); +} diff --git a/lib/features/booking_system/presentation/screens/book_page.dart b/lib/features/booking_system/presentation/screens/book_page.dart new file mode 100644 index 0000000..7342840 --- /dev/null +++ b/lib/features/booking_system/presentation/screens/book_page.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/booking_system/presentation/widgets/booking_appbar_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/utils/helpers/app_size.dart'; + +import '../../../../utils/resource_manager/color_manager.dart'; +import '../widgets/row_of_title_arrow_widget.dart'; + +class BookPage extends StatelessWidget { + const BookPage({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + padding: EdgeInsets.zero, + appBar: BookingAppBar(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 20, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Text( + 'Booking Details', + style: TextStyle( + fontSize: 15, + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox( + height: 5, + ), + Container( + padding: EdgeInsets.all(10), + height: deviceHeight(context) * 0.25, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: ColorsManager.onPrimaryColor, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RowOfTitleAndArrowWidget( + title: 'Space', + onTap: () {}, + ), + Divider( + color: ColorsManager.grayButtonColors, + ), + RowOfTitleAndArrowWidget( + title: 'Booking Date', + onTap: () {}, + ), + Divider( + color: ColorsManager.grayButtonColors, + ), + RowOfTitleAndArrowWidget( + title: 'Time Slot', + onTap: () {}, + ) + ], + ), + ), + SizedBox( + height: 30, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: DefaultButton( + backgroundColor: ColorsManager.blueColor1, + child: Text( + 'Book Now', + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/booking_system/presentation/screens/booking_system_page.dart b/lib/features/booking_system/presentation/screens/booking_system_page.dart new file mode 100644 index 0000000..d4dbe26 --- /dev/null +++ b/lib/features/booking_system/presentation/screens/booking_system_page.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/booking_system/data/booking_dummy_source.dart'; +import 'package:syncrow_app/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import '../blocs/past_bookings_bloc/past_bookings_bloc.dart'; +import '../widgets/booking_appbar_widget.dart'; +import '../widgets/current_balance_widget.dart'; +import '../widgets/past_booking_widget.dart'; +import '../widgets/upcoming_bookings_widget.dart'; + +class BookingSystemPage extends StatelessWidget { + const BookingSystemPage({super.key}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => UpcomingBookingsBloc(BookingDummySource()) + ..add(GetUpcomingBookingsEvent()), + ), + BlocProvider( + create: (_) => PastBookingsBloc(BookingDummySource()) + ..add(GetPastBookingsEvent()), + ) + ], + child: DefaultScaffold( + appBar: BookingAppBar(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 20, + ), + Expanded( + flex: 2, + child: CurrentBalanceWidget( + userBalance: HomeCubit.user!.points == null + ? '0' + : HomeCubit.user!.points.toString(), + ), + ), + SizedBox( + height: 20, + ), + Expanded( + flex: 8, + child: Column( + children: [ + UpcomingBookingsWidget(), + SizedBox( + height: 10, + ), + PastBookingsWidget(), + ], + ), + ) + ], + ), + ), + )); + } +} diff --git a/lib/features/booking_system/presentation/widgets/booking_appbar_widget.dart b/lib/features/booking_system/presentation/widgets/booking_appbar_widget.dart new file mode 100644 index 0000000..7b8632e --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/booking_appbar_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import '../../../../utils/resource_manager/color_manager.dart'; + +class BookingAppBar extends StatelessWidget implements PreferredSizeWidget { + const BookingAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Icons.arrow_back_ios_new)), + title: Text( + 'Booking', + style: TextStyle( + color: ColorsManager.blueColor1, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/lib/features/booking_system/presentation/widgets/booking_card_widget.dart b/lib/features/booking_system/presentation/widgets/booking_card_widget.dart new file mode 100644 index 0000000..897f545 --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/booking_card_widget.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +import '../../../../utils/resource_manager/color_manager.dart'; +import '../../domain/booking_model.dart'; + +class BookingCardWidget extends StatelessWidget { + const BookingCardWidget({ + super.key, + required this.bookingModel, + }); + + final BookingModel bookingModel; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: ColorsManager.onPrimaryColor, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + bookingModel.roomName, + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: 15, + ), + Text( + 'Booking Date', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 15, + ), + ), + Text( + bookingModel.date, + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + SizedBox( + height: 15, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Time slot', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 15, + ), + ), + Text( + bookingModel.timeSlot, + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox( + width: 80, + ), + Column( + children: [ + Text( + 'cost', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 15, + ), + ), + Text( + bookingModel.cost.toString(), + style: TextStyle( + color: ColorsManager.blackColor, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/booking_system/presentation/widgets/current_balance_widget.dart b/lib/features/booking_system/presentation/widgets/current_balance_widget.dart new file mode 100644 index 0000000..5491a3a --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/current_balance_widget.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +import '../../../../utils/resource_manager/color_manager.dart'; +import '../screens/book_page.dart'; + +class CurrentBalanceWidget extends StatelessWidget { + final String userBalance; + const CurrentBalanceWidget({ + super.key, + required this.userBalance, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: ColorsManager.onPrimaryColor, + borderRadius: BorderRadius.circular(20)), + child: Row( + children: [ + Expanded( + flex: 75, + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + 'Current Balance', + style: TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, + fontSize: 17, + ), + )), + SizedBox( + height: 10, + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + userBalance, + style: TextStyle( + color: ColorsManager.blueColor1, + fontWeight: FontWeight.bold, + fontSize: 50), + ), + SizedBox( + width: 5, + ), + Text( + 'Points', + style: TextStyle( + color: ColorsManager.blueColor1, + fontWeight: FontWeight.bold, + fontSize: 17), + ), + ], + )), + ], + ), + )), + Expanded( + flex: 25, + child: InkWell( + //TODO:should use custom Navigator + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => BookPage(), + )), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.only(left: 15), + height: double.infinity, + decoration: BoxDecoration( + color: ColorsManager.blueColor, + borderRadius: + BorderRadius.horizontal(right: Radius.circular(20)), + ), + child: Text( + 'Book Now', + style: TextStyle( + color: ColorsManager.onPrimaryColor, + fontWeight: FontWeight.bold, + fontSize: 20), + ), + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/booking_system/presentation/widgets/past_booking_widget.dart b/lib/features/booking_system/presentation/widgets/past_booking_widget.dart new file mode 100644 index 0000000..dd13aa0 --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/past_booking_widget.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../utils/helpers/app_size.dart'; +import '../../../../utils/resource_manager/color_manager.dart'; +import '../../../shared_widgets/default_button.dart'; +import '../blocs/past_bookings_bloc/past_bookings_bloc.dart'; +import 'booking_card_widget.dart'; + +class PastBookingsWidget extends StatelessWidget { + const PastBookingsWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Past Bookings', + style: TextStyle( + fontSize: 15, + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: 10, + ), + BlocBuilder( + builder: (context, state) { + if (state is PastBookingLoadingState) { + return CircularProgressIndicator(); + } else if (state is PastBookingErrorState) { + return DefaultButton( + onPressed: () => context + .read() + .add(GetPastBookingsEvent()), + child: Text('Try again'), + ); + } else if (state is PastBookingLoadedState) { + return SizedBox( + height: deviceHeight(context) * 0.3, + child: state.pastBookings.isEmpty + ? Text('You Dont Have past Bookings') + : ListView.separated( + separatorBuilder: (context, index) => SizedBox( + height: 10, + ), + itemCount: state.pastBookings.length, + itemBuilder: (context, index) { + final pastBooking = state.pastBookings[index]; + return BookingCardWidget(bookingModel: pastBooking); + }, + ), + ); + } else { + return SizedBox(); + } + }, + ) + ], + ); + } +} diff --git a/lib/features/booking_system/presentation/widgets/row_of_title_arrow_widget.dart b/lib/features/booking_system/presentation/widgets/row_of_title_arrow_widget.dart new file mode 100644 index 0000000..29ba474 --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/row_of_title_arrow_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../../../../utils/resource_manager/color_manager.dart'; + +class RowOfTitleAndArrowWidget extends StatelessWidget { + final String title; + final void Function() onTap; + const RowOfTitleAndArrowWidget({ + super.key, + required this.title, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Text( + title, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ), + IconButton( + onPressed: onTap, + icon: Icon( + Icons.arrow_forward_ios, + color: ColorsManager.grayColor, + size: 15, + ), + ) + ], + ); + } +} diff --git a/lib/features/booking_system/presentation/widgets/upcoming_bookings_widget.dart b/lib/features/booking_system/presentation/widgets/upcoming_bookings_widget.dart new file mode 100644 index 0000000..883f972 --- /dev/null +++ b/lib/features/booking_system/presentation/widgets/upcoming_bookings_widget.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../utils/helpers/app_size.dart'; +import '../../../../utils/resource_manager/color_manager.dart'; +import '../../../shared_widgets/default_button.dart'; +import '../blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart'; +import 'booking_card_widget.dart'; + +class UpcomingBookingsWidget extends StatelessWidget { + const UpcomingBookingsWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Upcoming Bookings', + style: TextStyle( + fontSize: 15, + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: 10, + ), + BlocBuilder( + builder: (context, state) { + if (state is UpcomingBookingLoadingState) { + //TODO:should use CustomLoadingWidget + return CircularProgressIndicator(); + } else if (state is UpcomingBookingErrorState) { +//TODO:shold use CustomErrorWidget + return DefaultButton( + onPressed: () => context + .read() + .add(GetUpcomingBookingsEvent()), + child: Text('Try again'), + ); + } else if (state is UpcomingBookingLoadedState) { + return SizedBox( + height: deviceHeight(context) * 0.3, + child: state.upcomingBookings.isEmpty + ? Text('You Dont Have Upcoming Bookings') + : ListView.separated( + separatorBuilder: (context, index) => SizedBox( + height: 10, + ), + itemCount: state.upcomingBookings.length, + itemBuilder: (context, index) { + final upcomingBooking = state.upcomingBookings[index]; + return BookingCardWidget( + bookingModel: upcomingBooking); + }, + ), + ); + } else { + return SizedBox(); + } + }, + ) + ], + ); + } +} diff --git a/lib/features/menu/bloc/menu_cubit.dart b/lib/features/menu/bloc/menu_cubit.dart index 624112f..dbbced2 100644 --- a/lib/features/menu/bloc/menu_cubit.dart +++ b/lib/features/menu/bloc/menu_cubit.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/booking_system/presentation/screens/booking_system_page.dart'; import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart'; import 'package:syncrow_app/features/menu/bloc/user_agreement.dart'; import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart'; @@ -44,6 +45,18 @@ class MenuCubit extends Cubit { } List> menuSections = [ +//Booking System + { + 'title': 'Booking System', + 'color': const Color(0xFF8AB9FF), + 'buttons': [ + { + 'title': 'Booking', + 'Icon': Assets.assetsIconsMenuBookingSystem, + 'page': BookingSystemPage() + }, + ], + }, //Home Management { 'title': 'Home Management', diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 9a3876e..861444c 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -495,6 +495,7 @@ class Assets { /// assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg static const String assetsIconsMenuIconsHomeManagementIconsCreateHome = "assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg"; + static const String assetsIconsMenuBookingSystem = 'assets/icons/Booking.svg'; /// Assets for assetsIconsMenuIconsHomeManagementIconsJoinAHome /// assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index f8cd72f..e66849b 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -234,4 +234,7 @@ abstract class ApiEndpoints { '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; static const String getReportLogs = '/devices/{uuid}/report-logs?code={code}'; + + //booking System APIs + static const String upcomingBookings = '/bookings/{uuid}/'; } diff --git a/lib/utils/helpers/app_size.dart b/lib/utils/helpers/app_size.dart new file mode 100644 index 0000000..9a86052 --- /dev/null +++ b/lib/utils/helpers/app_size.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; + +double deviceHeight(BuildContext context) => MediaQuery.sizeOf(context).height; +double deviceWidth(BuildContext context) => MediaQuery.sizeOf(context).width;