Offers And Discounts Complete

offers_discounts
Aamir Muhammad 1 month ago
parent ea124325f0
commit 024fa602e6

@ -465,6 +465,9 @@ class OffersAndDiscountsViewModel extends ChangeNotifier {
notifyListeners();
}
/// Check if search has text (for showing/hiding clear button)
bool get hasSearchText => searchQuery.isNotEmpty;
/// Get unique categories from the offers list
List<Category> getUniqueCategories() {
if (offersList.isEmpty) return [];

@ -146,6 +146,9 @@ class _LandingPageState extends State<LandingPage> {
await authVM.handleNewImeiRegistration(isNavigate: false);
}
// Load offers and discounts for all users (authenticated or not)
offersAndDiscountsViewModel.initOffersAndDiscounts();
if (appState.isAuthenticated) {
Clarity.setCustomUserId(appState.getAuthenticatedUser()!.patientId.toString());
@ -161,7 +164,6 @@ class _LandingPageState extends State<LandingPage> {
// myAppointmentsViewModel.getPatientAppointmentQueueDetails();
notificationsViewModel.initNotificationsViewModel();
insuranceViewModel.initInsuranceProvider();
offersAndDiscountsViewModel.initOffersAndDiscounts();
if (!appState.isRatedVisible) {
appointmentRatingViewModel.getLastRatingAppointment(
@ -212,13 +214,17 @@ class _LandingPageState extends State<LandingPage> {
RefreshIndicator(
color: AppColors.primaryRedColor,
onRefresh: () async {
// Refresh Offers and Discounts for all users
offersAndDiscountsViewModel.setIsoffersListLoading(true);
offersAndDiscountsViewModel.initOffersAndDiscounts(forceRefresh: true);
if (appState.isAuthenticated) {
// Refresh Appointments Data
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
// Refresh Appointments Data
// Refresh Wallet Data
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
@ -230,8 +236,6 @@ class _LandingPageState extends State<LandingPage> {
// Refresh Immediate LiveCare Data
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
offersAndDiscountsViewModel.setIsoffersListLoading(true);
offersAndDiscountsViewModel.initOffersAndDiscounts(forceRefresh: true);
// appointmentRatingViewModel.getLastRatingAppointment(
// onSuccess: (response) {
@ -406,54 +410,7 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.h, 0.h),
!appState.isAuthenticated
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
width: 50.w,
height: 60.h,
icon: AppAssets.symptomCheckerIcon,
fit: BoxFit.contain,
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true),
LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true),
SizedBox(height: 14.h),
CustomButton(
text: LocaleKeys.checkYourSymptoms.tr(context: context),
onPressed: () async {
context.navigateWithName(AppRoutes.userInfoSelection);
},
padding: EdgeInsetsGeometry.zero,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
height: 40.h,
),
],
).expanded
],
),
),
).paddingSymmetrical(24.w, 0.h)
: SizedBox.shrink(),
// Offers And Discounts Carousel - Auto-scrolling from right to left
//appState.isAuthenticated &&
appState.isEnabledOffersAndDiscountsCarousel
? Consumer<OffersAndDiscountsViewModel>(
builder: (context, offersVM, child) {
@ -506,45 +463,6 @@ class _LandingPageState extends State<LandingPage> {
separatorBuilder: (context, index) => SizedBox(width: 12.w),
),
).paddingSymmetrical(24.h, 0.h);
/////////
////////////
// return Column(
// children: [
// SizedBox(height: 12.h),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true),
// Row(
// children: [
// LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true),
// SizedBox(width: 2.h),
// Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
// ],
// ),
// ],
// ).paddingSymmetrical(24.h, 0.h).onPress(() {
// Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage()));
// }),
// SizedBox(height: 16.h),
// Container(
// height: 150.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 16.r,
// hasShadow: false,
// ),
// child: Center(
// child: CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryRedColor),
// ),
// ),
// ).paddingSymmetrical(24.h, 0.h),
// SizedBox(height: 18.h),
// ],
// );
}
// Show offers if available
@ -568,8 +486,7 @@ class _LandingPageState extends State<LandingPage> {
Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage()));
}),
SizedBox(height: 16.h),
OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h),
SizedBox(height: 18.h),
OffersAndDiscountsCarousel().paddingOnly(left: 24.w),
],
);
}
@ -580,10 +497,58 @@ class _LandingPageState extends State<LandingPage> {
)
: SizedBox.shrink(),
!appState.isAuthenticated
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
width: 50.w,
height: 60.h,
icon: AppAssets.symptomCheckerIcon,
fit: BoxFit.contain,
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true),
LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true),
SizedBox(height: 14.h),
CustomButton(
text: LocaleKeys.checkYourSymptoms.tr(context: context),
onPressed: () async {
context.navigateWithName(AppRoutes.userInfoSelection);
},
padding: EdgeInsetsGeometry.zero,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
height: 40.h,
),
],
).expanded
],
),
),
).paddingSymmetrical(24.w, 0.h)
: SizedBox.shrink(),
// Offers And Discounts Carousel - Auto-scrolling from right to left
//appState.isAuthenticated &&
appState.isAuthenticated
? Column(
children: [
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

@ -12,9 +12,11 @@ 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';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discount_payment_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -117,12 +119,20 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
viewModel = Provider.of<OffersAndDiscountsViewModel>(context, listen: false);
return CollapsingListView(
title: "${LocaleKeys.offerAndPackageDetails.tr(context: context)}",
title: LocaleKeys.offerAndPackageDetails.tr(context: context),
bottomChild: CustomButton(
text: LocaleKeys.buyNow.tr(context: context),
icon: AppAssets.shoppingCart,
iconColor: AppColors.whiteColor,
onPressed: () async {
if (!appState.isAuthenticated) {
getIt<DialogService>().showErrorBottomSheet(
message: LocaleKeys.loginToUseService.tr(),
onOkPressed: () {
Navigator.of(context).pop();
getIt<AuthenticationViewModel>().onLoginPressed();
});
} else {
// Ensure patient is authenticated (will re-authenticate if needed)
final isAuthenticated = await viewModel.ensureAuthenticated(
onSuccess: () {
@ -148,6 +158,7 @@ class _OffersAndDiscountsDetailedPageState extends State<OffersAndDiscountsDetai
page: OffersAndDiscountPaymentPage(),
),
);
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
@ -287,8 +298,12 @@ 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),
if (!appState.isAuthenticated) ...[
Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon)
] else ...[
Utils.getPaymentAmountWithSymbol(((offer.listPrice).toStringAsFixed(2)).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: offer.currency == "SAR")
]
],
),

@ -12,12 +12,14 @@ 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';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discount_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_history_page.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/widgets/offers_and_discount_type_selection_widget.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -41,11 +43,13 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
late OffersAndDiscountsViewModel offersAndDiscountsViewModel;
late AppState appState;
final TextEditingController _searchController = TextEditingController();
FocusNode textFocusNode = FocusNode();
@override
void initState() {
super.initState();
appState = getIt.get<AppState>();
scheduleMicrotask(() async {
offersAndDiscountsViewModel = Provider.of<OffersAndDiscountsViewModel>(context, listen: false);
@ -63,7 +67,7 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
onError: (error) {
// Hide loader on error
LoaderBottomSheet.hideLoader();
print("POMS Authentication failed: $error");
print("POMS POMS Authentication failed: $error");
// Authentication failed, but user can still browse offers
},
);
@ -166,9 +170,18 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
return CollapsingListView(
title: LocaleKeys.offerAndPackages.tr(context: context),
history: () {
if (!appState.isAuthenticated) {
getIt<DialogService>().showErrorBottomSheet(
message: LocaleKeys.loginToUseService.tr(),
onOkPressed: () {
Navigator.of(context).pop();
getIt<AuthenticationViewModel>().onLoginPressed();
});
} else {
Navigator.of(context).push(
CustomPageRoute(page: OffersAndDiscountsHistoryPage()),
);
}
},
child: SingleChildScrollView(
child: Consumer<OffersAndDiscountsViewModel>(builder: (context, offersAndDiscountVM, child) {
@ -180,18 +193,29 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
TextInputWidget(
labelText: LocaleKeys.search.tr(context: context),
hintText: LocaleKeys.typeAnyOffer.tr(),
hintColor: AppColors.textColor,
controller: _searchController,
onChange: (value) {
offersAndDiscountVM.setSearchQuery(value!);
},
leadingIcon: AppAssets.search_icon,
isEnable: true,
prefix: null,
isAllowLeadingIcon: true,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
isAllowLeadingIcon: true,
selectionType: SelectionTypeEnum.search,
focusNode: textFocusNode,
suffix: _searchController.text.isNotEmpty
? GestureDetector(
onTap: () {
_searchController.clear();
// bookAppointmentsViewModel.filterClinics("");
offersAndDiscountVM.setSearchQuery("");
textFocusNode.unfocus();
},
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
)
: null,
onChange: (value) {
offersAndDiscountVM.setSearchQuery(value!);
},
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
@ -342,6 +366,14 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
icon: AppAssets.shoppingCart,
iconColor: AppColors.whiteColor,
onPressed: () async {
if (!appState.isAuthenticated) {
getIt<DialogService>().showErrorBottomSheet(
message: LocaleKeys.loginToUseService.tr(),
onOkPressed: () {
Navigator.of(context).pop();
getIt<AuthenticationViewModel>().onLoginPressed();
});
} else {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context));
// Ensure patient is authenticated (will re-authenticate if needed)
@ -385,6 +417,7 @@ class _OffersAndDiscountsPageState extends State<OffersAndDiscountsPage> {
LoaderBottomSheet.hideLoader();
},
);
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -48,6 +48,8 @@ class TextInputWidget extends StatelessWidget {
final Color? labelColor;
final Function(String)? onSubmitted;
final String? btnTitle;
final bool showSearchClear;
final VoidCallback? onClear;
// new multiline options
final bool isMultiline;
@ -102,6 +104,8 @@ class TextInputWidget extends StatelessWidget {
// this.countryList = const [],
// this.onCountryChange,
this.btnTitle,
this.showSearchClear = false,
this.onClear,
});
final FocusNode _focusNode = FocusNode();
@ -253,7 +257,10 @@ class TextInputWidget extends StatelessWidget {
context,
child: DateRangeSelector(
onRangeSelected: (DateTime? start, DateTime? end) {
var picked = start.toString().split(" ").first;
var picked = start
.toString()
.split(" ")
.first;
if (picked != null && onChange != null) {
onChange!(picked.toString());
}
@ -521,6 +528,23 @@ class TextInputWidget extends StatelessWidget {
}
_buildTrailingIconForSearch(BuildContext context) {
if (showSearchClear) {
return GestureDetector(
onTap: () {
if (onClear != null) {
onClear!();
}
},
child: Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.zero,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.whiteColor),
child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle),
),
);
} else {
return Container(
height: 40.h,
width: 40.h,
@ -530,14 +554,13 @@ class TextInputWidget extends StatelessWidget {
child: Utils.buildSvgWithAssets(icon: AppAssets.search_icon),
);
}
}
}
class _ThousandSeparatorInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
TextEditingValue formatEditUpdate(TextEditingValue oldValue,
TextEditingValue newValue,) {
// Remove all commas to get the raw number
String newText = newValue.text.replaceAll(',', '');
@ -570,7 +593,11 @@ class _ThousandSeparatorInputFormatter extends TextInputFormatter {
count = 0;
}
}
integerPart = buffer.toString().split('').reversed.join();
integerPart = buffer
.toString()
.split('')
.reversed
.join();
}
final formatted = '$integerPart$decimalPart';
@ -578,9 +605,13 @@ class _ThousandSeparatorInputFormatter extends TextInputFormatter {
// Calculate new cursor position
int cursorOffset = newValue.selection.baseOffset;
// Count commas before cursor in the new formatted string
int commasInNew = ','.allMatches(formatted.substring(0, cursorOffset.clamp(0, formatted.length))).length;
int commasInNew = ','
.allMatches(formatted.substring(0, cursorOffset.clamp(0, formatted.length)))
.length;
// Count commas before cursor in the old value
int commasInOld = ','.allMatches(oldValue.text.substring(0, oldValue.selection.baseOffset.clamp(0, oldValue.text.length))).length;
int commasInOld = ','
.allMatches(oldValue.text.substring(0, oldValue.selection.baseOffset.clamp(0, oldValue.text.length)))
.length;
int newCursorPos = cursorOffset + (commasInNew - commasInOld);
newCursorPos = newCursorPos.clamp(0, formatted.length);

Loading…
Cancel
Save