diff --git a/lib/attraction_details/attraction_details_view.dart b/lib/attraction_details/attraction_details_view.dart index 6ca5de3..a8ac802 100644 --- a/lib/attraction_details/attraction_details_view.dart +++ b/lib/attraction_details/attraction_details_view.dart @@ -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, + ), + ], + ), ), ), diff --git a/lib/attractions/views/attractions_page_view.dart b/lib/attractions/views/attractions_page_view.dart index 9621fcd..0a7afbb 100644 --- a/lib/attractions/views/attractions_page_view.dart +++ b/lib/attractions/views/attractions_page_view.dart @@ -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 diff --git a/lib/core/inside_bottom_navigator.dart b/lib/core/inside_bottom_navigator.dart index c7256ba..d793606 100644 --- a/lib/core/inside_bottom_navigator.dart +++ b/lib/core/inside_bottom_navigator.dart @@ -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( diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index d9abccd..768c7ee 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -48,4 +48,5 @@ class RouteConstants { static const String qrPage = '/qrPage'; + static const String makeBooking = '/makeBooking'; } diff --git a/lib/my_pass/blocs/make_booking_bloc.dart b/lib/my_pass/blocs/make_booking_bloc.dart new file mode 100644 index 0000000..bbcc597 --- /dev/null +++ b/lib/my_pass/blocs/make_booking_bloc.dart @@ -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 { + MakeBookingBloc() : super(const MakeBookingState(loading: true)) { + on(_onLoadAvailableDates); + on(_onSelectDate); + } + + void _onLoadAvailableDates( + LoadAvailableDates event, Emitter 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 emit) { + emit(state.copyWith(startDate: event.startDate, endDate: event.endDate)); + } +} diff --git a/lib/my_pass/blocs/make_booking_events.dart b/lib/my_pass/blocs/make_booking_events.dart new file mode 100644 index 0000000..45bd0fe --- /dev/null +++ b/lib/my_pass/blocs/make_booking_events.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +abstract class MakeBookingEvent extends Equatable { + @override + List get props => []; +} + +class LoadAvailableDates extends MakeBookingEvent {} + +class SelectDate extends MakeBookingEvent { + final DateTime startDate; + final DateTime endDate; + + SelectDate(this.startDate, this.endDate); + + @override + List get props => [startDate, endDate]; +} diff --git a/lib/my_pass/blocs/make_booking_state.dart b/lib/my_pass/blocs/make_booking_state.dart new file mode 100644 index 0000000..d87a5af --- /dev/null +++ b/lib/my_pass/blocs/make_booking_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +class MakeBookingState extends Equatable { + final List 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? 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 get props => [availableDates, startDate, endDate, loading]; +} diff --git a/lib/my_pass/views/booking_page_view.dart b/lib/my_pass/views/booking_page_view.dart new file mode 100644 index 0000000..6e6a9aa --- /dev/null +++ b/lib/my_pass/views/booking_page_view.dart @@ -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( + builder: (context, state) { + if (state.loading) { + return const Center(child: CircularProgressIndicator()); + } + + final bloc = context.read(); + 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, + ), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/my_pass/views/qr_pass_page_view.dart b/lib/my_pass/views/qr_pass_page_view.dart index 9404b73..dfa35f0 100644 --- a/lib/my_pass/views/qr_pass_page_view.dart +++ b/lib/my_pass/views/qr_pass_page_view.dart @@ -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); + }, ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 728c299..92d85c5 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 7cfb73f..461c3c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: