Merge pull request #109 from SyncrowIOT/build_booking_system

Build booking system
This commit is contained in:
raf-dev1
2025-06-17 16:25:55 +03:00
committed by GitHub
24 changed files with 859 additions and 22 deletions

12
assets/icons/Booking.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9795_16810)">
<path d="M3.68007 14.1344H4.98041C5.73422 14.1344 5.73533 12.96 4.98041 12.96H3.68007C2.92629 12.96 2.92518 14.1344 3.68007 14.1344ZM3.68007 13.3994H4.98041C5.17143 13.3994 5.17143 13.695 4.98041 13.695H3.68007C3.48908 13.695 3.48908 13.3994 3.68007 13.3994Z" fill="white"/>
<path d="M5.29431 7.55226L3.86289 8.9837L3.36716 8.48794C3.16621 8.28714 2.85552 8.59777 3.05641 8.79869L3.70753 9.44981C3.81112 9.53559 3.91472 9.53559 4.01828 9.44981L5.60509 7.86301C5.80595 7.66209 5.49525 7.35145 5.29431 7.55226Z" fill="white"/>
<path d="M14.8368 9.72059C14.7481 9.55125 14.6838 9.36574 14.6401 9.17956C14.4259 8.28325 14.1003 7.41489 13.6727 6.59856L12.9576 5.23374C12.5529 4.46106 11.3583 4.58953 11.1328 5.437C10.5978 5.24455 9.99396 5.56087 9.84768 6.11024C9.40544 5.9511 8.89477 6.13855 8.6603 6.54527V1.7204V0.858486C8.66028 0.385137 8.27517 0 7.80176 0H2.68301C2.39892 0 2.39892 0.439453 2.68301 0.439453H7.80176C8.03282 0.439453 8.22082 0.627422 8.22082 0.858486V1.50067H0.439659V0.858486C0.439659 0.627422 0.627628 0.439453 0.858692 0.439453H1.45257C1.73666 0.439453 1.73666 0 1.45257 0H0.858692C0.385343 0 0.000205994 0.385137 0.000205994 0.858486V1.7204V12.3108V14.1415C0.000205994 14.6149 0.385343 15 0.858692 15H7.80176C8.27514 15 8.66027 14.6149 8.66027 14.1415V12.3108V11.4307L9.44546 11.819C9.63949 11.915 9.79896 12.0748 9.89452 12.269C10.2433 12.9779 11.1374 13.2717 11.8396 12.9038L13.1487 12.218C13.4004 12.0861 13.1965 11.6969 12.9448 11.8287L11.6357 12.5145C11.1494 12.7694 10.5312 12.5676 10.2888 12.075C10.1508 11.7945 9.92051 11.5637 9.64028 11.4251L6.30826 9.77698C5.68245 9.47303 6.13503 8.49126 6.7706 8.78769L7.98044 9.42457C8.16709 9.52283 8.3753 9.31506 8.27742 9.1282L6.17569 5.11652C5.83058 4.46771 6.75481 3.86883 7.10733 4.54184L8.59292 7.37742L8.98096 8.1181C9.10904 8.36271 9.50171 8.16518 9.37023 7.91417L8.99244 7.19309C8.83945 6.68329 9.46884 6.27226 9.87393 6.62766L10.2943 7.43004C10.4224 7.67464 10.8151 7.47712 10.6836 7.2261L10.2561 6.41024C10.2161 5.94152 10.7901 5.63988 11.153 5.94905L11.5761 6.75671C11.7042 7.00131 12.0968 6.80379 11.9653 6.55277L11.5421 5.745C11.4846 5.16976 12.2942 4.91437 12.5683 5.43765L13.2834 6.80247C13.6942 7.5866 14.0068 8.42074 14.2127 9.28163C14.2627 9.50496 14.3413 9.7217 14.4475 9.92446C14.7007 10.4077 14.5135 11.0068 14.0303 11.26C13.7786 11.3919 13.9826 11.7811 14.2342 11.6492C14.9321 11.2836 15.2024 10.4184 14.8368 9.72059ZM8.22082 14.1415C8.22082 14.3726 8.03282 14.5605 7.80176 14.5605H0.858692C0.627628 14.5605 0.439659 14.3726 0.439659 14.1415V12.5305H8.22082V14.1415ZM5.64987 8.85926C5.50072 9.17279 5.5297 9.54832 5.72405 9.83587C5.36068 10.2161 4.86217 10.4316 4.33022 10.4316C3.26581 10.4316 2.39985 9.56563 2.39985 8.50122C2.39985 7.43681 3.26581 6.57085 4.33022 6.57085C5.34018 6.57085 6.17092 7.35062 6.2533 8.33965C5.99306 8.41904 5.76686 8.61328 5.64987 8.85926ZM7.24526 4.0302C7.14946 3.95241 7.03893 3.89259 6.91758 3.85468C6.21375 3.63475 5.50491 4.28016 5.65983 5.00092H1.48843C1.44803 5.00092 1.41519 4.96808 1.41519 4.92768V3.35742C1.41519 3.31702 1.44803 3.28418 1.48843 3.28418H7.17202C7.21242 3.28418 7.24526 3.31702 7.24526 3.35742V4.0302ZM8.22082 5.72027L7.68472 4.69702V3.35742C7.68472 3.07474 7.45471 2.84473 7.17202 2.84473H1.48843C1.20574 2.84473 0.975733 3.07474 0.975733 3.35742V4.92771C0.975733 5.21039 1.20574 5.4404 1.48843 5.4404H5.84921L7.56065 8.70703L6.97102 8.39663C6.96841 8.39525 6.96574 8.39391 6.96308 8.39265C6.87577 8.3511 6.78445 8.3231 6.69155 8.30851C6.59306 7.09166 5.572 6.1314 4.33025 6.1314C3.02353 6.1314 1.96043 7.19449 1.96043 8.50122C1.96043 9.80795 3.02353 10.871 4.33025 10.871C4.98563 10.871 5.60016 10.6053 6.04658 10.1354C6.74367 10.5371 7.49956 10.8565 8.22082 11.2133V12.091H0.439659V1.94013H8.22082V5.72027Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_9795_16810">
<rect width="15" height="15" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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<String, dynamic> 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<String, dynamic> 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) {

View File

@ -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<List<BookingModel>> 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,
)
];
}
}

