diff --git a/lib/cart/views/my_pass_cart_page_view.dart b/lib/cart/views/my_pass_cart_page_view.dart index 1695922..8117b1e 100644 --- a/lib/cart/views/my_pass_cart_page_view.dart +++ b/lib/cart/views/my_pass_cart_page_view.dart @@ -47,7 +47,6 @@ class _MyPassesCartPageState extends State { child: CircularProgressIndicator(color: Color(0xffF95F62)), ); } - // ========== HANDLE API DATA (LOGGED IN USER) ========== else if (state is MyPassCartApiLoaded) { final apiCartData = state.apiCartData; @@ -73,15 +72,15 @@ class _MyPassesCartPageState extends State { final String cityName = cartItem.city.cityName; final String cardDisplayName = cartItem.displayCardMode; final String cardTypeName = cartItem.cardMode; - final int themeColor = - isFlexiCard ? 0xFFF95FAF : 0xFFF95F62; + final int themeColor = isFlexiCard ? 0xFFF95FAF : 0xFFF95F62; final int adultCount = cartItem.totalAdult; final int childCount = cartItem.totalChild; final int validityDuration = cartItem.noOfDays; final double totalPrice = cartItem.totalAmount.toDouble(); - final bool isUnlimitedCard = - cardTypeName.toLowerCase().contains("unlimited"); + final bool isUnlimitedCard = cardTypeName + .toLowerCase() + .contains("unlimited"); final String validityLabel = isUnlimitedCard ? "$validityDuration Days" : "${cartItem.noOfAttractions} Attractions"; @@ -111,9 +110,7 @@ class _MyPassesCartPageState extends State { bookingId: cartItem.id, couponId: cartItem.couponXid, ), - settings: RouteSettings( - arguments: checkoutData, - ), + settings: RouteSettings(arguments: checkoutData), ), ); }, @@ -135,7 +132,6 @@ class _MyPassesCartPageState extends State { ), ); } - // ========== HANDLE LOCAL DATA (NOT LOGGED IN) ========== else if (state is MyPassCartLoaded) { final cartData = state.cartData; @@ -146,8 +142,7 @@ class _MyPassesCartPageState extends State { cartData['card_type_name'] as String? ?? ''; final String cardDisplayName = cartData['card_display_name'] as String? ?? ''; - final int themeColor = - cartData['theme_color'] as int? ?? 0xFFF95FAF; + final int themeColor = cartData['theme_color'] as int? ?? 0xFFF95FAF; final int adultCount = cartData['adult_count'] as int? ?? 0; final int childCount = cartData['child_count'] as int? ?? 0; final double adultPrice = @@ -193,7 +188,6 @@ class _MyPassesCartPageState extends State { ), ); } - // ========== EMPTY STATE ========== else if (state is MyPassCartEmpty) { return Padding( @@ -221,7 +215,7 @@ class _MyPassesCartPageState extends State { SizedBox(height: 40.h), CustomFilledButton( onTap: () { - + Navigator.pop(context); }, label: "Buy a Pass", ), @@ -230,7 +224,6 @@ class _MyPassesCartPageState extends State { ), ); } - // ========== ERROR STATE ========== else if (state is MyPassCartError) { return Center( @@ -290,9 +283,7 @@ class _CartItemCard extends StatelessWidget { return Container( decoration: BoxDecoration( color: Colors.white, - border: Border.all( - color: Color(themeColor).withOpacity(0.2), - ), + border: Border.all(color: Color(themeColor).withOpacity(0.2)), borderRadius: BorderRadius.circular(8.r), ), child: Row( @@ -307,32 +298,32 @@ class _CartItemCard extends StatelessWidget { ), child: heroImage.isNotEmpty ? CachedNetworkImage( - imageUrl: heroImage, - width: 105.w, - height: 130.h, - fit: BoxFit.cover, - errorWidget: (context, url, error) => Image.asset( - "assets/images/card_banner.png", - scale: 4, - width: 105.w, - height: 123.h, - fit: BoxFit.cover, - ), - placeholder: (context, url) => Image.asset( - "assets/images/card_banner.png", - scale: 4, - width: 105.w, - height: 123.h, - fit: BoxFit.cover, - ), - ) + imageUrl: heroImage, + width: 105.w, + height: 130.h, + fit: BoxFit.cover, + errorWidget: (context, url, error) => Image.asset( + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ), + placeholder: (context, url) => Image.asset( + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ), + ) : Image.asset( - "assets/images/card_banner.png", - scale: 4, - width: 105.w, - height: 123.h, - fit: BoxFit.cover, - ), + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ), ), SizedBox(width: 6.66.w), @@ -367,7 +358,7 @@ class _CartItemCard extends StatelessWidget { SizedBox(width: 4.w), CustomText( text: - "$adultCount ${adultCount == 1 ? 'adult' : 'adults'}", + "$adultCount ${adultCount == 1 ? 'adult' : 'adults'}", color: const Color(0xFF8E8E8E), size: 12.sp, ), @@ -388,7 +379,7 @@ class _CartItemCard extends StatelessWidget { SizedBox(width: 4.w), CustomText( text: - "$childCount ${childCount == 1 ? 'Kid' : 'Kids'}", + "$childCount ${childCount == 1 ? 'Kid' : 'Kids'}", color: const Color(0xFF8E8E8E), size: 12.sp, ), @@ -428,10 +419,7 @@ class _CartItemCard extends StatelessWidget { children: [ TextSpan( text: "$cardDisplayName ", - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - ), + style: TextStyle(color: Colors.white, fontSize: 14.sp), ), ], ), @@ -443,4 +431,4 @@ class _CartItemCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/cart/views/my_postcard_cart_page_view.dart b/lib/cart/views/my_postcard_cart_page_view.dart index e1bb088..1f31b43 100644 --- a/lib/cart/views/my_postcard_cart_page_view.dart +++ b/lib/cart/views/my_postcard_cart_page_view.dart @@ -425,7 +425,7 @@ class _EmptyCartScreen extends StatelessWidget { SizedBox(height: 40.h), CustomFilledButton( onTap: () { - + Navigator.pop(context); }, label: "Design my postcard", ), diff --git a/lib/common_packages/custom_textfield.dart b/lib/common_packages/custom_textfield.dart index 4bb5407..2e455a2 100644 --- a/lib/common_packages/custom_textfield.dart +++ b/lib/common_packages/custom_textfield.dart @@ -13,7 +13,7 @@ class CustomTextField extends StatelessWidget { final TextInputType? keyboardType; final bool obscureText; final Widget? suffixIcon; - final Widget? prefixWidget; // ✅ NEW: optional prefix (e.g. CountryCodePicker) + final Widget? prefixWidget; final void Function(String)? onChanged; final int? maxLength; @@ -40,7 +40,7 @@ class CustomTextField extends StatelessWidget { this.keyboardType, this.obscureText = false, this.suffixIcon, - this.prefixWidget, // ✅ NEW + this.prefixWidget, this.onChanged, this.maxLength, this.numbersOnly = false, @@ -56,33 +56,26 @@ class CustomTextField extends StatelessWidget { void _capitalizeFirstLetter(String value) { if (value.isEmpty) return; - final capitalized = value[0].toUpperCase() + value.substring(1); - if (capitalized != value) { controller.value = controller.value.copyWith( text: capitalized, - selection: TextSelection.collapsed( - offset: capitalized.length, - ), + selection: TextSelection.collapsed(offset: capitalized.length), ); } } String? _internalValidator(String? value) { if (isPreview) return null; - if (value == null || value.trim().isEmpty) { return 'Please enter $label'; } - if (isEmail) { final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(value.trim())) { return 'Please enter a valid email address'; } } - if (isMobileNumber) { if (!RegExp(r'^\d+$').hasMatch(value)) { return 'Only numbers are allowed'; @@ -91,16 +84,12 @@ class CustomTextField extends StatelessWidget { return 'Mobile number must be $mobileLength digits'; } } - if (noSpace && value.contains(' ')) { return 'Spaces are not allowed'; } - - if (noSpecialCharacters && - !RegExp(r'^[a-zA-Z0-9\s]+$').hasMatch(value)) { + if (noSpecialCharacters && !RegExp(r'^[a-zA-Z0-9\s]+$').hasMatch(value)) { return 'Special characters are not allowed'; } - return null; } @@ -120,32 +109,35 @@ class CustomTextField extends StatelessWidget { if (numbersOnly) { inputFormatters.add(FilteringTextInputFormatter.digitsOnly); } - if (onlyLetters) { inputFormatters.add( FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')), ); } - if (noSpecialCharacters) { inputFormatters.add( FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9\s]')), ); } - if (noSpace) { - inputFormatters.add( - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ); + inputFormatters.add(FilteringTextInputFormatter.deny(RegExp(r'\s'))); } - if (maxLength != null) { inputFormatters.add(LengthLimitingTextInputFormatter(maxLength)); } } } - // ✅ Determine border radius — if prefixWidget is present, only round the right side + final fillColor = isPreview + ? Colors.grey.shade100 + : enabled + ? const Color(0xFFFFF5F5) + : Colors.grey.shade200; + + final borderColor = const Color(0xBBC83B61).withOpacity(0.4); + + // ✅ Full radius used for normal fields + // ✅ Only right-side radius when prefix is present (left side is the prefix container) final borderRadius = prefixWidget != null ? BorderRadius.only( topRight: Radius.circular(8.r), @@ -153,127 +145,297 @@ class CustomTextField extends StatelessWidget { ) : BorderRadius.circular(8.r); - // ✅ Determine fill color - final fillColor = isPreview - ? Colors.grey.shade100 - : enabled - ? const Color(0xFFFFF5F5) - : Colors.grey.shade200; - - final textFormField = TextFormField( - controller: controller, - maxLines: obscureText ? 1 : maxLines, - enabled: isPreview ? false : enabled, - obscureText: obscureText, - validator: validator ?? _internalValidator, - autovalidateMode: AutovalidateMode.onUserInteraction, - keyboardType: keyboardType ?? - (isMobileNumber - ? TextInputType.phone - : isEmail - ? TextInputType.emailAddress - : TextInputType.name), - inputFormatters: inputFormatters, - onChanged: (value) { - if (isFirstLetterCapital) { - _capitalizeFirstLetter(value); - } - if (onChanged != null) { - onChanged!(value); - } - }, - decoration: InputDecoration( - hintText: hint, - counterText: "", - hintStyle: TextStyle( - fontSize: 12.sp, - color: const Color(0xFF8E8E8E), - ), - filled: true, - fillColor: fillColor, - contentPadding: EdgeInsets.symmetric( - // ✅ Reduce left padding when prefixWidget takes up the left side - horizontal: prefixWidget != null ? 12.w : 24.w, - vertical: maxLines != null && maxLines! > 1 ? 12.h : 10.h, - ), - suffixIcon: suffixIcon, - enabledBorder: OutlineInputBorder( - borderRadius: borderRadius, - borderSide: BorderSide( - color: const Color(0xBBC83B61).withOpacity(0.4), - width: .4.w, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: borderRadius, - borderSide: BorderSide( - color: const Color(0xFFF95F62), - width: 1.w, - ), - ), - errorBorder: OutlineInputBorder( - borderRadius: borderRadius, - borderSide: BorderSide( - color: Colors.red, - width: 1.w, - ), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: borderRadius, - borderSide: BorderSide( - color: Colors.red, - width: 1.5.w, - ), - ), - errorStyle: TextStyle( - fontSize: 11.sp, - color: Colors.red, - height: 1.3, - ), - ), - ); - return Padding( padding: EdgeInsets.only(bottom: 14.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // ✅ Only show label row if label is not empty + // Label if (label.isNotEmpty) ...[ CustomText(text: label, size: 14.sp), SizedBox(height: 6.h), ], - // ✅ If prefixWidget provided, wrap it in a Row with the picker on the left if (prefixWidget != null) - IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Prefix container — styled to match the field - Container( - decoration: BoxDecoration( - color: fillColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8.r), - bottomLeft: Radius.circular(8.r), - ), - border: Border.all( - color: const Color(0xBBC83B61).withOpacity(0.4), - width: 0.4.w, - ), - ), - child: prefixWidget!, - ), - // TextField takes the remaining space - Expanded(child: textFormField), - ], - ), + // ✅ THE CORE FIX: + // We split the phone field into two parts: + // 1. The input ROW (prefix + text field) — wrapped in IntrinsicHeight + // so both sides match height perfectly + // 2. The error text — rendered OUTSIDE and BELOW the row + // so IntrinsicHeight is never affected by error text height + _PrefixFieldWithError( + prefixWidget: prefixWidget!, + fillColor: fillColor, + borderColor: borderColor, + borderRadius: borderRadius, + maxLines: maxLines, + obscureText: obscureText, + enabled: isPreview ? false : enabled, + controller: controller, + validator: validator ?? _internalValidator, + keyboardType: keyboardType ?? TextInputType.phone, + inputFormatters: inputFormatters, + hint: hint, + onChanged: (value) { + if (isFirstLetterCapital) _capitalizeFirstLetter(value); + if (onChanged != null) onChanged!(value); + }, + suffixIcon: suffixIcon, ) else - textFormField, + TextFormField( + controller: controller, + maxLines: obscureText ? 1 : maxLines, + enabled: isPreview ? false : enabled, + obscureText: obscureText, + validator: validator ?? _internalValidator, + autovalidateMode: AutovalidateMode.onUserInteraction, + keyboardType: keyboardType ?? + (isMobileNumber + ? TextInputType.phone + : isEmail + ? TextInputType.emailAddress + : TextInputType.name), + inputFormatters: inputFormatters, + onChanged: (value) { + if (isFirstLetterCapital) _capitalizeFirstLetter(value); + if (onChanged != null) onChanged!(value); + }, + decoration: InputDecoration( + hintText: hint, + counterText: "", + hintStyle: TextStyle( + fontSize: 12.sp, + color: const Color(0xFF8E8E8E), + ), + filled: true, + fillColor: fillColor, + contentPadding: EdgeInsets.symmetric( + horizontal: 24.w, + vertical: maxLines != null && maxLines! > 1 ? 12.h : 10.h, + ), + suffixIcon: suffixIcon, + enabledBorder: OutlineInputBorder( + borderRadius: borderRadius, + borderSide: BorderSide(color: borderColor, width: .4.w), + ), + focusedBorder: OutlineInputBorder( + borderRadius: borderRadius, + borderSide: BorderSide( + color: const Color(0xFFF95F62), width: 1.w), + ), + errorBorder: OutlineInputBorder( + borderRadius: borderRadius, + borderSide: BorderSide(color: Colors.red, width: 1.w), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: borderRadius, + borderSide: BorderSide(color: Colors.red, width: 1.5.w), + ), + errorStyle: TextStyle( + fontSize: 11.sp, + color: Colors.red, + height: 1.3, + ), + ), + ), ], ), ); } +} + +/// ✅ Separate StatefulWidget for the prefix + field combo. +/// It manually manages validation state and renders error text +/// OUTSIDE the IntrinsicHeight row — this is the key to perfect alignment. +class _PrefixFieldWithError extends StatefulWidget { + final Widget prefixWidget; + final Color fillColor; + final Color borderColor; + final BorderRadius borderRadius; + final int? maxLines; + final bool obscureText; + final bool enabled; + final TextEditingController controller; + final String? Function(String?)? validator; + final TextInputType keyboardType; + final List inputFormatters; + final String hint; + final void Function(String) onChanged; + final Widget? suffixIcon; + + const _PrefixFieldWithError({ + required this.prefixWidget, + required this.fillColor, + required this.borderColor, + required this.borderRadius, + required this.maxLines, + required this.obscureText, + required this.enabled, + required this.controller, + required this.validator, + required this.keyboardType, + required this.inputFormatters, + required this.hint, + required this.onChanged, + required this.suffixIcon, + }); + + @override + State<_PrefixFieldWithError> createState() => _PrefixFieldWithErrorState(); +} + +class _PrefixFieldWithErrorState extends State<_PrefixFieldWithError> { + String? _errorText; + bool _hasInteracted = false; + + void _validate(String value) { + if (!_hasInteracted) return; + setState(() { + _errorText = widget.validator?.call(value); + }); + } + + void _onChanged(String value) { + setState(() => _hasInteracted = true); + _validate(value); + widget.onChanged(value); + } + + // Called by Form.validate() via FormField + String? _formValidator(String? value) { + setState(() => _hasInteracted = true); + final error = widget.validator?.call(value); + // Update error text after frame + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() => _errorText = error); + }); + return error; + } + + bool get _hasError => _errorText != null && _errorText!.isNotEmpty; + + @override + Widget build(BuildContext context) { + final borderColor = widget.borderColor; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ✅ IntrinsicHeight ONLY wraps the input row (prefix + field) + // Error text is outside this, so IntrinsicHeight height is never affected by it + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Prefix container — matches field height perfectly via IntrinsicHeight + Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: widget.fillColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.r), + bottomLeft: Radius.circular(8.r), + ), + // ✅ No right border — avoids double border line + border: Border( + top: BorderSide( + color: _hasError ? Colors.red : borderColor, + width: _hasError ? 1.w : 0.4.w, + ), + bottom: BorderSide( + color: _hasError ? Colors.red : borderColor, + width: _hasError ? 1.w : 0.4.w, + ), + left: BorderSide( + color: _hasError ? Colors.red : borderColor, + width: _hasError ? 1.w : 0.4.w, + ), + ), + ), + child: widget.prefixWidget, + ), + + // Text field — takes remaining width + Expanded( + child: TextFormField( + controller: widget.controller, + maxLines: widget.obscureText ? 1 : widget.maxLines, + enabled: widget.enabled, + obscureText: widget.obscureText, + validator: _formValidator, + // ✅ No autovalidateMode here — we handle it manually + // so we can show error text outside the row + autovalidateMode: AutovalidateMode.disabled, + keyboardType: widget.keyboardType, + inputFormatters: widget.inputFormatters, + onChanged: _onChanged, + decoration: InputDecoration( + hintText: widget.hint, + counterText: "", + // ✅ errorText: null always — we render error ourselves below + errorText: null, + hintStyle: TextStyle( + fontSize: 12.sp, + color: const Color(0xFF8E8E8E), + ), + filled: true, + fillColor: widget.fillColor, + contentPadding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 10.h, + ), + suffixIcon: widget.suffixIcon, + enabledBorder: OutlineInputBorder( + borderRadius: widget.borderRadius, + borderSide: BorderSide( + color: _hasError ? Colors.red : borderColor, + width: _hasError ? 1.w : 0.4.w, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: widget.borderRadius, + borderSide: BorderSide( + color: _hasError + ? Colors.red + : const Color(0xFFF95F62), + width: 1.w, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: widget.borderRadius, + borderSide: + BorderSide(color: Colors.red, width: 1.w), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: widget.borderRadius, + borderSide: + BorderSide(color: Colors.red, width: 1.5.w), + ), + ), + ), + ), + ], + ), + ), + + // ✅ Error text rendered OUTSIDE the IntrinsicHeight row + // This is why the prefix box never grows when error appears + if (_hasError) ...[ + SizedBox(height: 4.h), + Padding( + padding: EdgeInsets.only(left: 4.w), + child: Text( + _errorText!, + style: TextStyle( + fontSize: 11.sp, + color: Colors.red, + height: 1.3, + ), + ), + ), + ], + ], + ); + } } \ 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 903f261..751ae17 100644 --- a/lib/create_account/view/create_account_view.dart +++ b/lib/create_account/view/create_account_view.dart @@ -328,7 +328,7 @@ class _CreateAccountViewState extends State { label: "City *", hint: "Enter your city", maxLength: 50, - noSpace: true, + // noSpace: true, controller: cityController, isFirstLetterCapital: true, ), @@ -341,7 +341,7 @@ class _CreateAccountViewState extends State { label: "State *", hint: "Enter your state", maxLength: 50, - noSpace: true, + // noSpace: true, controller: stateController, isFirstLetterCapital: true, ), @@ -354,7 +354,7 @@ class _CreateAccountViewState extends State { label: "Country *", hint: "Enter your country", maxLength: 50, - noSpace: true, + // noSpace: true, controller: countryController, isFirstLetterCapital: true, ), diff --git a/lib/my_pass/models/my_passes_details_model.dart b/lib/my_pass/models/my_passes_details_model.dart index c2f45fa..3197531 100644 --- a/lib/my_pass/models/my_passes_details_model.dart +++ b/lib/my_pass/models/my_passes_details_model.dart @@ -90,6 +90,7 @@ class Attraction { final num? ticketPriceChild; final String? bookingEmail; final String? bookingPhoneNumber; + final bool isBookingRequired; // ✅ added final String image; Attraction({ @@ -100,6 +101,7 @@ class Attraction { this.ticketPriceChild, this.bookingEmail, this.bookingPhoneNumber, + required this.isBookingRequired, required this.image, }); @@ -112,6 +114,7 @@ class Attraction { ticketPriceChild: json?['ticketPriceChild'], bookingEmail: json?['bookingEmail'], bookingPhoneNumber: json?['bookingPhoneNumber'], + isBookingRequired: json?['isBookingRequired'] ?? false, // ✅ safe image: json?['image'] ?? '', ); } @@ -125,6 +128,7 @@ class Attraction { 'ticketPriceChild': ticketPriceChild, 'bookingEmail': bookingEmail, 'bookingPhoneNumber': bookingPhoneNumber, + 'isBookingRequired': isBookingRequired, 'image': image, }; } diff --git a/lib/my_pass/views/pass_details_page_view.dart b/lib/my_pass/views/pass_details_page_view.dart index 614bb6f..eb2831f 100644 --- a/lib/my_pass/views/pass_details_page_view.dart +++ b/lib/my_pass/views/pass_details_page_view.dart @@ -241,9 +241,7 @@ class _PassDetailsViewState extends State { image: attraction.image, ticketPriceAdult: attraction.ticketPriceAdult, ticketPriceChild: attraction.ticketPriceChild, - bookingEmail: attraction.bookingEmail, - bookingPhoneNumber: - attraction.bookingPhoneNumber, + isBookingRequired: attraction.isBookingRequired, ), ), ), @@ -255,8 +253,7 @@ class _PassDetailsViewState extends State { image: '', ticketPriceAdult: null, ticketPriceChild: null, - bookingEmail: null, - bookingPhoneNumber: null, + isBookingRequired: false, ), ], SizedBox(height: 16.h), @@ -419,14 +416,8 @@ class _PassDetailsViewState extends State { required String image, num? ticketPriceAdult, num? ticketPriceChild, - String? bookingEmail, - String? bookingPhoneNumber, + required bool isBookingRequired, }) { - // Check if booking is required (both email and phone are empty/null) - final bool isBookingRequired = - (bookingEmail == null || bookingEmail.isEmpty) && - (bookingPhoneNumber == null || bookingPhoneNumber.isEmpty); - // Format the price display String priceText = ticketPriceAdult != null ? "\$$ticketPriceAdult/person" @@ -440,36 +431,34 @@ class _PassDetailsViewState extends State { ), child: Row( children: [ - /// 🔥 Attraction Image (Real Image Style Box) + /// 🔥 Attraction Image ClipRRect( borderRadius: BorderRadius.circular(12.r), child: image.isNotEmpty ? CachedNetworkImage( - imageUrl: image, - height: 100.w, - width: 90.w, - fit: BoxFit.cover, - - placeholder: (context, url) => Image.asset( - "assets/images/aa4.png", - height: 100.w, - width: 90.w, - fit: BoxFit.cover, - ), - - errorWidget: (context, url, error) => Image.asset( - "assets/images/aa4.png", - height: 100.w, - width: 90.w, - fit: BoxFit.cover, - ), - ) + imageUrl: image, + height: 100.w, + width: 90.w, + fit: BoxFit.cover, + placeholder: (context, url) => Image.asset( + "assets/images/aa4.png", + height: 100.w, + width: 90.w, + fit: BoxFit.cover, + ), + errorWidget: (context, url, error) => Image.asset( + "assets/images/aa4.png", + height: 100.w, + width: 90.w, + fit: BoxFit.cover, + ), + ) : Image.asset( - "assets/images/aa4.png", - height: 100.w, - width: 90.w, - fit: BoxFit.cover, - ), + "assets/images/aa4.png", + height: 100.w, + width: 90.w, + fit: BoxFit.cover, + ), ), SizedBox(width: 12.w), @@ -485,6 +474,8 @@ class _PassDetailsViewState extends State { fontWeight: FontWeight.w600, fontSize: 14.sp, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), SizedBox(height: 2.h), @@ -511,7 +502,7 @@ class _PassDetailsViewState extends State { SizedBox(height: 6.h), - // Show "Booking Required" tag only if both email and phone are null/empty + /// 🔥 Booking Required Tag if (isBookingRequired) Container( padding: EdgeInsets.symmetric( @@ -519,14 +510,16 @@ class _PassDetailsViewState extends State { vertical: 4.h, ), decoration: BoxDecoration( - color: Colors.blue.shade50, - borderRadius: BorderRadius.circular(8.r), + color: const Color(0xffC1D2F8), + border: Border.all(color: const Color(0xff2563EB)), + borderRadius: BorderRadius.circular(20.r), ), child: Text( "Booking Required", style: GoogleFonts.poppins( - fontSize: 10.sp, - color: Colors.blue.shade700, + fontSize: 11.sp, + color: const Color(0xff1A1A1A), + fontWeight: FontWeight.w400, ), ), ), @@ -536,12 +529,12 @@ class _PassDetailsViewState extends State { SizedBox(width: 8.w), - /// 🔥 QR Code Circle (Proper UI like Design) + /// 🔥 QR Code Circle Container( height: 44.w, width: 44.w, - decoration: BoxDecoration( - color: const Color(0xffF8EDED), // light pink circle bg + decoration: const BoxDecoration( + color: Color(0xffF8EDED), shape: BoxShape.circle, ), child: Padding( diff --git a/lib/my_pass/widgets/pass_attraction_card.dart b/lib/my_pass/widgets/pass_attraction_card.dart index 99fc8fc..1ccbe24 100644 --- a/lib/my_pass/widgets/pass_attraction_card.dart +++ b/lib/my_pass/widgets/pass_attraction_card.dart @@ -23,10 +23,11 @@ class PassAttractionCard extends StatelessWidget { final String imageUrl = attraction.coverImageUrl; /// Show "Booking Required" when both email and phone are empty/null - final bool showBookingRequired = - (attraction.bookingEmail.isEmpty || attraction.bookingEmail == null) || - (attraction.bookingPhoneNumber.isEmpty || - attraction.bookingPhoneNumber == null); + // final bool showBookingRequired = + // (attraction.bookingEmail.isEmpty || attraction.bookingEmail == null) || + // (attraction.bookingPhoneNumber.isEmpty || + // attraction.bookingPhoneNumber == null); + final bool showBookingRequired = attraction.isBookingRequired; /// Format the price display String priceText = attraction.ticketPriceAdult != null diff --git a/lib/profile/view/edit_profile/edit_profile_view.dart b/lib/profile/view/edit_profile/edit_profile_view.dart index 9c6de37..2f175dd 100644 --- a/lib/profile/view/edit_profile/edit_profile_view.dart +++ b/lib/profile/view/edit_profile/edit_profile_view.dart @@ -692,7 +692,7 @@ class _EditProfilePageState extends State { controller: cityController, enabled: !isLoading, maxLength: 50, - onlyLetters: true, + // onlyLetters: true, isPreview: true, ), ), diff --git a/lib/your_itinerary/view/your_itinerary_view.dart b/lib/your_itinerary/view/your_itinerary_view.dart index 6f90933..f8508f1 100644 --- a/lib/your_itinerary/view/your_itinerary_view.dart +++ b/lib/your_itinerary/view/your_itinerary_view.dart @@ -68,6 +68,9 @@ class _YourItineraryViewState extends State { final file = File('${dir.path}/itinerary_${widget.itineraryId}.pdf'); await file.writeAsBytes(state.pdfBytes); await OpenFilex.open(file.path); + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('PDF downloaded successfully!')), + // ); } else if (state is DownloadItineraryPdfFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.errorMessage)),