|
|
|
|
@ -7,6 +7,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_state.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
|
|
|
|
@ -37,6 +38,7 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
late OffersAndDiscountsViewModel viewModel;
|
|
|
|
|
late AppState appState;
|
|
|
|
|
bool hasLoadingError = false;
|
|
|
|
|
bool isImageLoaded = false;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
@ -45,6 +47,10 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
viewModel = Provider.of<OffersAndDiscountsViewModel>(context, listen: false);
|
|
|
|
|
// Clear previous offer details to prevent showing old data
|
|
|
|
|
viewModel.offerDetails = null;
|
|
|
|
|
// Reset image loaded state
|
|
|
|
|
setState(() {
|
|
|
|
|
isImageLoaded = false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
|
|
|
|
|
viewModel.getOfferDetails(
|
|
|
|
|
@ -73,14 +79,6 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _formatDateString(DateTime date) {
|
|
|
|
|
try {
|
|
|
|
|
return DateFormat('d MMM, yyyy').format(date);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine the status of the offer based on dates
|
|
|
|
|
String _getOfferStatus(DateTime validTo) {
|
|
|
|
|
DateTime now = DateTime.now();
|
|
|
|
|
@ -119,7 +117,7 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
viewModel = Provider.of<OffersAndDiscountsViewModel>(context, listen: false);
|
|
|
|
|
|
|
|
|
|
return CollapsingListView(
|
|
|
|
|
title: "${LocaleKeys.offers.tr(context: context)} ${LocaleKeys.details.tr(context: context)}",
|
|
|
|
|
title: "${LocaleKeys.offerAndPackageDetails.tr(context: context)}",
|
|
|
|
|
bottomChild: CustomButton(
|
|
|
|
|
text: LocaleKeys.buyNow.tr(context: context),
|
|
|
|
|
icon: AppAssets.shoppingCart,
|
|
|
|
|
@ -179,9 +177,37 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only show offer if it matches the requested offerId (prevents showing old cached data)
|
|
|
|
|
// Show skeleton loader while offer data is being loaded
|
|
|
|
|
if (offer == null || offer.id != widget.offerId) {
|
|
|
|
|
return SizedBox.shrink(); // Return empty widget while loading
|
|
|
|
|
return SingleChildScrollView(
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
// Skeleton loader for image
|
|
|
|
|
_buildImageLoadingShimmer(),
|
|
|
|
|
SizedBox(height: 24.h),
|
|
|
|
|
// Skeleton loader for title
|
|
|
|
|
_buildTextShimmer(width: 250.w, height: 24.h),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
// Skeleton loader for price
|
|
|
|
|
_buildTextShimmer(width: 150.w, height: 18.h),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
// Skeleton loaders for chips
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
_buildTextShimmer(width: 120.w, height: 32.h),
|
|
|
|
|
SizedBox(width: 12.w),
|
|
|
|
|
_buildTextShimmer(width: 80.w, height: 32.h),
|
|
|
|
|
SizedBox(width: 12.w),
|
|
|
|
|
_buildTextShimmer(width: 100.w, height: 32.h),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 24.h),
|
|
|
|
|
// Skeleton loader for description
|
|
|
|
|
_buildTextShimmer(width: double.infinity, height: 100.h),
|
|
|
|
|
],
|
|
|
|
|
).paddingSymmetrical(24.h, 0.h),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final status = _getOfferStatus(offer.validTo);
|
|
|
|
|
|
|
|
|
|
@ -199,7 +225,10 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
height: 250.h,
|
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
|
|
|
if (loadingProgress == null) return child;
|
|
|
|
|
if (loadingProgress == null) {
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
// Show shimmer while image is loading
|
|
|
|
|
return _buildImageLoadingShimmer();
|
|
|
|
|
},
|
|
|
|
|
errorBuilder: (context, error, stackTrace) {
|
|
|
|
|
@ -225,7 +254,10 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
height: 250.h,
|
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
|
|
|
if (loadingProgress == null) return child;
|
|
|
|
|
if (loadingProgress == null) {
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
// Show shimmer while image is loading
|
|
|
|
|
return _buildImageLoadingShimmer();
|
|
|
|
|
},
|
|
|
|
|
errorBuilder: (context, error, stackTrace) {
|
|
|
|
|
@ -255,10 +287,7 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
textDirection: appState.isArabic() ? ui.TextDirection.rtl : ui.TextDirection.ltr,
|
|
|
|
|
children: [
|
|
|
|
|
"${LocaleKeys.price.tr(context: context)}: ".toText18(isBold: true, weight: FontWeight.w700),
|
|
|
|
|
Utils.getPaymentAmountWithSymbol(
|
|
|
|
|
((offer.listPrice).toStringAsFixed(2)).toText14(isBold: true, color: AppColors.greyTextColor),
|
|
|
|
|
AppColors.greyTextColor,
|
|
|
|
|
13,
|
|
|
|
|
Utils.getPaymentAmountWithSymbol(((offer.listPrice).toStringAsFixed(2)).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
|
|
|
|
|
isSaudiCurrency: offer.currency == "SAR")
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
@ -273,7 +302,22 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
children: [
|
|
|
|
|
// Valid till chip
|
|
|
|
|
AppCustomChipWidget(
|
|
|
|
|
labelText: "${LocaleKeys.validTill.tr(context: context)} ${_formatDateString(offer.validTo)}",
|
|
|
|
|
// labelText: "${LocaleKeys.validTill.tr(context: context)} ${DateUtil.formatDateToDate(offer.validTo, appState.getLanguageCode() == "ar" ? true : false)}",
|
|
|
|
|
richText: Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
"${LocaleKeys.validTill.tr(context: context)}: ".toText10(color: AppColors.textColor, isBold: true, weight: FontWeight.w700),
|
|
|
|
|
Directionality(
|
|
|
|
|
textDirection: ui.TextDirection.ltr,
|
|
|
|
|
child: viewModel.formatExpiryDate(offer.validTo).toText10(
|
|
|
|
|
isEnglishOnly: true,
|
|
|
|
|
isBold: true,
|
|
|
|
|
weight: FontWeight.w700,
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
)),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
backgroundColor: AppColors.chipBgColor,
|
|
|
|
|
textColor: AppColors.textColor,
|
|
|
|
|
icon: AppAssets.calendar,
|
|
|
|
|
@ -319,29 +363,12 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 24.h),
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// Description Heading
|
|
|
|
|
LocaleKeys.description.tr(context: context).toText18(isBold: true, weight: FontWeight.w700),
|
|
|
|
|
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
|
|
|
|
|
// Description Content
|
|
|
|
|
HtmlWidget(
|
|
|
|
|
(appState.getLanguageID() == 1 ? offer.descriptionAr : offer.description),
|
|
|
|
|
),
|
|
|
|
|
//.toText16(
|
|
|
|
|
// color: AppColors.greyTextColor,
|
|
|
|
|
// height: 1.5,
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
HtmlWidget((appState.getLanguageID() == 1 ? offer.descriptionAr : offer.description)),
|
|
|
|
|
SizedBox(height: 24.h),
|
|
|
|
|
|
|
|
|
|
// Offer Lines Section
|
|
|
|
|
if (offer.offerLines.isNotEmpty) ...[
|
|
|
|
|
LocaleKeys.includedServices.tr(context: context).toText18(
|
|
|
|
|
isBold: true,
|
|
|
|
|
weight: FontWeight.w700,
|
|
|
|
|
),
|
|
|
|
|
LocaleKeys.includedServices.tr(context: context).toText18(isBold: true, weight: FontWeight.w700),
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
...offer.offerLines.map((line) {
|
|
|
|
|
return Container(
|
|
|
|
|
@ -355,23 +382,14 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
(appState.getLanguageID() == 1 ? line.nameAr : line.name).toText16(
|
|
|
|
|
isBold: true,
|
|
|
|
|
weight: FontWeight.w600,
|
|
|
|
|
),
|
|
|
|
|
(appState.getLanguageID() == 1 ? line.nameAr : line.name).toText16(isBold: true, weight: FontWeight.w600),
|
|
|
|
|
SizedBox(height: 4.h),
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
textDirection: appState.isArabic() ? ui.TextDirection.rtl : ui.TextDirection.ltr,
|
|
|
|
|
children: [
|
|
|
|
|
"${LocaleKeys.quantity.tr(context: context)}: ${line.quantity}".toText14(
|
|
|
|
|
color: AppColors.greyTextColor,
|
|
|
|
|
),
|
|
|
|
|
if (line.discountValue > 0)
|
|
|
|
|
"${line.discountValue}% ${LocaleKeys.off.tr(context: context)}".toText14(
|
|
|
|
|
color: AppColors.primaryRedColor,
|
|
|
|
|
isBold: true,
|
|
|
|
|
),
|
|
|
|
|
"${LocaleKeys.quantity.tr(context: context)}: ${line.quantity}".toText14(color: AppColors.greyTextColor),
|
|
|
|
|
if (line.discountValue > 0) "${line.discountValue}% ${LocaleKeys.off.tr(context: context)}".toText14(color: AppColors.primaryRedColor, isBold: true),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
@ -404,4 +422,20 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper method to build shimmer loading placeholder for text elements
|
|
|
|
|
Widget _buildTextShimmer({required double width, required double height}) {
|
|
|
|
|
return Shimmer.fromColors(
|
|
|
|
|
baseColor: AppColors.shimmerBaseColor,
|
|
|
|
|
highlightColor: AppColors.shimmerHighlightColor,
|
|
|
|
|
child: Container(
|
|
|
|
|
width: width,
|
|
|
|
|
height: height,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
|
borderRadius: BorderRadius.circular(8.h),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|