From 9b79fb6db7dfe3f2cf4180ccb5f5e5b9efdcd74c Mon Sep 17 00:00:00 2001 From: Vinayakkadge04 Date: Mon, 27 Oct 2025 15:27:21 +0530 Subject: [PATCH] Completed Offer with Flexi Card Screen --- assets/logo/logo_city_cards_orange.png | Bin 0 -> 10318 bytes .../views/attractions_page_view.dart | 1 + lib/checkout/{ => view}/checkout_view.dart | 32 ++-- .../widget/login_email_bottomsheet.dart | 107 ++++++++++++ .../widget/verify_otp_bottomsheet.dart | 113 +++++++++++++ lib/common_packages/custom_search_field.dart | 8 +- lib/core/app_router.dart | 14 +- lib/core/route_constants.dart | 1 + .../city_selection_view.dart | 21 --- lib/main.dart | 2 +- .../bloc/search_offers_listing_bloc.dart | 75 +++++++++ .../view/search_offers_with_listing.dart | 152 ++++++++++++++++++ pubspec.lock | 8 + pubspec.yaml | 1 + 14 files changed, 496 insertions(+), 39 deletions(-) create mode 100644 assets/logo/logo_city_cards_orange.png rename lib/checkout/{ => view}/checkout_view.dart (93%) create mode 100644 lib/checkout/widget/login_email_bottomsheet.dart create mode 100644 lib/checkout/widget/verify_otp_bottomsheet.dart create mode 100644 lib/offer_section/bloc/search_offers_listing_bloc.dart create mode 100644 lib/offer_section/view/search_offers_with_listing.dart diff --git a/assets/logo/logo_city_cards_orange.png b/assets/logo/logo_city_cards_orange.png new file mode 100644 index 0000000000000000000000000000000000000000..beb6038c6b09dcb76b875e649e51182f56749108 GIT binary patch literal 10318 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 literal 0 HcmV?d00001 diff --git a/lib/attractions/views/attractions_page_view.dart b/lib/attractions/views/attractions_page_view.dart index f927f5f..3ae95f2 100644 --- a/lib/attractions/views/attractions_page_view.dart +++ b/lib/attractions/views/attractions_page_view.dart @@ -56,6 +56,7 @@ class AttractionsPage extends StatelessWidget { // 🔍 Search field CommonSearchField( hint: "Search attractions...", + hintColor: Colors.grey.shade500, onChanged: (value) { if (value.isEmpty) { bloc.add(LoadAttractions()); diff --git a/lib/checkout/checkout_view.dart b/lib/checkout/view/checkout_view.dart similarity index 93% rename from lib/checkout/checkout_view.dart rename to lib/checkout/view/checkout_view.dart index ea3e1b2..fc18b20 100644 --- a/lib/checkout/checkout_view.dart +++ b/lib/checkout/view/checkout_view.dart @@ -1,3 +1,4 @@ +import 'package:citycards_customer/checkout/widget/login_email_bottomsheet.dart'; import 'package:citycards_customer/common_packages/app_bar.dart'; import 'package:citycards_customer/common_packages/custom_filled_button.dart'; import 'package:citycards_customer/common_packages/custom_text.dart'; @@ -11,6 +12,7 @@ class CheckoutView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( + resizeToAvoidBottomInset: true, backgroundColor: Colors.white, body: SafeArea( child: Padding( @@ -34,9 +36,7 @@ class CheckoutView extends StatelessWidget { Container( decoration: BoxDecoration( color: Colors.white, - border: Border.all( - color: Color(0xFFF95FAF).withOpacity(0.2), - ), + border: Border.all(color: Color(0xFFF95FAF).withOpacity(0.2)), borderRadius: BorderRadius.circular(8.r), ), child: Expanded( @@ -117,10 +117,7 @@ class CheckoutView extends StatelessWidget { SizedBox(height: 7.h), Row( children: [ - Image.asset( - "assets/icons/kid.png", - scale: 4, - ), + Image.asset("assets/icons/kid.png", scale: 4), SizedBox(width: 4.w), CustomText( text: "3 Kids", @@ -196,10 +193,7 @@ class CheckoutView extends StatelessWidget { SizedBox(height: 15.h), Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, - vertical: 12.h, - ), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), decoration: BoxDecoration( color: Color(0xFFFFF5F5), borderRadius: BorderRadius.circular(8.r), @@ -322,10 +316,22 @@ class CheckoutView extends StatelessWidget { ), const Spacer(), CustomFilledButton( - onTap: () {}, + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => const LoginEmailBottomsheet(), + ); + + }, label: "Login to Checkout", ), - SizedBox(height: 25.h,) + SizedBox(height: 25.h), ], ), ), diff --git a/lib/checkout/widget/login_email_bottomsheet.dart b/lib/checkout/widget/login_email_bottomsheet.dart new file mode 100644 index 0000000..18af4db --- /dev/null +++ b/lib/checkout/widget/login_email_bottomsheet.dart @@ -0,0 +1,107 @@ +import 'package:citycards_customer/checkout/widget/verify_otp_bottomsheet.dart'; +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class LoginEmailBottomsheet extends StatelessWidget { + const LoginEmailBottomsheet({super.key}); + + @override + Widget build(BuildContext context) { + return AnimatedPadding( + + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + padding: EdgeInsets.only( + top: 24.h, + left: 20.h, + right: 20.h, + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, // shrink to fit content + children: [ + Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4), + SizedBox(height: 8.h), + CustomText(text: "Get Started", size: 18.sp, weight: FontWeight.w500), + SizedBox(height: 42.h), + CustomText( + text: "Enter your email to begin your CityCards journey", + size: 14.sp, + color: const Color(0xFF000000).withOpacity(.6), + ), + SizedBox(height: 12.h), + + TextField( + decoration: InputDecoration( + filled: true, + contentPadding: EdgeInsets.symmetric(vertical: 6.h), + fillColor: const Color(0xFFFFF5F5), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w), + borderRadius: BorderRadius.circular(8.sp), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w), + borderRadius: BorderRadius.circular(8.sp), + ), + prefixIcon: const Icon(Icons.email_outlined, color: Color(0xFFF95F62)), + hintText: "john.doe@gmail.com", + hintStyle: TextStyle( + color: const Color(0xFF000000).withOpacity(0.6), + fontSize: 12.sp, + ), + ), + ), + + SizedBox(height: 38.h), + CustomFilledButton( + onTap: () { + Navigator.pop(context); + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => VerifyOtpBottomsheet(), + ); + }, + label: "Continue", + width: double.infinity, + ), + + SizedBox(height: 20.h), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Already have an account?", + style: TextStyle( + color: Colors.black.withOpacity(0.6), + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: " Sign in", + style: TextStyle( + color: const Color(0xFFF95F62), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + SizedBox(height: 15.h), + ], + ), + ), + ); + } +} diff --git a/lib/checkout/widget/verify_otp_bottomsheet.dart b/lib/checkout/widget/verify_otp_bottomsheet.dart new file mode 100644 index 0000000..792a3b7 --- /dev/null +++ b/lib/checkout/widget/verify_otp_bottomsheet.dart @@ -0,0 +1,113 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class VerifyOtpBottomsheet extends StatelessWidget { + VerifyOtpBottomsheet({super.key}); + + @override + Widget build(BuildContext context) { + return AnimatedPadding( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + padding: EdgeInsets.only( + top: 24.h, + left: 20.h, + right: 20.h, + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, // shrink to fit content + children: [ + Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4), + SizedBox(height: 8.h), + CustomText( + text: "Verify your phone", + size: 18.sp, + weight: FontWeight.w500, + ), + SizedBox(height: 42.h), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Enter the verification code sent to your email id", + style: TextStyle( + fontSize: 14.sp, + color: Colors.black.withOpacity(0.6), + ), + ), + TextSpan( + text: " frank7824@mail.com", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + ], + ), + ), + SizedBox(height: 15.h), + + OtpTextField( + numberOfFields: 6, + borderWidth: 0.4.w, + fieldWidth: 48.w, + fieldHeight: 60.h, + borderRadius: BorderRadius.circular(8.r), + filled: true, + fillColor: const Color(0xFFFFF5F5), + borderColor: const Color(0xFFBB474A), + cursorColor: const Color(0xFFF95F62), + showFieldAsBox: true, + textStyle: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w500, + ), + onCodeChanged: (code) {}, + onSubmit: (code) { + debugPrint("OTP entered: $code"); + }, + ), + + SizedBox(height: 42.h), + CustomFilledButton( + onTap: () {}, + label: "Continue", + width: double.infinity, + ), + + SizedBox(height: 20.h), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Already have an account?", + style: TextStyle( + color: Colors.black.withOpacity(0.6), + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: " Sign in", + style: TextStyle( + color: const Color(0xFFF95F62), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + SizedBox(height: 15.h), + ], + ), + ), + ); + } +} diff --git a/lib/common_packages/custom_search_field.dart b/lib/common_packages/custom_search_field.dart index 07fd398..4f48d36 100644 --- a/lib/common_packages/custom_search_field.dart +++ b/lib/common_packages/custom_search_field.dart @@ -4,11 +4,16 @@ import 'package:google_fonts/google_fonts.dart'; class CommonSearchField extends StatelessWidget { final ValueChanged onChanged; final String hint; + final bool showSuffix; + final Color hintColor; + const CommonSearchField({ super.key, required this.onChanged, this.hint = "Search attractions", + this.showSuffix = false, + required this.hintColor }); @override @@ -17,9 +22,10 @@ class CommonSearchField extends StatelessWidget { onChanged: onChanged, decoration: InputDecoration( hintText: hint, - hintStyle: GoogleFonts.poppins(color: Colors.grey.shade500), + hintStyle: GoogleFonts.poppins(color: hintColor), filled: true, fillColor: Colors.white, + suffixIcon: showSuffix ? Image.asset("assets/icons/search.png",scale: 4,) : null, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Color(0xffF95F62).withOpacity(0.4)), diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 1e54b8c..5c4693f 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -1,7 +1,7 @@ import 'package:citycards_customer/Profile/profile_page_view.dart'; import 'package:citycards_customer/attraction_details/attraction_details_view.dart'; import 'package:citycards_customer/buy_a_pass/view/buy_pass_view.dart'; -import 'package:citycards_customer/checkout/checkout_view.dart'; +import 'package:citycards_customer/checkout/view/checkout_view.dart'; import 'package:citycards_customer/common_bloc/language_selection_bloc.dart'; import 'package:citycards_customer/contact_us/contact_us_view.dart'; import 'package:citycards_customer/edit_profile/edit_profile_view.dart'; @@ -12,6 +12,8 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_detail_bloc import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_start_view.dart'; import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_view.dart'; +import 'package:citycards_customer/offer_section/bloc/search_offers_listing_bloc.dart'; +import 'package:citycards_customer/offer_section/view/search_offers_with_listing.dart'; import 'package:citycards_customer/privacy/privacy_view.dart'; import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.dart'; import 'package:flutter/material.dart'; @@ -31,8 +33,6 @@ class AppRouter { return BlocProvider( create: (_) => NavigationBloc(), child: const HomePage(), - - ); }, ); @@ -131,6 +131,14 @@ class AppRouter { return MaterialPageRoute(builder: (_){ return CheckoutView(); }); + + case RouteConstants.searchOffer: + return MaterialPageRoute(builder: (_){ + return BlocProvider( + create: (_) => OffersBloc(), + child: SearchOffersWithListing(), + ); + }); default: return MaterialPageRoute( builder: (_) => diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index c8573b5..7ddc72a 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -34,6 +34,7 @@ class RouteConstants { static const String buyPass ='/buyPass'; static const String checkout ='/checkout'; + static const String searchOffer = '/searchOffer'; diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/city_selection_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/city_selection_view.dart index 5936b3a..be65f52 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/city_selection_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/city_selection_view.dart @@ -143,27 +143,6 @@ class CitySelectionView extends StatelessWidget { ), ), - // Container( - // height: 56.h, - // width: double.infinity, - // padding: EdgeInsets.only(left: 20.w), - // decoration: BoxDecoration( - // color: Colors.white, - // border: Border.all(color: Color(0xFFF95F62)), - // borderRadius: BorderRadius.circular(28.r), - // ), - // child: Row( - // children: [ - // Image.asset("assets/icons/location.png", scale: 4), - // SizedBox(width: 12.w), - // CustomText( - // text: "Tokyo", - // color: Color(0xFF737373), - // size: 14.sp, - // ), - // ], - // ), - // ), SizedBox(height: 16.h), Align( alignment: Alignment.topLeft, diff --git a/lib/main.dart b/lib/main.dart index be24e47..a012bbf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,7 @@ class MyApp extends StatelessWidget { builder: (context, child) { return MaterialApp( onGenerateRoute: _appRouter.onGenerateRoute, - initialRoute: RouteConstants.buyPass, + initialRoute: RouteConstants.checkout, debugShowCheckedModeBanner: false, title: 'City Cards', theme: ThemeData( diff --git a/lib/offer_section/bloc/search_offers_listing_bloc.dart b/lib/offer_section/bloc/search_offers_listing_bloc.dart new file mode 100644 index 0000000..2b7e77b --- /dev/null +++ b/lib/offer_section/bloc/search_offers_listing_bloc.dart @@ -0,0 +1,75 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +// ----- Events ----- +abstract class OffersEvent {} + +class LoadOffers extends OffersEvent {} + +class SearchOffers extends OffersEvent { + final String query; + SearchOffers(this.query); +} + +// ----- State ----- +class OffersState { + final List> offers; + const OffersState(this.offers); +} + +// ----- Bloc ----- +class OffersBloc extends Bloc { + OffersBloc() : super(const OffersState([])) { + on(_onLoadOffers); + on(_onSearchOffers); + } + + final List> _allOffers = [ + { + "image": "assets/images/aa1.png", + "title": "Astor Hotels Ultra Deluxe", + "description": "15% Discount on all treatments for first-time clients" + }, + { + "image": "assets/images/aa2.png", + "title": "Green Valley Spa Lux", + "description": "20% off on spa memberships and treatments" + }, + { + "image": "assets/images/aa3.png", + "title": "Ocean Breeze Resort", + "description": "Complimentary breakfast with every booking for first-time guests" + }, + { + "image": "assets/images/aa4.png", + "title": "Mountain Retreat of Light", + "description": "10% Discount on group bookings of 5 or more guests" + }, + { + "image": "assets/images/card_banner.png", + "title": "Mountain View Retreat", + "description": "Free hiking gear rental for all visitors during their stay" + }, + { + "image": "assets/images/city_germany.jpg", + "title": "Sunny Shores Hotel", + "description": "10% Discount on group bookings of 5 or more guests" + }, + + + ]; + + void _onLoadOffers(event,emit) { + emit(OffersState(_allOffers)); + } + + void _onSearchOffers(event,emit) { + final filtered = _allOffers + .where((offer) => + offer["title"]!.toLowerCase().contains(event.query.toLowerCase()) || + offer["description"]! + .toLowerCase() + .contains(event.query.toLowerCase())) + .toList(); + emit(OffersState(filtered)); + } +} diff --git a/lib/offer_section/view/search_offers_with_listing.dart b/lib/offer_section/view/search_offers_with_listing.dart new file mode 100644 index 0000000..99cad29 --- /dev/null +++ b/lib/offer_section/view/search_offers_with_listing.dart @@ -0,0 +1,152 @@ +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/offer_section/bloc/search_offers_listing_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class SearchOffersWithListing extends StatelessWidget { + SearchOffersWithListing({super.key}); + + final List category = ["Beach", "Hike", "Popular", "Best in Summer"]; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => OffersBloc()..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,), + Row( + children: [ + Icon(Icons.arrow_back), + SizedBox(width: 8.w), + CustomText(text: "Offers with Flexi 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), + + SingleChildScrollView( + scrollDirection: Axis.horizontal, + + child: Row( + children: [ + ...List.generate(category.length, (index) { + return Padding( + padding: EdgeInsets.only(right: 8.0.w), + child: Container( + padding: EdgeInsets.symmetric( + vertical: 6.h, + horizontal: 12.w, + ), + decoration: BoxDecoration( + color: Color(0xFFFEE7E7), + borderRadius: BorderRadius.circular(100.sp), + border: Border.all(color: Color(0xFFFDCDCE)), + ), + child: Center( + child: CustomText(text: category[index]), + ), + ), + ); + }), + ], + ), + ), + + SizedBox(height: 20.h), + + /// Offer list + Expanded( + child: BlocBuilder( + builder: (context, state) { + final offers = state.offers; + + if (offers.isEmpty) { + return const Center( + child: Text( + "No offers found", + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ); + } + + 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 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: Image.asset( + offer["image"] ?? "", + width: double.infinity, + height: 120.5.h, + fit: BoxFit.cover, + ), + ), + SizedBox(height: 8.h), + CustomText( + text: offer["title"] ?? "", + size: 18.sp, + ), + SizedBox(height: 8.h), + CustomText( + text: offer["description"] ?? "", + color: Colors.black.withOpacity(.6), + size: 12.sp, + ), + ], + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index a796bed..efe589c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -150,6 +150,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_otp_text_field: + dependency: "direct main" + description: + name: flutter_otp_text_field + sha256: e7e589dc51cde120d63da6db55f3cef618f5d013d12adba76137ca1a51ce1390 + url: "https://pub.dev" + source: hosted + version: "1.5.1+1" flutter_plugin_android_lifecycle: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c476cc3..f07bf2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: intl: ^0.20.2 image_picker: ^1.2.0 image: ^4.5.4 + flutter_otp_text_field: ^1.5.1+1 dev_dependencies: flutter_test: