diff --git a/lib/buy_a_pass/repository/buy_pass_repository.dart b/lib/buy_a_pass/repository/buy_pass_repository.dart index 643804b..1144608 100644 --- a/lib/buy_a_pass/repository/buy_pass_repository.dart +++ b/lib/buy_a_pass/repository/buy_pass_repository.dart @@ -27,10 +27,11 @@ class BuyPassRepository { required int totalChild, required int noOfAttractions, required int noOfDays, + required double baseAmount, }) async { try { final response = await _apiService.postApi( - url: ApiUrls.addToCartPasses, // add this key in ApiUrls + url: ApiUrls.addToCartPasses, data: { "cityXid": cityXid, "cardTypeXid": cardTypeXid, @@ -38,6 +39,8 @@ class BuyPassRepository { "cardMode": cardMode, "totalAdult": totalAdult, "totalChild": totalChild, + "baseAmount": baseAmount, + "taxAmount": 2, // Fixed tax amount "noOfAttractions": noOfAttractions, "noOfDays": noOfDays, }, @@ -48,4 +51,4 @@ class BuyPassRepository { throw Exception('Failed to add passes to cart: $e'); } } -} +} \ No newline at end of file diff --git a/lib/buy_a_pass/widget/payment_card_view.dart b/lib/buy_a_pass/widget/payment_card_view.dart index e2e2036..9fc53ae 100644 --- a/lib/buy_a_pass/widget/payment_card_view.dart +++ b/lib/buy_a_pass/widget/payment_card_view.dart @@ -181,11 +181,12 @@ class PaymentCard extends StatelessWidget { cityXid: cityXid, cardTypeXid: cardTypeXid, cardXid: cardXid, - cardMode: isSelectivePass ? 'flexi' : 'fixed', + cardMode: isSelectivePass ? 'flexi' : 'unlimited', totalAdult: adults, totalChild: children, noOfAttractions: isSelectivePass ? selectedValue : 0, noOfDays: isUnlimitedCard ? selectedValue : 0, + baseAmount: totalPrice, ); // ✅ Extract bookingId from response diff --git a/lib/checkout/view/checkout_view.dart b/lib/checkout/view/checkout_view.dart index 2359911..9aa119e 100644 --- a/lib/checkout/view/checkout_view.dart +++ b/lib/checkout/view/checkout_view.dart @@ -116,11 +116,11 @@ class _CheckoutContent extends StatelessWidget { /// 🆕 Handle payment flow with client secret /// 🆕 Handle payment flow with client secret - SIMPLIFIED VERSION - Future _handlePaymentFlow(BuildContext context, String clientSecret, int bookingId) async { + Future _handlePaymentFlow(BuildContext context, String clientSecret, int bookingId,double finalTotal) async { final paymentSuccess = await StripePaymentScreen.showAsBottomSheet( context: context, clientSecret: clientSecret, - amount: checkoutData.totalPrice.toDouble(), + amount: finalTotal, currencySymbol: '\$', title: 'Complete Payment', loadingMessage: 'Processing your pass payment...', @@ -149,6 +149,7 @@ class _CheckoutContent extends StatelessWidget { ); }, onPaymentCancelled: () { + Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Payment cancelled'), @@ -182,22 +183,31 @@ class _CheckoutContent extends StatelessWidget { if (state is CheckoutCouponsLoadedState) { // Check if clientSecret is available (payment initiated) if (state.clientSecret != null && state.clientSecret!.isNotEmpty) { - // Trigger payment flow + // ✅ Calculate finalTotal here + double discountPercentage = 0.0; + if (state.appliedCoupon != null) { + discountPercentage = state.appliedCoupon!.discountPercent.toDouble(); + } + + final num subtotal = checkoutData.totalPrice; + final double discountAmount = subtotal * (discountPercentage / 100); + final double totalBeforeTax = subtotal - discountAmount; + final double taxAmount = 2; + final double finalTotal = totalBeforeTax + taxAmount; + + // ✅ Trigger payment flow with finalTotal WidgetsBinding.instance.addPostFrameCallback((_) { - _handlePaymentFlow(context, state.clientSecret!, state.bookingId ?? bookingId); + _handlePaymentFlow( + context, + state.clientSecret!, + state.bookingId ?? bookingId, + finalTotal, // ✅ Pass the calculated finalTotal + ); }); } // 🆕 Listen for payment confirmation success if (state.isPaymentConfirmed) { - // Show success message - // ScaffoldMessenger.of(context).showSnackBar( - // const SnackBar( - // content: Text('Payment confirmed successfully!'), - // backgroundColor: Colors.green, - // ), - // ); - // Navigate to success page or back Future.delayed(const Duration(seconds: 2), () { if (context.mounted) { @@ -255,9 +265,10 @@ class _CheckoutContent extends StatelessWidget { final num subtotal = checkoutData.totalPrice; final double discountAmount = subtotal * (discountPercentage / 100); - final double taxRate = 0.05; // 5% tax + // final double taxRate = 0.05; // 5% tax final double totalBeforeTax = subtotal - discountAmount; - final double taxAmount = totalBeforeTax * taxRate; + // final double taxAmount = totalBeforeTax * taxRate; + final double taxAmount = 2; final double finalTotal = totalBeforeTax + taxAmount; return Scaffold( @@ -531,11 +542,18 @@ class _CheckoutContent extends StatelessWidget { ), builder: (_) => AllCouponsBottomsheet( onCouponSelected: (selectedCoupon) { + final coupon = selectedCoupon as AllCouponsModel; // Apply the selected coupon context.read().add( ApplyCouponEvent( coupon: selectedCoupon), ); + context.read().add( + ApplyCouponToBackendEvent( + bookingId: bookingId, + couponCode: coupon.couponCode, + ), + ); }, ), ); diff --git a/lib/itinerary_creation/models/my_itinerary_model.dart b/lib/itinerary_creation/models/my_itinerary_model.dart index 3b202ea..a2e2c87 100644 --- a/lib/itinerary_creation/models/my_itinerary_model.dart +++ b/lib/itinerary_creation/models/my_itinerary_model.dart @@ -1,18 +1,29 @@ class MyItineraryResponse { + bool isUnlimitedPass; List itineraries; - MyItineraryResponse({required this.itineraries}); + MyItineraryResponse({ + required this.isUnlimitedPass, + required this.itineraries, + }); + + factory MyItineraryResponse.fromJson(Map? json) { + json ??= {}; - factory MyItineraryResponse.fromJson(List? json) { return MyItineraryResponse( - itineraries: json == null + isUnlimitedPass: json['isUnlimitedPass'] ?? false, + itineraries: json['itineraries'] == null ? [] - : json.map((e) => MyItinerary.fromJson(e)).toList(), + : List>.from(json['itineraries']) + .map((e) => MyItinerary.fromJson(e)) + .toList(), ); } - List> toJson() => - itineraries.map((e) => e.toJson()).toList(); + Map toJson() => { + "isUnlimitedPass": isUnlimitedPass, + "itineraries": itineraries.map((e) => e.toJson()).toList(), + }; } class MyItinerary { @@ -61,22 +72,21 @@ class MyItinerary { id: (json['id'] as num?)?.toInt() ?? 0, userXid: (json['userXid'] as num?)?.toInt() ?? 0, cityXid: (json['cityXid'] as num?)?.toInt() ?? 0, - address: json['Address'] ?? "", + address: json['Address']?.toString() ?? "", latitude: (json['latitude'] as num?)?.toDouble() ?? 0.0, longitude: (json['longitude'] as num?)?.toDouble() ?? 0.0, - tripEnergy: json['tripEnergy'] ?? "", + tripEnergy: json['tripEnergy']?.toString() ?? "", travelingWithKids: json['travelingWithKids'] ?? false, dietaryPreferences: json['dietaryPreferences'] == null ? [] : List.from(json['dietaryPreferences']), - preferences: - Preferences.fromJson(json['preferences'] ?? {}), + preferences: Preferences.fromJson(json['preferences']), totalDays: (json['totalDays'] as num?)?.toInt() ?? 0, - aiModel: json['aiModel'] ?? "", - promptVersion: json['promptVersion'] ?? "", + aiModel: json['aiModel']?.toString() ?? "", + promptVersion: json['promptVersion']?.toString() ?? "", isActive: json['isActive'] ?? false, - createdAt: json['createdAt'] ?? "", - updatedAt: json['updatedAt'] ?? "", + createdAt: json['createdAt']?.toString() ?? "", + updatedAt: json['updatedAt']?.toString() ?? "", days: json['days'] == null ? [] : List>.from(json['days']) @@ -166,8 +176,8 @@ class ItineraryDay { id: (json['id'] as num?)?.toInt() ?? 0, itineraryXid: (json['itineraryXid'] as num?)?.toInt() ?? 0, dayNumber: (json['dayNumber'] as num?)?.toInt() ?? 0, - title: json['title'] ?? "", - summary: json['summary'] ?? "", + title: json['title']?.toString() ?? "", + summary: json['summary']?.toString() ?? "", items: json['items'] == null ? [] : List>.from(json['items']) @@ -216,11 +226,11 @@ class DayItem { id: (json['id'] as num?)?.toInt() ?? 0, itineraryDayXid: (json['itineraryDayXid'] as num?)?.toInt() ?? 0, - timeSlot: json['timeSlot'] ?? "", - title: json['title'] ?? "", - description: json['description'] ?? "", - locationName: json['locationName'] ?? "", - imageUrl: json['imageUrl'] ?? "", + timeSlot: json['timeSlot']?.toString() ?? "", + title: json['title']?.toString() ?? "", + description: json['description']?.toString() ?? "", + locationName: json['locationName']?.toString() ?? "", + imageUrl: json['imageUrl']?.toString() ?? "", latitude: (json['latitude'] as num?)?.toDouble() ?? 0.0, longitude: (json['longitude'] as num?)?.toDouble() ?? 0.0, ); diff --git a/lib/itinerary_creation/repository/itinerary_repository.dart b/lib/itinerary_creation/repository/itinerary_repository.dart index 4993c7e..aa44efa 100644 --- a/lib/itinerary_creation/repository/itinerary_repository.dart +++ b/lib/itinerary_creation/repository/itinerary_repository.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:citycards_customer/itinerary_creation/models/itinerary_city_model.dart'; import 'package:dio/dio.dart'; +import '../../localPreference/local_preference.dart'; import '../../networkApiServices/api_urls.dart'; import '../../networkApiServices/network_api_services.dart'; import '../models/my_itinerary_model.dart'; @@ -11,8 +12,9 @@ class ItineraryRepository { final NetworkApiService _apiService = NetworkApiService(); Future fetchMyItineraries() async { + final int cityId = await LocalPreference.getSelectedCityId(); final response = await _apiService.getApi( - url: ApiUrls.myItineraries, // 👈 Make sure this endpoint exists + url: '${ApiUrls.myItineraries}/$cityId', // 👈 Make sure this endpoint exists ); /// Because API returns LIST diff --git a/lib/postcard/blocs/postcard_creation_bloc.dart b/lib/postcard/blocs/postcard_creation_bloc.dart index 35c9d41..d7598f9 100644 --- a/lib/postcard/blocs/postcard_creation_bloc.dart +++ b/lib/postcard/blocs/postcard_creation_bloc.dart @@ -248,4 +248,18 @@ class PostcardCreationBloc emit(state.copyWith(isGift: event.isGift)); }); } + + // Add this getter method in PostcardCreationBloc class + String getFormattedMessage() { + if (state.message == null || state.message!.isEmpty) { + return ''; + } + + if (state.selectedFont == null || state.selectedFont!.isEmpty) { + // Default font (Poppins) + return '${state.message}'; + } + + return '${state.message}'; + } } \ No newline at end of file diff --git a/lib/postcard/views/postcard_checkout_page_view.dart b/lib/postcard/views/postcard_checkout_page_view.dart index 8a0aab4..443cb12 100644 --- a/lib/postcard/views/postcard_checkout_page_view.dart +++ b/lib/postcard/views/postcard_checkout_page_view.dart @@ -80,7 +80,7 @@ class _PostcardCheckoutPageViewState extends State { if (creationState.imagePath != null && creationState.imagePath!.isNotEmpty) { imageFile = File(creationState.imagePath!); } - + final postcardBloc = context.read(); context.read().add( UpdateCheckoutDataEvent( countryName: widget.countryName, @@ -90,7 +90,7 @@ class _PostcardCheckoutPageViewState extends State { address1: creationState.address, address2: widget.address2, pcTitle: widget.pcTitle, - pcContent: creationState.message ?? '', + pcContent: postcardBloc.getFormattedMessage(), pcImageFile: imageFile, pcNumber: widget.pcNumber, pcDatetime: widget.pcDatetime, diff --git a/lib/postcard/views/write_message_step_page_view.dart b/lib/postcard/views/write_message_step_page_view.dart index f06ca9a..47656aa 100644 --- a/lib/postcard/views/write_message_step_page_view.dart +++ b/lib/postcard/views/write_message_step_page_view.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -8,6 +10,7 @@ import '../blocs/postcard_creation_events.dart'; import '../blocs/postcard_creation_state.dart'; import '../widgets/postcard_preview_widget.dart'; import '../widgets/step_progressbar.dart'; +import 'dart:ui' as ui; class WriteMessageStepPageView extends StatefulWidget { const WriteMessageStepPageView({super.key}); @@ -45,10 +48,10 @@ class _WriteMessageStepPageViewState extends State { TextPosition(offset: _controller.text.length)); final fonts = [ - {"name": "Default", "font": GoogleFonts.poppins()}, - {"name": "Classic", "font": GoogleFonts.playfairDisplay()}, - {"name": "Handwriting", "font": GoogleFonts.dancingScript()}, - {"name": "Elegant", "font": GoogleFonts.cormorantGaramond()}, + {"name": "Default", "font": GoogleFonts.poppins(), "cleanName": "Poppins"}, + {"name": "Patrick Hand", "font": GoogleFonts.patrickHand(), "cleanName": "Patrick Hand"}, + {"name": "Indie Flower", "font": GoogleFonts.indieFlower(), "cleanName": "Indie Flower"}, + {"name": "Gloria Hallelujah", "font": GoogleFonts.gloriaHallelujah(), "cleanName": "Gloria Hallelujah"}, ]; return SafeArea( @@ -91,12 +94,7 @@ class _WriteMessageStepPageViewState extends State { maxLines: 8, maxLength: 400, cursorColor: const Color(0xffF95F62), - style: TextStyle( - fontFamily: state.selectedFont ?? - GoogleFonts.poppins().fontFamily, - fontSize: 14.sp, - color: Colors.black, - ), + style: _getTextFieldStyle(state.selectedFont, fonts), decoration: InputDecoration( border: InputBorder.none, hintText: "Add Your Message Here", @@ -133,43 +131,52 @@ class _WriteMessageStepPageViewState extends State { children: fonts.map((font) { final TextStyle fontStyle = font['font'] as TextStyle; final String fontName = font["name"] as String; - final isSelected = state.selectedFont == - fontStyle.fontFamily || - (state.selectedFont == null && - fontName == "Default"); + final String cleanName = font["cleanName"] as String; + + final isSelected = state.selectedFont == cleanName || + (state.selectedFont == null && fontName == "Default"); return GestureDetector( - onTap: () => bloc - .add(ChangeFontStyle(fontStyle.fontFamily ?? "")), + onTap: () => bloc.add(ChangeFontStyle(cleanName)), child: Container( - margin: const EdgeInsets.only(right: 12), - padding: const EdgeInsets.all(12), - width: 90.w, + padding: const EdgeInsets.all(6), + width: 100.w, + height: 100.h, decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(12), - border: Border.all( + ), + child: CustomPaint( + painter: DottedBorderPainter( color: isSelected ? const Color(0xffF95F62) : const Color(0xffE0E0E0), - width: 1.5, + strokeWidth: 1.5, + dashWidth: 4, + dashSpace: 3, + borderRadius: 12, + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Aa", + style: fontStyle.copyWith( + fontSize: 24.sp, + color: const Color(0xff1A1A1A), + )), + const SizedBox(height: 4), + Text(fontName, + textAlign: TextAlign.center, + style: fontStyle.copyWith( + fontSize: 11.sp, + color: isSelected + ? const Color(0xffF95F62) + : const Color(0xff2D3134), + )), + ], + ), ), - ), - child: Column( - children: [ - Text("Aa", - style: fontStyle.copyWith( - fontSize: 20.sp, - color: const Color(0xff1A1A1A), - )), - const SizedBox(height: 4), - Text(fontName, - style: TextStyle( - fontSize: 12.sp, - color: isSelected - ? const Color(0xffF95F62) - : const Color(0xff2D3134), - )), - ], ), ), ); @@ -209,5 +216,117 @@ class _WriteMessageStepPageViewState extends State { }, ); } + + // Helper method to get the correct font style for the text field + TextStyle _getTextFieldStyle(String? selectedFont, List> fonts) { + if (selectedFont == null || selectedFont.isEmpty) { + return GoogleFonts.poppins(fontSize: 14.sp, color: Colors.black); + } + + // Find matching font by cleanName + for (var font in fonts) { + if (font['cleanName'] == selectedFont) { + final TextStyle fontStyle = font['font'] as TextStyle; + return fontStyle.copyWith(fontSize: 14.sp, color: Colors.black); + } + } + + // Default fallback to Poppins + return GoogleFonts.poppins(fontSize: 14.sp, color: Colors.black); + } } +// Custom Painter for Dotted Border +class DottedBorderPainter extends CustomPainter { + final Color color; + final double strokeWidth; + final double dashWidth; + final double dashSpace; + final double borderRadius; + + DottedBorderPainter({ + required this.color, + this.strokeWidth = 1.5, + this.dashWidth = 4, + this.dashSpace = 3, + this.borderRadius = 12, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke; + + final path = Path() + ..addRRect(RRect.fromRectAndRadius( + Rect.fromLTWH(0, 0, size.width, size.height), + Radius.circular(borderRadius), + )); + + // Create dashed path + final dashPath = _createDashedPath(path, dashWidth, dashSpace); + canvas.drawPath(dashPath, paint); + } + + Path _createDashedPath(Path source, double dashWidth, double dashSpace) { + final Path dest = Path(); + for (final PathMetric metric in source.computeMetrics()) { + double distance = 0.0; + bool draw = true; + while (distance < metric.length) { + final double length = draw ? dashWidth : dashSpace; + if (distance + length > metric.length) { + if (draw) { + dest.addPath( + metric.extractPath(distance, metric.length), + Offset.zero, + ); + } + break; + } else { + if (draw) { + dest.addPath( + metric.extractPath(distance, distance + length), + Offset.zero, + ); + } + distance += length; + draw = !draw; + } + } + } + return dest; + } + + @override + bool shouldRepaint(DottedBorderPainter oldDelegate) { + return oldDelegate.color != color || + oldDelegate.strokeWidth != strokeWidth || + oldDelegate.dashWidth != dashWidth || + oldDelegate.dashSpace != dashSpace; + } +} + +// Lined Paper Painter (assuming this exists in your original code) +class LinedPaperPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = const Color(0xFFE0E0E0) + ..strokeWidth = 1; + + const lineSpacing = 30.0; + for (double i = lineSpacing; i < size.height; i += lineSpacing) { + canvas.drawLine( + Offset(0, i), + Offset(size.width, i), + paint, + ); + } + } + + @override + bool shouldRepaint(LinedPaperPainter oldDelegate) => false; +} \ No newline at end of file diff --git a/lib/postcard/widgets/back_card_widget.dart b/lib/postcard/widgets/back_card_widget.dart index a05c97f..2009c15 100644 --- a/lib/postcard/widgets/back_card_widget.dart +++ b/lib/postcard/widgets/back_card_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:html/parser.dart' as html_parser; class BackCardWidget extends StatelessWidget { final String message; @@ -26,8 +27,77 @@ class BackCardWidget extends StatelessWidget { this.scale = 1.08, }); + // Parse HTML message and extract font family and text + Map _parseHtmlMessage(String htmlMessage) { + if (htmlMessage.isEmpty) { + return {'text': '', 'fontFamily': ''}; + } + + // Check if message contains HTML tags + if (!htmlMessage.contains('