From 1c0666bc0abf6a4f4260056991e27a59cfe56368 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 14 Jun 2026 16:31:25 +0300 Subject: [PATCH 1/8] Updates --- lib/core/api/api_client.dart | 2 +- lib/core/app_state.dart | 7 ++-- .../widgets/otp_verification_screen.dart | 1 + .../book_appointments_view_model.dart | 2 +- .../immediate_livecare_view_model.dart | 4 +-- .../appointment_rating_view_model.dart | 1 + lib/features/paytabs/paytabs_view_model.dart | 2 +- .../widgets/appointment_doctor_card.dart | 2 +- lib/presentation/authentication/register.dart | 2 ++ .../authentication/register_step2.dart | 2 ++ .../select_livecare_clinic_page.dart | 32 +++++++++++++++---- 11 files changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 0c8a2314..5478c379 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -211,7 +211,7 @@ class ApiClientImp implements ApiClient { } body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4774598; + // body['PatientID'] = 6424682; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 3369ef49..2731cc1a 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -47,17 +47,16 @@ class AppState { AuthenticatedUser? _authenticatedChildUser; bool isPaytabsEnabled = false; - bool isPayfortHostedPageEnabled = true; void setIsPaytabsEnabled(bool value) { - // isPaytabsEnabled = value; - isPaytabsEnabled = false; + isPaytabsEnabled = value; + // isPaytabsEnabled = true; } void setIsPayfortHostedPageEnabled(bool value) { isPayfortHostedPageEnabled = value; - // isPayfortHostedPageEnabled = false; + // isPayfortHostedPageEnabled = true; } int? _superUserID; diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 372f7e8f..1b5c80d1 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -451,6 +451,7 @@ class _OTPVerificationScreenState extends State { @override void initState() { + Clarity.setCurrentScreenName('OTP Verification Screen'); super.initState(); _otpController = TextEditingController(); _startResendTimer(); diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 200b51de..a4a61f35 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -844,7 +844,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; - final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat formatter = DateFormat('HH:mm', "en-US"); final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 68ae6952..68246f00 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -121,8 +121,8 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { } Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = - await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!); + final result = await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees( + _appState.getAuthenticatedUser()!.age ?? 22, _appState.getAuthenticatedUser()!.gender ?? 1, immediateLiveCareSelectedClinic.serviceID ?? 1); result.fold( (failure) async { diff --git a/lib/features/my_appointments/appointment_rating_view_model.dart b/lib/features/my_appointments/appointment_rating_view_model.dart index 6dd41ff5..cef25419 100644 --- a/lib/features/my_appointments/appointment_rating_view_model.dart +++ b/lib/features/my_appointments/appointment_rating_view_model.dart @@ -25,6 +25,7 @@ class AppointmentRatingViewModel extends ChangeNotifier { bool isRateClinic = false; Future getLastRatingAppointment({Function(dynamic)? onSuccess, Function(String)? onError}) async { + appointmentRatedList.clear(); final result = await myAppointmentsRepo.getLastRatingAppointment(); result.fold( diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 35a924af..d572ab33 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -65,7 +65,7 @@ class PayTabsViewModel extends ChangeNotifier { "12626"), alternativePaymentMethods: [], linkBillingNameWithCardHolderName: true, - simplifyApplePayValidation: true, + simplifyApplePayValidation: false, ); paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 59783f20..f4d20944 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -60,7 +60,7 @@ class AppointmentDoctorCard extends StatelessWidget { Column( children: [ Utils.buildImgWithNetwork( - url: patientAppointmentHistoryResponseModel.doctorImageURL!, + url: patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", iconColor: AppColors.transparent, width: 63.h, height: 63.h, diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index e77038a5..8d44f377 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -1,3 +1,4 @@ +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -31,6 +32,7 @@ class _RegisterNew extends State { @override void initState() { + Clarity.setCurrentScreenName('Registration Step 1 Screen'); super.initState(); _nationalIdFocusNode = FocusNode(); _dobFocusNode = FocusNode(); diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index cb96e63d..46919e95 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -1,3 +1,4 @@ +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -34,6 +35,7 @@ class _RegisterNew extends State { @override void initState() { + Clarity.setCurrentScreenName('Registration Step 2 Screen'); super.initState(); authVM = context.read(); insuranceVM = context.read(); diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index bd2b6462..b9d63f55 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.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/app_state.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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; @@ -114,11 +116,11 @@ class SelectLivecareClinicPage extends StatelessWidget { ), ), ), - Column( - children: [ - CustomButton( - text: LocaleKeys.yesPleasImInAHurry.tr(context: context), - onPressed: () async { + getIt.get().isAuthenticated + ? Column(children: [ + CustomButton( + text: LocaleKeys.yesPleasImInAHurry.tr(context: context), + onPressed: () async { Navigator.pop(context); GetLiveCareClinicListResponseModel liveCareClinic = GetLiveCareClinicListResponseModel( iD: selectedClinic.liveCareClinicID, @@ -177,8 +179,24 @@ class SelectLivecareClinicPage extends StatelessWidget { height: 50.h, ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 24.h), - ], - ), + ]) + : CustomButton( + text: LocaleKeys.loginToUseServiceLiveCare.tr(), + onPressed: () async { + await getIt.get().onLoginPressed(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + isBold: true, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.login1, + iconColor: AppColors.whiteColor, + iconSize: 24.h, + ).paddingSymmetrical(24.h, 120.h), ], ), ); From bec841f07cf323c53b9af150c5e8c316bc97f937 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 17 Jun 2026 15:46:30 +0300 Subject: [PATCH 2/8] updates & fixes --- lib/core/api/api_client.dart | 2 +- lib/core/utils/utils.dart | 8 +++--- .../book_appointments_view_model.dart | 4 ++- .../my_appointments_view_model.dart | 4 ++- lib/features/payfort/payfort_view_model.dart | 7 ++++- lib/features/paytabs/paytabs_view_model.dart | 2 +- lib/features/weather/weather_view_model.dart | 7 ++++- .../appointment_payment_page.dart | 12 +++++---- .../appointments/appointment_queue_page.dart | 8 +++--- .../appointment_checkin_bottom_sheet.dart | 7 +++++ .../review_appointment_page.dart | 9 ++++++- .../waiting_appointment_payment_page.dart | 4 +-- .../habib_wallet/recharge_wallet_page.dart | 3 ++- .../hmg_services/services_page.dart | 4 +-- lib/presentation/home/landing_page.dart | 11 ++++---- .../home/widgets/habib_wallet_card.dart | 2 +- .../medical_file/medical_file_page.dart | 7 +++-- .../patient_sickleaves_list_page.dart | 4 ++- .../widgets/medical_report_card.dart | 2 +- .../my_family/widget/family_cards.dart | 27 ++++++++++--------- ...scription_delivery_order_summary_page.dart | 2 ++ .../profile_settings/accessibility_page.dart | 12 ++++----- 22 files changed, 97 insertions(+), 51 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 5478c379..9164d9cf 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -211,7 +211,7 @@ class ApiClientImp implements ApiClient { } body['TokenID'] = "@dm!n"; - // body['PatientID'] = 6424682; + // body['PatientID'] = 3648831; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 9e8a044d..2d4cdfff 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -916,7 +916,8 @@ class Utils { appState.isArabic() ? Container() : Utils.buildSvgWithAssets( - icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, + // icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, + icon: (getIt.get().getAuthenticatedUser()!.outSa == 0) ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, iconColor: iconColor, width: iconSize.h, height: iconSize.h, @@ -928,7 +929,8 @@ class Utils { !appState.isArabic() ? Container() : Utils.buildSvgWithAssets( - icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, + // icon: isSaudiCurrency ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, + icon: (getIt.get().getAuthenticatedUser()!.outSa == 0) ? AppAssets.saudi_riyal_icon : AppAssets.uae_dirham_symbol, iconColor: iconColor, width: iconSize.h, height: iconSize.h, @@ -958,7 +960,7 @@ class Utils { WidgetSpan( alignment: PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, - child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: resolvedIconColor), + child: Utils.buildSvgWithAssets(icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: resolvedIconColor), ), TextSpan( // text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index a4a61f35..e34e2188 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -1127,7 +1127,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - print(failure); + if(onError != null) { + onError(failure.message); + } }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 90f504d2..0862c40d 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1010,7 +1010,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } // patientQueueDetailsList.first.callType = 1; - patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + // if (patientQueueDetailsList.length > 1) { + patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + // } } else { patientQueueDetailsList.clear(); isPatientHasQueueAppointment = false; diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 8d26eab1..aebb8d5f 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -319,7 +319,7 @@ class PayfortViewModel extends ChangeNotifier { "amount": Platform.isAndroid ? (num.parse(amount) * 100).toInt() : (num.parse(amount) * 100).toInt().toString(), // "amount": "100000", "currency": getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", - "language": "en", + "language": getIt.get().isArabic() ? "ar" : "en", "customer_email": "CustID_$fileNumber@HMG.com", "sdk_token": sdkTokenResponse!.sdkToken, "customer_name": "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}", @@ -353,6 +353,11 @@ class PayfortViewModel extends ChangeNotifier { // Process real payment rejection errors here print("Actual Payment Failure: ${e.details.toString()}"); + if (onFailed != null) { + final paymentMap = (e.details as Map).cast(); + paymentMap["message"] = e.details["response_message"] ?? "Payment failed with unknown error."; + onFailed(PayFortFailureResult.fromMap(paymentMap)); + } } catch (error) { print("Generic Error: $error"); } diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index d572ab33..35a924af 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -65,7 +65,7 @@ class PayTabsViewModel extends ChangeNotifier { "12626"), alternativePaymentMethods: [], linkBillingNameWithCardHolderName: true, - simplifyApplePayValidation: false, + simplifyApplePayValidation: true, ); paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( diff --git a/lib/features/weather/weather_view_model.dart b/lib/features/weather/weather_view_model.dart index a145fcca..081ef192 100644 --- a/lib/features/weather/weather_view_model.dart +++ b/lib/features/weather/weather_view_model.dart @@ -23,10 +23,14 @@ class WeatherMonitorViewModel extends ChangeNotifier { final List _cityInfoList = []; bool isLoading = false; + bool isWeatherNeedToLoad = true; List get cityInfoList => _cityInfoList; Future fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isWeatherNeedToLoad) { + return; + } isLoading = true; notifyListeners(); _cityInfoList.clear(); @@ -34,7 +38,7 @@ class WeatherMonitorViewModel extends ChangeNotifier { // Check if location permission is already granted final isLocationEnabled = await locationUtils.isLocationPermissionEnabled(); - if (isLocationEnabled) { + if (isLocationEnabled && getIt.get().userLat != 0.0 && getIt.get().userLong != 0.0) { // Permission already granted, fetch data directly isLocationAvailable = true; final result = await weatherRepo.getCityInfo(); @@ -61,6 +65,7 @@ class WeatherMonitorViewModel extends ChangeNotifier { if (onSuccess != null) { onSuccess(apiResponse); } + isWeatherNeedToLoad = false; } }, ); diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 79b99a1e..b3814f00 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -69,11 +69,13 @@ class _AppointmentPaymentPageState extends State { widget.patientAppointmentHistoryResponseModel.clinicID, widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) { myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { - if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - setState(() { - isShowTamara = true; - }); + if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) { + if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } } }); }, onError: (err) { diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart index 59522149..f2bd1d18 100644 --- a/lib/presentation/appointments/appointment_queue_page.dart +++ b/lib/presentation/appointments/appointment_queue_page.dart @@ -26,6 +26,8 @@ class AppointmentQueuePage extends StatelessWidget { @override Widget build(BuildContext context) { appState = getIt.get(); + final currentQueue = getIt.get().currentPatientQueueDetails; + final currentStatus = getIt.get().currentQueueStatus; return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, myAppointmentsVM, child) { @@ -67,7 +69,7 @@ class AppointmentQueuePage extends StatelessWidget { labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context), backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), - ).toShimmer2(isShow: myAppointmentsVM.patientQueueDetailsList.isEmpty), + ).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.greyColor, @@ -88,7 +90,7 @@ class AppointmentQueuePage extends StatelessWidget { SizedBox(height: 8.h), (myAppointmentsVM.currentPatientQueueDetails.queueNo ?? "IM-123") .toText32(isBold: true) - .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty), + .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), SizedBox(height: 8.h), CustomButton( text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), @@ -103,7 +105,7 @@ class AppointmentQueuePage extends StatelessWidget { height: 40.h, iconColor: AppColors.whiteColor, iconSize: 18.h, - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), ], ), ), diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index 817ebfa0..dd6cf8eb 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -153,9 +154,15 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + + getIt.get().setIsWalletAmountToBeLoaded(true); + getIt.get().initHabibWalletProvider(); + getIt.get().getPatientBalanceAmount(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { // await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); + Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 2b0b0368..b7fb6469 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -432,8 +432,15 @@ class _ReviewAppointmentPageState extends State { if (bookAppointmentsViewModel.isLiveCareSchedule) { await bookAppointmentsViewModel.insertSpecificAppointmentForLiveCare(onError: (err) { - print(err.data["ErrorEndUserMessage"]); LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, onSuccess: (apiResp) async { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart index c7ef62bb..9b9e85bb 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart @@ -194,7 +194,7 @@ class _WaitingAppointmentPaymentPageState extends State { ), ), const Spacer(), - LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, isBold: true), + ((getIt.get().getAuthenticatedUser()!.outSa == 1) ? LocaleKeys.aed.tr(context: context) : LocaleKeys.sar.tr(context: context)) + .toText20(color: AppColors.greyTextColor, isBold: true), ], ), ], diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 6cc1bb93..75144cf7 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -544,7 +544,7 @@ class _ServicesPageState extends State { return Row( children: [ Utils.buildSvgWithAssets( - icon: AppAssets.saudi_riyal_icon, + icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon, iconColor: AppColors.inputLabelTextColor, width: 24.h, height: 24.h, @@ -554,7 +554,7 @@ class _ServicesPageState extends State { NumberFormat.decimalPattern() .format(habibWalletVM.habibWalletAmount) .toString() - .toText28(isBold: true, isEnglishOnly: true) + .toText28(isBold: true) .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 5f46f94f..f3719666 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; @@ -1110,7 +1111,7 @@ class _LandingPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildQueueHeader(currentStatus), + _buildQueueHeader(currentQueue, currentStatus), SizedBox(height: 8.h), _buildQueueGreeting(), SizedBox(height: 2.h), @@ -1119,11 +1120,11 @@ class _LandingPageState extends State { color: AppColors.textColorLight, ), SizedBox(height: 8.h), - (currentQueue.queueNo ?? "IM-123").toText28(isBold: true).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + (currentQueue.queueNo ?? "IM-123").toText28(isBold: true).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), SizedBox(height: 6.h), _buildServingNowSection(), SizedBox(height: 5.h), - _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), ], ), ), @@ -1135,7 +1136,7 @@ class _LandingPageState extends State { } // Queue Card: Header with status chip and icon - Widget _buildQueueHeader(int currentStatus) { + Widget _buildQueueHeader(PatientInQueueList currentQueue, int currentStatus) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -1144,7 +1145,7 @@ class _LandingPageState extends State { labelText: currentStatus == 0 ? LocaleKeys.inQueue.tr() : LocaleKeys.yourTurn.tr(), backgroundColor: Utils.getCardBorderColor(currentStatus).withValues(alpha: 0.20), textColor: Utils.getCardBorderColor(currentStatus), - ).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + ).toShimmer2(isShow: (currentQueue.queueNo == null || currentQueue.queueNo!.isEmpty)), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.greyColor, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 1d9bd656..0bfba696 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -91,7 +91,7 @@ class HabibWalletCard extends StatelessWidget { Row( children: [ Utils.buildSvgWithAssets( - icon: AppAssets.saudi_riyal_icon, + icon: (getIt.get().getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon, iconColor: AppColors.inputLabelTextColor, width: 24.h, height: 24.h, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 81343e42..66135dda 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1226,10 +1226,13 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}".toText16(isBold: true), + (!appState.isArabic() + ? "${LocaleKeys.sick2.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}" + : "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}") + .toText16(isBold: true), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index c31d80ac..431a8faf 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -53,7 +53,9 @@ class _PatientSickleavesListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}", + title: (!appState.isArabic() + ? "${LocaleKeys.sick2.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}" + : "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}"), child: SingleChildScrollView( child: Consumer(builder: (context, model, child) { return Column( diff --git a/lib/presentation/medical_file/widgets/medical_report_card.dart b/lib/presentation/medical_file/widgets/medical_report_card.dart index e2464392..1dc080c7 100644 --- a/lib/presentation/medical_file/widgets/medical_report_card.dart +++ b/lib/presentation/medical_file/widgets/medical_report_card.dart @@ -62,7 +62,7 @@ class MedicalReportCard extends StatelessWidget { ), if (listReady.isNotEmpty) AppCustomChipWidget( - labelText: '${listRequest.length.toString().padLeft(2, '0')} ${LocaleKeys.readyToDownload.tr()}', + labelText: '${listReady.length.toString().padLeft(2, '0')} ${LocaleKeys.readyToDownload.tr()}', icon: AppAssets.sucess, iconColor: AppColors.successColor, textColor: AppColors.successColor, diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 2f48bcbe..8feb8434 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -444,7 +444,7 @@ class _FamilyCardsState extends State { // Original logic: final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); final canSwitch = true; // Disabled - allows all users to switch return Container( - padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 12.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Opacity( opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active @@ -454,8 +454,8 @@ class _FamilyCardsState extends State { mainAxisSize: MainAxisSize.min, children: [ UserAvatarWidget( - width: 72.h, - height: 70.h, + width: 70.h, + height: 66.h, gender: profile.gender, age: profile.age, fit: BoxFit.cover, @@ -463,7 +463,7 @@ class _FamilyCardsState extends State { isCircular: true, isFamilyMember: true, // Prevent AppState fallback ), - SizedBox(height: 8.h), + SizedBox(height: 4.h), (profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1), // SizedBox(height: 8.h), // CustomChipWidget( @@ -476,14 +476,17 @@ class _FamilyCardsState extends State { // textColor: AppColors.textColor), widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), widget.isShowDetails - ? CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "${LocaleKeys.age.tr(context: context)}: ${profile.age ?? "N/A"} ${LocaleKeys.years.tr(context: context)}", - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor, - ) + ? Wrap(direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ + AppCustomChipWidget( + icon: AppAssets.file_icon, + richText: "${LocaleKeys.fileno.tr(context: context)}: ${profile.responseId}".toText10(isBold: true), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.age.tr(context: context)}: ${profile.age ?? "N/A"} ${LocaleKeys.years.tr(context: context)}", + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 6.w), + ), + ]) : SizedBox(), widget.isShowDetails ? SizedBox(height: 8.h) diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart index a5823b4f..968c4b5b 100644 --- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -104,6 +104,8 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { url: "https://maps.googleapis.com/maps/api/staticmap?center=${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&zoom=15&size=350x165&maptype=roadmap&markers=color:red%7C${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", iconColor: AppColors.blackBgColor, fit: BoxFit.contain, + width: double.infinity, + height: 165.h, ), ), ], diff --git a/lib/presentation/profile_settings/accessibility_page.dart b/lib/presentation/profile_settings/accessibility_page.dart index 7fe9b61b..328c1936 100644 --- a/lib/presentation/profile_settings/accessibility_page.dart +++ b/lib/presentation/profile_settings/accessibility_page.dart @@ -417,17 +417,17 @@ class _AccessibilityPageState extends State { // Price with SAR symbol Row( children: [ - "1234".toText24( - isBold: true, - color: AppColors.blackBgColor, // Always use base color - ), - SizedBox(width: 4.w), SvgPicture.asset( - AppAssets.saudi_riyal_icon, + (Utils.appState.getAuthenticatedUser()!.outSa == 1) ? AppAssets.uae_dirham_symbol : AppAssets.saudi_riyal_icon, colorFilter: ColorFilter.mode(AppColors.blackBgColor, BlendMode.srcIn), width: 16.w, height: 16.h, ), + SizedBox(width: 4.h), + "1234".toText24( + isBold: true, + color: AppColors.blackBgColor, // Always use base color + ), ], ), SizedBox(height: 16.h), From 64eb04011edb3bf05d355b710cd351f69f2c5f5c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 22 Jun 2026 18:01:55 +0300 Subject: [PATCH 3/8] Clarity implementation updates --- lib/core/api/api_client.dart | 4 +- lib/core/utils/utils.dart | 2 + lib/presentation/authentication/login.dart | 152 +++++++++--------- .../immediate_livecare_payment_page.dart | 12 +- lib/presentation/home/landing_page.dart | 2 + lib/widgets/input_widget.dart | 115 ++++++------- 6 files changed, 150 insertions(+), 137 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 9164d9cf..d2ea81f9 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,8 +210,8 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; - // body['PatientID'] = 3648831; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4772993; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 2d4cdfff..320bd1cf 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -4,12 +4,14 @@ import 'dart:io'; import 'dart:math' as dartMath; import 'dart:typed_data'; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:crypto/crypto.dart' as crypto; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get_it/get_it.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index c38e9ad9..dee44de0 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -94,27 +94,29 @@ class LoginScreenState extends State { LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor ), SizedBox(height: 32.h), Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget - TextInputWidget( - labelText: LocaleKeys.nationalIdFileNumber.tr(context: context), - hintText: "xxxxxxxxx", - controller: authVm.nationalIdController, - focusNode: _nationalIdFocusNode, - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: true, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), - leadingIcon: AppAssets.student_card, - errorMessage: authVm.nationalIdError, - hasError: authVm.nationalIdError != null, - fontFamily: "Poppins", - onChange: (value) { - // Clear error when user starts typing - authVm.clearNationalIdError(); - }, + ClarityUnmask( + child: TextInputWidget( + labelText: LocaleKeys.nationalIdFileNumber.tr(context: context), + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + focusNode: _nationalIdFocusNode, + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: true, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), + leadingIcon: AppAssets.student_card, + errorMessage: authVm.nationalIdError, + hasError: authVm.nationalIdError != null, + fontFamily: "Poppins", + onChange: (value) { + // Clear error when user starts typing + authVm.clearNationalIdError(); + }, + ), ), SizedBox(height: 16.h), CustomButton( @@ -205,65 +207,67 @@ class LoginScreenState extends State { child: SingleChildScrollView( child: Consumer( builder: (context, authVm, child) { - return GenericBottomSheet( - countryCode: authVm.selectedCountrySignup.countryCode, - initialPhoneNumber: "", - textController: phoneNumberController, - isEnableCountryDropdown: true, - onCountryChange: (country) { - authVm.onCountryChange(country); - // Clear error when country changes - authVm.clearPhoneNumberError(); - }, - onChange: (value) { - authVm.onPhoneNumberChange(value); - // Clear error when user starts typing - authVm.clearPhoneNumberError(); - }, - phoneNumberError: authVm.phoneNumberError, - buttons: [ - if (authVm.selectedCountrySignup != CountryEnum.others) + return ClarityUnmask( + child: GenericBottomSheet( + countryCode: authVm.selectedCountrySignup.countryCode, + initialPhoneNumber: "", + textController: phoneNumberController, + isEnableCountryDropdown: true, + onCountryChange: (country) { + authVm.onCountryChange(country); + // Clear error when country changes + authVm.clearPhoneNumberError(); + }, + onChange: (value) { + authVm.onPhoneNumberChange(value); + // Clear error when user starts typing + authVm.clearPhoneNumberError(); + }, + phoneNumberError: authVm.phoneNumberError, + buttons: [ + if (authVm.selectedCountrySignup != CountryEnum.others) + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(context: context), + onPressed: () async { + validatePhoneAndProceed(OTPTypeEnum.sms); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: Colors.white, + iconColor: Colors.white, + icon: AppAssets.message, + ), + ), + if (authVm.selectedCountrySignup != CountryEnum.others) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor), + ), + ], + ), Padding( - padding: EdgeInsets.only(bottom: 10.h), + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(context: context), + text: LocaleKeys.sendOTPWHATSAPP.tr(context: context), onPressed: () async { - validatePhoneAndProceed(OTPTypeEnum.sms); + validatePhoneAndProceed(OTPTypeEnum.whatsapp); }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedBorderColor, - textColor: Colors.white, - iconColor: Colors.white, - icon: AppAssets.message, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.textColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + iconColor: null, + applyThemeColor: false, ), ), - if (authVm.selectedCountrySignup != CountryEnum.others) - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.h), - child: LocaleKeys.oR.tr(context: context).toText16(color: AppColors.textColor), - ), - ], - ), - Padding( - padding: EdgeInsets.only(bottom: 10.h, top: 10.h), - child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(context: context), - onPressed: () async { - validatePhoneAndProceed(OTPTypeEnum.whatsapp); - }, - backgroundColor: AppColors.whiteColor, - borderColor: AppColors.textColor, - textColor: AppColors.textColor, - icon: AppAssets.whatsapp, - iconColor: null, - applyThemeColor: false, - ), - ), - ], + ], + ), ); }, ), 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 aa23fef8..04fd3073 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -63,11 +63,13 @@ class _ImmediateLiveCarePaymentPageState extends State= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - setState(() { - isShowTamara = true; - }); + if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) { + if (num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } } }); }); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f3719666..531b310a 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -145,6 +145,8 @@ class _LandingPageState extends State { } if (appState.isAuthenticated) { + Clarity.setCustomUserId(appState.getAuthenticatedUser()!.patientId.toString()); + habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); habibWalletVM.getLakumAccountInformation(); diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 16a955c8..b7fe686f 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,5 +1,6 @@ import 'dart:ui' as ui; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -405,64 +406,66 @@ class TextInputWidget extends StatelessWidget { child: Localizations.override( context: context, locale: const Locale('en', 'US'), // Force English locale for TextField - child: TextField( - hintLocales: const [Locale('en', 'US')], - enabled: isEnable, - scrollPadding: EdgeInsets.zero, - keyboardType: isMultiline - ? TextInputType.multiline - : (isWalletAmountInput! ? const TextInputType.numberWithOptions(decimal: true) : keyboardType), - controller: controller, - readOnly: isReadOnly, - textAlignVertical: TextAlignVertical.top, - textAlign: isArabic ? TextAlign.right : TextAlign.left, - textDirection: ui.TextDirection.ltr, - onChanged: onChange, - focusNode: focusNode ?? _focusNode, - autofocus: autoFocus, - textInputAction: TextInputAction.done, - cursorHeight: isWalletAmountInput! ? 40.h : 20.h, - maxLength: isWalletAmountInput! ? 7 : 100, - inputFormatters: isWalletAmountInput! - ? [ - _ThousandSeparatorInputFormatter(), - ] - : null, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - onSubmitted: onSubmitted, - minLines: isMultiline ? minLines : 1, - maxLines: isMultiline ? maxLines : 1, - style: TextStyle( - fontSize: fontS, - height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), - fontWeight: FontWeight.w600, - color: AppColors.textColor, - letterSpacing: -1, - fontFamily: fontFamily, - locale: const Locale('en', 'US'), // Force English locale for text style - ), - decoration: InputDecoration( - counterText: "", - isDense: true, - hintText: hintText, - hintStyle: TextStyle( - fontFamily: isArabic ? 'CairoArabic' : 'Poppins', - fontSize: 14.f, - height: 21 / 16, + child: ClarityUnmask( + child: TextField( + hintLocales: const [Locale('en', 'US')], + enabled: isEnable, + scrollPadding: EdgeInsets.zero, + keyboardType: isMultiline + ? TextInputType.multiline + : (isWalletAmountInput! ? const TextInputType.numberWithOptions(decimal: true) : keyboardType), + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: isArabic ? TextAlign.right : TextAlign.left, + textDirection: ui.TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + maxLength: isWalletAmountInput! ? 7 : 100, + inputFormatters: isWalletAmountInput! + ? [ + _ThousandSeparatorInputFormatter(), + ] + : null, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + onSubmitted: onSubmitted, + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), fontWeight: FontWeight.w600, - color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), - letterSpacing: -0.75, + color: AppColors.textColor, + letterSpacing: -1, + fontFamily: fontFamily, + locale: const Locale('en', 'US'), // Force English locale for text style + ), + decoration: InputDecoration( + counterText: "", + isDense: true, + hintText: hintText, + hintStyle: TextStyle( + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + fontSize: 14.f, + height: 21 / 16, + fontWeight: FontWeight.w600, + color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), + letterSpacing: -0.75, + ), + prefixIconConstraints: BoxConstraints(minWidth: 30.h), + prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true), + contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + // Show red border when field has error - but we handle this in the parent Container + // The parent Container in build() already shows red border based on hasError flag ), - prefixIconConstraints: BoxConstraints(minWidth: 30.h), - prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true), - contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0), - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - // Show red border when field has error - but we handle this in the parent Container - // The parent Container in build() already shows red border based on hasError flag ), ), ), From 6abcdf135ecaad6f7fa1cb8864486f0f518880d2 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 24 Jun 2026 14:40:45 +0300 Subject: [PATCH 4/8] Clarity implementation, LiveCare update insurance API implemented --- assets/langs/ar-SA.json | 4 +- assets/langs/en-US.json | 4 +- lib/core/api/api_client.dart | 135 +++++++- lib/core/api_consts.dart | 2 + lib/core/dependencies.dart | 5 + .../immediate_livecare_repo.dart | 43 +++ .../immediate_livecare_view_model.dart | 22 ++ lib/generated/locale_keys.g.dart | 2 + .../appointment_details_page.dart | 1 + .../immediate_livecare_payment_details.dart | 79 +++-- lib/services/clarity_service.dart | 291 ++++++++++++++++++ lib/splashPage.dart | 41 +++ lib/widgets/buttons/custom_button.dart | 122 +++++--- lib/widgets/clarity_interaction_tracker.dart | 200 ++++++++++++ 14 files changed, 872 insertions(+), 79 deletions(-) create mode 100644 lib/services/clarity_service.dart create mode 100644 lib/widgets/clarity_interaction_tracker.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index d39e8bd7..0f434598 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1958,5 +1958,7 @@ "invoiceHistory": "سجل الفواتير", "refund": "استرداد", "parkingQR": "رمز الاستجابة السريعة لوقوف السيارات", - "parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:" + "parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:", + "livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.", + "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 61a6da03..cedcd40a 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1947,7 +1947,9 @@ "amountOnly": "Amount", "invoiceHistory": "Invoice History", "parkingQR": "Parking QR Code", - "parkingQRDesc": "Scan the below QR Code at the exit parking gate:" + "parkingQRDesc": "Scan the below QR Code at the exit parking gate:", + "livecareUpdateInsuranceSent": "Your insurance update request is being processed. You will be notified once the update is completed.", + "livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?" } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index d2ea81f9..96b4d9f1 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -208,9 +208,9 @@ class ApiClientImp implements ApiClient { if (url == 'https://uat.hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo') { url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo"; body['TokenID'] = "@dm!n"; - } + } - // body['TokenID'] = "@dm!n"; + body['TokenID'] = "@dm!n"; // body['PatientID'] = 4772993; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; @@ -266,29 +266,77 @@ class ApiClientImp implements ApiClient { onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "SocketException: $e"); + // Enhanced Clarity logging for network errors + Clarity.setCustomTag('api_err_type', 'socket_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); - Clarity.sendCustomEvent('api_failure'); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'POST'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_socket_exception'); return; } on http.ClientException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "ClientException: $e"); + + // Enhanced Clarity logging + Clarity.setCustomTag('api_err_type', 'client_exception'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'POST'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_client_exception'); + return; } on TimeoutException catch (_) { final message = LocaleKeys.networkTimeout.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "TimeoutException"); + // Enhanced Clarity logging for timeouts + Clarity.setCustomTag('api_err_type', 'timeout'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); - Clarity.sendCustomEvent('api_timeout_failure'); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'POST'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_timeout'); + return; } catch (e) { final message = LocaleKeys.networkUnknownError.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "UnknownException: $e"); + + // Enhanced Clarity logging for unknown errors + Clarity.setCustomTag('api_err_type', 'unknown_exception'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'POST'); + + // Truncate exception message for Clarity + final exceptionMsg = e.toString(); + final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg; + Clarity.setCustomTag('api_err_exception', truncatedMsg); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_unknown_exception'); + return; } @@ -489,21 +537,74 @@ class ApiClientImp implements ApiClient { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "SocketException: $e"); + + // Clarity logging for GET requests + Clarity.setCustomTag('api_err_type', 'socket_exception'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'GET'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_get_socket_exception'); + return; } on http.ClientException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "ClientException: $e"); + + Clarity.setCustomTag('api_err_type', 'client_exception'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'GET'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_get_client_exception'); + return; } on TimeoutException catch (_) { final message = LocaleKeys.networkTimeout.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "TimeoutException"); + + Clarity.setCustomTag('api_err_type', 'timeout'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'GET'); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_get_timeout'); + return; } catch (e) { final message = LocaleKeys.networkUnknownError.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "UnknownException: $e"); + + Clarity.setCustomTag('api_err_type', 'unknown_exception'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'GET'); + + final exceptionMsg = e.toString(); + final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg; + Clarity.setCustomTag('api_err_exception', truncatedMsg); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_get_unknown_exception'); + return; } @@ -513,6 +614,19 @@ class ApiClientImp implements ApiClient { if (statusCode < 200 || statusCode >= 400) { onFailure(LocaleKeys.networkErrorWhileFetching.tr(), statusCode); logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode); + + // Clarity logging for GET status code errors + Clarity.setCustomTag('api_err_type', 'status_code_error'); + Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); + Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); + Clarity.setCustomTag('api_err_method', 'GET'); + Clarity.setCustomTag('api_err_status', statusCode.toString()); + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('api_get_status_error'); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); onSuccess(parsed, statusCode); @@ -695,6 +809,15 @@ class ApiClientImp implements ApiClient { return '...${url.substring(url.length - (clarityLimit - 3))}'; } + String _getEndpointName(String endpoint) { + // Extract the last part of the endpoint path for cleaner tracking + const int clarityLimit = 100; + final cleanEndpoint = endpoint.split('/').last; + + if (cleanEndpoint.length <= clarityLimit) return cleanEndpoint; + return '...${cleanEndpoint.substring(cleanEndpoint.length - (clarityLimit - 3))}'; + } + String _sanitizeRequestBody(String body) { if (body.isEmpty) return ''; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index aa51a5da..7e368079 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -622,6 +622,8 @@ var GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime'; var ADD_NEW_CALL_FOR_PATIENT_ER = 'Services/DoctorApplication.svc/REST/NewCallForPatientER'; +var ADD_LIVECARE_INSURANCE_UPDATE_REQUEST = 'Services/ER_VirtualCall.svc/REST/ER_InsertRequests'; + var GET_LIVECARE_HISTORY = 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory'; var CANCEL_LIVECARE_REQUEST = 'Services/ER_VirtualCall.svc/REST/DeleteErRequest'; var SEND_LIVECARE_INVOICE_EMAIL = 'Services/Notifications.svc/REST/SendInvoiceForLiveCare'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 3c6e923e..c4f7402c 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -75,6 +75,7 @@ import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/clarity_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/firebase_service.dart'; @@ -366,5 +367,9 @@ class AppDependencies { )); getIt.registerLazySingleton(() => PayTabsViewModel(appState: getIt())); + + getIt.registerLazySingleton( + () => ClarityService(appState: getIt.get()), + ); } } diff --git a/lib/features/immediate_livecare/immediate_livecare_repo.dart b/lib/features/immediate_livecare/immediate_livecare_repo.dart index 8775378b..6726a09b 100644 --- a/lib/features/immediate_livecare/immediate_livecare_repo.dart +++ b/lib/features/immediate_livecare/immediate_livecare_repo.dart @@ -25,6 +25,9 @@ abstract class ImmediateLiveCareRepo { {Function(dynamic)? onSuccess, Function(String)? onError}); Future>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> addInsuranceUpdateRequestForLiveCare(String name, String mobileNumber, String fileNumber, + {Function(dynamic)? onSuccess, Function(String)? onError}); } class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo { @@ -250,4 +253,44 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> addInsuranceUpdateRequestForLiveCare(String name, String mobileNumber, String fileNumber, + {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async { + Map mapDevice = { + "PatientName": name, + "MobileNumber": mobileNumber, + "FileNumber": fileNumber, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ADD_LIVECARE_INSURANCE_UPDATE_REQUEST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + // onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } 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/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 68246f00..84a79c02 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -245,6 +245,28 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { ); } + Future addInsuranceUpdateRequestForLiveCare({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await immediateLiveCareRepo.addInsuranceUpdateRequestForLiveCare("${_appState.getAuthenticatedUser()?.firstName ?? ''} ${_appState.getAuthenticatedUser()?.lastName ?? ''}", + _appState.getAuthenticatedUser()!.mobileNumber ?? "", _appState.getAuthenticatedUser()!.patientId.toString() ?? ""); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { patientLiveCareHistoryList = []; patientHasPendingLiveCareRequest = false; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 8d29d818..509fc86a 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1940,5 +1940,7 @@ abstract class LocaleKeys { static const refund = 'refund'; static const parkingQR = 'parkingQR'; static const parkingQRDesc = 'parkingQRDesc'; + static const livecareUpdateInsuranceSent = 'livecareUpdateInsuranceSent'; + static const livecareUpdateInsurance = 'livecareUpdateInsurance'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index daf0839a..d2faf2b1 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -40,6 +40,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appoin import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; +import 'package:hmg_patient_app_new/services/clarity_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 8bbf23af..57fd5e94 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -186,27 +186,64 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: InsuranceHomePage(), - ), - ) - .then((val) async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr()); - await immediateLiveCareVM.getLiveCareImmediateAppointmentFees(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.livecareUpdateInsurance.tr(), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr()); + await immediateLiveCareVM.addInsuranceUpdateRequestForLiveCare(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.livecareUpdateInsuranceSent.tr()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + + // Navigator.of(context) + // .push( + // CustomPageRoute( + // page: InsuranceHomePage(), + // ), + // ) + // .then((val) async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr()); + // await immediateLiveCareVM.getLiveCareImmediateAppointmentFees(onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // }, onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // }); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, diff --git a/lib/services/clarity_service.dart b/lib/services/clarity_service.dart new file mode 100644 index 00000000..e8600821 --- /dev/null +++ b/lib/services/clarity_service.dart @@ -0,0 +1,291 @@ +import 'package:clarity_flutter/clarity_flutter.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; + +/// Centralized service for Clarity tracking and analytics +/// Provides methods for tracking user actions, errors, and app state +class ClarityService { + final AppState _appState; + + ClarityService({required AppState appState}) : _appState = appState; + + /// Initialize Clarity with user context + void setUserContext() { + if (kDebugMode) return; // Skip in debug mode + + try { + if (_appState.isAuthenticated) { + final user = _appState.getAuthenticatedUser(); + if (user != null) { + // Set user ID + Clarity.setCustomUserId(user.patientId.toString()); + + // Set user segments for filtering + Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown'); + Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa'); + Clarity.setCustomTag('auth_status', 'authenticated'); + } + } else { + Clarity.setCustomTag('auth_status', 'guest'); + } + } catch (e) { + debugPrint('Failed to set Clarity user context: $e'); + } + } + + /// Track screen views / route changes + void trackScreenView(String screenName, {Map? properties}) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('current_screen', _truncate(screenName, 100)); + + if (properties != null) { + properties.forEach((key, value) { + Clarity.setCustomTag(key, _truncate(value, 250)); + }); + } + + Clarity.sendCustomEvent('screen_view_$screenName'); + } catch (e) { + debugPrint('Failed to track screen view: $e'); + } + } + + /// Track user actions (button clicks, form submissions, etc.) + void trackAction(String actionName, {Map? properties}) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('action_name', _truncate(actionName, 100)); + + if (properties != null) { + properties.forEach((key, value) { + Clarity.setCustomTag('action_$key', _truncate(value, 250)); + }); + } + + Clarity.sendCustomEvent('user_action_$actionName'); + } catch (e) { + debugPrint('Failed to track action: $e'); + } + } + + /// Track API calls with success/failure + void trackApiCall({ + required String endpoint, + required String method, + required bool isSuccess, + int? statusCode, + String? errorType, + String? errorMessage, + }) { + if (kDebugMode) return; + + try { + final eventType = isSuccess ? 'api_success' : 'api_failure'; + + Clarity.setCustomTag('api_endpoint', _getEndpointName(endpoint)); + Clarity.setCustomTag('api_method', method); + Clarity.setCustomTag('api_status', isSuccess ? 'success' : 'failure'); + + if (statusCode != null) { + Clarity.setCustomTag('api_status_code', statusCode.toString()); + } + + if (errorType != null) { + Clarity.setCustomTag('api_error_type', _truncate(errorType, 100)); + } + + if (errorMessage != null) { + Clarity.setCustomTag('api_error_msg', _truncate(errorMessage, 250)); + } + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('api_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent(eventType); + } catch (e) { + debugPrint('Failed to track API call: $e'); + } + } + + /// Track app errors and exceptions + void trackError({ + required String errorType, + required String errorMessage, + String? stackTrace, + bool isFatal = false, + Map? additionalContext, + }) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('error_type', _truncate(errorType, 100)); + Clarity.setCustomTag('error_fatal', isFatal.toString()); + Clarity.setCustomTag('error_message', _truncate(errorMessage, 250)); + + if (stackTrace != null) { + // Only send first 250 chars of stack trace + Clarity.setCustomTag('error_stack', _truncate(stackTrace, 250)); + } + + if (additionalContext != null) { + additionalContext.forEach((key, value) { + Clarity.setCustomTag('error_$key', _truncate(value, 250)); + }); + } + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('error_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent(isFatal ? 'app_fatal_error' : 'app_error'); + } catch (e) { + debugPrint('Failed to track error: $e'); + } + } + + /// Track user frustration events (rage clicks, dead clicks) + void trackFrustration({ + required String frustrationType, + required String elementLabel, + Map? context, + }) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('frustration_type', frustrationType); + Clarity.setCustomTag('frustration_element', _truncate(elementLabel, 100)); + + if (context != null) { + context.forEach((key, value) { + Clarity.setCustomTag('frustration_$key', _truncate(value, 250)); + }); + } + + Clarity.sendCustomEvent('user_frustration_$frustrationType'); + } catch (e) { + debugPrint('Failed to track frustration: $e'); + } + } + + /// Track feature usage + void trackFeatureUsage(String featureName, {Map? metadata}) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('feature_name', _truncate(featureName, 100)); + + if (metadata != null) { + metadata.forEach((key, value) { + Clarity.setCustomTag('feature_$key', _truncate(value, 250)); + }); + } + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('feature_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('feature_used_$featureName'); + } catch (e) { + debugPrint('Failed to track feature usage: $e'); + } + } + + /// Track payment events + void trackPayment({ + required String paymentStatus, + String? paymentMethod, + String? amount, + String? transactionId, + }) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('payment_status', paymentStatus); + + if (paymentMethod != null) { + Clarity.setCustomTag('payment_method', paymentMethod); + } + + if (amount != null) { + Clarity.setCustomTag('payment_amount', amount); + } + + if (transactionId != null) { + Clarity.setCustomTag('payment_tx_id', _truncate(transactionId, 100)); + } + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('payment_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('payment_$paymentStatus'); + } catch (e) { + debugPrint('Failed to track payment: $e'); + } + } + + /// Track appointment booking flow + void trackAppointmentEvent({ + required String eventType, + String? doctorId, + String? clinicId, + String? appointmentDate, + }) { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('appointment_event', eventType); + + if (doctorId != null) { + Clarity.setCustomTag('appointment_doctor', doctorId); + } + + if (clinicId != null) { + Clarity.setCustomTag('appointment_clinic', clinicId); + } + + if (appointmentDate != null) { + Clarity.setCustomTag('appointment_date', appointmentDate); + } + + if (_appState.isAuthenticated) { + Clarity.setCustomTag('appointment_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); + } + + Clarity.sendCustomEvent('appointment_$eventType'); + } catch (e) { + debugPrint('Failed to track appointment event: $e'); + } + } + + /// Clear user context on logout + void clearUserContext() { + if (kDebugMode) return; + + try { + Clarity.setCustomTag('auth_status', 'guest'); + Clarity.setCustomTag('user_action', 'logout'); + Clarity.sendCustomEvent('user_logged_out'); + } catch (e) { + debugPrint('Failed to clear Clarity user context: $e'); + } + } + + /// Helper: Truncate string to fit Clarity's limits + String _truncate(String text, int maxLength) { + if (text.length <= maxLength) return text; + return '...${text.substring(text.length - (maxLength - 3))}'; + } + + /// Helper: Extract endpoint name from full URL + String _getEndpointName(String endpoint) { + final cleanEndpoint = endpoint.split('/').last; + return _truncate(cleanEndpoint, 100); + } +} + + diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 2edf222e..dc49428a 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:flutter/foundation.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -91,6 +93,45 @@ class _SplashScreenState extends State { ); Clarity.initialize(context, config); + + // Set initial app context tags + try { + final appState = getIt.get(); + + // Set device and app metadata + Clarity.setCustomTag('app_version', ApiConsts.appVersionID.toString()); + Clarity.setCustomTag('device_type', appState.deviceTypeID ?? 'unknown'); + Clarity.setCustomTag('platform', Platform.isIOS ? 'ios' : 'android'); + + // Set user context if authenticated + if (appState.isAuthenticated) { + final user = appState.getAuthenticatedUser(); + if (user != null) { + // Set user ID for session tracking + Clarity.setCustomUserId(user.patientId.toString()); + + // Set custom tags for filtering sessions + Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown'); + Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa'); + Clarity.setCustomTag('auth_status', 'authenticated'); + + // Optional: Set other non-sensitive user properties + if (user.mobileNumber != null) { + // Mask phone number for privacy (show only last 4 digits) + final maskedPhone = '***${user.mobileNumber!.substring(user.mobileNumber!.length - 4)}'; + Clarity.setCustomTag('user_phone_masked', maskedPhone); + } + } + } else { + Clarity.setCustomTag('auth_status', 'guest'); + } + + // Send initialization event + Clarity.sendCustomEvent('app_initialized'); + + } catch (e) { + debugPrint('Failed to set Clarity user context: $e'); + } } navigateToTeleConsult() async { diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 3f4902e0..f3eb94fb 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -60,39 +61,73 @@ class CustomButton extends StatelessWidget { final fontS = fontSize ?? (16.f); final isButtonDisabled = isDisabled || onPressed == null; - return GestureDetector( - onTap: isButtonDisabled ? null : onPressed, - child: Container( - height: height == null - ? 56.h - : (isFoldable || isTablet) - ? height! + 10.h - : height, - width: width, - padding: padding, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor, - borderRadius: radius, - customBorder: BorderRadius.circular(radius), - side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) - Padding( - padding: text.isNotEmpty ? EdgeInsets.only(right: 6.w, left: 6.w) : EdgeInsets.zero, - child: Utils.buildSvgWithAssets( - icon: icon!, - iconColor: isButtonDisabled ? AppColors.greyTextColor : iconColor, - isDisabled: isButtonDisabled, - width: iconS, - height: iconS, - applyThemeColor: applyThemeColor), - ), - if (text.isNotEmpty && icon != null) - Flexible( - child: Text( + return Semantics( + button: true, + enabled: !isButtonDisabled, + label: text, + hint: isButtonDisabled ? 'Button is disabled' : 'Tap to $text', + child: GestureDetector( + onTap: isButtonDisabled + ? null + : () { + // Log button tap to Clarity before executing callback + try { + Clarity.setCustomTag('button_text', text.length > 100 ? text.substring(0, 100) : text); + Clarity.setCustomTag('button_action', 'tap'); + Clarity.setCustomTag('button_disabled', 'false'); + Clarity.sendCustomEvent('button_tapped'); + } catch (e) { + // Silence Clarity errors to not interrupt user flow + } + + // Execute the actual button callback + onPressed?.call(); + }, + child: Container( + height: height == null + ? 56.h + : (isFoldable || isTablet) + ? height! + 10.h + : height, + width: width, + padding: padding, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor, + borderRadius: radius, + customBorder: BorderRadius.circular(radius), + side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) + Padding( + padding: text.isNotEmpty ? EdgeInsets.only(right: 6.w, left: 6.w) : EdgeInsets.zero, + child: Utils.buildSvgWithAssets( + icon: icon!, + iconColor: isButtonDisabled ? AppColors.greyTextColor : iconColor, + isDisabled: isButtonDisabled, + width: iconS, + height: iconS, + applyThemeColor: applyThemeColor), + ), + if (text.isNotEmpty && icon != null) + Flexible( + child: Text( + text, + overflow: textOverflow ?? TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.center, + style: context.dynamicTextStyle( + fontSize: fontS, + color: isButtonDisabled ? AppColors.greyTextColor : textColor, + letterSpacing: 0, + fontWeight: isBold ? FontWeight.w700 : fontWeight, + ), + ), + ), + if (text.isNotEmpty && icon == null) + Text( text, overflow: textOverflow ?? TextOverflow.ellipsis, maxLines: 1, @@ -104,22 +139,9 @@ class CustomButton extends StatelessWidget { fontWeight: isBold ? FontWeight.w700 : fontWeight, ), ), - ), - if (text.isNotEmpty && icon == null) - Text( - text, - overflow: textOverflow ?? TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.center, - style: context.dynamicTextStyle( - fontSize: fontS, - color: isButtonDisabled ? AppColors.greyTextColor : textColor, - letterSpacing: 0, - fontWeight: isBold ? FontWeight.w700 : fontWeight, - ), - ), - ], - ), - )); + ], + ), + )), + ); } } diff --git a/lib/widgets/clarity_interaction_tracker.dart b/lib/widgets/clarity_interaction_tracker.dart new file mode 100644 index 00000000..e27aec86 --- /dev/null +++ b/lib/widgets/clarity_interaction_tracker.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; +import 'package:clarity_flutter/clarity_flutter.dart'; + +/// A wrapper widget that tracks user interactions with Clarity for UX frustration analysis. +/// Helps identify rage clicks, dead clicks, and rapid scrolls. +/// +/// Usage: +/// ```dart +/// ClarityInteractionTracker( +/// semanticLabel: 'Login Button', +/// interactionType: ClarityInteractionType.button, +/// onTap: () { /* your action */ }, +/// child: YourWidget(), +/// ) +/// ``` +class ClarityInteractionTracker extends StatelessWidget { + final Widget child; + final String semanticLabel; + final VoidCallback? onTap; + final ClarityInteractionType interactionType; + final Map? customTags; + final bool enabled; + + const ClarityInteractionTracker({ + super.key, + required this.child, + required this.semanticLabel, + this.onTap, + this.interactionType = ClarityInteractionType.button, + this.customTags, + this.enabled = true, + }); + + void _logInteraction(BuildContext context) { + if (!enabled) return; + + try { + // Set common tags + Clarity.setCustomTag('interaction_type', interactionType.name); + Clarity.setCustomTag('interaction_label', + semanticLabel.length > 100 ? semanticLabel.substring(0, 100) : semanticLabel + ); + Clarity.setCustomTag('interaction_enabled', enabled.toString()); + + // Set route/screen name if available + final route = ModalRoute.of(context); + if (route != null && route.settings.name != null) { + Clarity.setCustomTag('screen_route', route.settings.name!); + } + + // Add custom tags if provided + if (customTags != null) { + customTags!.forEach((key, value) { + // Ensure key and value fit Clarity's 255 char limit + final truncatedValue = value.length > 250 ? value.substring(0, 250) : value; + Clarity.setCustomTag(key, truncatedValue); + }); + } + + // Send event to Clarity + Clarity.sendCustomEvent('user_interaction_${interactionType.name}'); + } catch (e) { + // Silence Clarity errors to not interrupt user flow + debugPrint('Clarity interaction tracking error: $e'); + } + } + + @override + Widget build(BuildContext context) { + return Semantics( + label: semanticLabel, + button: interactionType == ClarityInteractionType.button, + enabled: enabled, + excludeSemantics: false, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: enabled && onTap != null + ? () { + _logInteraction(context); + onTap?.call(); + } + : null, + child: child, + ), + ); + } +} + +/// Types of interactions that can be tracked +enum ClarityInteractionType { + button, + link, + card, + listItem, + icon, + image, + input, + custom, +} + +/// Extension to easily wrap any widget with Clarity tracking +extension ClarityTrackingExtension on Widget { + /// Wraps this widget with Clarity interaction tracking + Widget trackWithClarity({ + required String label, + VoidCallback? onTap, + ClarityInteractionType type = ClarityInteractionType.custom, + Map? customTags, + bool enabled = true, + }) { + return ClarityInteractionTracker( + semanticLabel: label, + onTap: onTap, + interactionType: type, + customTags: customTags, + enabled: enabled, + child: this, + ); + } +} + +/// Wrapper for ScrollView to track rapid scrolling behavior +class ClarityScrollTracker extends StatefulWidget { + final Widget child; + final String scrollViewLabel; + final ScrollController? controller; + + const ClarityScrollTracker({ + super.key, + required this.child, + required this.scrollViewLabel, + this.controller, + }); + + @override + State createState() => _ClarityScrollTrackerState(); +} + +class _ClarityScrollTrackerState extends State { + late ScrollController _controller; + bool _isRapidScrolling = false; + DateTime? _lastScrollTime; + int _rapidScrollCount = 0; + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? ScrollController(); + _controller.addListener(_onScroll); + } + + void _onScroll() { + final now = DateTime.now(); + + // Detect rapid scrolling (scroll events within 100ms) + if (_lastScrollTime != null) { + final timeDiff = now.difference(_lastScrollTime!).inMilliseconds; + + if (timeDiff < 100) { + _rapidScrollCount++; + + if (!_isRapidScrolling && _rapidScrollCount > 5) { + _isRapidScrolling = true; + _logRapidScroll(); + } + } else if (timeDiff > 500) { + // Reset if scrolling slows down + _isRapidScrolling = false; + _rapidScrollCount = 0; + } + } + + _lastScrollTime = now; + } + + void _logRapidScroll() { + try { + Clarity.setCustomTag('scroll_type', 'rapid'); + Clarity.setCustomTag('scroll_view', widget.scrollViewLabel); + Clarity.setCustomTag('scroll_position', _controller.position.pixels.toString()); + Clarity.sendCustomEvent('rapid_scroll_detected'); + } catch (e) { + debugPrint('Clarity scroll tracking error: $e'); + } + } + + @override + void dispose() { + if (widget.controller == null) { + _controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + From 3dd4665f6e785939de18eb58c2eee0129c0492f6 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 24 Jun 2026 18:07:47 +0300 Subject: [PATCH 5/8] Payfort request & response insert --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 5 +- .../models/payfort_request_insert_model.dart | 128 ++++++++++++++++++ .../models/payfort_response_insert_model.dart | 77 +++++++++++ lib/features/payfort/payfort_repo.dart | 59 ++++++++ lib/features/payfort/payfort_view_model.dart | 103 +++++++++++++- .../appointment_payment_page.dart | 14 ++ .../immediate_livecare_payment_page.dart | 14 ++ .../waiting_appointment_payment_page.dart | 14 ++ .../wallet_payment_confirm_page.dart | 19 ++- .../ancillary_order_payment_page.dart | 14 ++ 11 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 lib/features/payfort/models/payfort_request_insert_model.dart create mode 100644 lib/features/payfort/models/payfort_response_insert_model.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 96b4d9f1..56eadd57 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -211,7 +211,7 @@ class ApiClientImp implements ApiClient { } body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4772993; + // body['PatientID'] = 255375; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 7e368079..04d0c962 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -988,7 +988,10 @@ var IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE = 'Services/OUTPs.svc/REST/HIS_IsDo //PAYFORT var getPayFortProjectDetails = "Services/PayFort_Serv.svc/REST/GetPayFortProjectDetails"; -var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse"; + +var ADD_PAYFORT_REQUEST = "Services/PayFort_Serv.svc/REST/AddRequest"; +var ADD_PAYFORT_RESPONSE = "Services/PayFort_Serv.svc/REST/AddResponse"; + // var payFortEnvironment = FortEnvironment.production; // var applePayMerchantId = "merchant.com.hmgwebservices"; // var payFortEnvironment = FortEnvironment.test; diff --git a/lib/features/payfort/models/payfort_request_insert_model.dart b/lib/features/payfort/models/payfort_request_insert_model.dart new file mode 100644 index 00000000..56f354f5 --- /dev/null +++ b/lib/features/payfort/models/payfort_request_insert_model.dart @@ -0,0 +1,128 @@ +class PayfortRequestInsertModel { + String? accessCode; + String? commandType; + num? amount; + String? customerEmail; + String? orderDescription; + String? serviceID; + String? returnURL; + String? paymentOption; + String? clientRequestID; + String? projectID; + String? fileNumber; + String? customerName; + String? currency; + String? liveServiceID; + String? longitude; + String? latitude; + String? merchantReference; + String? merchantIdentifier; + String? deviceToken; + String? sHARequestPhase; + String? sHAResponsePhase; + String? signature; + String? language; + String? integrationId; + String? iPAdress; + String? isSchedule; + String? appointmentDate; + String? appointmentNo; + String? doctorID; + + PayfortRequestInsertModel( + {this.accessCode, + this.commandType, + this.amount, + this.customerEmail, + this.orderDescription, + this.serviceID, + this.returnURL, + this.paymentOption, + this.clientRequestID, + this.projectID, + this.fileNumber, + this.customerName, + this.currency, + this.liveServiceID, + this.longitude, + this.latitude, + this.merchantReference, + this.merchantIdentifier, + this.deviceToken, + this.sHARequestPhase, + this.sHAResponsePhase, + this.signature, + this.language, + this.integrationId, + this.iPAdress, + this.isSchedule, + this.appointmentDate, + this.appointmentNo, + this.doctorID}); + + PayfortRequestInsertModel.fromJson(Map json) { + accessCode = json['Access_code']; + commandType = json['CommandType']; + amount = json['Amount']; + customerEmail = json['CustomerEmail']; + orderDescription = json['OrderDescription']; + serviceID = json['Service_ID']; + returnURL = json['ReturnURL']; + paymentOption = json['PaymentOption']; + clientRequestID = json['ClientRequestID']; + projectID = json['ProjectID']; + fileNumber = json['FileNumber']; + customerName = json['CustomerName']; + currency = json['Currency']; + liveServiceID = json['Live_ServiceID']; + longitude = json['Longitude']; + latitude = json['Latitude']; + merchantReference = json['Merchant_Reference']; + merchantIdentifier = json['Merchant_Identifier']; + deviceToken = json['DeviceToken']; + sHARequestPhase = json['SHA_RequestPhase']; + sHAResponsePhase = json['SHA_ResponsePhase']; + signature = json['Signature']; + language = json['Language']; + integrationId = json['Integration_Id']; + iPAdress = json['IPAdress']; + isSchedule = json['IsSchedule']; + appointmentDate = json['AppointmentDate']; + appointmentNo = json['AppointmentNo']; + doctorID = json['DoctorID']; + } + + Map toJson() { + final Map data = new Map(); + data['Access_code'] = this.accessCode; + data['CommandType'] = this.commandType; + data['Amount'] = this.amount; + data['CustomerEmail'] = this.customerEmail; + data['OrderDescription'] = this.orderDescription; + data['Service_ID'] = this.serviceID; + data['ReturnURL'] = this.returnURL; + data['PaymentOption'] = this.paymentOption; + data['ClientRequestID'] = this.clientRequestID; + data['ProjectID'] = this.projectID; + data['FileNumber'] = this.fileNumber; + data['CustomerName'] = this.customerName; + data['Currency'] = this.currency; + data['Live_ServiceID'] = this.liveServiceID; + data['Longitude'] = this.longitude; + data['Latitude'] = this.latitude; + data['Merchant_Reference'] = this.merchantReference; + data['Merchant_Identifier'] = this.merchantIdentifier; + data['DeviceToken'] = this.deviceToken; + data['SHA_RequestPhase'] = this.sHARequestPhase; + data['SHA_ResponsePhase'] = this.sHAResponsePhase; + data['Signature'] = this.signature; + data['Language'] = this.language; + data['Integration_Id'] = this.integrationId; + data['IPAdress'] = this.iPAdress; + data['IsSchedule'] = this.isSchedule; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNo'] = this.appointmentNo; + data['DoctorID'] = this.doctorID; + return data; + } +} diff --git a/lib/features/payfort/models/payfort_response_insert_model.dart b/lib/features/payfort/models/payfort_response_insert_model.dart new file mode 100644 index 00000000..452ca2a5 --- /dev/null +++ b/lib/features/payfort/models/payfort_response_insert_model.dart @@ -0,0 +1,77 @@ +class PayfortResponseInsertModel { + String? fortId; + String? commandType; + num? amount; + String? sadadOLP; + String? paymentOption; + String? customerIP; + String? eCI; + String? responseMessage; + String? cardNumber; + String? cardBin; + int? installmentsNumber; + String? status; + String? merchantRef; + bool? isRefund; + bool? remmeberMe; + String? reconciliationReference; + + PayfortResponseInsertModel({ + this.fortId, + this.commandType, + this.amount, + this.sadadOLP, + this.paymentOption, + this.customerIP, + this.eCI, + this.responseMessage, + this.cardNumber, + this.cardBin, + this.installmentsNumber, + this.status, + this.merchantRef, + this.isRefund, + this.remmeberMe, + this.reconciliationReference, + }); + + PayfortResponseInsertModel.fromJson(Map json) { + fortId = json['Fort_id']; + commandType = json['CommandType']; + amount = json['Amount']; + sadadOLP = json['Sadad_OLP']; + paymentOption = json['Payment_Option']; + customerIP = json['Customer_IP']; + eCI = json['ECI']; + responseMessage = json['Response_Message']; + cardNumber = json['Card_Number']; + cardBin = json['Card_Bin']; + installmentsNumber = json['Installments_Number']; + status = json['Status']; + merchantRef = json['Merchant_Ref']; + isRefund = json['IsRefund']; + remmeberMe = json['RemmeberMe']; + reconciliationReference = json['Reconciliation_Reference']; + } + + Map toJson() { + final Map data = Map(); + data['Fort_id'] = this.fortId; + data['CommandType'] = this.commandType; + data['Amount'] = this.amount; + data['Sadad_OLP'] = this.sadadOLP; + data['Payment_Option'] = this.paymentOption; + data['Customer_IP'] = this.customerIP; + data['ECI'] = this.eCI; + data['Response_Message'] = this.responseMessage; + data['Card_Number'] = this.cardNumber; + data['Card_Bin'] = this.cardBin; + data['Installments_Number'] = this.installmentsNumber; + data['Status'] = this.status; + data['Merchant_Ref'] = this.merchantRef; + data['IsRefund'] = this.isRefund; + data['RemmeberMe'] = this.remmeberMe; + data['Reconciliation_Reference'] = this.reconciliationReference; + return data; + } +} diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index c00b01c2..3cd27a9d 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -8,6 +8,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/sdk_token_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -28,6 +30,10 @@ abstract class PayfortRepo { {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}); Future>> getTamaraInstallmentsDetails(); + + Future>> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel}); + + Future>> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel}); } class PayfortRepoImp implements PayfortRepo { @@ -291,4 +297,57 @@ class PayfortRepoImp implements PayfortRepo { } } + @override + Future> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(ADD_PAYFORT_REQUEST, body: payfortRequestInsertModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, isAllowAny: true, isPaymentServices: true); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(ADD_PAYFORT_RESPONSE, body: payfortResponseInsertModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, isAllowAny: true, isPaymentServices: true); + 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/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index aebb8d5f..fd0cfe10 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -8,11 +8,15 @@ import 'package:flutter/services.dart'; import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/sdk_token_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -92,6 +96,44 @@ class PayfortViewModel extends ChangeNotifier { ); } + Future payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + // payfortProjectDetailsRespModel = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + // payfortProjectDetailsRespModel = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future checkPaymentStatus({required String transactionID, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.checkPaymentStatus(transactionID: transactionID); @@ -291,13 +333,19 @@ class PayfortViewModel extends ChangeNotifier { } Future initiatePaymentWithCard( // var request, { - { + String paymentOption, + String liveServiceID, + String isScheduleLiveCare, + String appointmentDate, + String appointmentNo, + String doctorID, { SucceededCallback? onSucceeded, FailedCallback? onFailed, String countryIsoCode = "SA", String transactionID = "", String amount = "0", String fileNumber = "0", + String orderDescription = "", }) async { dynamic paymentResult; @@ -325,6 +373,38 @@ class PayfortViewModel extends ChangeNotifier { "customer_name": "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}", }; + PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel(); + + payfortRequestInsertModel.accessCode = payfortProjectDetailsRespModel!.accessCode; + payfortRequestInsertModel.commandType = "PURCHASE"; + payfortRequestInsertModel.amount = num.parse(amount); + payfortRequestInsertModel.customerEmail = getIt.get().getAuthenticatedUser()!.emailAddress; + payfortRequestInsertModel.orderDescription = orderDescription; + payfortRequestInsertModel.serviceID = payfortProjectDetailsRespModel!.servID.toString(); + payfortRequestInsertModel.paymentOption = paymentOption; + payfortRequestInsertModel.clientRequestID = transactionID; + payfortRequestInsertModel.projectID = payfortProjectDetailsRespModel!.projectID.toString(); + payfortRequestInsertModel.fileNumber = getIt.get().getAuthenticatedUser()!.patientId.toString(); + payfortRequestInsertModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}"; + payfortRequestInsertModel.currency = getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; + payfortRequestInsertModel.liveServiceID = liveServiceID; + payfortRequestInsertModel.latitude = getIt.get().userLat.toString(); + payfortRequestInsertModel.longitude = getIt.get().userLong.toString(); + payfortRequestInsertModel.merchantReference = transactionID; + payfortRequestInsertModel.merchantIdentifier = payfortProjectDetailsRespModel!.merchantIdentifier; + payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + payfortRequestInsertModel.sHARequestPhase = payfortProjectDetailsRespModel!.shaRequest; + payfortRequestInsertModel.sHAResponsePhase = payfortProjectDetailsRespModel!.shaResponse; + payfortRequestInsertModel.signature = sdkTokenResponse!.signature; + payfortRequestInsertModel.language = getIt.get().isArabic() ? "ar" : "en"; + payfortRequestInsertModel.integrationId = "1"; + payfortRequestInsertModel.isSchedule = isScheduleLiveCare; + payfortRequestInsertModel.appointmentDate = appointmentDate; + payfortRequestInsertModel.appointmentNo = appointmentNo; + payfortRequestInsertModel.doctorID = doctorID; + + await payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); + LoaderBottomSheet.hideLoader(); paymentResult = await FlutterAmazonpaymentservices.normalPay(request, ApiConsts.payFortEnvironmentType, isShowResponsePage: false); @@ -332,6 +412,27 @@ class PayfortViewModel extends ChangeNotifier { String responseCode = paymentMap['response_code'] ?? ""; print("Payment Success Data: ${paymentMap.toString()}"); + PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel(); + + payfortResponseInsertModel.fortId = paymentMap['fort_id'] ?? ""; + payfortResponseInsertModel.commandType = paymentMap['command'] ?? ""; + payfortResponseInsertModel.amount = num.tryParse(paymentMap['amount'] ?? "0") ?? 0; + payfortResponseInsertModel.sadadOLP = ""; + payfortResponseInsertModel.paymentOption = paymentMap['payment_option'] ?? ""; + payfortResponseInsertModel.customerIP = paymentMap['customer_ip'] ?? ""; + payfortResponseInsertModel.eCI = paymentMap['eci'] ?? ""; + payfortResponseInsertModel.responseMessage = paymentMap['response_message'] ?? ""; + payfortResponseInsertModel.cardNumber = paymentMap['card_number'] ?? ""; + payfortResponseInsertModel.cardBin = paymentMap['card_bin'] ?? ""; + payfortResponseInsertModel.installmentsNumber = int.tryParse(paymentMap['installments_number'] ?? "0") ?? 0; + payfortResponseInsertModel.status = paymentMap['status'] ?? ""; + payfortResponseInsertModel.merchantRef = paymentMap['merchant_reference'] ?? ""; + payfortResponseInsertModel.isRefund = false; + payfortResponseInsertModel.remmeberMe = true; + payfortResponseInsertModel.reconciliationReference = ""; + + await payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); + if (responseCode.startsWith("14") || responseCode == "20064") { if (onSucceeded != null) { onSucceeded(PayFortResult.fromMap(paymentMap)); diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index b3814f00..66c88a28 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -228,8 +228,15 @@ class _AppointmentPaymentPageState extends State { serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 1); payfortViewModel.initiatePaymentWithCard( + "mada", + "0", + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false", + widget.patientAppointmentHistoryResponseModel.appointmentDate!, + widget.patientAppointmentHistoryResponseModel.appointmentNo!, + widget.patientAppointmentHistoryResponseModel.doctorID!, transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), + orderDescription: "Appointment Payment", amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); @@ -444,9 +451,16 @@ class _AppointmentPaymentPageState extends State { serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 1); payfortViewModel.initiatePaymentWithCard( + "visa", + "0", + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false", + widget.patientAppointmentHistoryResponseModel.appointmentDate!, + widget.patientAppointmentHistoryResponseModel.appointmentNo!, + widget.patientAppointmentHistoryResponseModel.doctorID!, transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), + orderDescription: "Appointment Payment", onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); 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 04fd3073..972606a9 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -184,9 +184,16 @@ class _ImmediateLiveCarePaymentPageState extends State { serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 1); payfortViewModel.initiatePaymentWithCard( + "mada", + "0", + "false", + DateUtil.convertDateToString(DateTime.now()), + "0", + "0", transactionID: transID, fileNumber: habibWalletVM.fileNumber.toString(), amount: habibWalletVM.walletRechargeAmount.toString(), + orderDescription: "Advance Payment", onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(); @@ -204,7 +212,7 @@ class _WalletPaymentConfirmPageState extends State { habibWalletVM.setIsWalletAmountToBeLoaded(true); habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); - Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + // Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); }, @@ -353,9 +361,16 @@ class _WalletPaymentConfirmPageState extends State { serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 1); payfortViewModel.initiatePaymentWithCard( + "visa", + "0", + "false", + DateUtil.convertDateToString(DateTime.now()), + "0", + "0", transactionID: transID, fileNumber: habibWalletVM.fileNumber.toString(), amount: habibWalletVM.walletRechargeAmount.toString(), + orderDescription: "Advance Payment", onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(); @@ -380,7 +395,7 @@ class _WalletPaymentConfirmPageState extends State { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); - Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + // Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); }, onError: (err) { diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 99ed0928..ae0a4c02 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -171,9 +171,16 @@ class _AncillaryOrderPaymentPageState extends State { ); payfortViewModel.initiatePaymentWithCard( + "mada", + "0", + "false", + DateUtil.convertDateToString(DateTime.now()), + "0", + "0", transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), amount: widget.totalAmount.toString(), + orderDescription: "Ancillary Order Payment", onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); @@ -286,9 +293,16 @@ class _AncillaryOrderPaymentPageState extends State { ); payfortViewModel.initiatePaymentWithCard( + "visa", + "0", + "false", + DateUtil.convertDateToString(DateTime.now()), + "0", + "0", transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), amount: widget.totalAmount.toString(), + orderDescription: "Ancillary Order Payment", onSucceeded: (paymentResult) async { print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); From e64f147f4968027cf8c404952f09dea99d11e83f Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 25 Jun 2026 15:12:22 +0300 Subject: [PATCH 6/8] updates --- lib/core/api/api_client.dart | 4 +- .../my_appointments_view_model.dart | 7 +- .../appointment_details_page.dart | 10 +++ .../appointment_payment_page.dart | 8 +-- .../widgets/select_gender_widget.dart | 9 ++- lib/presentation/home/landing_page.dart | 68 ++++++++++--------- 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 56eadd57..6a44cb0e 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,8 +210,8 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; - // body['PatientID'] = 255375; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4769748; // body['PatientID'] = 89754444; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 0862c40d..9182f9e7 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -588,7 +588,12 @@ class MyAppointmentsViewModel extends ChangeNotifier { final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + if(onError != null) { + onError(failure.message); + } + }, (apiResponse) { if (apiResponse.messageStatus == 2) { onError!(apiResponse.errorMessage!); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index d2faf2b1..4d181230 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1216,6 +1216,16 @@ class _AppointmentDetailsPageState extends State { isDismissible: false, isFullScreen: false, isSuccessDialog: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }); // LoaderBottomSheet.hideLoader(); case 15: diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 66c88a28..46130b12 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -232,8 +232,8 @@ class _AppointmentPaymentPageState extends State { "0", widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false", widget.patientAppointmentHistoryResponseModel.appointmentDate!, - widget.patientAppointmentHistoryResponseModel.appointmentNo!, - widget.patientAppointmentHistoryResponseModel.doctorID!, + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + widget.patientAppointmentHistoryResponseModel.doctorID.toString(), transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), orderDescription: "Appointment Payment", @@ -455,8 +455,8 @@ class _AppointmentPaymentPageState extends State { "0", widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false", widget.patientAppointmentHistoryResponseModel.appointmentDate!, - widget.patientAppointmentHistoryResponseModel.appointmentNo!, - widget.patientAppointmentHistoryResponseModel.doctorID!, + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + widget.patientAppointmentHistoryResponseModel.doctorID.toString(), transactionID: transID, fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), diff --git a/lib/presentation/blood_donation/widgets/select_gender_widget.dart b/lib/presentation/blood_donation/widgets/select_gender_widget.dart index 1368c4e3..e51d3e78 100644 --- a/lib/presentation/blood_donation/widgets/select_gender_widget.dart +++ b/lib/presentation/blood_donation/widgets/select_gender_widget.dart @@ -57,9 +57,12 @@ class SelectGenderWidget extends StatelessWidget { ), ], ).paddingSymmetrical(16.h, 16.h).onPress(() { - final genderValue = isArabic - ? GenderTypeEnum.values[index].typeAr - : GenderTypeEnum.values[index].type; + final genderValue = + // isArabic + // ? GenderTypeEnum.values[index].typeAr + + // : + GenderTypeEnum.values[index].type; bloodDonationViewModel.onGenderChange(genderValue); Navigator.of(context).pop(); })); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 531b310a..85f1e972 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -448,31 +448,31 @@ class _LandingPageState extends State { : SizedBox.shrink(), // Offers And Discounts Carousel - Auto-scrolling from right to left - appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel - ? Column( - children: [ - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage())); - }), - SizedBox(height: 16.h), - OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h), - SizedBox(height: 18.h), - ], - ) - : SizedBox.shrink(), + // appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel + // ? Column( + // children: [ + // SizedBox(height: 12.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true), + // Row( + // children: [ + // LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + // SizedBox(width: 2.h), + // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + // ], + // ), + // ], + // ).paddingSymmetrical(24.h, 0.h).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage())); + // }), + // SizedBox(height: 16.h), + // OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h), + // SizedBox(height: 18.h), + // ], + // ) + // : SizedBox.shrink(), appState.isAuthenticated ? Column( @@ -516,7 +516,7 @@ class _LandingPageState extends State { ), ).paddingSymmetrical(24.h, 16.h) : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 && !immediateLiveCareVM.patientHasPendingLiveCareRequest ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -536,9 +536,11 @@ class _LandingPageState extends State { height: isTablet ? 290.h : 255.h, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, + itemCount: () { + final int baseCount = + myAppointmentsVM.patientAppointmentsHistoryList.length + (immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0); + return baseCount < 3 ? baseCount : 3; + }(), shrinkWrap: true, padding: EdgeInsets.only(left: 16.h, right: 16.h), itemBuilder: (context, index) { @@ -571,10 +573,14 @@ class _LandingPageState extends State { height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space child: Builder( builder: (context) { + final int baseAppointmentCount = myAppointmentsVM.patientAppointmentsHistoryList.length; + final int liveCareCount = immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0; + final int totalCount = baseAppointmentCount + liveCareCount; + final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length + : totalCount < 3 + ? totalCount : 3; final bool isArabic = appState.isArabic(); return Swiper( From b5d95a5a74b8248bffb107110d248de8639b5c14 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 25 Jun 2026 17:15:29 +0300 Subject: [PATCH 7/8] updates --- lib/features/payfort/payfort_view_model.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index fd0cfe10..81d66de2 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -6,13 +6,13 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; -import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; @@ -24,6 +24,8 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:crypto/crypto.dart'; +import 'models/apple_pay_request_insert_model.dart'; + class PayfortViewModel extends ChangeNotifier { PayfortRepo payfortRepo; ErrorHandlerService errorHandlerService; From eafb7aa10580a6b9660853c64ed47f058ffe3b79 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sat, 27 Jun 2026 22:55:58 +0300 Subject: [PATCH 8/8] updates --- .../svg/doctor_profile_schedule_icon.svg | 9 ++++ assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 3 +- lib/core/api_consts.dart | 2 +- lib/core/app_assets.dart | 1 + .../habib_wallet/habib_wallet_view_model.dart | 2 +- lib/features/payfort/payfort_view_model.dart | 1 - lib/generated/locale_keys.g.dart | 1 + .../book_appointment/doctor_profile_page.dart | 9 ++++ .../habib_wallet/habib_wallet_page.dart | 37 ++++++++++--- .../widgets/wallet_balance_list.dart | 54 +++++++++++++++++++ lib/widgets/app_language_change.dart | 1 + pubspec.yaml | 2 +- 13 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 assets/images/svg/doctor_profile_schedule_icon.svg create mode 100644 lib/presentation/habib_wallet/widgets/wallet_balance_list.dart diff --git a/assets/images/svg/doctor_profile_schedule_icon.svg b/assets/images/svg/doctor_profile_schedule_icon.svg new file mode 100644 index 00000000..20e36299 --- /dev/null +++ b/assets/images/svg/doctor_profile_schedule_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 0f434598..b69827d4 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1960,5 +1960,6 @@ "parkingQR": "رمز الاستجابة السريعة لوقوف السيارات", "parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:", "livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.", - "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟" + "livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟", + "schedule": "جدول" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index cedcd40a..bb5b5fe4 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1949,7 +1949,8 @@ "parkingQR": "Parking QR Code", "parkingQRDesc": "Scan the below QR Code at the exit parking gate:", "livecareUpdateInsuranceSent": "Your insurance update request is being processed. You will be notified once the update is completed.", - "livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?" + "livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?", + "schedule": "Schedule" } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 04d0c962..664432fd 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -5,7 +5,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_assets.dart b/lib/core/app_assets.dart index 2e27d633..aba22eb3 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -193,6 +193,7 @@ class AppAssets { static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg'; static const String doctor_profile_rating_icon = '$svgBasePath/doctor_profile_rating_icon.svg'; static const String doctor_profile_reviews_icon = '$svgBasePath/doctor_profile_reviews_icon.svg'; + static const String doctor_profile_schedule_icon = '$svgBasePath/doctor_profile_schedule_icon.svg'; static const String bookmark_icon = '$svgBasePath/bookmark_icon.svg'; static const String bookmark_filled_icon = '$svgBasePath/bookmark_filled_icon.svg'; static const String waiting_appointment_icon = '$svgBasePath/waitingAppo.svg'; diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 3bee29de..cc87bb7b 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -335,7 +335,6 @@ class HabibWalletViewModel extends ChangeNotifier { walletRechargeAmount = 0; selectedRechargeType = 1; advancePaymentHospitals.clear(); - habibWalletBalanceList.clear(); selectedHospital = null; fileNumber = ''; depositorName = ''; @@ -417,6 +416,7 @@ class HabibWalletViewModel extends ChangeNotifier { if (!isWalletAmountToBeLoaded) { return; } + habibWalletBalanceList.clear(); isWalletAmountLoading = true; notifyListeners(); diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 81d66de2..f567264f 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -6,7 +6,6 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart'; -import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 509fc86a..c82e2d12 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1942,5 +1942,6 @@ abstract class LocaleKeys { static const parkingQRDesc = 'parkingQRDesc'; static const livecareUpdateInsuranceSent = 'livecareUpdateInsuranceSent'; static const livecareUpdateInsurance = 'livecareUpdateInsurance'; + static const schedule = 'schedule'; } diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index be097651..f54f95bf 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -192,6 +192,15 @@ class DoctorProfilePage extends StatelessWidget { isCloseButtonVisible: true, ); }), + SizedBox(width: 36.w), + Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_schedule_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), + SizedBox(height: 16.h), + LocaleKeys.schedule.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), + LocaleKeys.view.tr(context: context).toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), + ], + ).onPress(() {}) ], ), SizedBox(height: 16.h), diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index c4ee9356..07df55ea 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/wallet_balance_list.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/withdraw_request_create_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart'; @@ -97,15 +98,39 @@ class _HabibWalletState extends State { Spacer(), LocaleKeys.balanceAmount.tr(context: context).toText14(isBold: true, color: Colors.white), SizedBox(height: 4.h), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, - textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false, letterSpacing: -1) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h); - }), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, + textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false, letterSpacing: -1) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h); + }), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + height: 24.h, + width: 24.h, + iconColor: AppColors.whiteColor, + ), + ), + ], + ), ], ), ), - ), + ).onPress(() { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.habibWallet.tr(context: context), + context, + // child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), + child: HabibWalletBalanceList(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart b/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart new file mode 100644 index 00000000..62817e98 --- /dev/null +++ b/lib/presentation/habib_wallet/widgets/wallet_balance_list.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +class HabibWalletBalanceList extends StatelessWidget { + const HabibWalletBalanceList({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Consumer(builder: (context, habibWalletVM, child) { + return ListView.separated( + itemCount: habibWalletVM.habibWalletBalanceList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (context, index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: habibWalletVM.habibWalletBalanceList[index].projectDescription!.toText16(isBold: true, color: AppColors.textColor)), + Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletBalanceList[index].patientAdvanceBalanceAmount!, //did the changes because of negative value + textColor: AppColors.textColor, + iconColor: AppColors.textColor, + iconSize: 18.h, + isExpanded: false, + fontSize: 28.f, + fontWeight: FontWeight.w600, + letterSpacing: -1), + ], + ).paddingSymmetrical(0, 12.h); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(height: 1, color: AppColors.textColor.withAlpha(20)); + }, + ).paddingSymmetrical(16.h, 24.h).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading); + }), + ), + ], + ); + } +} diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index ff8f9c59..c2ae0272 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -75,6 +75,7 @@ class _AppLanguageChangeState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + habibWalletVM.setIsWalletAmountToBeLoaded(true); habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); habibWalletVM.getLakumAccountInformation(); diff --git a/pubspec.yaml b/pubspec.yaml index 880ec18e..ba0c0ed5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -87,7 +87,7 @@ dependencies: open_filex: ^4.7.0 flutter_swiper_view: path: package/flutter_swiper_view - flutter_callkit_incoming: ^3.0.0 + flutter_callkit_incoming: 3.0.0 in_app_update: ^4.2.5 # device_preview: ^1.3.1 image_picker_android: ^0.8.13+16