bug fixes done

This commit is contained in:
2026-03-20 18:08:52 +05:30
parent 177f891a31
commit d1038e846e
9 changed files with 383 additions and 232 deletions

View File

@@ -47,7 +47,6 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
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<MyPassesCartPage> {
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<MyPassesCartPage> {
bookingId: cartItem.id,
couponId: cartItem.couponXid,
),
settings: RouteSettings(
arguments: checkoutData,
),
settings: RouteSettings(arguments: checkoutData),
),
);
},
@@ -135,7 +132,6 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
),
);
}
// ========== HANDLE LOCAL DATA (NOT LOGGED IN) ==========
else if (state is MyPassCartLoaded) {
final cartData = state.cartData;
@@ -146,8 +142,7 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
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<MyPassesCartPage> {
),
);
}
// ========== EMPTY STATE ==========
else if (state is MyPassCartEmpty) {
return Padding(
@@ -221,7 +215,7 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
SizedBox(height: 40.h),
CustomFilledButton(
onTap: () {
Navigator.pop(context);
},
label: "Buy a Pass",
),
@@ -230,7 +224,6 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
),
);
}
// ========== 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 {
),
);
}
}
}

View File

@@ -425,7 +425,7 @@ class _EmptyCartScreen extends StatelessWidget {
SizedBox(height: 40.h),
CustomFilledButton(
onTap: () {
Navigator.pop(context);
},
label: "Design my postcard",
),

View File

@@ -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<TextInputFormatter> 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,
),
),
),
],
],
);
}
}

View File

@@ -328,7 +328,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
label: "City *",
hint: "Enter your city",
maxLength: 50,
noSpace: true,
// noSpace: true,
controller: cityController,
isFirstLetterCapital: true,
),
@@ -341,7 +341,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
label: "State *",
hint: "Enter your state",
maxLength: 50,
noSpace: true,
// noSpace: true,
controller: stateController,
isFirstLetterCapital: true,
),
@@ -354,7 +354,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
label: "Country *",
hint: "Enter your country",
maxLength: 50,
noSpace: true,
// noSpace: true,
controller: countryController,
isFirstLetterCapital: true,
),

View File

@@ -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,
};
}

View File

@@ -241,9 +241,7 @@ class _PassDetailsViewState extends State<PassDetailsView> {
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<PassDetailsView> {
image: '',
ticketPriceAdult: null,
ticketPriceChild: null,
bookingEmail: null,
bookingPhoneNumber: null,
isBookingRequired: false,
),
],
SizedBox(height: 16.h),
@@ -419,14 +416,8 @@ class _PassDetailsViewState extends State<PassDetailsView> {
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<PassDetailsView> {
),
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<PassDetailsView> {
fontWeight: FontWeight.w600,
fontSize: 14.sp,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 2.h),
@@ -511,7 +502,7 @@ class _PassDetailsViewState extends State<PassDetailsView> {
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<PassDetailsView> {
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<PassDetailsView> {
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(

View File

@@ -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

View File

@@ -692,7 +692,7 @@ class _EditProfilePageState extends State<EditProfilePage> {
controller: cityController,
enabled: !isLoading,
maxLength: 50,
onlyLetters: true,
// onlyLetters: true,
isPreview: true,
),
),

View File

@@ -68,6 +68,9 @@ class _YourItineraryViewState extends State<YourItineraryView> {
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)),