working on make booking page
This commit is contained in:
@@ -4,6 +4,8 @@ import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import '../core/route_constants.dart';
|
||||
|
||||
class AttractionDetailsView extends StatelessWidget {
|
||||
const AttractionDetailsView({super.key});
|
||||
|
||||
@@ -258,44 +260,49 @@ class AttractionDetailsView extends StatelessWidget {
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24.w,
|
||||
vertical: 18.h,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFF95F62),
|
||||
borderRadius: BorderRadius.circular(10.r),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomText(
|
||||
text: "Via CityCards",
|
||||
size: 16.sp,
|
||||
weight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
CustomText(
|
||||
text: "Create a booking via app",
|
||||
size: 11.sp,
|
||||
weight: FontWeight.w400,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
InkWell(
|
||||
onTap: (){
|
||||
Navigator.of(context).pushNamed(RouteConstants.makeBooking);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24.w,
|
||||
vertical: 18.h,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFF95F62),
|
||||
borderRadius: BorderRadius.circular(10.r),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomText(
|
||||
text: "Via CityCards",
|
||||
size: 16.sp,
|
||||
weight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
CustomText(
|
||||
text: "Create a booking via app",
|
||||
size: 11.sp,
|
||||
weight: FontWeight.w400,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class AttractionsPage extends StatelessWidget {
|
||||
children: [
|
||||
// App bar
|
||||
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
|
||||
backWidget(context, "Your Attraction"),
|
||||
backWidget(context, "Your Attraction", Colors.black),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 🔍 Search field
|
||||
|
||||
@@ -6,9 +6,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../attraction_details/attraction_details_view.dart';
|
||||
import '../attractions/views/attractions_page_view.dart';
|
||||
import '../my_pass/views/booking_page_view.dart';
|
||||
import '../my_pass/views/qr_pass_page_view.dart';
|
||||
import '../postcard/blocs/postcard_creation_bloc.dart';
|
||||
import '../postcard/views/postcard_creation_page_view.dart';
|
||||
import '../search_offers/bloc/search_offers_listing_bloc.dart';
|
||||
import '../search_offers/view/search_offers_with_listing.dart';
|
||||
|
||||
Widget buildOffstageNavigator(
|
||||
int index,
|
||||
@@ -36,6 +39,22 @@ Widget buildOffstageNavigator(
|
||||
return MaterialPageRoute(builder: (_) {
|
||||
return AttractionDetailsView();
|
||||
});
|
||||
|
||||
case RouteConstants.makeBooking:
|
||||
return MaterialPageRoute(builder: (_) {
|
||||
return MakeBookingView(title: 'asffdsf', description: 'afdsfadsfasdfads',);
|
||||
});
|
||||
|
||||
case RouteConstants.searchOffer:
|
||||
return MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return BlocProvider(
|
||||
create: (_) => OffersBloc(),
|
||||
child: SearchOffersWithListing(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// 🔹 Upload Photo Page (start of postcard creation flow)
|
||||
case RouteConstants.uploadPhotoPage:
|
||||
return MaterialPageRoute(
|
||||
|
||||
@@ -48,4 +48,5 @@ class RouteConstants {
|
||||
|
||||
|
||||
static const String qrPage = '/qrPage';
|
||||
static const String makeBooking = '/makeBooking';
|
||||
}
|
||||
|
||||
35
lib/my_pass/blocs/make_booking_bloc.dart
Normal file
35
lib/my_pass/blocs/make_booking_bloc.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'make_booking_events.dart';
|
||||
import 'make_booking_state.dart';
|
||||
|
||||
class MakeBookingBloc extends Bloc<MakeBookingEvent, MakeBookingState> {
|
||||
MakeBookingBloc() : super(const MakeBookingState(loading: true)) {
|
||||
on<LoadAvailableDates>(_onLoadAvailableDates);
|
||||
on<SelectDate>(_onSelectDate);
|
||||
}
|
||||
|
||||
void _onLoadAvailableDates(
|
||||
LoadAvailableDates event, Emitter<MakeBookingState> emit) async {
|
||||
emit(state.copyWith(loading: true));
|
||||
|
||||
// Simulate API load delay
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Dummy available dates
|
||||
final now = DateTime.now();
|
||||
final available = [
|
||||
now.add(const Duration(days: 2)),
|
||||
now.add(const Duration(days: 5)),
|
||||
now.add(const Duration(days: 7)),
|
||||
now.add(const Duration(days: 10)),
|
||||
now.add(const Duration(days: 11)),
|
||||
now.add(const Duration(days: 13)),
|
||||
];
|
||||
|
||||
emit(state.copyWith(availableDates: available, loading: false));
|
||||
}
|
||||
|
||||
void _onSelectDate(SelectDate event, Emitter<MakeBookingState> emit) {
|
||||
emit(state.copyWith(startDate: event.startDate, endDate: event.endDate));
|
||||
}
|
||||
}
|
||||
18
lib/my_pass/blocs/make_booking_events.dart
Normal file
18
lib/my_pass/blocs/make_booking_events.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class MakeBookingEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadAvailableDates extends MakeBookingEvent {}
|
||||
|
||||
class SelectDate extends MakeBookingEvent {
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
SelectDate(this.startDate, this.endDate);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startDate, endDate];
|
||||
}
|
||||
32
lib/my_pass/blocs/make_booking_state.dart
Normal file
32
lib/my_pass/blocs/make_booking_state.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class MakeBookingState extends Equatable {
|
||||
final List<DateTime> availableDates;
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final bool loading;
|
||||
|
||||
const MakeBookingState({
|
||||
this.availableDates = const [],
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.loading = false,
|
||||
});
|
||||
|
||||
MakeBookingState copyWith({
|
||||
List<DateTime>? availableDates,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
bool? loading,
|
||||
}) {
|
||||
return MakeBookingState(
|
||||
availableDates: availableDates ?? this.availableDates,
|
||||
startDate: startDate ?? this.startDate,
|
||||
endDate: endDate ?? this.endDate,
|
||||
loading: loading ?? this.loading,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [availableDates, startDate, endDate, loading];
|
||||
}
|
||||
245
lib/my_pass/views/booking_page_view.dart
Normal file
245
lib/my_pass/views/booking_page_view.dart
Normal file
@@ -0,0 +1,245 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
import '../blocs/make_booking_bloc.dart';
|
||||
import '../blocs/make_booking_events.dart';
|
||||
import '../blocs/make_booking_state.dart';
|
||||
|
||||
class MakeBookingView extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
|
||||
const MakeBookingView({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => MakeBookingBloc()..add(LoadAvailableDates()),
|
||||
child: BlocBuilder<MakeBookingBloc, MakeBookingState>(
|
||||
builder: (context, state) {
|
||||
if (state.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final bloc = context.read<MakeBookingBloc>();
|
||||
final now = DateTime.now();
|
||||
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 🔙 Back + Title
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.arrow_back, size: 20),
|
||||
SizedBox(width: 6.w),
|
||||
Text(
|
||||
"Make Booking",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// 🏝 Attraction title
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
description,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// 📅 Calendar
|
||||
Container(
|
||||
padding: EdgeInsets.all(12.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12.withOpacity(0.05),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"When are you visiting?",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
|
||||
TableCalendar(
|
||||
focusedDay: now,
|
||||
firstDay: now,
|
||||
lastDay: now.add(const Duration(days: 365)),
|
||||
calendarFormat: CalendarFormat.month,
|
||||
availableCalendarFormats: const {
|
||||
CalendarFormat.month: 'Month'
|
||||
},
|
||||
rangeStartDay: state.startDate,
|
||||
rangeEndDay: state.endDate,
|
||||
rangeSelectionMode: RangeSelectionMode.toggledOn,
|
||||
onRangeSelected: (start, end, focusedDay) {
|
||||
if (start != null && end != null) {
|
||||
bloc.add(SelectDate(start, end));
|
||||
}
|
||||
},
|
||||
headerStyle: HeaderStyle(
|
||||
titleCentered: true,
|
||||
formatButtonVisible: false,
|
||||
titleTextStyle: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
calendarStyle: CalendarStyle(
|
||||
rangeHighlightColor:
|
||||
const Color(0xffFF5A5F).withOpacity(0.2),
|
||||
rangeStartDecoration: const BoxDecoration(
|
||||
color: Color(0xffFF5A5F),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
rangeEndDecoration: const BoxDecoration(
|
||||
color: Color(0xffFF5A5F),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
todayDecoration: const BoxDecoration(
|
||||
color: Color(0xffFFEAEA),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
outsideDaysVisible: false,
|
||||
),
|
||||
|
||||
// Custom day builder for unavailable days
|
||||
calendarBuilders: CalendarBuilders(
|
||||
defaultBuilder: (context, day, focusedDay) {
|
||||
final isAvailable = state.availableDates
|
||||
.any((d) => isSameDay(d, day));
|
||||
|
||||
if (!isAvailable) {
|
||||
// ❌ Strike-through unavailable date
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'${day.day}',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 12.h,
|
||||
child: Container(
|
||||
width: 14.w,
|
||||
height: 1.2.h,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ Normal available day
|
||||
return Center(
|
||||
child: Text(
|
||||
'${day.day}',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 40.h),
|
||||
|
||||
// ✅ Confirm Booking button
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (state.startDate != null && state.endDate != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Booking confirmed from "
|
||||
"${state.startDate!.toLocal().toString().split(' ')[0]} "
|
||||
"to ${state.endDate!.toLocal().toString().split(' ')[0]}",
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Please select a valid date range"),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(vertical: 14.h),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffFF5A5F),
|
||||
borderRadius: BorderRadius.circular(30.r),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Confirm Booking",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class QrPassView extends StatelessWidget {
|
||||
children: [
|
||||
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
|
||||
SizedBox(height: 10.h),
|
||||
backWidget(context, "Back"),
|
||||
backWidget(context, "Back", Colors.black),
|
||||
SizedBox(height: 20.h),
|
||||
SizedBox(height: 10.h),
|
||||
Text(
|
||||
@@ -124,7 +124,9 @@ class QrPassView extends StatelessWidget {
|
||||
SizedBox(height: 12.h),
|
||||
actionButton(
|
||||
label: "View All Available Offers",
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(RouteConstants.searchOffer);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
24
pubspec.lock
24
pubspec.lock
@@ -105,6 +105,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -640,6 +648,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
simple_gesture_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -693,6 +709,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
table_calendar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: table_calendar
|
||||
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -43,6 +43,8 @@ dependencies:
|
||||
flutter_otp_text_field: ^1.5.1+1
|
||||
google_maps_flutter: ^2.13.1
|
||||
geolocator: ^14.0.2
|
||||
equatable: ^2.0.7
|
||||
table_calendar: ^3.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user