From 69046649aab64ae5cba36e638be09b9340486a50 Mon Sep 17 00:00:00 2001 From: Vinayakkadge04 Date: Mon, 27 Oct 2025 18:45:57 +0530 Subject: [PATCH] Completed purchase detail bottomsheet & add details screen --- lib/add_details/add_details_view.dart | 184 +++++++++++++++ lib/checkout/bloc/purchase_details_bloc.dart | 24 ++ lib/checkout/view/checkout_view.dart | 102 +++++---- .../widget/all_coupons_bottomsheet.dart | 38 +++- .../widget/purchase_details_bottomsheet.dart | 215 ++++++++++++++++++ lib/core/app_router.dart | 6 + lib/core/route_constants.dart | 1 + 7 files changed, 510 insertions(+), 60 deletions(-) create mode 100644 lib/add_details/add_details_view.dart create mode 100644 lib/checkout/bloc/purchase_details_bloc.dart create mode 100644 lib/checkout/widget/purchase_details_bottomsheet.dart diff --git a/lib/add_details/add_details_view.dart b/lib/add_details/add_details_view.dart new file mode 100644 index 0000000..3e43bb4 --- /dev/null +++ b/lib/add_details/add_details_view.dart @@ -0,0 +1,184 @@ +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'; +import 'package:citycards_customer/common_packages/custom_textfield.dart'; +import 'package:citycards_customer/core/route_constants.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class AddDetailsView extends StatelessWidget { + AddDetailsView({super.key}); + + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController addressController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showCart: false, + ), + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24.sp), + ), + SizedBox(width: 8.w), + Text( + "Add details", + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 42.h), + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "Tell us about yourself", + size: 18.sp, + weight: FontWeight.w500, + ), + ), + SizedBox(height: 12.h), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "First Name", + hint: "Enter your first name", + controller: firstNameController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "Last Name", + hint: "Enter your last name", + controller: lastNameController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "Email", + hint: "Enter your email address", + controller: emailController, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "Phone Number", + hint: "Enter your phone number", + controller: phoneController, + ), + ), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: CustomTextField( + label: "City", + hint: "Enter the name of your city", + controller: phoneController, + ), + ), + + Padding( + padding: EdgeInsets.only(bottom: 12.h, left: 12.w, right: 12.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: "Country", size: 14.sp), + SizedBox(height: 6.h), + Container( + height: 42.h, + padding: EdgeInsets.symmetric(horizontal: 24.w), + decoration: BoxDecoration( + color: const Color(0xFFFFF5F5), + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: const Color(0xBBC83B61).withOpacity(0.4), + width: 0.4.w, + ), + ), + child: DropdownButtonHideUnderline( + child: StatefulBuilder( + builder: (context, setState) { + String? selectedCountry; + return DropdownButton( + value: selectedCountry, + isExpanded: true, + icon: const Icon( + Icons.keyboard_arrow_down, + color: Color(0xFF8E8E8E), + ), + hint: Text( + "Select your country", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xFF8E8E8E), + ), + ), + style: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF2D3134), + ), + onChanged: (value) { + setState(() { + selectedCountry = value; + }); + }, + items: ["India", "USA", "UK", "Canada"].map(( + value, + ) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: TextStyle(fontSize: 14.sp), + ), + ); + }).toList(), + ); + }, + ), + ), + ), + ], + ), + ), + + const Spacer(), + + CustomFilledButton( + onTap: () { + + }, + label: "Continue", + width: double.infinity, + ), + SizedBox(height: 50.h), + ], + ), + ), + ), + ); + } +} diff --git a/lib/checkout/bloc/purchase_details_bloc.dart b/lib/checkout/bloc/purchase_details_bloc.dart new file mode 100644 index 0000000..c22cc45 --- /dev/null +++ b/lib/checkout/bloc/purchase_details_bloc.dart @@ -0,0 +1,24 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class PurchaseDetails {} + +class SetPurchaseDetailsEvent extends PurchaseDetails { + final String buyPassValue; + + SetPurchaseDetailsEvent(this.buyPassValue); +} + +class PurchaseDetailsState { + final String buyPassState; + + PurchaseDetailsState(this.buyPassState); +} + +class PurchaseDetailsBloc + extends Bloc { + PurchaseDetailsBloc() : super(PurchaseDetailsState("")) { + on((event, emit){ + emit(PurchaseDetailsState(event.buyPassValue)); + }); + } +} diff --git a/lib/checkout/view/checkout_view.dart b/lib/checkout/view/checkout_view.dart index 6f5bec8..c308708 100644 --- a/lib/checkout/view/checkout_view.dart +++ b/lib/checkout/view/checkout_view.dart @@ -59,8 +59,8 @@ class CheckoutView extends StatelessWidget { child: Image.asset( "assets/images/card_banner.png", scale: 4, - width: 103.w, - height: 160.h, + width: 105.w, + height: 123.h, fit: BoxFit.cover, ), ), @@ -73,13 +73,13 @@ class CheckoutView extends StatelessWidget { weight: FontWeight.w500, size: 16.sp, ), - SizedBox(height: 8.h), + SizedBox(height: 5.h), CustomText( text: "2 Days", color: Color(0xFF8E8E8E), size: 12.sp, ), - SizedBox(height: 12.h), + SizedBox(height: 5.h), SizedBox( width: MediaQuery.of(context).size.width * .5, @@ -109,10 +109,26 @@ class CheckoutView extends StatelessWidget { scale: 4, ), SizedBox(width: 4.w), - CustomText( - text: "Qty : 2", - color: Color(0xFF8E8E8E), - size: 12.sp, + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Qty:", + style: TextStyle( + color: Color(0xFF8E8E8E), + fontSize: 12.sp, + ), + ), + TextSpan( + text: " 2", + style: TextStyle( + color: Color(0xFF000000), + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), ), ], ), @@ -120,7 +136,7 @@ class CheckoutView extends StatelessWidget { ), ), - SizedBox(height: 7.h), + SizedBox(height: 5.h), Row( children: [ Image.asset("assets/icons/kid.png", scale: 4), @@ -141,16 +157,6 @@ class CheckoutView extends StatelessWidget { ), ], ), - - SizedBox( - width: 193.w, - child: CustomText( - text: - "Dive into an extensive selection of thrilling destinations!", - color: Color(0xFF000000).withOpacity(0.6), - size: 11.sp, - ), - ), ], ), ], @@ -158,7 +164,7 @@ class CheckoutView extends StatelessWidget { Container( width: 35.w, - height: 160.h, + height: 123.h, decoration: BoxDecoration( color: Color(0xFFF95FAF), borderRadius: BorderRadius.only( @@ -224,19 +230,19 @@ class CheckoutView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ GestureDetector( - onTap: (){ - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(12.r), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), ), - ), - builder: (_) => AllCouponsBottomsheet(), - ); - }, + builder: (_) => AllCouponsBottomsheet(), + ); + }, child: CustomText( text: "View all coupons", color: Color(0xFFF95F62), @@ -251,24 +257,23 @@ class CheckoutView extends StatelessWidget { ), const Spacer(), - Container( - padding: EdgeInsets.symmetric( - horizontal: 20.w, - vertical: 10.h, - ), - decoration: BoxDecoration( - border: Border.all(color: Color(0xFFF95F62)), - borderRadius: BorderRadius.circular(8.r), - ), - child: CustomText( - text: "Apply", - color: Color(0xFFF95F62), - size: 14.sp, - ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 10.h, ), - + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(8.r), + ), + child: CustomText( + text: "Apply", + color: Color(0xFFF95F62), + size: 14.sp, + ), + ), ], - ) + ), ), SizedBox(height: 15.h), @@ -350,6 +355,7 @@ class CheckoutView extends StatelessWidget { builder: (_) => const LoginEmailBottomsheet(), ); }, + width: double.infinity, label: "Login to Checkout", ), SizedBox(height: 25.h), diff --git a/lib/checkout/widget/all_coupons_bottomsheet.dart b/lib/checkout/widget/all_coupons_bottomsheet.dart index c9ca2e2..bb54b92 100644 --- a/lib/checkout/widget/all_coupons_bottomsheet.dart +++ b/lib/checkout/widget/all_coupons_bottomsheet.dart @@ -1,3 +1,4 @@ +import 'package:citycards_customer/checkout/widget/purchase_details_bottomsheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:citycards_customer/common_packages/custom_text.dart'; @@ -67,18 +68,31 @@ class AllCouponsBottomsheet extends StatelessWidget { coupons[index], fit: BoxFit.cover, ), - Container( - width: 110.w, - height: 44.h, - decoration: BoxDecoration( - color: Color(0xFFF95F62), - borderRadius: BorderRadius.circular(12.r), - ), - child: Center( - child: CustomText( - text: "Apply Coupon", - size: 12.sp, - color: Colors.white, + GestureDetector( + onTap: (){ + Navigator.pop(context); + showModalBottomSheet(context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => const PurchaseDetailsBottomsheet()); + }, + child: Container( + width: 110.w, + height: 44.h, + decoration: BoxDecoration( + color: Color(0xFFF95F62), + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: CustomText( + text: "Apply Coupon", + size: 12.sp, + color: Colors.white, + ), ), ), ), diff --git a/lib/checkout/widget/purchase_details_bottomsheet.dart b/lib/checkout/widget/purchase_details_bottomsheet.dart new file mode 100644 index 0000000..0fa7376 --- /dev/null +++ b/lib/checkout/widget/purchase_details_bottomsheet.dart @@ -0,0 +1,215 @@ +import 'package:citycards_customer/checkout/bloc/purchase_details_bloc.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_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class PurchaseDetailsBottomsheet extends StatelessWidget { + const PurchaseDetailsBottomsheet({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => PurchaseDetailsBloc(), + child: AnimatedPadding( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + padding: EdgeInsets.only( + top: 24.h, + left: 20.w, + right: 20.w, + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: BlocBuilder( + builder: (context, state) { + final selected = state.buyPassState; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // --- Handle Bar --- + Container( + height: 4.h, + width: 40.w, + decoration: BoxDecoration( + color: const Color(0xFF2D3134), + borderRadius: BorderRadius.circular(4.r), + ), + ), + SizedBox(height: 12.h), + + CustomText( + text: "Purchase Details", + size: 18.sp, + weight: FontWeight.w600, + ), + SizedBox(height: 22.h), + + // --- Option 1: Buy for Myself --- + GestureDetector( + onTap: () { + context.read().add( + SetPurchaseDetailsEvent("myself"), + ); + }, + child: Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + border: Border.all( + color: selected == "myself" + ? const Color(0xFFF95F62) + : Colors.transparent, + ), + borderRadius: BorderRadius.circular(10.r), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + selected == "myself" + ? Icons.radio_button_checked + : Icons.radio_button_off, + color: selected == "myself" + ? const Color(0xFFF95F62) + : Color(0xFF2B2929).withOpacity(.6), + size: 24.sp, + ), + SizedBox(width: 8.w), + CustomText( + text: "Buy Pass for Myself", + color: selected == "myself" + ? const Color(0xFFF95F62) + : Color(0xFF2B2929).withOpacity(.6), + size: 16.sp, + weight: FontWeight.w500, + ), + ], + ), + if (selected == "myself") ...[ + SizedBox(height: 6.h), + CustomText( + text: "Frank Adam", + size: 14.sp, + weight: FontWeight.w400, + color: Colors.black.withOpacity(0.6), + ), + SizedBox(height: 4.h), + CustomText( + text: "132 My Street, Kingston, NY 12401", + size: 12.sp, + color: const Color( + 0xFF000000, + ).withOpacity(0.4), + ), + ], + ], + ), + ), + if (selected == "myself") + Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, + vertical: 6.h, + ), + decoration: BoxDecoration( + color: Color(0xFFF95F62).withOpacity(0.12), + border: Border.all( + color: const Color(0xFFF95F62), + width: 1, + ), + borderRadius: BorderRadius.circular(12.r), + ), + child: CustomText( + text: "Edit Details", + size: 16.sp, + weight: FontWeight.w500, + color: const Color(0xFFF95F62), + ), + ), + ], + ), + ), + ), + SizedBox(height: 16.h), + + // --- Option 2: Gift the Pass --- + GestureDetector( + onTap: () { + context.read().add( + SetPurchaseDetailsEvent("gift"), + ); + }, + child: Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + border: Border.all( + color: selected == "gift" + ? const Color(0xFFF95F62) + : Colors.transparent, + ), + borderRadius: BorderRadius.circular(12.r), + ), + child: Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + selected == "gift" + ? Icons.radio_button_checked + : Icons.radio_button_off, + color: selected == "gift" + ? const Color(0xFFF95F62) + : Color(0xFF2F2A2A).withOpacity(0.4), + size: 24.sp, + ), + SizedBox(width: 8.w), + CustomText( + text: "Gift the pass", + color: selected == "gift" + ? const Color(0xFFF95F62) + : Color(0xFF2F2A2A).withOpacity(0.4), + size: 16.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 6.h), + if (selected == "gift") + CustomText( + text: "Gift the pass for someone else", + size: 12.sp, + color: const Color(0xFF000000).withOpacity(0.6), + ), + ], + ), + ), + ), + ), + + SizedBox(height: 24.h), + + // --- Proceed Button --- + CustomFilledButton( + onTap: () {}, + label: "Proceed", + width: double.infinity, + ), + SizedBox(height: 20.h), + ], + ); + }, + ), + ), + ); + } +} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index b8406f3..5d5c985 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -1,4 +1,5 @@ import 'package:citycards_customer/Profile/profile_page_view.dart'; +import 'package:citycards_customer/add_details/add_details_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/view/checkout_view.dart'; @@ -141,6 +142,11 @@ class AppRouter { ); }); + case RouteConstants.addDetails: + return MaterialPageRoute(builder: (_){ + return AddDetailsView(); + }); + case RouteConstants.createAcct: return MaterialPageRoute(builder: (_){ return CreateAccountView(); diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index 1306057..dfb4090 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -36,6 +36,7 @@ class RouteConstants { static const String checkout ='/checkout'; static const String searchOffer = '/searchOffer'; static const String createAcct = '/createAcct'; + static const String addDetails = '/addDetails'; }