View File

@ -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<List<BookingModel>> 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<String, dynamic>?;
// final error = message?['error'] as Map<String, dynamic>?;
// final errorMessage = error?['error'] as String? ?? '';
// final formattedErrorMessage =
// [_defaultErrorMessage, errorMessage].join(': ');
// throw APIException(formattedErrorMessage);
// } catch (e) {
// final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
// throw APIException(formattedErrorMessage);
// }
//
}
}
}

View File

@ -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<String, dynamic> 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<BookingModel> fromJsonList(List<dynamic> jsonList) => jsonList
.map(
(bookModel) => BookingModel.fromJson(bookModel),
)
.toList();
Map<String, dynamic> toJson() => {
'uuid': uuid,
'roomName': roomName,
'date': date,
'timeSlot': timeSlot,
'cost': cost,
};
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_app/features/booking_system/domain/booking_model.dart';
abstract interface class BookingService {
Future<List<BookingModel>> get();
}

View File

@ -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<PastBookingsEvent, PastBookingsState> {
final BookingService _bookingService;
PastBookingsBloc(this._bookingService) : super(PastBookingsInitial()) {
on<GetPastBookingsEvent>(_onGetPastBookingsEvent);
}
Future<void> _onGetPastBookingsEvent(
GetPastBookingsEvent event,
Emitter<PastBookingsState> emit,
) async {
emit(PastBookingLoadingState());
try {
final pastBookings = await _bookingService.get();
emit(
PastBookingLoadedState(pastBookings: pastBookings),
);
} catch (e) {
emit(
PastBookingErrorState(e.toString()),
);
}
}
}

View File

@ -0,0 +1,10 @@
part of 'past_bookings_bloc.dart';
sealed class PastBookingsEvent extends Equatable {
const PastBookingsEvent();
@override
List<Object> get props => [];
}
class GetPastBookingsEvent extends PastBookingsEvent {}

View File

@ -0,0 +1,24 @@
part of 'past_bookings_bloc.dart';
sealed class PastBookingsState extends Equatable {
const PastBookingsState();
@override
List<Object> get props => [];
}
final class PastBookingsInitial extends PastBookingsState {}
final class PastBookingLoadingState extends PastBookingsState {}
final class PastBookingLoadedState extends PastBookingsState {
final List<BookingModel> pastBookings;
const PastBookingLoadedState({
required this.pastBookings,
});
}
final class PastBookingErrorState extends PastBookingsState {
final String errorMsg;
const PastBookingErrorState(this.errorMsg);
}

View File

@ -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<UpcomingBookingsEvent, UpcomingBookingsState> {
final BookingService _bookingService;
UpcomingBookingsBloc(this._bookingService)
: super(UpcomingBookingsInitial()) {
on<GetUpcomingBookingsEvent>(_onGetUpcomingBookingsEvent);
}
Future<void> _onGetUpcomingBookingsEvent(
GetUpcomingBookingsEvent event,
Emitter<UpcomingBookingsState> emit,
) async {
emit(UpcomingBookingLoadingState());
try {
final upcomingBookings = await _bookingService.get();
emit(
UpcomingBookingLoadedState(upcomingBookings: upcomingBookings),
);
} catch (e) {
emit(
UpcomingBookingErrorState(e.toString()),
);
}
}
}

View File

@ -0,0 +1,10 @@
part of 'upcoming_bookings_bloc.dart';
sealed class UpcomingBookingsEvent extends Equatable {
const UpcomingBookingsEvent();
@override
List<Object> get props => [];
}
class GetUpcomingBookingsEvent extends UpcomingBookingsEvent {}

View File

@ -0,0 +1,24 @@
part of 'upcoming_bookings_bloc.dart';
sealed class UpcomingBookingsState extends Equatable {
const UpcomingBookingsState();
@override
List<Object> get props => [];
}
final class UpcomingBookingsInitial extends UpcomingBookingsState {}
final class UpcomingBookingLoadingState extends UpcomingBookingsState {}
final class UpcomingBookingLoadedState extends UpcomingBookingsState {
final List<BookingModel> upcomingBookings;
const UpcomingBookingLoadedState({
required this.upcomingBookings,
});
}
final class UpcomingBookingErrorState extends UpcomingBookingsState {
final String errorMsg;
const UpcomingBookingErrorState(this.errorMsg);
}

View File

@ -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,
),
),
),
),
],
),
);
}
}

View File

@ -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<UpcomingBookingsBloc>(
create: (_) => UpcomingBookingsBloc(BookingDummySource())
..add(GetUpcomingBookingsEvent()),
),
BlocProvider<PastBookingsBloc>(
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(),
],
),
)
],
),
),
));
}
}

View File

@ -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);
}

View File

@ -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,
),
),
],
),
],
)
],
),
);
}
}

View File

@ -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),
),
),
),
)
],
),
);
}
}

View File

@ -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<PastBookingsBloc, PastBookingsState>(
builder: (context, state) {
if (state is PastBookingLoadingState) {
return CircularProgressIndicator();
} else if (state is PastBookingErrorState) {
return DefaultButton(
onPressed: () => context
.read<PastBookingsBloc>()
.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();
}
},
)
],
);
}
}

View File

@ -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,
),
)
],
);
}
}

View File

@ -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<UpcomingBookingsBloc, UpcomingBookingsState>(
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<UpcomingBookingsBloc>()
.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();
}
},
)
],
);
}
}

View File

@ -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<MenuState> {
}
List<Map<String, Object>> menuSections = [
//Booking System
{
'title': 'Booking System',
'color': const Color(0xFF8AB9FF),
'buttons': [
{
'title': 'Booking',
'Icon': Assets.assetsIconsMenuBookingSystem,
'page': BookingSystemPage()
},
],
},
//Home Management
{
'title': 'Home Management',

View File

@ -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

View File

@ -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}/';
}

View File

@ -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;