From 5d08e07de3f1dd909d7e890b1b1aae6952f692bb Mon Sep 17 00:00:00 2001 From: mystery012728 Date: Tue, 10 Feb 2026 19:05:42 +0530 Subject: [PATCH] added pass details screen new and updated create account page and more changes... --- assets/images/unlimited_card_details.png | Bin 0 -> 53995 bytes lib/attractions/widget/attraction_card.dart | 2 +- lib/buy_a_pass/view/buy_pass_view.dart | 8 +- .../custom_dash_border_painter.dart | 49 ++ lib/core/app_router.dart | 27 +- lib/core/inside_bottom_navigator.dart | 41 +- lib/core/route_constants.dart | 3 + .../bloc/create_account_bloc.dart | 17 +- .../bloc/create_account_event.dart | 14 +- .../repository/create_account_repository.dart | 22 +- .../view/create_account_view.dart | 72 ++- .../bloc/get_itinerary_bloc.dart | 17 +- .../bloc/get_itinerary_state.dart | 9 + .../views/magic_itinerary_view.dart | 96 ++- lib/login/view/verify_otp_bottomsheet.dart | 2 +- .../views/pass_attraction_details_view.dart | 594 ++++++++++++++++++ .../views/pass_attractions_page_view.dart | 160 +++++ lib/my_pass/views/pass_details_page_view.dart | 460 ++++++++++++++ lib/my_pass/views/qr_pass_page_view.dart | 144 ----- .../search_pass_offers_with_listing.dart | 348 ++++++++++ lib/my_pass/widgets/pass_attraction_card.dart | 191 ++++++ lib/profile/bloc/profile/profile_bloc.dart | 7 +- lib/profile/bloc/profile/profile_event.dart | 17 + .../repository/profile_repository.dart | 83 ++- .../view/edit_profile/edit_profile_view.dart | 67 +- .../view/search_offers_with_listing.dart | 12 +- 26 files changed, 2220 insertions(+), 242 deletions(-) create mode 100644 assets/images/unlimited_card_details.png create mode 100644 lib/common_packages/custom_dash_border_painter.dart create mode 100644 lib/my_pass/views/pass_attraction_details_view.dart create mode 100644 lib/my_pass/views/pass_attractions_page_view.dart create mode 100644 lib/my_pass/views/pass_details_page_view.dart delete mode 100644 lib/my_pass/views/qr_pass_page_view.dart create mode 100644 lib/my_pass/views/search_pass_offers_with_listing.dart create mode 100644 lib/my_pass/widgets/pass_attraction_card.dart diff --git a/assets/images/unlimited_card_details.png b/assets/images/unlimited_card_details.png new file mode 100644 index 0000000000000000000000000000000000000000..1345db4bfecdee7b8b083df7a103fba9d216f34b GIT binary patch literal 53995 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 n?v?<+nNbRA#- false; +} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 8079eba..ea01376 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -15,6 +15,8 @@ import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_s import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_view.dart'; import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_empty_view.dart'; import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_view.dart'; +import 'package:citycards_customer/my_pass/views/pass_attractions_page_view.dart'; +import 'package:citycards_customer/my_pass/views/search_pass_offers_with_listing.dart'; import 'package:citycards_customer/offer_pass_detail/offer_pass_detail_view.dart'; import 'package:citycards_customer/search_offers/bloc/search_offers_listing_bloc.dart'; import 'package:citycards_customer/search_offers/view/search_offers_with_listing.dart'; @@ -28,6 +30,7 @@ import '../cart/views/my_cart_view_page.dart'; import '../common_bloc/bottom_navigation_bloc.dart'; import '../home/views/home_page_view.dart'; import '../home/views/registered_user_home_page.dart'; +import '../my_pass/views/pass_attraction_details_view.dart'; import '../profile/view/contact_us/contact_us_view.dart'; import '../profile/view/edit_profile/edit_profile_view.dart'; import '../profile/view/faq/faq_view.dart'; @@ -70,6 +73,9 @@ class AppRouter { case RouteConstants.attractionsPage: final args = settings.arguments as String; return MaterialPageRoute(builder: (_) => AttractionsPage(source: args)); + case RouteConstants.passAttractionsPage: + final args = settings.arguments as String; + return MaterialPageRoute(builder: (_) => PassAttractionsPage(source: args)); case RouteConstants.profile: return MaterialPageRoute( builder: (_) { @@ -150,10 +156,18 @@ class AppRouter { ); case RouteConstants.attractionDetails: - final attractionId = settings.arguments as Attraction; + final attractionId = settings.arguments as int; return MaterialPageRoute( builder: (_) { - return AttractionDetailsView(attractionId: attractionId.id,); + return AttractionDetailsView(attractionId: attractionId); + }, + ); + + case RouteConstants.passAttractionDetails: + final attractionID = settings.arguments as int; + return MaterialPageRoute( + builder: (_) { + return PassAttractionDetailsView(attractionId: attractionID); }, ); @@ -190,6 +204,15 @@ class AppRouter { ); }, ); + case RouteConstants.searchPassOffer: + return MaterialPageRoute( + builder: (_) { + return BlocProvider( + create: (_) => OffersBloc(OffersRepository()), + child: PassOffersScreen(), + ); + }, + ); case RouteConstants.addDetails: final bookingId = settings.arguments as int; diff --git a/lib/core/inside_bottom_navigator.dart b/lib/core/inside_bottom_navigator.dart index 8173f63..a63601d 100644 --- a/lib/core/inside_bottom_navigator.dart +++ b/lib/core/inside_bottom_navigator.dart @@ -2,6 +2,9 @@ import 'package:citycards_customer/attractions/models/attraction_model.dart'; import 'package:citycards_customer/core/route_constants.dart'; import 'package:citycards_customer/home/views/registered_user_home_page.dart'; import 'package:citycards_customer/my_pass/blocs/my_pass_bloc.dart'; +import 'package:citycards_customer/my_pass/views/pass_attraction_details_view.dart'; +import 'package:citycards_customer/my_pass/views/pass_attractions_page_view.dart'; +import 'package:citycards_customer/my_pass/views/search_pass_offers_with_listing.dart'; import 'package:citycards_customer/postcard/views/add_filter_step_page_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,10 +21,11 @@ import '../itinerary_creation/views/itinerary_creation_view.dart'; import '../itinerary_creation/views/magic_itinerary_view.dart'; import '../my_pass/views/booking_page_view.dart'; import '../my_pass/views/booking_successful_page_view.dart'; -import '../my_pass/views/qr_pass_page_view.dart'; +import '../my_pass/views/pass_details_page_view.dart'; import '../offer_pass_detail/offer_pass_detail_view.dart'; import '../postcard/blocs/postcard_creation_bloc.dart'; import '../postcard/views/postcard_creation_page_view.dart'; +import '../profile/view/privacy/privacy_view.dart'; import '../search_offers/bloc/offers_bloc.dart'; import '../search_offers/bloc/search_offers_listing_bloc.dart'; import '../search_offers/repository/offers_repository.dart'; @@ -54,12 +58,25 @@ Widget buildOffstageNavigator( return MaterialPageRoute( builder: (_) => AttractionsPage(source: args), ); + case RouteConstants.passAttractionsPage: + final args = settings.arguments as String; + return MaterialPageRoute( + builder: (_) => PassAttractionsPage(source: args), + ); case RouteConstants.attractionDetails: - final attraction = settings.arguments as Attraction; + final attractionID = settings.arguments as int; return MaterialPageRoute( builder: (_) { - return AttractionDetailsView(attractionId: attraction.id); + return AttractionDetailsView(attractionId: attractionID); + }, + ); + + case RouteConstants.passAttractionDetails: + final attractionID = settings.arguments as int; + return MaterialPageRoute( + builder: (_) { + return PassAttractionDetailsView(attractionId: attractionID); }, ); @@ -99,6 +116,22 @@ Widget buildOffstageNavigator( ); }, ); + case RouteConstants.searchPassOffer: + return MaterialPageRoute( + builder: (_) { + return BlocProvider( + create: (_) => OffersBloc(OffersRepository()), + child: PassOffersScreen(), + ); + }, + ); + + case RouteConstants.privacyPolicy: + return MaterialPageRoute( + builder: (_) { + return const PrivacyPolicyPage(); + }, + ); // 🔹 Upload Photo Page (start of postcard creation flow) case RouteConstants.uploadPhotoPage: @@ -129,7 +162,7 @@ Widget buildOffstageNavigator( final previousBloc = BlocProvider.of(context); return BlocProvider.value( value: previousBloc, - child: const QrPassView(), + child: const PassDetailsView(), ); }, ); diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index 0d8c270..d4ac16b 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -9,6 +9,7 @@ class RouteConstants { static const String home = '/home'; static const String registeredUserHome = '/registeredUserHome'; static const String attractionsPage = "/attractions"; + static const String passAttractionsPage = "/passAttractionsPage"; static const String postCardPage = "/postcards"; static const String uploadPhotoPage = "/uploadPhoto"; static const String addFilterPage = "/addFilter"; @@ -37,12 +38,14 @@ class RouteConstants { /**************************** Attraction Page *****************************************/ static const String attractionDetails ='/attractionDetails'; + static const String passAttractionDetails ='/passAttractionDetails'; /**************************** By Pass Page Page *****************************************/ static const String buyPass ='/buyPass'; static const String checkout ='/checkout'; static const String searchOffer = '/searchOffer'; + static const String searchPassOffer = '/searchPassOffer'; static const String createAcct = '/createAcct'; static const String addDetails = '/addDetails'; static const String offerPassDetail = "/offerPassDetail"; diff --git a/lib/create_account/bloc/create_account_bloc.dart b/lib/create_account/bloc/create_account_bloc.dart index cd62f97..b808abe 100644 --- a/lib/create_account/bloc/create_account_bloc.dart +++ b/lib/create_account/bloc/create_account_bloc.dart @@ -28,14 +28,21 @@ class CreateAccountBloc extends Bloc { mobileNumber: event.mobileNumber, address1: event.address1, address2: event.address2, + city: event.city, + state: event.state, + country: event.country, + postalCode: event.postalCode, ); + await LocalPreference.setLogin(true); + // ✅ FIX: Parse directly from response, just like verify OTP + final userModel = UserRegisteredModel.fromJson(response); - final userModel = UserRegisteredModel.fromJson(response['data'] ?? {}); await LocalPreference.setTokens( accessToken: userModel.accessToken, refreshToken: userModel.refreshToken, refreshTokenMaxAge: userModel.refreshTokenMaxAge, ); + await LocalPreference.setUserDetails( userId: userModel.user.id, firstName: userModel.user.firstName, @@ -45,10 +52,12 @@ class CreateAccountBloc extends Bloc { role: userModel.user.role, roleId: userModel.user.roleId, ); + await LocalPreference.setProfileImage(userModel.user.profileImage); + emit(CreateAccountSuccess( - message: response['message'] ?? 'Account created successfully', - userData: response['data'] ?? {}, + message: 'Account created successfully', + userData: response, )); } catch (e) { emit(CreateAccountFailure( @@ -63,4 +72,4 @@ class CreateAccountBloc extends Bloc { ) { emit(const CreateAccountInitial()); } -} \ No newline at end of file +} diff --git a/lib/create_account/bloc/create_account_event.dart b/lib/create_account/bloc/create_account_event.dart index 5bd6fd7..26a484b 100644 --- a/lib/create_account/bloc/create_account_event.dart +++ b/lib/create_account/bloc/create_account_event.dart @@ -14,6 +14,10 @@ class CreateAccountSubmitted extends CreateAccountEvent { final String mobileNumber; final String address1; final String address2; + final String city; + final String state; + final String country; + final String postalCode; const CreateAccountSubmitted({ required this.firstName, @@ -22,6 +26,10 @@ class CreateAccountSubmitted extends CreateAccountEvent { required this.mobileNumber, required this.address1, required this.address2, + required this.city, + required this.state, + required this.country, + required this.postalCode, }); @override @@ -32,9 +40,13 @@ class CreateAccountSubmitted extends CreateAccountEvent { mobileNumber, address1, address2, + city, + state, + country, + postalCode, ]; } class CreateAccountReset extends CreateAccountEvent { const CreateAccountReset(); -} \ No newline at end of file +} diff --git a/lib/create_account/repository/create_account_repository.dart b/lib/create_account/repository/create_account_repository.dart index 738f7d4..2f54d8c 100644 --- a/lib/create_account/repository/create_account_repository.dart +++ b/lib/create_account/repository/create_account_repository.dart @@ -11,17 +11,25 @@ class CreateAccountRepository { required String mobileNumber, required String address1, required String address2, + required String city, + required String state, + required String country, + required String postalCode, }) async { try { final response = await _apiServices.postApi( url: ApiUrls.createAccount, data: { - 'firstName': firstName, - 'lastName': lastName, - 'emailAddress': emailAddress, - 'mobileNumber': mobileNumber, - 'address1': address1, - 'address2': address2, + "firstName": firstName, + "lastName": lastName, + "emailAddress": emailAddress, + "mobileNumber": mobileNumber, + "address1": address1, + "address2": address2, + "city": city, + "state": state, + "country": country, + "postalCode": postalCode, }, ); @@ -30,4 +38,4 @@ class CreateAccountRepository { throw Exception('Failed to create account: $e'); } } -} \ No newline at end of file +} diff --git a/lib/create_account/view/create_account_view.dart b/lib/create_account/view/create_account_view.dart index be78665..e492639 100644 --- a/lib/create_account/view/create_account_view.dart +++ b/lib/create_account/view/create_account_view.dart @@ -5,7 +5,11 @@ import 'package:citycards_customer/common_packages/custom_textfield.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../core/route_constants.dart'; +import '../../itinerary_creation/bloc/get_itinerary_bloc.dart'; import '../../localPreference/local_preference.dart'; +import '../../postcard/blocs/myPostCards/my_postcard_bloc.dart'; +import '../../postcard/blocs/myPostCards/my_postcard_event.dart'; import '../../profile/bloc/profile/profile_bloc.dart'; import '../../profile/bloc/profile/profile_event.dart'; import '../bloc/create_account_bloc.dart'; @@ -22,16 +26,24 @@ class CreateAccountView extends StatelessWidget { final TextEditingController emailController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); final TextEditingController addressController = TextEditingController(); + final TextEditingController cityController = TextEditingController(); + final TextEditingController stateController = TextEditingController(); + final TextEditingController countryController = TextEditingController(); + final TextEditingController postalController = TextEditingController(); void _submitForm(BuildContext context) { if (firstNameController.text.trim().isEmpty || lastNameController.text.trim().isEmpty || emailController.text.trim().isEmpty || phoneController.text.trim().isEmpty || - addressController.text.trim().isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Please fill all fields'))); + addressController.text.trim().isEmpty || + cityController.text.trim().isEmpty || + stateController.text.trim().isEmpty || + countryController.text.trim().isEmpty || + postalController.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please fill all fields')), + ); return; } @@ -43,6 +55,10 @@ class CreateAccountView extends StatelessWidget { mobileNumber: phoneController.text.trim(), address1: addressController.text.trim(), address2: '', + city: cityController.text.trim(), + state: stateController.text.trim(), + country: countryController.text.trim(), + postalCode: postalController.text.trim(), ), ); } @@ -56,14 +72,19 @@ class CreateAccountView extends StatelessWidget { child: BlocListener( listener: (ctx, state) async { if (state is CreateAccountSuccess) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(state.message))); await LocalPreference.setLogin(true); final userId = await LocalPreference.getUserId(); context.read().add(FetchProfileEvent(userId: userId!)); context.read().add(CheckLoginStatusEvent()); + context.read().add(CheckLoginStatus()); + context.read().add(CheckLoginAndFetchItinerary()); + // context.read().add(FetchDraftPostCards()); + context.read().add(RefreshDraftPostCards()); + context.read().add(RefreshOrderPostCards()); Navigator.pop(context); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(state.message))); } else if (state is CreateAccountFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -168,14 +189,45 @@ class CreateAccountView extends StatelessWidget { Padding( padding: EdgeInsets.symmetric(horizontal: 12.w), child: CustomTextField( - label: "Address 1", + label: "Address", hint: "Enter address manually or tap to search", controller: addressController, ), ), - + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "City", + hint: "Enter your city", + controller: cityController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "State", + hint: "Enter your state", + controller: stateController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "Country", + hint: "Enter your country", + controller: countryController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "Postal Code", + hint: "Enter postal / zip code", + controller: postalController, + keyboardType: TextInputType.number, + ), + ), SizedBox(height: 20.h), - BlocBuilder( builder: (context, state) { if (state is CreateAccountLoading) { diff --git a/lib/itinerary_creation/bloc/get_itinerary_bloc.dart b/lib/itinerary_creation/bloc/get_itinerary_bloc.dart index 23e850b..bf0cb73 100644 --- a/lib/itinerary_creation/bloc/get_itinerary_bloc.dart +++ b/lib/itinerary_creation/bloc/get_itinerary_bloc.dart @@ -23,18 +23,21 @@ class GetItineraryBloc extends Bloc { try { emit(GetItineraryLoading()); - // Check login status final isLoggedIn = await LocalPreference.getLogin(); - // Uncomment above and remove below line when ready for production - // final isLoggedIn = true; // For testing if (!isLoggedIn) { emit(GetItineraryNotLoggedIn()); return; } - // If logged in, fetch itineraries final response = await _repository.fetchMyItineraries(); + + // Check if user has unlimited pass + if (!response.isUnlimitedPass) { + emit(GetItineraryRequiresPass(itineraries: response.itineraries)); + return; + } + emit(GetItinerarySuccessfully(itineraries: response.itineraries)); } catch (e) { emit(GetItineraryFailed( @@ -53,6 +56,12 @@ class GetItineraryBloc extends Bloc { final response = await _repository.fetchMyItineraries(); + // Check if user has unlimited pass + if (!response.isUnlimitedPass) { + emit(GetItineraryRequiresPass(itineraries: response.itineraries)); + return; + } + emit(GetItinerarySuccessfully(itineraries: response.itineraries)); } catch (e) { emit(GetItineraryFailed( diff --git a/lib/itinerary_creation/bloc/get_itinerary_state.dart b/lib/itinerary_creation/bloc/get_itinerary_state.dart index 616e7a9..4f9afc8 100644 --- a/lib/itinerary_creation/bloc/get_itinerary_state.dart +++ b/lib/itinerary_creation/bloc/get_itinerary_state.dart @@ -22,6 +22,15 @@ class GetItinerarySuccessfully extends GetItineraryState { List get props => [itineraries]; } +class GetItineraryRequiresPass extends GetItineraryState { + final List itineraries; + + const GetItineraryRequiresPass({required this.itineraries}); + + @override + List get props => [itineraries]; +} + class GetItineraryFailed extends GetItineraryState { final String error; diff --git a/lib/itinerary_creation/views/magic_itinerary_view.dart b/lib/itinerary_creation/views/magic_itinerary_view.dart index 68ea535..1af2f78 100644 --- a/lib/itinerary_creation/views/magic_itinerary_view.dart +++ b/lib/itinerary_creation/views/magic_itinerary_view.dart @@ -9,6 +9,7 @@ import 'package:citycards_customer/postcard/widgets/dotted_border_container.dart import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../common_bloc/bottom_navigation_bloc.dart'; import '../../login/view/login_email_bottomsheet.dart'; import 'package:intl/intl.dart'; @@ -43,7 +44,6 @@ class _MagicItineraryViewState extends State { showDivider: false, ), SizedBox(height: 24.h), - // BLoC Builder for all states BlocBuilder( builder: (context, state) { @@ -56,6 +56,8 @@ class _MagicItineraryViewState extends State { ); } else if (state is GetItineraryNotLoggedIn) { return NotLoggedInItineraryView(); + } else if (state is GetItineraryRequiresPass) { + return RequiresUnlimitedPassView(); } else if (state is GetItinerarySuccessfully) { if (state.itineraries.isEmpty) { return NoItineraryView(); @@ -192,8 +194,8 @@ class NotLoggedInItineraryView extends StatelessWidget { } } -class NoItineraryView extends StatelessWidget { - const NoItineraryView({super.key}); +class RequiresUnlimitedPassView extends StatelessWidget { + const RequiresUnlimitedPassView({super.key}); @override Widget build(BuildContext context) { @@ -201,17 +203,17 @@ class NoItineraryView extends StatelessWidget { children: [ SizedBox(height: 40.h), - // Illustration for no itineraries - Icon( - Icons.travel_explore, - size: 120.sp, - color: Colors.grey.withOpacity(0.3), + // Illustration image + Image.asset( + "assets/images/no_itinerary.png", // Update with your actual asset path + height: 300.h, + fit: BoxFit.contain, ), SizedBox(height: 32.h), CustomText( - text: "No Itineraries Yet", + text: "You do not possess an Unlimited Pass! 😔", size: 18.sp, weight: FontWeight.w600, textAlign: TextAlign.center, @@ -222,8 +224,7 @@ class NoItineraryView extends StatelessWidget { Padding( padding: EdgeInsets.symmetric(horizontal: 24.w), child: CustomText( - text: - "You haven't created any itineraries yet. Start planning your next adventure!", + text: "Get your Unlimited Pass and create a custom itinerary!", size: 14.sp, color: Color(0xFF656565), textAlign: TextAlign.center, @@ -234,14 +235,9 @@ class NoItineraryView extends StatelessWidget { CustomFilledButton( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ItineraryCreationStartPage(), - ), - ); + context.read().add(NavigationTabChanged(0)); }, - label: "Create My Itinerary", + label: "Buy Unlimited CityCard", showArrow: true, ), ], @@ -249,6 +245,70 @@ class NoItineraryView extends StatelessWidget { } } +class NoItineraryView extends StatelessWidget { + const NoItineraryView({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + children: [ + SizedBox(height: 60.h), + + /// Illustration Image + Center( + child: Image.asset( + "assets/images/no_itinerary.png", + height: 260.h, + fit: BoxFit.contain, + ), + ), + + SizedBox(height: 32.h), + + /// Title + CustomText( + text: "You Don’t have an Itinerary Yet! 😟", + size: 18.sp, + weight: FontWeight.w600, + textAlign: TextAlign.center, + ), + + SizedBox(height: 12.h), + + /// Subtitle + CustomText( + text: + "Create your own personalized magic itinerary that suites your travel needs", + size: 14.sp, + color: const Color(0xFF656565), + textAlign: TextAlign.center, + ), + + SizedBox(height: 32.h), + + /// Button + CustomFilledButton( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ItineraryCreationStartPage(), + ), + ); + }, + label: "Create My Itinerary", + showArrow: true, + ), + + SizedBox(height: 40.h), + ], + ), + ); + } +} + class ErrorItineraryView extends StatelessWidget { final String error; final VoidCallback onRetry; diff --git a/lib/login/view/verify_otp_bottomsheet.dart b/lib/login/view/verify_otp_bottomsheet.dart index 262ad3f..4242dd6 100644 --- a/lib/login/view/verify_otp_bottomsheet.dart +++ b/lib/login/view/verify_otp_bottomsheet.dart @@ -58,7 +58,7 @@ class _VerifyOtpBottomsheetState extends State { ); } else { // User doesn't exist - navigate to create account - Navigator.of(context).pushReplacementNamed(RouteConstants.createAcct,arguments: widget.emailAddress); + Navigator.of(context).pushNamed(RouteConstants.createAcct,arguments: widget.emailAddress); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Please complete your profile'), diff --git a/lib/my_pass/views/pass_attraction_details_view.dart b/lib/my_pass/views/pass_attraction_details_view.dart new file mode 100644 index 0000000..032477e --- /dev/null +++ b/lib/my_pass/views/pass_attraction_details_view.dart @@ -0,0 +1,594 @@ +import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.dart'; +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:latlong2/latlong.dart'; +import '../../attraction_details/bloc/attraction_details_bloc.dart'; +import '../../attraction_details/bloc/attraction_details_event.dart'; +import '../../attraction_details/bloc/attraction_details_state.dart'; +import '../../attraction_details/repository/attraction_details_repository.dart'; +import '../../core/route_constants.dart'; + +class PassAttractionDetailsView extends StatelessWidget { + final int? attractionId; + + const PassAttractionDetailsView({ + super.key, + required this.attractionId, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => AttractionDetailsBloc( + repository: AttractionDetailsRepository(), + )..add(FetchAttractionDetails(attractionId: attractionId??0)), + child: BlocBuilder( + builder: (context, state) { + if (state is AttractionDetailsLoading) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (state is AttractionDetailsError) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: Text( + state.message, + style: TextStyle(color: Colors.red), + ), + ), + ); + } + + if (state is AttractionDetailsLoaded) { + final attraction = state.attractionDetails; + final coverImage = attraction.attractionGalleries + .firstWhere( + (gallery) => gallery.isCoverImage, + orElse: () => attraction.attractionGalleries.first, + ) + .filePathUrl; + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + Image.network( + coverImage, + height: 377.h, + width: double.infinity, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + 'assets/images/koh_rong_samloem_banner.png', + height: 377.h, + width: double.infinity, + fit: BoxFit.cover, + ); + }, + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 20.w, vertical: 10.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: true, + isProfilePage: false, + showDivider: true, + ), + SizedBox(height: 10.h), + Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon( + Icons.arrow_back, + size: 24.sp, + color: Colors.white, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: Text( + attraction.title, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), + ), + ), + Positioned( + bottom: 31.h, + left: 12.w, + right: 60.w, // Add this - leaves space for share button + child: Text( + attraction.title, + style: TextStyle( + color: Colors.white, + fontSize: 44.sp, + fontWeight: FontWeight.w500, + height: 1.2, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Positioned( + bottom: 31.h, + right: 17.w, + child: GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => + const ShareBottomSheet(), + ); + }, + child: Container( + height: 36.h, + width: 36.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20.r), + ), + child: Center( + child: Icon( + Icons.share_sharp, + color: Colors.black, + size: 18.sp, + ), + ), + ), + ), + ), + ], + ), + + // About Section + Padding( + padding: + EdgeInsets.only(left: 16.w, right: 16.w, top: 20.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "About", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 12.32.h), + Text( + attraction.description, + style: TextStyle( + color: Color(0xFF262626), + fontWeight: FontWeight.w400, + fontSize: 14.sp, + height: 1.5, + ), + ), + ], + ), + ), + SizedBox(height: 41.h), + + // Booking Section + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "How to make a booking?", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: Color(0xFFF95F62)), + ), + child: Row( + children: [ + Icon( + Icons.call, + color: Color(0xFFF95F62), + size: 32.w, + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CustomText( + text: "Contact Number", + color: Colors.black.withOpacity(.6), + size: 12.sp, + weight: FontWeight.w500, + ), + SizedBox(height: 6.h), + CustomText( + text: attraction.bookingPhoneNumber??"N/A", + color: Colors.black, + size: 14.sp, + weight: FontWeight.w600, + ), + SizedBox(height: 6.h), + CustomText( + text: "Tap to call", + color: Colors.black.withOpacity(.4), + size: 12.sp, + weight: FontWeight.w400, + ), + ], + ), + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: Color(0xFFF95F62)), + ), + child: Row( + children: [ + Icon( + Icons.email_sharp, + color: Color(0xFFF95F62), + size: 32.w, + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CustomText( + text: "Email", + color: Colors.black.withOpacity(.6), + size: 12.sp, + weight: FontWeight.w500, + ), + SizedBox(height: 6.h), + CustomText( + text: attraction.bookingEmail??"N/A", + color: Colors.black, + size: 14.sp, + weight: FontWeight.w600, + ), + SizedBox(height: 6.h), + CustomText( + text: "Tap to email", + color: Colors.black.withOpacity(.4), + size: 12.sp, + weight: FontWeight.w400, + ), + ], + ), + ), + ], + ), + ), + SizedBox(height: 16.h), + 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, + ), + ], + ), + ), + ), + SizedBox(height: 30.h), + Divider(color: Colors.black.withOpacity(0.2)), + SizedBox(height: 30.h), + Text( + "What is included", + style: TextStyle( + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 4.h), + + // Dynamic Inclusions from API + Wrap( + runSpacing: 16.h, + spacing: 16.w, + children: attraction.attractionInclusions + .where((inclusion) => inclusion.isInclusion) + .map( + (inclusion) => includedBox( + "assets/icons/bus.png", + inclusion.title, + inclusion.description, + ), + ) + .toList(), + ), + SizedBox(height: 30.h), + // Divider(color: Colors.black.withOpacity(0.2)), + SizedBox(height: 30.h), + Text( + "Exact Location", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 8.h), + CustomText( + text: "View the location on map", + size: 12.sp, + color: Colors.black.withOpacity(.6), + ), + SizedBox(height: 17.h), + Container( + height: 178.7.h, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(13.54.r), + border: Border.all( + color: Colors.grey.withOpacity(0.3), + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(13.54.r), + child: FlutterMap( + options: MapOptions( + initialCenter: LatLng( + attraction.latitudeCoordinate, + attraction.longitudeCoordinate, + ), + initialZoom: 15.0, + interactionOptions: InteractionOptions( + flags: InteractiveFlag.all & ~InteractiveFlag.rotate, + ), + ), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'com.example.citycards_customer', + ), + MarkerLayer( + markers: [ + Marker( + point: LatLng( + attraction.latitudeCoordinate, + attraction.longitudeCoordinate, + ), + width: 40.w, + height: 40.h, + child: Icon( + Icons.location_on, + color: Color(0xFFF95F62), + size: 40.sp, + ), + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 17.h), + CustomText( + text: attraction.address, + size: 12.sp, + color: Colors.black.withOpacity(0.6), + ), + SizedBox(height: 30.h), + Divider(color: Colors.black.withOpacity(0.2)), + SizedBox(height: 30.h), + Text( + "People frequently ask", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 15.h), + Column( + children: attraction.attractionFaqs.map((faq) { + return Padding( + padding: EdgeInsets.only(bottom: 15.h), + child: faqBox( + title: faq.faqQuestion, + desc: faq.faqAnswer, + ), + ); + }).toList(), + ), + + ], + ), + ), + SizedBox(height: 24.h), + ], + ), + ), + ), + ); + } + + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: Text("Something went wrong"), + ), + ); + }, + ), + ); + } + + Widget includedBox(String icon, String title, String disc) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), + decoration: BoxDecoration( + color: Color(0xFFFFF5F5), + borderRadius: BorderRadius.circular(10.r), + border: Border.all(color: Color(0xFFFDCDCE)), + ), + child: Row( + children: [ + Image.asset(icon, scale: 4), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: title, + size: 16.sp, + weight: FontWeight.w500, + color: Color(0xFF212121), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4.h), + CustomText( + text: disc, + size: 11.sp, + weight: FontWeight.w400, + color: Color(0xFF666666), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ); + } + + Widget faqBox({ + required String title, + required String desc, + }) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + decoration: BoxDecoration( + color: const Color(0xFFFFF5F5), + border: Border.all(color: const Color(0xFFFDCDCE)), + borderRadius: BorderRadius.circular(10.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: CustomText( + text: title, + size: 16.sp, + weight: FontWeight.w500, + color: const Color(0xFF212121), + ), + ), + SizedBox(width: 20.w), + Icon( + Icons.arrow_forward_ios_outlined, + size: 18.sp, + color: Colors.black, + ), + ], + ), + SizedBox(height: 9.h), + CustomText( + text: desc, + size: 11.sp, + color: const Color(0xFF7D7D7D), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/my_pass/views/pass_attractions_page_view.dart b/lib/my_pass/views/pass_attractions_page_view.dart new file mode 100644 index 0000000..a4b427e --- /dev/null +++ b/lib/my_pass/views/pass_attractions_page_view.dart @@ -0,0 +1,160 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/back_widget.dart'; +import 'package:citycards_customer/my_pass/widgets/pass_attraction_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../attractions/blocs/attractions_bloc.dart'; +import '../../attractions/blocs/attractions_event.dart'; +import '../../attractions/blocs/attractions_state.dart'; +import '../../attractions/repository/attractions_repository.dart'; +import '../../attractions/widget/attraction_card.dart'; +import '../../attractions/widget/filter_chip.dart'; +import '../../common_packages/custom_search_field.dart'; + +class PassAttractionsPage extends StatelessWidget { + final String source; + const PassAttractionsPage({super.key, required this.source}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) { + final bloc = AttractionsBloc( + repository: AttractionsRepository(), + ); + + bloc.add( + const FetchAttractionsByCategory(), // No categoryXid parameter + ); + + return bloc; + }, + child: BlocBuilder( + builder: (context, state) { + final bloc = context.read(); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // App bar + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + backWidget(context, "Your Attraction", Colors.black), + const SizedBox(height: 20), + + // 🔍 Search field (UI kept, logic disabled) + CommonSearchField( + hint: "Search attractions...", + hintColor: Colors.grey.shade500, + onChanged: (value) { + // ❌ Search logic intentionally disabled + // UI only, no API call + }, + ), + + const SizedBox(height: 16), + + // 🏖️ Category chips row - DYNAMIC + if (state is AttractionsLoaded) + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: state.categories + .map( + (category) => buildCategoryChip( + category.categoryName ?? '', + isSelected: state.selectedCategoryId == category.id, + onTap: () { + bloc.add( + FetchAttractionsByCategory( + categoryXid: category.id, + ), + ); + }, + ), + ) + .toList(), + ), + ), + // else + // // Show placeholder chips while loading + // SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: Row( + // children: [ + // buildCategoryChip("Beach", isSelected: true, onTap: () {}), + // buildCategoryChip("Hike", isSelected: false, onTap: () {}), + // buildCategoryChip("Adventure", isSelected: false, onTap: () {}), + // buildCategoryChip("Best in Summer", isSelected: false, onTap: () {}), + // ], + // ), + // ), + + const SizedBox(height: 10), + + // 🙏️ Attraction list + if (state is AttractionsLoading) + const Center( + child: Padding( + padding: EdgeInsets.only(top: 60), + child: CircularProgressIndicator(), + ), + ) + else if (state is AttractionsLoaded) + state.attractions.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.only(top: 60), + child: Text( + "No attractions found", + style: TextStyle( + color: Colors.grey, + fontSize: 14.sp, + ), + ), + ), + ) + : Column( + children: state.attractions + .map( + (attraction) => PassAttractionCard( + attraction: attraction, + ), + ) + .toList(), + ) + else if (state is AttractionsError) + Center( + child: Padding( + padding: const EdgeInsets.only(top: 60), + child: Text( + state.message, + style: TextStyle( + color: Colors.red, + fontSize: 14.sp, + ), + ), + ), + ) + else + const SizedBox(), + ], + ), + ), + ), + ); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/my_pass/views/pass_details_page_view.dart b/lib/my_pass/views/pass_details_page_view.dart new file mode 100644 index 0000000..e7574a0 --- /dev/null +++ b/lib/my_pass/views/pass_details_page_view.dart @@ -0,0 +1,460 @@ +import 'package:citycards_customer/my_pass/blocs/my_pass_bloc.dart'; +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 '../../common_packages/app_bar.dart'; +import '../../common_packages/back_widget.dart'; +import '../../common_packages/custom_dash_border_painter.dart'; +import '../../core/route_constants.dart'; + +class PassDetailsView extends StatelessWidget { + const PassDetailsView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is MyPassLoaded) { + final pass = state.selectedPass!; + + return SafeArea( + child: Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + /// App Bar + SizedBox(height: 10.h), + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + + SizedBox(height: 10.h), + backWidget(context, "Back", Colors.black), + + SizedBox(height: 20.h), + + /// ------------------------------- + /// UNLIMITED CARD CONTAINER + /// ------------------------------- + CustomPaint( + painter: DashedBorderPainter( + color: const Color(0xffF95F62), + radius: 20.r, + ), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 18.h), + decoration: BoxDecoration( + color: const Color(0xffF95F62).withOpacity(0.08), + borderRadius: BorderRadius.circular(20.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + /// Title + Text( + pass.title, + style: GoogleFonts.poppins( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: const Color(0xffF95F62), + ), + ), + + SizedBox(height: 18.h), + + /// MAIN CONTENT ROW + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + /// IMAGE + ClipRRect( + borderRadius: BorderRadius.circular(14.r), + child: Image.asset( + "assets/images/unlimited_card_details.png", + height: 100.w, + width: 100.w, + fit: BoxFit.contain, + ), + ), + + SizedBox(width: 14.w), + + /// RIGHT CONTENT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + /// Adults + Kids (WRAP prevents overflow) + Wrap( + spacing: 10.w, + runSpacing: 10.h, + children: [ + _infoChip( + icon: Icons.person_outline, + text: "Adults-${pass.adults ?? 0}", + ), + _infoChip( + icon: Icons.person_outline, + text: "Kids-${pass.kids ?? 0}", + ), + ], + ), + + SizedBox(height: 12.h), + + /// Days Container (NOT full width) + _infoChip( + icon: Icons.access_time, + text: "${pass.duration} Days", + ), + + SizedBox(height: 14.h), + + /// Valid Till + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.calendar_today_outlined, + size: 15.sp, + color: const Color(0xffF95F62), + ), + SizedBox(width: 6.w), + + /// "Valid till:" → Black + Text( + "Valid till: ", + style: GoogleFonts.poppins( + fontSize: 13.sp, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + + /// Date → Red + Text( + pass.validity ?? "", + style: GoogleFonts.poppins( + fontSize: 13.sp, + fontWeight: FontWeight.w600, + color: const Color(0xffF95F62), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 24.h), + _sectionTitle("Suggested Attractions"), + SizedBox(height: 12.h), + + _attractionCard(), + SizedBox(height: 12.h), + _attractionCard(), + + SizedBox(height: 16.h), + + _outlineButton( + "View all Attractions", + () { + Navigator.pushNamed( + context, + RouteConstants.passAttractionsPage, + arguments: "qrPass", + ); + }, + ), + + SizedBox(height: 24.h), + + /// ------------------------------- + /// RECOMMENDED OFFERS + /// ------------------------------- + _sectionTitle("Recommended Offers"), + SizedBox(height: 12.h), + + Row( + children: [ + Expanded(child: _offerCard()), + SizedBox(width: 12.w), + Expanded(child: _offerCard()), + ], + ), + + SizedBox(height: 16.h), + + _outlineButton( + "View all Offers", + () { + Navigator.pushNamed( + context, + RouteConstants.searchPassOffer, + ); + }, + ), + + SizedBox(height: 20.h), + + GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + RouteConstants.privacyPolicy, // ✅ pass offerId + ); + }, + child: Center( + child: Text( + "Learn about policies", + style: GoogleFonts.poppins( + fontSize: 12.sp, + decoration: TextDecoration.underline, + ), + ), + ), + ), + + SizedBox(height: 30.h), + ], + ), + ), + ), + ); + } + + return const Center(child: CircularProgressIndicator()); + }, + ); + } + + Widget _sectionTitle(String title) { + return Text( + title, + style: GoogleFonts.poppins( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ); + } + + Widget _outlineButton(String title, VoidCallback onTap) { + return InkWell( + onTap: onTap, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 14.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + border: Border.all(color: const Color(0xffF95F62)), + ), + child: Center( + child: Text( + title, + style: GoogleFonts.poppins( + color: const Color(0xffF95F62), + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ); + } + + Widget _attractionCard() { + return Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.r), + border: Border.all(color: const Color(0xffF2D6D6)), + ), + child: Row( + children: [ + + /// 🔥 Attraction Image (Real Image Style Box) + ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.asset( + "assets/images/aa4.png", // <-- your attraction image + height: 90.w, + width: 90.w, + fit: BoxFit.cover, + ), + ), + + SizedBox(width: 12.w), + + /// 🔥 Text Section + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Koh Rong Samloem", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ), + + SizedBox(height: 2.h), + + Text( + "Krong Siem Reap", + style: GoogleFonts.poppins( + fontSize: 12.sp, + color: Colors.grey.shade600, + ), + ), + + SizedBox(height: 4.h), + + Text( + "from \$25/person", + style: GoogleFonts.poppins( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + + SizedBox(height: 6.h), + + Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.h, + ), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(8.r), + ), + child: Text( + "Booking Required", + style: GoogleFonts.poppins( + fontSize: 10.sp, + color: Colors.blue.shade700, + ), + ), + ) + ], + ), + ), + + SizedBox(width: 8.w), + + /// 🔥 QR Code Circle (Proper UI like Design) + Container( + height: 44.w, + width: 44.w, + decoration: BoxDecoration( + color: const Color(0xffF8EDED), // light pink circle bg + shape: BoxShape.circle, + ), + child: Padding( + padding: EdgeInsets.all(10.w), + child: Image.asset( + "assets/images/qr_image.png", + fit: BoxFit.contain, + ), + ), + ), + ], + ), + ); + } + + Widget _infoChip({ + required IconData icon, + required String text, + }) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xffF95F62)), + borderRadius: BorderRadius.circular(14.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14.sp, color: const Color(0xffF95F62)), + SizedBox(width: 6.w), + Text( + text, + style: GoogleFonts.poppins( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: const Color(0xffF95F62), + ), + ), + ], + ), + ); + } + + Widget _offerCard() { + return Container( + padding: EdgeInsets.all(6.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.r), + border: Border.all(color: const Color(0xffF2D6D6)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + /// 🔥 Top Offer Image + ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.asset( + "assets/images/aa4.png", // <-- your offer image + height: 120.h, // 🔥 closer to design ratio + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + SizedBox(height: 12.h), + + /// 🔥 Title + Text( + "Astor Hotels Ultra Deluxe", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 16.sp, + ), + ), + + SizedBox(height: 6.h), + + /// 🔥 Description + Text( + "15% Discount on all treatments for first-time clients", + style: GoogleFonts.poppins( + fontSize: 12.sp, + color: Colors.grey.shade700, + height: 1.4, + ), + ), + ], + ), + ); + } +} diff --git a/lib/my_pass/views/qr_pass_page_view.dart b/lib/my_pass/views/qr_pass_page_view.dart deleted file mode 100644 index 1ea3a98..0000000 --- a/lib/my_pass/views/qr_pass_page_view.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'package:citycards_customer/my_pass/blocs/my_pass_bloc.dart'; -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 '../../common_packages/app_bar.dart'; -import '../../common_packages/back_widget.dart'; -import '../../core/route_constants.dart'; -import '../widgets/action_button_widget.dart'; -import '../widgets/qr_container_widget.dart'; - -class QrPassView extends StatelessWidget { - const QrPassView({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is MyPassLoaded) { - final pass = state.selectedPass!; - return SafeArea( - child: Scaffold( - backgroundColor: Colors.white, - body: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), - SizedBox(height: 10.h), - backWidget(context, "Back", Colors.black), - SizedBox(height: 20.h), - SizedBox(height: 10.h), - Text( - "Scan this at the site of\nattraction", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 13.sp, - color: Colors.black87, - ), - ), - SizedBox(height: 20.h), - - /// ♻️ Reusable QR Container Component - QrContainerWidget( - qrImagePath: "assets/images/qr_image.png", - cityCardTitle: "Melbourne CityCards", - qrCode: "IYFHHVN254ADSD", - cardType: pass.title, - ), - - SizedBox(height: 24.h), - - /// 🎟 Card details section - Container( - padding: EdgeInsets.symmetric( - vertical: 10, - horizontal: 40, - ), - decoration: BoxDecoration( - color: pass.title.toLowerCase() == "unlimited card" - ? const Color(0xffF95F62).withOpacity(0.1) - : const Color(0xffF95FAF).withOpacity(0.1), - borderRadius: BorderRadius.circular(25.r), - border: Border.all( - color: pass.title.toLowerCase() == "unlimited card" - ? const Color(0xffF95F62) - : const Color(0xffF95FAF), - ), - ), - child: Text( - pass.title, - style: GoogleFonts.poppins( - fontSize: 16.sp, - color: const Color(0xffFF5A5F), - fontWeight: FontWeight.w500, - ), - ), - ), - SizedBox(height: 6.h), - Text( - "Adults-${pass.adults} • Kids-${pass.kids} • ${pass.duration}", - style: GoogleFonts.poppins( - fontSize: 12.sp, - color: Color(0xff212121), - fontWeight: FontWeight.w400, - ), - ), - SizedBox(height: 4.h), - Text( - "Valid Till: ${pass.validity}", - style: GoogleFonts.poppins( - fontSize: 12.sp, - color: Color(0xff212121), - fontWeight: FontWeight.w400, - ), - ), - - SizedBox(height: 28.h), - Align( - alignment: Alignment.centerLeft, - child: Text( - "Learn about policies", - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 12.sp, - fontWeight: FontWeight.w500, - decoration: TextDecoration.underline, - ), - ), - ), - SizedBox(height: 24.h), - - /// 🔘 Buttons - Column( - children: [ - actionButton( - label: "View All Attractions", - onPressed: () { - Navigator.of(context).pushNamed(RouteConstants.attractionsPage, arguments: "qrPass"); - }, - ), - SizedBox(height: 12.h), - actionButton( - label: "View All Available Offers", - onPressed: () { - Navigator.of(context).pushNamed(RouteConstants.searchOffer); - }, - ), - ], - ), - ], - ), - ), - ), - ); - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ); - } -} diff --git a/lib/my_pass/views/search_pass_offers_with_listing.dart b/lib/my_pass/views/search_pass_offers_with_listing.dart new file mode 100644 index 0000000..37cedd9 --- /dev/null +++ b/lib/my_pass/views/search_pass_offers_with_listing.dart @@ -0,0 +1,348 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/custom_search_field.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/core/route_constants.dart'; +import 'package:citycards_customer/search_offers/bloc/offers_bloc.dart'; +import 'package:citycards_customer/search_offers/bloc/offers_event.dart'; +import 'package:citycards_customer/search_offers/bloc/offers_state.dart'; +import 'package:citycards_customer/search_offers/repository/offers_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../common_packages/common_app_texts.dart'; +import '../../networkApiServices/api_urls.dart'; + +class PassOffersScreen extends StatefulWidget { + const PassOffersScreen({super.key}); + + @override + State createState() => _PassOffersScreenState(); +} + +class _PassOffersScreenState extends State { + int? selectedCategoryId; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => OffersBloc(OffersRepository())..add(LoadOffers()), + child: Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showCart: false, + showDivider: true, + ), + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back), + ), + SizedBox(width: 8.w), + CustomText( + text: "Offers with ${CommonAppText.selectiveCard} Card", + size: 12.sp, + ), + ], + ), + SizedBox(height: 33.h), + Builder( + builder: (context) => CommonSearchField( + hint: "Search offers", + hintColor: const Color(0xFFF95F62).withOpacity(.6), + showSuffix: true, + onChanged: (value) { + context.read().add(SearchOffers(value)); + }, + ), + ), + SizedBox(height: 20.h), + + /// Dynamic Categories + BlocBuilder( + builder: (context, state) { + if (state is OffersLoaded) { + final categories = state.categories; + + if (categories.isEmpty) { + return SizedBox.shrink(); + } + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...List.generate(categories.length, (index) { + final category = categories[index]; + final isSelected = + selectedCategoryId == category.id; + + return Padding( + padding: EdgeInsets.only(right: 8.0.w), + child: GestureDetector( + onTap: () { + setState(() { + if (selectedCategoryId == category.id) { + // Deselect if already selected + selectedCategoryId = null; + context + .read() + .add(LoadOffers()); + } else { + // Select new category + selectedCategoryId = category.id; + context.read().add( + LoadOffers( + categoryXid: category.id), + ); + } + }); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.h, + horizontal: 12.w, + ), + decoration: BoxDecoration( + color: isSelected + ? Color(0xFFF95F62) + : Color(0xFFFEE7E7), + borderRadius: + BorderRadius.circular(100.sp), + border: Border.all( + color: isSelected + ? Color(0xFFF95F62) + : Color(0xFFFDCDCE), + ), + ), + child: Center( + child: CustomText( + text: category.categoryName, + color: isSelected + ? Colors.white + : Color(0xFFF95F62), + ), + ), + ), + ), + ); + }), + ], + ), + ); + } + + return SizedBox.shrink(); + }, + ), + SizedBox(height: 20.h), + + /// Offer list + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state is OffersLoading) { + return const Center( + child: CircularProgressIndicator( + color: Color(0xFFF95F62), + ), + ); + } + + if (state is OffersError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 48.sp, + color: Colors.red, + ), + SizedBox(height: 16.h), + Text( + "Error: ${state.message}", + style: TextStyle( + color: Colors.red, + fontSize: 14.sp, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + if (state is OffersLoaded) { + final offers = state.offers; + + if (offers.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.local_offer_outlined, + size: 48.sp, + color: Colors.grey, + ), + SizedBox(height: 16.h), + Text( + "No offers found", + style: TextStyle( + color: Colors.grey, + fontSize: 14.sp, + ), + ), + ], + ), + ); + } + + return GridView.builder( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16.w, + mainAxisSpacing: 22.h, + childAspectRatio: 0.65, + ), + itemCount: offers.length, + itemBuilder: (context, index) { + final offer = offers[index]; + return InkWell( + onTap: () { + Navigator.of(context).pushNamed( + RouteConstants.offerPassDetail, + arguments: offer.id, // ✅ pass offerId + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, + vertical: 6.h, + ), + decoration: BoxDecoration( + border: Border.all( + color: + Color(0xFFF95F62).withOpacity(.24), + ), + borderRadius: BorderRadius.circular(12.sp), + ), + child: Column( + children: [ + ClipRRect( + borderRadius: + BorderRadius.circular(8.sp), + child: offer.mobileBannerImage != null && + offer.mobileBannerImage! + .isNotEmpty + ? Image.network( + '${ApiUrls.baseUrl}/${offer.mobileBannerImage}', + width: double.infinity, + height: 120.5.h, + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) { + return Container( + width: double.infinity, + height: 120.5.h, + color: Color(0xFFFEE7E7), + child: Icon( + Icons.local_offer, + size: 40.sp, + color: Color(0xFFF95F62) + .withOpacity(.6), + ), + ); + }, + loadingBuilder: (context, child, + loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Container( + width: double.infinity, + height: 120.5.h, + color: Color(0xFFFEE7E7), + child: Center( + child: + CircularProgressIndicator( + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + loadingProgress + .expectedTotalBytes! + : null, + strokeWidth: 2, + color: + Color(0xFFF95F62), + ), + ), + ); + }, + ) + : Container( + width: double.infinity, + height: 120.5.h, + color: Color(0xFFFEE7E7), + child: Icon( + Icons.local_offer, + size: 40.sp, + color: Color(0xFFF95F62) + .withOpacity(.6), + ), + ), + ), + SizedBox(height: 8.h), + CustomText( + text: offer.title, + size: 18.sp, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 8.h), + CustomText( + text: offer.description, + color: Colors.black.withOpacity(.6), + size: 12.sp, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ); + } + + return const Center( + child: Text( + "No data available", + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/my_pass/widgets/pass_attraction_card.dart b/lib/my_pass/widgets/pass_attraction_card.dart new file mode 100644 index 0000000..6519c40 --- /dev/null +++ b/lib/my_pass/widgets/pass_attraction_card.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../attractions/models/attraction_model.dart'; +import '../../common_packages/common_app_texts.dart'; +import '../../core/route_constants.dart'; + +class PassAttractionCard extends StatelessWidget { + final Attraction attraction; + const PassAttractionCard({super.key, required this.attraction}); + + @override + Widget build(BuildContext context) { + /// CARD TITLES (instead of categories) + final List tags = attraction.cards + .map((e) => e.title) + .where((e) => e.isNotEmpty) + .toList(); + + /// GALLERY IMAGE (handled safely in model) + final String imageUrl = attraction.coverImageUrl; + + return InkWell( + onTap: () { + Navigator.of(context).pushNamed( + RouteConstants.passAttractionDetails, + arguments: attraction.id, + ); + }, + child: Container( + margin: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.w), + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xffFDCDCE)), + borderRadius: BorderRadius.circular(15.r), + color: const Color(0xffFFF5F5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// IMAGE (network with fallback) + ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: imageUrl.isNotEmpty + ? Image.network( + imageUrl, + height: 94.h, + width: 94.w, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => _imageFallback(), + ) + : _imageFallback(), + ), + + SizedBox(width: 10.w), + + /// CONTENT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + attraction.title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + + SizedBox(height: 6.h), + + Text( + attraction.address, + style: GoogleFonts.poppins( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff464646), + ), + ), + + SizedBox(height: 6.h), + + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "from \$${attraction.ticketPriceAdult}", + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + TextSpan( + text: "/person", + style: TextStyle( + fontSize: 10.sp, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + + SizedBox(height: 6.h), + + /// TAGS (CARD TITLES) + attraction.isBookingRequired == false + ? Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: tags + .map( + (tag) => Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.h, + ), + decoration: BoxDecoration( + color: tag == + "${CommonAppText.selectiveCard} Card" + ? const Color(0xffF95FAF) + .withOpacity(0.1) + : const Color(0xffF95F62) + .withOpacity(0.1), + border: Border.all( + color: tag == + "${CommonAppText.selectiveCard} Card" + ? const Color(0xffF95FAF) + : const Color(0xffF95F62), + ), + borderRadius: + BorderRadius.circular(20.r), + ), + child: Text( + tag, + style: GoogleFonts.poppins( + fontSize: 11.sp, + color: const Color(0xff1A1A1A), + fontWeight: FontWeight.w400, + ), + ), + ), + ) + .toList(), + ) + : Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.h, + ), + decoration: BoxDecoration( + color: const Color(0xffC1D2F8), + border: Border.all( + color: const Color(0xff2563EB), + ), + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + "Booking Required", + style: GoogleFonts.poppins( + fontSize: 11.sp, + color: const Color(0xff1A1A1A), + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + /// SAME PLACEHOLDER AS BEFORE + Widget _imageFallback() { + return Container( + height: 94.h, + width: 94.w, + color: Colors.grey.shade200, + child: Icon( + Icons.image_not_supported_outlined, + size: 28.sp, + color: Colors.grey, + ), + ); + } +} diff --git a/lib/profile/bloc/profile/profile_bloc.dart b/lib/profile/bloc/profile/profile_bloc.dart index 4a3a6e3..afc961e 100644 --- a/lib/profile/bloc/profile/profile_bloc.dart +++ b/lib/profile/bloc/profile/profile_bloc.dart @@ -26,7 +26,6 @@ class ProfileBloc extends Bloc { Emitter emit, ) async { try { - emit(const ProfileLoading()); final profile = await _profileRepository.fetchUserProfile(); @@ -54,6 +53,12 @@ class ProfileBloc extends Bloc { print('📄 [BLOC] Address1: ${event.address1}'); print('📄 [BLOC] Address2: ${event.address2}'); + // ⭐ NEW DEBUG LOGS + print('📄 [BLOC] City: ${event.city}'); + print('📄 [BLOC] State: ${event.state}'); + print('📄 [BLOC] Country: ${event.country}'); + print('📄 [BLOC] Postal Code: ${event.postalCode}'); + if (event.profileImageFile != null) { print('📄 [BLOC] ✅ Profile Image File Present in Event'); print('📄 [BLOC] File Path: ${event.profileImageFile!.path}'); diff --git a/lib/profile/bloc/profile/profile_event.dart b/lib/profile/bloc/profile/profile_event.dart index 3ec20c4..bd10d29 100644 --- a/lib/profile/bloc/profile/profile_event.dart +++ b/lib/profile/bloc/profile/profile_event.dart @@ -18,6 +18,7 @@ class FetchProfileEvent extends ProfileEvent { List get props => [userId]; } +/// Event to update user profile /// Event to update user profile class UpdateProfileEvent extends ProfileEvent { final int userId; @@ -26,6 +27,10 @@ class UpdateProfileEvent extends ProfileEvent { final String mobileNumber; final String? address1; final String? address2; + final String? city; // ⭐ NEW + final String? state; // ⭐ NEW + final String? country; // ⭐ NEW + final String? postalCode; // ⭐ NEW final File? profileImageFile; const UpdateProfileEvent({ @@ -35,6 +40,10 @@ class UpdateProfileEvent extends ProfileEvent { required this.mobileNumber, this.address1, this.address2, + this.city, // ⭐ NEW + this.state, // ⭐ NEW + this.country, // ⭐ NEW + this.postalCode, // ⭐ NEW this.profileImageFile, }); @@ -46,6 +55,10 @@ class UpdateProfileEvent extends ProfileEvent { mobileNumber, address1, address2, + city, // ⭐ NEW + state, // ⭐ NEW + country, // ⭐ NEW + postalCode, // ⭐ NEW profileImageFile, ]; @@ -56,6 +69,10 @@ class UpdateProfileEvent extends ProfileEvent { 'mobileNumber': mobileNumber, if (address1 != null && address1!.isNotEmpty) 'address1': address1, if (address2 != null && address2!.isNotEmpty) 'address2': address2, + if (city != null && city!.isNotEmpty) 'city': city, // ⭐ NEW + if (state != null && state!.isNotEmpty) 'state': state, // ⭐ NEW + if (country != null && country!.isNotEmpty) 'country': country, // ⭐ NEW + if (postalCode != null && postalCode!.isNotEmpty) 'postalCode': postalCode, // ⭐ NEW }; } } diff --git a/lib/profile/repository/profile_repository.dart b/lib/profile/repository/profile_repository.dart index cbd035e..bf9cb48 100644 --- a/lib/profile/repository/profile_repository.dart +++ b/lib/profile/repository/profile_repository.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; + import '../models/profile_model.dart'; import '../../networkApiServices/network_api_services.dart'; import '../../networkApiServices/api_urls.dart'; @@ -9,7 +10,7 @@ import '../../localPreference/local_preference.dart'; class ProfileRepository { final NetworkApiService _apiService = NetworkApiService(); - /// Fetch user profile (userId from local storage) + /// ✅ Fetch user profile (userId from local storage) Future fetchUserProfile() async { final int? userId = await LocalPreference.getUserId(); @@ -20,11 +21,10 @@ class ProfileRepository { return ProfileModel.fromJson(response.data); } - /// Update user profile (userId from local storage) - /// ⭐ FIXED: Now uses multipart/form-data for file upload + /// ✅ Update user profile (Multipart with Image + New Address Fields) Future updateUserProfile({ required Map data, - File? profileImageFile, // ⭐ NEW: Accept File instead of base64 + File? profileImageFile, }) async { final int? userId = await LocalPreference.getUserId(); @@ -32,31 +32,56 @@ class ProfileRepository { print('📤 [UPDATE PROFILE] User ID: $userId'); print('📤 [UPDATE PROFILE] URL: ${ApiUrls.userProfile}/$userId'); print('📤 [UPDATE PROFILE] Data Keys: ${data.keys.toList()}'); - print('📤 [UPDATE PROFILE] First Name: ${data['firstName']}'); - print('📤 [UPDATE PROFILE] Last Name: ${data['lastName']}'); - print('📤 [UPDATE PROFILE] Mobile: ${data['mobileNumber']}'); - print('📤 [UPDATE PROFILE] Address1: ${data['address1']}'); - print('📤 [UPDATE PROFILE] Address2: ${data['address2']}'); - print('📤 [UPDATE PROFILE] Profile Image File: ${profileImageFile?.path}'); + + print('📤 First Name: ${data['firstName']}'); + print('📤 Last Name: ${data['lastName']}'); + print('📤 Mobile: ${data['mobileNumber']}'); + print('📤 Address1: ${data['address1']}'); + print('📤 Address2: ${data['address2']}'); + + // ⭐ NEW DEBUG LOGS + print('📤 City: ${data['city']}'); + print('📤 State: ${data['state']}'); + print('📤 Country: ${data['country']}'); + print('📤 Postal Code: ${data['postalCode']}'); + + print('📤 Profile Image File: ${profileImageFile?.path}'); } - // ⭐ Create FormData for multipart/form-data upload + /// ✅ Create FormData (Multipart) final formData = FormData(); - // Add text fields + /// ✅ Add Text Fields formData.fields.addAll([ MapEntry('firstName', data['firstName']), MapEntry('lastName', data['lastName']), MapEntry('mobileNumber', data['mobileNumber']), + if (data['address1'] != null && data['address1'].toString().isNotEmpty) MapEntry('address1', data['address1']), + if (data['address2'] != null && data['address2'].toString().isNotEmpty) MapEntry('address2', data['address2']), + + /// ⭐ NEW FIELDS + if (data['city'] != null && data['city'].toString().isNotEmpty) + MapEntry('city', data['city']), + + if (data['state'] != null && data['state'].toString().isNotEmpty) + MapEntry('state', data['state']), + + if (data['country'] != null && data['country'].toString().isNotEmpty) + MapEntry('country', data['country']), + + if (data['postalCode'] != null && + data['postalCode'].toString().isNotEmpty) + MapEntry('postalCode', data['postalCode']), ]); - // ⭐ Add profile image file if provided + /// ✅ Add Profile Image File if (profileImageFile != null) { final fileName = profileImageFile.path.split('/').last; + formData.files.add( MapEntry( 'profileImage', @@ -68,48 +93,38 @@ class ProfileRepository { ); if (kDebugMode) { - print('📤 [UPDATE PROFILE] ✅ Profile Image File Added'); - print('📤 [UPDATE PROFILE] File Name: $fileName'); - print('📤 [UPDATE PROFILE] File Path: ${profileImageFile.path}'); final fileSize = await profileImageFile.length(); - print('📤 [UPDATE PROFILE] File Size: ${(fileSize / 1024).toStringAsFixed(2)} KB'); + print('📤 ✅ Profile Image Added'); + print('📤 File Name: $fileName'); + print( + '📤 File Size: ${(fileSize / 1024).toStringAsFixed(2)} KB'); } } else { if (kDebugMode) { - print('📤 [UPDATE PROFILE] ⚠️ No profile image file provided'); + print('📤 ⚠️ No profile image provided'); } } - // ⭐ Send as multipart/form-data + /// ✅ API Call (Multipart PUT) final response = await _apiService.putApi( url: '${ApiUrls.userProfile}/$userId', data: formData, ); if (kDebugMode) { - print('📤 [UPDATE PROFILE] ✅ Response Status: Success'); - print('📤 [UPDATE PROFILE] Full Response: ${response.data}'); - - // Check if response has nested 'user' object - if (response.data.containsKey('user')) { - print('📤 [UPDATE PROFILE] ✅ Response has nested "user" object'); - print('📤 [UPDATE PROFILE] User Data: ${response.data['user']}'); - print('📤 [UPDATE PROFILE] Updated Profile Image: ${response.data['user']['profileImage']}'); - } else { - print('📤 [UPDATE PROFILE] Response structure: ${response.data.keys.toList()}'); - print('📤 [UPDATE PROFILE] Updated Profile Image: ${response.data['profileImage']}'); - } + print('📤 ✅ Response Success'); + print('📤 Full Response: ${response.data}'); } - // Extract user data from nested response + /// ✅ Handle Nested Response (user object) final userData = response.data.containsKey('user') ? response.data['user'] : response.data; if (kDebugMode) { - print('📤 [UPDATE PROFILE] Parsing ProfileModel from: $userData'); + print('📤 Parsing ProfileModel from: $userData'); } return ProfileModel.fromJson(userData); } -} \ No newline at end of file +} diff --git a/lib/profile/view/edit_profile/edit_profile_view.dart b/lib/profile/view/edit_profile/edit_profile_view.dart index b58ca4c..718d451 100644 --- a/lib/profile/view/edit_profile/edit_profile_view.dart +++ b/lib/profile/view/edit_profile/edit_profile_view.dart @@ -30,6 +30,10 @@ class _EditProfilePageState extends State { final TextEditingController phoneController = TextEditingController(); final TextEditingController address1Controller = TextEditingController(); final TextEditingController address2Controller = TextEditingController(); + final TextEditingController stateController = TextEditingController(); + final TextEditingController countryController = TextEditingController(); + final TextEditingController cityController = TextEditingController(); + final TextEditingController zipCodeController = TextEditingController(); final _formKey = GlobalKey(); final ImagePicker _picker = ImagePicker(); @@ -64,6 +68,10 @@ class _EditProfilePageState extends State { phoneController.text = profile.mobileNumber; address1Controller.text = profile.address1 ?? ''; address2Controller.text = profile.address2 ?? ''; + stateController.text = profile.stateName ?? ''; + countryController.text = profile.country ?? ''; + cityController.text = profile.cityName ?? ''; + zipCodeController.text = profile.zipCode ?? ''; // ⭐ REMOVED setState - image is now managed by BLoC state if (kDebugMode && profile.profileImage != null && profile.profileImage!.isNotEmpty) { @@ -321,6 +329,19 @@ class _EditProfilePageState extends State { address2: address2Controller.text.trim().isEmpty ? null : address2Controller.text.trim(), + // ⭐ ADD THESE NEW FIELDS + city: cityController.text.trim().isEmpty + ? null + : cityController.text.trim(), + state: stateController.text.trim().isEmpty + ? null + : stateController.text.trim(), + country: countryController.text.trim().isEmpty + ? null + : countryController.text.trim(), + postalCode: zipCodeController.text.trim().isEmpty + ? null + : zipCodeController.text.trim(), profileImageFile: imageFileToSend, ), ); @@ -333,6 +354,10 @@ class _EditProfilePageState extends State { phoneController.dispose(); address1Controller.dispose(); address2Controller.dispose(); + stateController.dispose(); + countryController.dispose(); + cityController.dispose(); + zipCodeController.dispose(); super.dispose(); } @@ -495,7 +520,7 @@ class _EditProfilePageState extends State { Padding( padding: EdgeInsets.symmetric(horizontal: 12.0.w), child: CustomTextField( - label: "Address 1", + label: "Address", hint: "Enter address manually or tap to search", controller: address1Controller, enabled: !isLoading, @@ -512,6 +537,46 @@ class _EditProfilePageState extends State { ), ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0.w), + child: CustomTextField( + label: "State", + hint: "Select your State", + controller: stateController, + enabled: !isLoading, + ), + ), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0.w), + child: CustomTextField( + label: "Country", + hint: "Select your Country", + controller: countryController, + enabled: !isLoading, + ), + ), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0.w), + child: CustomTextField( + label: "City", + hint: "Enter the name of your city", + controller: cityController, + enabled: !isLoading, + ), + ), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0.w), + child: CustomTextField( + label: "ZIP Code", + hint: "Enter the ZIP code you reside in", + controller: zipCodeController, + enabled: !isLoading, + ), + ), + SizedBox(height: 26.h), // Buttons diff --git a/lib/search_offers/view/search_offers_with_listing.dart b/lib/search_offers/view/search_offers_with_listing.dart index 059201b..f6bfb88 100644 --- a/lib/search_offers/view/search_offers_with_listing.dart +++ b/lib/search_offers/view/search_offers_with_listing.dart @@ -221,12 +221,12 @@ class _OffersScreenState extends State { itemBuilder: (context, index) { final offer = offers[index]; return InkWell( - onTap: () { - Navigator.of(context).pushNamed( - RouteConstants.offerPassDetail, - arguments: offer.id, // ✅ pass offerId - ); - }, + // onTap: () { + // Navigator.of(context).pushNamed( + // RouteConstants.offerPassDetail, + // arguments: offer.id, // ✅ pass offerId + // ); + // }, child: Container( padding: EdgeInsets.symmetric( horizontal: 6.w,