From 82dad582c4eacf446b95baaa9c6657c05765ba79 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 17 May 2026 15:09:31 +0300 Subject: [PATCH 01/15] Updates --- lib/core/app_state.dart | 2 +- lib/presentation/home/landing_page.dart | 2 +- lib/presentation/symptoms_checker/triage_screen.dart | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 5eb7a05d..fce61fc7 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -44,7 +44,7 @@ class AppState { AuthenticatedUser? _authenticatedRootUser; AuthenticatedUser? _authenticatedChildUser; - bool isPaytabsEnabled = false; + bool isPaytabsEnabled = true; int? _superUserID; bool isChildLoggedIn = false; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f31a04cc..869f56b2 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1179,7 +1179,7 @@ class _LandingPageState extends State { }, backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), textColor: AppColors.alertColor, - borderColor: AppColors.infoBannerBgColor, + borderColor: AppColors.warningColorYellow.withValues(alpha: 0.01), fontSize: 12.f, fontWeight: FontWeight.w600, borderRadius: 12.r, diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 79c47d09..f6faa807 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -78,7 +78,8 @@ class _TriagePageState extends State { } // Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered - if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) { + // if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) { + if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30 || viewModel.shouldStopTriage) { // Navigate to results/possible conditions screen context.navigateWithName(AppRoutes.possibleConditionsPage); return; From 502fc82c28584d1ff46716d41cec001be5d11de9 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 17 May 2026 16:51:28 +0300 Subject: [PATCH 02/15] updates --- .../profile_settings/widgets/profile_picture_widget.dart | 1 - lib/widgets/image_picker.dart | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/profile_settings/widgets/profile_picture_widget.dart b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart index 06cf3a8f..b5aa89ee 100644 --- a/lib/presentation/profile_settings/widgets/profile_picture_widget.dart +++ b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart @@ -1,4 +1,3 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index 1b6fd3ab..fd044b98 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -78,7 +78,7 @@ class ImageOptions { } }, onFilesTap: () async { - FilePickerResult? result = await FilePicker.pickFiles( + FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: [ 'jpg', @@ -94,7 +94,8 @@ class ImageOptions { 'zip', ], ); - List files = result!.paths.map((path) => File(path!)).toList(); + if (result == null) return; + List files = result.paths.map((path) => File(path!)).toList(); image(result.files.first.path.toString(), files.first); }, ), From ab86d53261df9f7dc40ed9a62c1444d22c2c31c0 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 18 May 2026 12:33:51 +0300 Subject: [PATCH 03/15] Updates --- android/app/src/main/AndroidManifest.xml | 5 +++++ assets/langs/ar-SA.json | 3 ++- assets/langs/en-US.json | 3 ++- lib/core/api_consts.dart | 2 +- lib/core/app_state.dart | 6 +++++- .../authentication_view_model.dart | 2 ++ lib/features/paytabs/paytabs_view_model.dart | 18 ++++++++++++++++-- lib/generated/locale_keys.g.dart | 1 + .../home/lakum_wallet_details.dart | 4 ++-- 9 files changed, 36 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0fb36b85..ba1f830e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -41,6 +41,11 @@ android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" tools:node="remove" /> + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index e0faf4e5..37530389 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1850,5 +1850,6 @@ "points": "نقاط", "transactions": "المعاملات", "pharmacy": "الصيدلية", - "visitsOrders": "الزيارات/الطلبات" + "visitsOrders": "الزيارات/الطلبات", + "earned": "حصل" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index e8409260..257b2211 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1840,7 +1840,8 @@ "points": "Points", "transactions": "Transactions", "pharmacy": "Pharmacy", - "visitsOrders": "Visits/Orders" + "visitsOrders": "Visits/Orders", + "earned": "Earned" } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 8dbc9ce3..bf4ebbed 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.preProd; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index d571fa87..f256321e 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -46,7 +46,11 @@ class AppState { AuthenticatedUser? _authenticatedRootUser; AuthenticatedUser? _authenticatedChildUser; - bool isPaytabsEnabled = true; + bool isPaytabsEnabled = false; + + void setIsPaytabsEnabled(bool value) { + isPaytabsEnabled = value; + } int? _superUserID; bool isChildLoggedIn = false; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index eaaa6848..262f7063 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1190,6 +1190,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + _appState.setIsPaytabsEnabled(Utils.havePrivilege(120)); // Refresh privileges after user switching to ensure correct privileges are loaded if (isSwitchUser) { @@ -1719,6 +1720,7 @@ class AuthenticationViewModel extends ChangeNotifier { projectDetailListModel.add(ProjectDetailListModel.fromJson(v)); }); _appState.setProjectsDetailList(projectDetailListModel); + _appState.setIsPaytabsEnabled(Utils.havePrivilege(120)); } }, ); diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index c922c752..f6cfb003 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -78,10 +78,15 @@ class PayTabsViewModel extends ChangeNotifier { notifyListeners(); } - startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { - FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) { + startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async { + bool _callbackFired = false; // prevent duplicate callback invocations + + await FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) { + if (_callbackFired) return; // ignore any subsequent callback calls + final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { + _callbackFired = true; // Cast Map to Map final data = Map.from(event["data"] as Map); paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); @@ -95,11 +100,13 @@ class PayTabsViewModel extends ChangeNotifier { onError!(errorMessage); } } else if (event["status"] == "error") { + _callbackFired = true; final errorMessage = event["message"] ?? "An error occurred"; debugPrint("Error occurred in transaction: $errorMessage"); debugPrint("Full error event: $event"); onError!(errorMessage); } else if (event["status"] == "event") { + // Intermediate SDK events – do NOT mark _callbackFired, just log final eventMessage = event["message"] ?? "Event occurred"; debugPrint("Event occurred: $eventMessage"); } @@ -107,9 +114,14 @@ class PayTabsViewModel extends ChangeNotifier { } startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { + bool _callbackFired = false; // prevent duplicate callback invocations + FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) { + if (_callbackFired) return; // ignore any subsequent callback calls + final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { + _callbackFired = true; // Cast Map to Map final data = Map.from(event["data"] as Map); paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); @@ -123,11 +135,13 @@ class PayTabsViewModel extends ChangeNotifier { onError!(errorMessage); } } else if (event["status"] == "error") { + _callbackFired = true; final errorMessage = event["message"] ?? "An error occurred"; debugPrint("Error occurred in transaction: $errorMessage"); debugPrint("Full error event: $event"); onError!(errorMessage); } else if (event["status"] == "event") { + // Intermediate SDK events – do NOT mark _callbackFired, just log final eventMessage = event["message"] ?? "Event occurred"; debugPrint("Event occurred: $eventMessage"); } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 34787232..e0dd8614 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1843,5 +1843,6 @@ abstract class LocaleKeys { static const transactions = 'transactions'; static const pharmacy = 'pharmacy'; static const visitsOrders = 'visitsOrders'; + static const earned = 'earned'; } diff --git a/lib/presentation/home/lakum_wallet_details.dart b/lib/presentation/home/lakum_wallet_details.dart index 79b71ec0..d2ef15f4 100644 --- a/lib/presentation/home/lakum_wallet_details.dart +++ b/lib/presentation/home/lakum_wallet_details.dart @@ -304,7 +304,7 @@ class LakumWalletDetails extends StatelessWidget { Widget _buildTransactionItem(PointsDetails transaction) { // Determine if points were gained or used - bool isGained = transaction.operationType?.toLowerCase() == 'gain' || transaction.operationType?.toLowerCase() == 'credit' || (transaction.points ?? 0) > 0; + bool isGained = transaction.subTransactionType == 1 || transaction.operationType?.toLowerCase() == 'cr' || (transaction.points ?? 0) > 0; Color transactionColor = isGained ? AppColors.habibPharmacyColor : AppColors.primaryRedColor; String pointsText = "${isGained ? '+' : '-'}${transaction.points?.abs() ?? 0}"; @@ -345,7 +345,7 @@ class LakumWalletDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (transaction.subTransactionTypeDescription ?? transaction.operationType ?? "Transaction").toText14( + (isGained ? LocaleKeys.earned.tr() : LocaleKeys.consumed.tr()).toText14( isBold: true, maxlines: 2, ), From 68248f0cf0a65076b2e70a4d64f1c7cf6c921108 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 18 May 2026 17:21:35 +0300 Subject: [PATCH 04/15] updates --- lib/presentation/habib_wallet/wallet_payment_confirm_page.dart | 2 +- lib/presentation/insurance/insurance_approval_details_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 1363025e..64d2828b 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -451,7 +451,7 @@ class _WalletPaymentConfirmPageState extends State { customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", - orderDescription: "Appointment Payment", + orderDescription: "Advance Payment", orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()), merchantReference: transID, merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index c80767eb..e3b684bf 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -152,7 +152,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { child: insuranceApprovalResponseModel.apporvalDetails != null ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.approvalDetails.toText16(isBold: true), + LocaleKeys.approvalDetails.tr().toText16(isBold: true), ListView.separated( padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, From 171a6a2d74aeb0e9093e8abc56f360e90896808b Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 19 May 2026 12:58:32 +0300 Subject: [PATCH 05/15] fixes --- .../book_appointments_view_model.dart | 4 ++- .../prescriptions_view_model.dart | 2 +- .../widgets/appointment_doctor_card.dart | 29 ++++++++++--------- lib/presentation/home/landing_page.dart | 18 ++++++++++++ .../widgets/update_email_widget.dart | 4 ++- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index e6cfa6c2..890ef76d 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -735,7 +735,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr()); + if (onError != null) { + onError.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr()); + } }, (apiResponse) async { if (apiResponse.messageStatus == 2) { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 475368f0..bfb83a2c 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -323,7 +323,7 @@ class PrescriptionsViewModel extends ChangeNotifier { isGmsAvailable: getIt.get().isGMSAvailable, ), direction: AxisDirection.down), - ); + ) ?? false; print("Location Selected: $result"); if (result) { LocationViewModel locationViewModel = getIt.get(); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index ac346d10..ecf164a4 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -98,21 +98,24 @@ class AppointmentDoctorCard extends StatelessWidget { children: [ Row( children: [ - patientAppointmentHistoryResponseModel.doctorNameObj!.toText16( - isBold: true, - isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), - textOverflow: TextOverflow.ellipsis, + Expanded( + child: patientAppointmentHistoryResponseModel.doctorNameObj!.toText16( + isBold: true, + isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), + textOverflow: TextOverflow.ellipsis, + maxlines: 2 + ), ), SizedBox(width: 12.w), - (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && - patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) - ? Image.network( - patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", - width: 20.h, - height: 15.h, - fit: BoxFit.cover, - ) - : SizedBox.shrink(), + // (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && + // patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) + // ? Image.network( + // patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + // width: 20.h, + // height: 15.h, + // fit: BoxFit.cover, + // ) + // : SizedBox.shrink(), ], ), SizedBox(height: 8.h), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 5c1badf3..5219cda6 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -213,6 +213,24 @@ class _LandingPageState extends State { // Refresh Immediate LiveCare Data immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); + + appointmentRatingViewModel.getLastRatingAppointment( + onSuccess: (response) { + if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + appointmentRatingViewModel.getAppointmentDetails( + appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, + appointmentRatingViewModel.appointmentRatedList.last.projectID!, + onSuccess: ((response) { + appointmentRatingViewModel.setClinicOrDoctor(false); + appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); + appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); + openLastRating(); + appState.setRatedVisible(true); + }), + ); + } + }, + ); } }, child: SingleChildScrollView( diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart index 4f13726a..55f848a6 100644 --- a/lib/presentation/profile_settings/widgets/update_email_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -1,11 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -130,7 +132,7 @@ class _UpdateEmailDialogState extends State { profileSettingsViewModel!.clearEmailError(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { - Navigator.of(context).pop(); + Navigator.of(getIt().navigatorKey.currentContext!).pop(); profileSettingsViewModel!.getProfileSettings(); }, isFullScreen: false, isAutoDismiss: true); }, From 50f78942ed01807a9ea8e3c138645ce21b034515 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 19 May 2026 19:29:06 +0300 Subject: [PATCH 06/15] updates --- AGENTS.md | 0 lib/presentation/ask_doctor/doctor_response_page.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..e69de29b diff --git a/lib/presentation/ask_doctor/doctor_response_page.dart b/lib/presentation/ask_doctor/doctor_response_page.dart index 1aed8cfb..922d2fdb 100644 --- a/lib/presentation/ask_doctor/doctor_response_page.dart +++ b/lib/presentation/ask_doctor/doctor_response_page.dart @@ -133,7 +133,7 @@ class _DoctorResponsePageState extends State { ).onPress(() { showCommonBottomSheetWithoutHeight( context, - title: askDoctorVM.doctorResponsesList[index].requestTypeDescription!, + title: askDoctorVM.doctorResponsesList[index].requestTypeDescription ?? "", child: DoctorResponseTransactions(doctorResponseModel: askDoctorVM.doctorResponsesList[index],), callBackFunc: () {}, isFullScreen: false, From 8ffd7485d6f0c64c195bd0737497be2fa1e10712 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 20 May 2026 11:53:26 +0300 Subject: [PATCH 07/15] updates --- lib/core/api_consts.dart | 2 +- .../models/paytabs_transaction_response_model.dart | 12 ++++++------ lib/features/paytabs/paytabs_view_model.dart | 10 +++++++--- .../appointments/appointment_payment_page.dart | 1 + .../livecare/immediate_livecare_payment_page.dart | 1 + .../waiting_appointment_payment_page.dart | 1 + .../er_online_checkin_payment_page.dart | 1 + .../habib_wallet/wallet_payment_confirm_page.dart | 1 + .../todo_section/ancillary_order_payment_page.dart | 1 + 9 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index bf4ebbed..fa64812f 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/paytabs/models/paytabs_transaction_response_model.dart b/lib/features/paytabs/models/paytabs_transaction_response_model.dart index 57d23349..85760132 100644 --- a/lib/features/paytabs/models/paytabs_transaction_response_model.dart +++ b/lib/features/paytabs/models/paytabs_transaction_response_model.dart @@ -1,5 +1,5 @@ class PaytabsTransactionResponseModel { - int? tranTotal; + dynamic tranTotal; String? transactionReference; String? cartCurrency; String? cartDescription; @@ -13,10 +13,10 @@ class PaytabsTransactionResponseModel { bool? isAuthorized; String? trace; dynamic cartAmount; - int? merchantId; - int? profileId; + dynamic merchantId; + dynamic profileId; bool? isProcessed; - int? serviceId; + dynamic serviceId; PaymentInfo? paymentInfo; bool? isSuccess; @@ -98,8 +98,8 @@ class PaytabsTransactionResponseModel { class PaymentInfo { String? cardScheme; String? cardType; - int? expiryMonth; - int? expiryYear; + dynamic expiryMonth; + dynamic expiryYear; String? paymentDescription; String? paymentMethod; diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index f6cfb003..0c610e6f 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -3,6 +3,7 @@ import 'package:flutter_paytabs_bridge/BaseBillingShippingInfo.dart'; import 'package:flutter_paytabs_bridge/IOSThemeConfiguration.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkLocale.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkTokenFormat.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkTokeniseType.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart'; import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart'; @@ -49,7 +50,8 @@ class PayTabsViewModel extends ChangeNotifier { appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), alternativePaymentMethods: [], linkBillingNameWithCardHolderName: true, - simplifyApplePayValidation: true); + simplifyApplePayValidation: true, + ); paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( // logoImage: "assets/images/png/hmg_logo.png", @@ -71,9 +73,11 @@ class PayTabsViewModel extends ChangeNotifier { placeholderColorDark: "2E3039", buttonColor: "ED1C2B", buttonColorDark: "ED1C2B", - // buttonFont: "Poppins", - ); + inputsCornerRadius: 12 + // buttonFont: "Poppins", + ); paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY; + paymentConfiguration.tokenFormat = PaymentSdkTokenFormat.Hex32Format; notifyListeners(); } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index d909b73a..bae7514c 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -795,6 +795,7 @@ class _AppointmentPaymentPageState extends State { //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { if (appState.isPaytabsEnabled) { + selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); paytabsViewModel.setPaymentConfiguration( "Appointment Payment", diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 57e17608..e2bb0c17 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -632,6 +632,7 @@ class _ImmediateLiveCarePaymentPageState extends State //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { if (appState.isPaytabsEnabled) { + selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); paytabsViewModel.setPaymentConfiguration( "ER Online Check-In Payment", diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 64d2828b..58840c66 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -389,6 +389,7 @@ class _WalletPaymentConfirmPageState extends State { //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { if(appState.isPaytabsEnabled) { + selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 24ed9fc5..e48adeeb 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -632,6 +632,7 @@ class _AncillaryOrderPaymentPageState extends State { } // Only proceed with Apple Pay if insert was successful if(appState.isPaytabsEnabled) { + selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); paytabsViewModel.setPaymentConfiguration( "Ancillary Orders Payment", From f08775cbd4f51545936ac329734478e100804006 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 20 May 2026 13:15:28 +0300 Subject: [PATCH 08/15] Updates & fixes --- android/app/proguard-rules.pro | 10 +++- ios/Podfile | 3 +- lib/core/utils/utils.dart | 10 ++++ .../authentication_view_model.dart | 7 ++- .../widgets/appointment_card.dart | 4 +- lib/presentation/authentication/register.dart | 2 +- .../book_appointment/widgets/clinic_card.dart | 1 + lib/presentation/home/landing_page.dart | 46 +++++++++++-------- .../widgets/preferred_language_widget.dart | 4 +- .../update_emergency_contact_widget.dart | 6 ++- .../ancillary_procedures_details_page.dart | 3 +- 11 files changed, 64 insertions(+), 32 deletions(-) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 94ad242d..b2ea0470 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -77,4 +77,12 @@ -keep class com.peng.pennavmap.db.** { *; } -keep class com.hiennv.flutter_callkit_incoming.** { *; } --keepattributes Signature, Annotation, InnerClasses, EnclosingMethod \ No newline at end of file +-keepattributes Signature, Annotation, InnerClasses, EnclosingMethod + +-keep class com.peng.pennavmap.** { *; } +-keep class com.peng.bus.** { *; } +# EventBus subscribers (reflection-based) +-keepclassmembers class * { + @com.peng.bus.PISubscribe ; +} +-keep class com.peng.pennavmap.models.bus.BackButtonEventBusData { *; } diff --git a/ios/Podfile b/ios/Podfile index 4c8bed13..a74c7fb2 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '14.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -54,6 +54,7 @@ post_install do |installer| 'PERMISSION_EVENTS_FULL_ACCESS=1', ## dart: PermissionGroup.reminders 'PERMISSION_REMINDERS=1', + 'PERMISSION_PHOTOS=1', ## dart: PermissionGroup.notification 'PERMISSION_NOTIFICATIONS=1', ] diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index ef83cb9f..6ab5865a 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -771,6 +771,7 @@ class Utils { double height = 24, BoxFit fit = BoxFit.cover, bool applyThemeColor = true, + String? errorAsset, }) { final Color? resolvedColor = iconColor ?? (applyThemeColor && AppColors.isDarkMode ? AppColors.textColor : null); return SvgPicture.asset( @@ -779,6 +780,15 @@ class Utils { width: width, height: height, fit: fit, + errorBuilder: errorAsset != null + ? (context, error, stackTrace) => SvgPicture.asset( + errorAsset, + colorFilter: resolvedColor != null ? ColorFilter.mode(isDisabled ? resolvedColor.withOpacity(0.5) : resolvedColor, BlendMode.srcIn) : null, + width: width, + height: height, + fit: fit, + ) + : null, ); } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 262f7063..9254f1a8 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1377,15 +1377,14 @@ class AuthenticationViewModel extends ChangeNotifier { checkLastLoginStatus(Function() onSuccess) async { Future.delayed(Duration(seconds: 1), () async { if (cacheService.getBool(key: CacheConst.quickLoginEnabled) == null) { - if (_appState.getSelectDeviceByImeiRespModelElement != null && - (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { + final deviceElement = _appState.getSelectDeviceByImeiRespModelElement; + if (deviceElement != null && (deviceElement.logInType == 1 || deviceElement.logInType == 4)) { phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; nationalIdController.text = _appState.getAuthenticatedUser()!.patientIdentificationNo!; onSuccess(); - } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && - _appState.getAuthenticatedUser() != null) { + } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && deviceElement == null) && _appState.getAuthenticatedUser() != null) { phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e17d52dd..03c12c3b 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -639,7 +639,9 @@ class _AppointmentCardState extends State { ), ); } else { - if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && + widget.patientAppointmentHistoryResponseModel.projectID != null && + widget.patientAppointmentHistoryResponseModel.clinicID != null) { widget.bookAppointmentsViewModel.getAppointmentNearestGate( projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID); } diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 61b5924e..57c8b3b5 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -51,7 +51,7 @@ class _RegisterNew extends State { // Clear errors when leaving register screen // Use post frame callback to avoid calling notifyListeners during dispose WidgetsBinding.instance.addPostFrameCallback((_) { - final authVm = context.read(); + final authVm = getIt.get(); authVm.clearNationalIdError(); authVm.clearDobError(); authVm.clearPhoneNumberError(); diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart index fb3e2c79..68b9b1e0 100644 --- a/lib/presentation/book_appointment/widgets/clinic_card.dart +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -49,6 +49,7 @@ class ClinicCard extends StatelessWidget { width: 24.w, height: 24.h, fit: BoxFit.contain, + errorAsset: "assets/images/clinicIcons/1.svg" // Note: Add error handling in the Utils.buildSvgWithAssets if possible // or use a try-catch wrapper ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 5219cda6..441dd770 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -59,6 +59,7 @@ import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -156,9 +157,13 @@ class _LandingPageState extends State { appointmentRatingViewModel.getLastRatingAppointment( onSuccess: (response) { if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + final lastAppointment = appointmentRatingViewModel.appointmentRatedList.last; + final appointmentNo = lastAppointment.appointmentNo; + final projectID = lastAppointment.projectID; + if (appointmentNo == null || projectID == null) return; appointmentRatingViewModel.getAppointmentDetails( - appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, - appointmentRatingViewModel.appointmentRatedList.last.projectID!, + appointmentNo, + projectID, onSuccess: ((response) { appointmentRatingViewModel.setClinicOrDoctor(false); appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); @@ -214,23 +219,24 @@ class _LandingPageState extends State { immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); - appointmentRatingViewModel.getLastRatingAppointment( - onSuccess: (response) { - if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - appointmentRatingViewModel.getAppointmentDetails( - appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, - appointmentRatingViewModel.appointmentRatedList.last.projectID!, - onSuccess: ((response) { - appointmentRatingViewModel.setClinicOrDoctor(false); - appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); - appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); - openLastRating(); - appState.setRatedVisible(true); - }), - ); - } - }, - ); + // appointmentRatingViewModel.getLastRatingAppointment( + // onSuccess: (response) { + // if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + // appointmentRatingViewModel.getAppointmentDetails( + // appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, + // appointmentRatingViewModel.appointmentRatedList.last.projectID!, + // onSuccess: ((response) { + // appointmentRatingViewModel.setClinicOrDoctor(false); + // appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); + // appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); + // openLastRating(); + // appState.setRatedVisible(true); + // }), + // ); + // } + // }, + // ); + } }, child: SingleChildScrollView( @@ -1394,7 +1400,7 @@ class _LandingPageState extends State { void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( - context, + getIt().navigatorKey.currentContext!, // title: "", isCloseButtonVisible: false, child: StatefulBuilder( diff --git a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart index 32dcc57b..d0f85090 100644 --- a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart +++ b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/int_extensions.dart'; @@ -7,6 +8,7 @@ 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/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -70,7 +72,7 @@ class _PreferredLanguageWidgetState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { - Navigator.of(context).pop(); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).pop(); profileSettingsViewModel.getProfileSettings(); }, isFullScreen: false, isAutoDismiss: true); }, diff --git a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart index e1faadca..f1a34565 100644 --- a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart @@ -130,9 +130,11 @@ class _UpdateEmergencyContactDialogState extends State().navigatorKey.currentContext; + if (navContext == null) return; + showCommonBottomSheetWithoutHeight(navContext, title: LocaleKeys.success.tr(context: navContext), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { - Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(navContext).pop(); profileSettingsViewModel!.getProfileSettings(); }, isFullScreen: false, isAutoDismiss: true); }, diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index 41428a6e..65e1bdaf 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -66,6 +66,7 @@ class _AncillaryOrderDetailsListState extends State { } void _autoSelectEligibleProcedures() { + if (!mounted) return; selectedProcedures.clear(); if (todoSectionViewModel.patientAncillaryOrderProceduresList.isNotEmpty) { final procedures = todoSectionViewModel.patientAncillaryOrderProceduresList[0].ancillaryOrderProcDetailsList; @@ -77,7 +78,7 @@ class _AncillaryOrderDetailsListState extends State { } } } - setState(() {}); + if (mounted) setState(() {}); } bool _isProcedureDisabled(AncillaryOrderProcDetail procedure) { From 98450b4f94a5a5bcf48dea22e96fc7b749611f44 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 21 May 2026 14:50:39 +0300 Subject: [PATCH 09/15] Appointment parking QR implemented --- .../images/svg/appointment_parking_icon.svg | 5 ++ assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 3 +- lib/core/api_consts.dart | 2 + lib/core/app_assets.dart | 1 + ...appointment_parking_QR_response_model.dart | 84 +++++++++++++++++++ ...nt_appointment_history_response_model.dart | 4 + .../my_appointments/my_appointments_repo.dart | 39 +++++++++ .../my_appointments_view_model.dart | 22 +++++ lib/features/paytabs/paytabs_view_model.dart | 4 +- lib/generated/locale_keys.g.dart | 1 + .../appointment_details_page.dart | 29 +++++++ .../widgets/appointment_doctor_card.dart | 2 +- 13 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 assets/images/svg/appointment_parking_icon.svg create mode 100644 lib/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart diff --git a/assets/images/svg/appointment_parking_icon.svg b/assets/images/svg/appointment_parking_icon.svg new file mode 100644 index 00000000..0a1c6daa --- /dev/null +++ b/assets/images/svg/appointment_parking_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 37530389..88d35279 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1851,5 +1851,6 @@ "transactions": "المعاملات", "pharmacy": "الصيدلية", "visitsOrders": "الزيارات/الطلبات", - "earned": "حصل" + "earned": "حصل", + "getParkingQR": "الحصول على رمز الاستجابة السريعة لوقوف السيارات" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 257b2211..fccd6841 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1841,7 +1841,8 @@ "transactions": "Transactions", "pharmacy": "Pharmacy", "visitsOrders": "Visits/Orders", - "earned": "Earned" + "earned": "Earned", + "getParkingQR": "Get Parking QR" } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fa64812f..7ad7f855 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -998,6 +998,8 @@ const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceRep const DOWNLOAD_PHARMACY_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport'; +const GET_APPOINTMENT_PARKING_QR = 'Services/outps.svc/rest/pms_getParkingTicketByAppointmentNo'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index d6e041da..e4d23c08 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -256,6 +256,7 @@ class AppAssets { static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; static const String bluetooth = '$svgBasePath/bluetooth.svg'; static const String calendar_filled_icon = '$svgBasePath/calendar_filled_icon.svg'; + static const String appointment_parking_icon = '$svgBasePath/appointment_parking_icon.svg'; //smartwatch static const String watchActivity = '$svgBasePath/watch_activity.svg'; diff --git a/lib/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart b/lib/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart new file mode 100644 index 00000000..a0a2c4d9 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart @@ -0,0 +1,84 @@ +class AppointmentParkingQRResponseModel { + String? admissionDate; + int? admissionNo; + String? appointmentDate; + int? appointmentNo; + int? createdBy; + String? createdOn; + String? gUID; + String? invoiceDate; + int? invoiceNo; + bool? isActive; + bool? isOutInPatient; + int? isPatient; + bool? isUsed; + int? patientID; + int? projectID; + dynamic ticketBase64; + int? ticketID; + String? ticketURL; + + AppointmentParkingQRResponseModel( + {this.admissionDate, + this.admissionNo, + this.appointmentDate, + this.appointmentNo, + this.createdBy, + this.createdOn, + this.gUID, + this.invoiceDate, + this.invoiceNo, + this.isActive, + this.isOutInPatient, + this.isPatient, + this.isUsed, + this.patientID, + this.projectID, + this.ticketBase64, + this.ticketID, + this.ticketURL}); + + AppointmentParkingQRResponseModel.fromJson(Map json) { + admissionDate = json['AdmissionDate']; + admissionNo = json['AdmissionNo']; + appointmentDate = json['AppointmentDate']; + appointmentNo = json['AppointmentNo']; + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + gUID = json['GUID']; + invoiceDate = json['InvoiceDate']; + invoiceNo = json['InvoiceNo']; + isActive = json['IsActive']; + isOutInPatient = json['IsOutInPatient']; + isPatient = json['IsPatient']; + isUsed = json['IsUsed']; + patientID = json['PatientID']; + projectID = json['ProjectID']; + ticketBase64 = json['TicketBase64'].cast(); + ticketID = json['TicketID']; + ticketURL = json['TicketURL']; + } + + Map toJson() { + final Map data = new Map(); + data['AdmissionDate'] = this.admissionDate; + data['AdmissionNo'] = this.admissionNo; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNo'] = this.appointmentNo; + data['CreatedBy'] = this.createdBy; + data['CreatedOn'] = this.createdOn; + data['GUID'] = this.gUID; + data['InvoiceDate'] = this.invoiceDate; + data['InvoiceNo'] = this.invoiceNo; + data['IsActive'] = this.isActive; + data['IsOutInPatient'] = this.isOutInPatient; + data['IsPatient'] = this.isPatient; + data['IsUsed'] = this.isUsed; + data['PatientID'] = this.patientID; + data['ProjectID'] = this.projectID; + data['TicketBase64'] = this.ticketBase64; + data['TicketID'] = this.ticketID; + data['TicketURL'] = this.ticketURL; + return data; + } +} diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index 420c0482..d9a71d9f 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -76,6 +76,7 @@ class PatientAppointmentHistoryResponseModel { num? patientTaxAmount; String? doctorNationalityFlagURL; bool? isClinicReBookingAllowed; + bool? isParkingAvailable; PatientAppointmentHistoryResponseModel({ this.setupID, @@ -154,6 +155,7 @@ class PatientAppointmentHistoryResponseModel { this.patientTaxAmount, this.doctorNationalityFlagURL, this.isClinicReBookingAllowed, + this.isParkingAvailable, }); PatientAppointmentHistoryResponseModel.fromJson(Map json) { @@ -244,6 +246,7 @@ class PatientAppointmentHistoryResponseModel { patientTaxAmount = json['PatientTaxAmount']; doctorNationalityFlagURL = json['DoctorNationalityFlagURL']; isClinicReBookingAllowed = json['IsClinicReBookingAllowed']; + isParkingAvailable = json['IsParkingAvailable']; } Map toJson() { @@ -317,6 +320,7 @@ class PatientAppointmentHistoryResponseModel { data['SMSButtonVisable'] = this.sMSButtonVisable; data['ServiceID'] = this.serviceID; data['IsClinicReBookingAllowed'] = this.isClinicReBookingAllowed; + data['IsParkingAvailable'] = this.isParkingAvailable; return data; } } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 553491e1..d7a025fc 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; @@ -92,6 +93,8 @@ abstract class MyAppointmentsRepo { }); Future>>> getAppointmentInvoice(num appointmentNum); + + Future>> getParkingQR(num appointmentNum, int projectID); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -1183,4 +1186,40 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getParkingQR(num appointmentNum, int projectID) async { + Map mapDevice = {"AppointmentNo": appointmentNum, "ProjectID": projectID}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_APPOINTMENT_PARKING_QR, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + AppointmentParkingQRResponseModel appointmentParkingQRResponseModel = AppointmentParkingQRResponseModel.fromJson(response['ParkingTicketsForAppointment'][0]); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentParkingQRResponseModel, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 0ad21586..5aae8211 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; @@ -100,6 +101,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { AppointmentRatedResponseModel? appointmentRatedResponseModel; bool isAppointmentRatedResponseLoading = false; + AppointmentParkingQRResponseModel? appointmentParkingQRResponseModel; + MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { @@ -722,6 +725,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } + Future getParkingQR(num appointmentNum, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getParkingQR(appointmentNum, projectID); + + result.fold( + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + appointmentParkingQRResponseModel = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + }, + ); + } + // Method to force refresh favorite doctors list void refreshFavouriteDoctors() { isFavouriteDoctorsDataFetched = false; diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 0c610e6f..3243269a 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -23,7 +23,7 @@ class PayTabsViewModel extends ChangeNotifier { required this.appState, }); - setPaymentConfiguration(String paymentDescription, num amount) { + setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { String cartID = DateTime.now().millisecondsSinceEpoch.toString(); paymentConfiguration = PaymentSdkConfigurationDetails( profileId: PaymentSdkDefaultConfig.profileID, @@ -76,7 +76,7 @@ class PayTabsViewModel extends ChangeNotifier { inputsCornerRadius: 12 // buttonFont: "Poppins", ); - paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY; + paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.USER_OPTIONAL_DEFAULT_ON; paymentConfiguration.tokenFormat = PaymentSdkTokenFormat.Hex32Format; notifyListeners(); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index e0dd8614..e1d21e12 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1844,5 +1844,6 @@ abstract class LocaleKeys { static const pharmacy = 'pharmacy'; static const visitsOrders = 'visitsOrders'; static const earned = 'earned'; + static const getParkingQR = 'getParkingQR'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 2ec8f568..d28a3054 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -874,6 +874,35 @@ class _AppointmentDetailsPageState extends State { } }) : SizedBox.shrink(), + (widget.patientAppointmentHistoryResponseModel.isParkingAvailable ?? false) + ? MedicalFileCard( + label: LocaleKeys.getParkingQR.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.appointment_parking_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myAppointmentsViewModel.getParkingQR( + widget.patientAppointmentHistoryResponseModel.appointmentNo, + widget.patientAppointmentHistoryResponseModel.projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }) + : SizedBox.shrink(), ], ); }), diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index ecf164a4..299c3db9 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -216,7 +216,7 @@ class AppointmentDoctorCard extends StatelessWidget { AppointmentType.isArrived(patientAppointmentHistoryResponseModel), ), ), - if (timerWidget != null) timerWidget ?? SizedBox() + if (!AppointmentType.isArrived(patientAppointmentHistoryResponseModel) && timerWidget != null) timerWidget ?? SizedBox() ], ), ), From 89670445b31b2b533933271f8a494ac1ae5dd6e5 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 1 Jun 2026 17:06:49 +0300 Subject: [PATCH 10/15] updates --- lib/core/api/api_client.dart | 14 +++++++++----- lib/core/api_consts.dart | 2 +- lib/core/app_state.dart | 1 + lib/core/utils/date_util.dart | 2 +- .../lakum_inquiry_information_response_model.dart | 2 +- lib/main.dart | 2 +- .../immediate_livecare_pending_request_page.dart | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index f8919251..3d10df4e 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api/http_client_manager.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -243,10 +244,13 @@ class ApiClientImp implements ApiClient { // Handle body encoding based on isBodyPlainText flag final dynamic requestBody = isBodyPlainText ? body : json.encode(body); - debugPrint("uri: ${Uri.parse(url.trim())}"); - var requestBodyJSON = json.encode(body); - // debugPrint("body: $requestBodyJSON", wrapWidth: 2048); - log("body: $requestBodyJSON"); + + if (kDebugMode) { + debugPrint("uri: ${Uri.parse(url.trim())}"); + var requestBodyJSON = json.encode(body); + debugPrint("body: $requestBodyJSON", wrapWidth: 2048); + // log("body: $requestBodyJSON"); + } http.Response response; try { @@ -255,7 +259,7 @@ class ApiClientImp implements ApiClient { body: requestBody, headers: headers, ); - log("response: ${response.body.toString()}"); + // log("response: ${response.body.toString()}"); } on SocketException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index db257e45..542a169c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index f256321e..aae2d03b 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -50,6 +50,7 @@ class AppState { void setIsPaytabsEnabled(bool value) { isPaytabsEnabled = value; + // isPaytabsEnabled = false; } int? _superUserID; diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 5aa48b07..eab4c4f1 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -18,7 +18,7 @@ class DateUtil { final endIndex = date.indexOf(end, startIndex + start.length); return DateTime.fromMillisecondsSinceEpoch(int.parse( date.substring(startIndex + start.length, endIndex), - )); + ), isUtc: true).add(Duration(hours: 3)); } static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { diff --git a/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart b/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart index 621434fe..1e49ef61 100644 --- a/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart +++ b/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart @@ -99,7 +99,7 @@ class LakumInquiryInformationResponseModel { memberName = json['MemberName']; memberUniversalId = json['MemberUniversalId']; mobileNumber = json['MobileNumber']; - pointsBalance = json['PointsBalance']; + pointsBalance = json['PointsBalance'] ?? 0; pointsBalanceAmount = json['PointsBalanceAmount']; pointsWillBeExpired = json['PointsWillBeExpired']; prefLang = json['PrefLang']; diff --git a/lib/main.dart b/lib/main.dart index ae7f7036..0119cd1c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -96,7 +96,7 @@ Future callAppStateInitializations() async { PlatformDispatcher.instance.onError = (error, stack) { if (!kDebugMode) { FirebaseCrashlytics.instance.recordError(error, stack, - fatal: true, + fatal: false, printDetails: true, reason: "${appState.isAuthenticated ? "Authenticated User ID: ${appState.getAuthenticatedUser()!.patientId} - ${appState.getAuthenticatedUser()!.mobileNumber}" : "Unauthenticated User"} - Uncaught asynchronous error"); diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart index b8e58fa6..9a1d1fd7 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart @@ -197,7 +197,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State Date: Tue, 2 Jun 2026 20:39:35 +0300 Subject: [PATCH 11/15] Wallet amount reload --- .../habib_wallet/habib_wallet_view_model.dart | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 00ba55cc..5605281d 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -30,6 +30,7 @@ class HabibWalletViewModel extends ChangeNotifier { num habibWalletAmount = 0; num walletRechargeAmount = 0; String notesText = ""; + bool isWalletAmountToBeLoaded = true; bool isBottomSheetContentLoading = false; @@ -392,6 +393,11 @@ class HabibWalletViewModel extends ChangeNotifier { notifyListeners(); } + setIsWalletAmountToBeLoaded(bool value) { + isWalletAmountToBeLoaded = value; + notifyListeners(); + } + String getSelectedRechargeTypeValue() { switch (selectedRechargeType) { case 1: @@ -406,6 +412,9 @@ class HabibWalletViewModel extends ChangeNotifier { } Future getPatientBalanceAmount({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isWalletAmountToBeLoaded) { + return; + } isWalletAmountLoading = true; notifyListeners(); @@ -425,6 +434,7 @@ class HabibWalletViewModel extends ChangeNotifier { habibWalletBalanceList.removeWhere((element) => element.patientAdvanceBalanceAmount == 0); isWalletAmountLoading = false; + isWalletAmountToBeLoaded = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -462,12 +472,7 @@ class HabibWalletViewModel extends ChangeNotifier { Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await habibWalletRepo.HISCreateAdvancePayment( - paymentMethodName: paymentMethodName, - paidAmount: paidAmount, - paymentReference: paymentReference, - patientID: patientID, - projectID: projectID, - depositorName: depositorName); + paymentMethodName: paymentMethodName, paidAmount: paidAmount, paymentReference: paymentReference, patientID: patientID, projectID: projectID, depositorName: depositorName); result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), @@ -489,8 +494,7 @@ class HabibWalletViewModel extends ChangeNotifier { ); } - Future addAdvanceNumberRequest( - {required String advanceNumber, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await habibWalletRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference); result.fold( @@ -538,8 +542,7 @@ class HabibWalletViewModel extends ChangeNotifier { lakumAccountInfo = LakumInquiryInformationResponseModel(); notifyListeners(); - final result = await habibWalletRepo.getLakumAccountInformation( - identificationNumber: getIt.get().getAuthenticatedUser()!.patientIdentificationNo ?? ""); + final result = await habibWalletRepo.getLakumAccountInformation(identificationNumber: getIt.get().getAuthenticatedUser()!.patientIdentificationNo ?? ""); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), From bcdbfa99ede0b265bb5dc1fc51b96c4d8426063d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 3 Jun 2026 10:20:48 +0300 Subject: [PATCH 12/15] Fixes & update to stores VersionID 21.3 --- lib/core/api_consts.dart | 2 +- lib/core/utils/push_notification_handler.dart | 8 ++-- .../emergency_services_view_model.dart | 12 +++++ .../habib_wallet/habib_wallet_view_model.dart | 12 +++-- .../todo_section/todo_section_view_model.dart | 14 +++--- .../er_online_checkin_payment_page.dart | 4 ++ .../habib_wallet/habib_wallet_page.dart | 34 +++++++------- .../wallet_payment_confirm_page.dart | 8 +++- lib/presentation/home/landing_page.dart | 1 + .../widgets/invoice_list_card.dart | 44 +++++++++---------- lib/splashPage.dart | 17 +++++++ pubspec.yaml | 1 + 12 files changed, 101 insertions(+), 56 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 542a169c..9f3bb907 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -290,7 +290,7 @@ class ApiConsts { static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; // ************ static values for Api **************** - static final double appVersionID = 21.2; + static final double appVersionID = 21.3; // static final double appVersionID = 50.7; static final int appChannelId = 3; diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 4b7250ba..e179a1ab 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -49,10 +49,10 @@ _incomingCall(Map data) async { log('the value of the _incomingCall remote message is $data'); // Check if there's already a call in progress to prevent duplicates - if (_isCallInProgress && _currentCallId != null) { - log('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); - return; - } + // if (_isCallInProgress && _currentCallId != null) { + // log('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); + // return; + // } String roomID = data['session_id'] ?? ''; String callTypeID = data['AppointmentNo'] ?? ''; diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 83d2a564..d581d0f5 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -117,6 +117,13 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool historyLoading = false; OrderDislpay currentlyDisplayedOrder = OrderDislpay.ALL; + bool isAdvanceERBalanceNeedToBeLoaded = true; + + setIsAdvanceERBalanceNeedToBeLoaded(bool value) { + isAdvanceERBalanceNeedToBeLoaded = value; + notifyListeners(); + } + setSelectedRRTProcedure(RRTProceduresResponseModel procedure) { selectedRRTProcedure = procedure; notifyListeners(); @@ -331,6 +338,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future checkPatientERAdvanceBalance({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isAdvanceERBalanceNeedToBeLoaded) return; + final result = await emergencyServicesRepo.checkPatientERAdvanceBalance(); result.fold( @@ -338,6 +347,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { (failure) { patientHasAdvanceERBalance = false; isERBookAppointment = true; + isAdvanceERBalanceNeedToBeLoaded = true; if (onSuccess != null) { onSuccess(failure.message); } @@ -347,9 +357,11 @@ class EmergencyServicesViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); patientHasAdvanceERBalance = false; isERBookAppointment = true; + isAdvanceERBalanceNeedToBeLoaded = true; } else if (apiResponse.messageStatus == 1) { patientHasAdvanceERBalance = apiResponse.data; isERBookAppointment = !patientHasAdvanceERBalance; + isAdvanceERBalanceNeedToBeLoaded = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 5605281d..76886045 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -325,10 +325,12 @@ class HabibWalletViewModel extends ChangeNotifier { } initHabibWalletProvider() { - isWalletAmountLoading = true; + if (isWalletAmountToBeLoaded) { + isWalletAmountLoading = true; + habibWalletAmount = 0; + isLakumAccountInfoLoading = true; + } isBottomSheetContentLoading = false; - isLakumAccountInfoLoading = true; - habibWalletAmount = 0; walletRechargeAmount = 0; selectedRechargeType = 1; advancePaymentHospitals.clear(); @@ -538,6 +540,10 @@ class HabibWalletViewModel extends ChangeNotifier { } Future getLakumAccountInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isWalletAmountToBeLoaded) { + return; + } + isLakumAccountInfoLoading = true; lakumAccountInfo = LakumInquiryInformationResponseModel(); notifyListeners(); diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index 131774d6..7d1fb88a 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -13,13 +13,13 @@ class TodoSectionViewModel extends ChangeNotifier { String? notificationsCount = "0"; initializeTodoSectionViewModel() async { - // if (isAncillaryOrdersNeedReloading) { - patientAncillaryOrdersList.clear(); - isAncillaryOrdersLoading = true; + if (isAncillaryOrdersNeedReloading) { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; isAncillaryDetailsProceduresLoading = true; notificationsCount = "0"; getPatientOnlineAncillaryOrderList(); - // } + } getPatientDashboard(); } @@ -67,9 +67,9 @@ class TodoSectionViewModel extends ChangeNotifier { } Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { - // if (!isAncillaryOrdersNeedReloading) { - // return; - // } + if (!isAncillaryOrdersNeedReloading) { + return; + } patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index ea24490b..fca64321 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -139,6 +139,7 @@ class _ErOnlineCheckinPaymentPageState extends State paymentReference: transactionData.transactionReference!, appointmentNo: "0", onSuccess: (val) { + emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true); LoaderBottomSheet.hideLoader(); if (emergencyServicesViewModel.isERBookAppointment) { showCommonBottomSheetWithoutHeight( @@ -228,6 +229,7 @@ class _ErOnlineCheckinPaymentPageState extends State paymentReference: transactionData.transactionReference!, appointmentNo: "0", onSuccess: (val) { + emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true); LoaderBottomSheet.hideLoader(); if (emergencyServicesViewModel.isERBookAppointment) { showCommonBottomSheetWithoutHeight( @@ -510,6 +512,7 @@ class _ErOnlineCheckinPaymentPageState extends State paymentReference: transactionData.transactionReference!, appointmentNo: "0", onSuccess: (val) { + emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true); LoaderBottomSheet.hideLoader(); if (emergencyServicesViewModel.isERBookAppointment) { showCommonBottomSheetWithoutHeight( @@ -590,6 +593,7 @@ class _ErOnlineCheckinPaymentPageState extends State paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, appointmentNo: "0", onSuccess: (val) { + emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true); LoaderBottomSheet.hideLoader(); if (emergencyServicesViewModel.isERBookAppointment) { showCommonBottomSheetWithoutHeight( diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 4f056dad..c4ee9356 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -136,23 +136,23 @@ class _HabibWalletState extends State { }, ), ), - SizedBox(width: 8.w), - Flexible( - child: CustomButton( - height: 40.h, - icon: AppAssets.refundIcon, - iconSize: 24.w, - backgroundColor: AppColors.successColor, - textColor: Colors.white, - text: LocaleKeys.withdraw.tr(context: context), - borderWidth: 0.w, - fontWeight: FontWeight.w600, - borderColor: Colors.transparent, - padding: EdgeInsets.fromLTRB(4, 0, 12, 0), - fontSize: 14.f, - onPressed: () => Navigator.of(context).push(CustomPageRoute(page: WithdrawRequestCreatePage())), - ), - ), + // SizedBox(width: 8.w), + // Flexible( + // child: CustomButton( + // height: 40.h, + // icon: AppAssets.refundIcon, + // iconSize: 24.w, + // backgroundColor: AppColors.successColor, + // textColor: Colors.white, + // text: LocaleKeys.withdraw.tr(context: context), + // borderWidth: 0.w, + // fontWeight: FontWeight.w600, + // borderColor: Colors.transparent, + // padding: EdgeInsets.fromLTRB(4, 0, 12, 0), + // fontSize: 14.f, + // onPressed: () => Navigator.of(context).push(CustomPageRoute(page: WithdrawRequestCreatePage())), + // ), + // ), ], ), SizedBox(height: 24.h), diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 58840c66..dd3c5ff8 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -127,6 +127,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { + habibWalletVM.setIsWalletAmountToBeLoaded(true); habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); @@ -228,6 +229,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { + habibWalletVM.setIsWalletAmountToBeLoaded(true); habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); @@ -411,8 +413,9 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { - habibWalletVM.initHabibWalletProvider(); - habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.setIsWalletAmountToBeLoaded(true); + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); @@ -503,6 +506,7 @@ class _WalletPaymentConfirmPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { + habibWalletVM.setIsWalletAmountToBeLoaded(true); habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 8512dddc..40028315 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -213,6 +213,7 @@ class _LandingPageState extends State { habibWalletVM.getLakumAccountInformation(); // Refresh Ancillary Orders + todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); todoSectionViewModel.initializeTodoSectionViewModel(); // Refresh Immediate LiveCare Data diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index dc5414cd..0bd312c5 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -212,28 +212,28 @@ class InvoiceListCard extends StatelessWidget { iconSize: 14.h, ), ), - if (getInvoicesListResponseModel.isAllowRefund == true) ...[ - SizedBox(width: 8.w), - Expanded( - child: CustomButton( - text: LocaleKeys.requestRefund.tr(context: context), - onPressed: () { - _handleRefundInvoiceTap(context); - }, - icon: AppAssets.refundIcon, - iconColor: AppColors.caloriesCalculatorColor, - backgroundColor: AppColors.blueColor.withValues(alpha: 0.14), - borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01), - textColor: AppColors.caloriesCalculatorColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - iconSize: 14.h, - ), - ), - ] + // if (getInvoicesListResponseModel.isAllowRefund == true) ...[ + // SizedBox(width: 8.w), + // Expanded( + // child: CustomButton( + // text: LocaleKeys.requestRefund.tr(context: context), + // onPressed: () { + // _handleRefundInvoiceTap(context); + // }, + // icon: AppAssets.refundIcon, + // iconColor: AppColors.caloriesCalculatorColor, + // backgroundColor: AppColors.blueColor.withValues(alpha: 0.14), + // borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01), + // textColor: AppColors.caloriesCalculatorColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: 40.h, + // iconSize: 14.h, + // ), + // ), + // ] ], ), ], diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 22b9f979..a93f75a1 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:clarity_flutter/clarity_flutter.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; @@ -55,7 +57,13 @@ class _SplashScreenState extends State { await notificationService.initialize(onNotificationClick: (payload) { // Handle notification click here }); + ZoomService().initializeZoomSDK(); + + // if (!kDebugMode) { + // _initializeClarity(); + // } + if (isAppOpenedFromCall) { navigateToTeleConsult(); } else { @@ -76,6 +84,15 @@ class _SplashScreenState extends State { // zoom.initSdk(initConfig); } + void _initializeClarity() { + final config = ClarityConfig( + projectId: "x0qgorlez4", // You can find it on the Settings page of Clarity dashboard. + logLevel: LogLevel.Verbose, // Optional: Set the log level (Verbose, Debug, Info, Warning, Error, None) + ); + + Clarity.initialize(context, config); + } + navigateToTeleConsult() async { String roomID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID); String callTypeID = await Utils.getStringFromPrefs(CacheConst.callTypeID); diff --git a/pubspec.yaml b/pubspec.yaml index 33cbdf4b..59e906b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -105,6 +105,7 @@ dependencies: in_app_review: ^2.0.11 flutter_paytabs_bridge: ^2.7.13 + clarity_flutter: 1.9.0 dev_dependencies: flutter_test: From 6249daa67d13895ffc33463fc176974d42ff6efb Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 3 Jun 2026 10:30:59 +0300 Subject: [PATCH 13/15] parking QR disabled & other fixes --- lib/generated/locale_keys.g.dart | 11 ++-- .../appointment_details_page.dart | 58 +++++++++---------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1b4f6ae9..821c419f 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -2,7 +2,7 @@ // ignore_for_file: constant_identifier_names -abstract class LocaleKeys { +abstract class LocaleKeys { static const english = 'english'; static const arabic = 'arabic'; static const login = 'login'; @@ -1851,7 +1851,7 @@ abstract class LocaleKeys { static const invalidIbanFormat = 'invalidIbanFormat'; static const refundHistory = 'refundHistory'; static const noRefundHistoryFound = 'noRefundHistoryFound'; - static const selectProcedures = 'selectProcedures'; + static const selectProcedure = 'selectProcedure'; static const noProceduresAvailableForRefund = 'noProceduresAvailableForRefund'; static const selectAll = 'selectAll'; static const selectRefundMethod = 'selectRefundMethod'; @@ -1866,6 +1866,7 @@ abstract class LocaleKeys { static const refundReason5 = 'refundReason5'; static const refundReason6 = 'refundReason6'; static const refundReason7 = 'refundReason7'; + static const selectProcedures = 'selectProcedures'; static const redeem = 'redeem'; static const points = 'points'; static const transactions = 'transactions'; @@ -1873,14 +1874,14 @@ abstract class LocaleKeys { static const visitsOrders = 'visitsOrders'; static const earned = 'earned'; static const getParkingQR = 'getParkingQR'; - static const cannotOpenThisFile = 'cannotOpenThisFile'; - static const thisInvoiceIsNotEligibleForRefund = 'thisInvoiceIsNotEligibleForRefund'; static const refundDetails = 'refundDetails'; static const requestNo = 'requestNo'; static const referenceNo = 'referenceNo'; - static const refund = 'refund'; static const advanceNumber = 'advanceNumber'; static const amountOnly = 'amountOnly'; static const invoiceHistory = 'invoiceHistory'; + static const refund = 'refund'; + static const thisInvoiceIsNotEligibleForRefund = 'thisInvoiceIsNotEligibleForRefund'; + } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index d28a3054..ddf9ec86 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -874,35 +874,35 @@ class _AppointmentDetailsPageState extends State { } }) : SizedBox.shrink(), - (widget.patientAppointmentHistoryResponseModel.isParkingAvailable ?? false) - ? MedicalFileCard( - label: LocaleKeys.getParkingQR.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.appointment_parking_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); - myAppointmentsViewModel.getParkingQR( - widget.patientAppointmentHistoryResponseModel.appointmentNo, - widget.patientAppointmentHistoryResponseModel.projectID, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - }) - : SizedBox.shrink(), + // (widget.patientAppointmentHistoryResponseModel.isParkingAvailable ?? false) + // ? MedicalFileCard( + // label: LocaleKeys.getParkingQR.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.appointment_parking_icon, + // isLargeText: true, + // iconSize: 36.w, + // ).onPress(() { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + // myAppointmentsViewModel.getParkingQR( + // widget.patientAppointmentHistoryResponseModel.appointmentNo, + // widget.patientAppointmentHistoryResponseModel.projectID, + // onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }, + // ); + // }) + // : SizedBox.shrink(), ], ); }), From 31035ed0cc971101c9e0abf5095e03cb80e2a528 Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Wed, 3 Jun 2026 10:49:59 +0300 Subject: [PATCH 14/15] Hamza Fix For Arabic Search --- assets/langs/ar-SA.json | 15 ++++++++++- assets/langs/en-US.json | 11 ++++++++ lib/core/utils/utils.dart | 26 +++++++++++++++++++ .../book_appointments_view_model.dart | 10 ++++++- lib/generated/locale_keys.g.dart | 11 ++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f4ec8bc8..dd87955b 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -230,6 +230,17 @@ "companyName": "اسم الشركة:", "receiptOn": "الإيصال على:", "expiryDate": "تاريخ الانتهاء:", + "myCards": "بطاقاتي", + "savedCards": "البطاقات المحفوظة", + "addNewCard": "إضافة بطاقة جديدة", + "noSavedCards": "لا توجد بطاقات محفوظة", + "cardEndingWith": "البطاقة المنتهية بـ", + "expiry": "الصلاحية", + "defaultCard": "افتراضي", + "deleteCard": "حذف البطاقة؟", + "deleteCardConfirmation": "هل أنت متأكد من حذف هذه البطاقة المنتهية بـ", + "cardDeletedSuccessfully": "تم حذف البطاقة بنجاح", + "addCardComingSoon": "وظيفة إضافة البطاقة قريباً", "expiryPoints": "منتهي الصلاحية", "expiryOn": "ينتهي في:", "procedureName": "اسم الإجراء:", @@ -1895,5 +1906,7 @@ "referenceNo": "رقم المرجع", "advanceNumber": "رقم الدفعة المقدمة", "amountOnly": "المبلغ", - "invoiceHistory": "سجل الفواتير" + "invoiceHistory": "سجل الفواتير", + "thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد", + "refund": "استرداد" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 047da473..ce4f869a 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -227,6 +227,17 @@ "companyName": "Company Name:", "receiptOn": "Receipt on:", "expiryDate": "Expiry Date:", + "myCards": "My Cards", + "savedCards": "Saved Cards", + "addNewCard": "Add New Card", + "noSavedCards": "No saved cards", + "cardEndingWith": "Card ending with", + "expiry": "Expiry", + "defaultCard": "Default", + "deleteCard": "Delete Card?", + "deleteCardConfirmation": "Are you sure you want to delete this card ending in", + "cardDeletedSuccessfully": "Card deleted successfully", + "addCardComingSoon": "Add card functionality coming soon", "expiryPoints": "Expired", "expiryOn": "Expiry on:", "procedureName": "Procedure Name:", diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 6ab5865a..35532d69 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -1154,4 +1154,30 @@ class Utils { } return result; } + + /// Normalize Arabic text for search by handling Hamza variations and removing diacritics + /// This makes searching more flexible for Arabic text + static String normalizeArabicText(String text) { + if (text.isEmpty) return text; + + String normalized = text; + + // Normalize Hamza variations to a single character 'ا' + normalized = normalized.replaceAll('أ', 'ا'); // Hamza on Alif + normalized = normalized.replaceAll('إ', 'ا'); // Hamza below Alif + normalized = normalized.replaceAll('آ', 'ا'); // Madda on Alif + normalized = normalized.replaceAll('ء', 'ا'); // Hamza alone + normalized = normalized.replaceAll('ؤ', 'و'); // Hamza on Waw + normalized = normalized.replaceAll('ئ', 'ي'); // Hamza on Ya + + // Normalize Alif Maqsura to Ya + normalized = normalized.replaceAll('ى', 'ي'); + + // Remove Arabic diacritics (Tashkeel) + normalized = normalized.replaceAll(RegExp(r'[\u064B-\u065F]'), ''); // Fatha, Damma, Kasra, etc. + normalized = normalized.replaceAll(RegExp(r'[\u0670]'), ''); // Superscript Alif + normalized = normalized.replaceAll(RegExp(r'[\u0640]'), ''); // Tatweel + + return normalized; + } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 890ef76d..200b51de 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -372,7 +372,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { _filteredClinicsList = List.from(clinicsList); showSortFilterButtons = false; } else { - _filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList(); + // Normalize the search query for better Arabic text matching + String normalizedQuery = Utils.normalizeArabicText(query.toLowerCase()); + + _filteredClinicsList = clinicsList.where((clinic) { + String clinicName = clinic.clinicDescription?.toLowerCase() ?? ''; + String normalizedClinicName = Utils.normalizeArabicText(clinicName); + return normalizedClinicName.contains(normalizedQuery); + }).toList(); + showSortFilterButtons = query.length >= 3; } notifyListeners(); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 821c419f..a588a98d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -231,6 +231,17 @@ abstract class LocaleKeys { static const companyName = 'companyName'; static const receiptOn = 'receiptOn'; static const expiryDate = 'expiryDate'; + static const myCards = 'myCards'; + static const savedCards = 'savedCards'; + static const addNewCard = 'addNewCard'; + static const noSavedCards = 'noSavedCards'; + static const cardEndingWith = 'cardEndingWith'; + static const expiry = 'expiry'; + static const defaultCard = 'defaultCard'; + static const deleteCard = 'deleteCard'; + static const deleteCardConfirmation = 'deleteCardConfirmation'; + static const cardDeletedSuccessfully = 'cardDeletedSuccessfully'; + static const addCardComingSoon = 'addCardComingSoon'; static const expiryPoints = 'expiryPoints'; static const expiryOn = 'expiryOn'; static const procedureName = 'procedureName'; From 3dac6cb3a7f4fc53e0c119115f4129d852a9522f Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Wed, 3 Jun 2026 15:25:49 +0300 Subject: [PATCH 15/15] Last Login UAE Fix --- .../authentication/authentication_view_model.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8685a947..7393a88f 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1668,14 +1668,26 @@ class AuthenticationViewModel extends ChangeNotifier { patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0, + projectOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude: _appState.userLong, mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!, patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), - nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo) + nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo, + zipCode: _appState.getSelectDeviceByImeiRespModelElement!.isOther == true + ? "0" + : _appState.getSelectDeviceByImeiRespModelElement!.outSa == true + ? CountryEnum.unitedArabEmirates.countryCode + : CountryEnum.saudiArabia.countryCode, + isRegister: false, + logInTokenId: "", + searchType: 2, + patientIdentificationId: "0", + otpSendType: loginType) .toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo'];