diff --git a/android/app/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar index a5658605..2ded149e 100644 Binary files a/android/app/libs/PenNavUI.aar and b/android/app/libs/PenNavUI.aar differ diff --git a/android/app/libs/Penguin.aar b/android/app/libs/Penguin.aar index a769c7a2..41ea58d1 100644 Binary files a/android/app/libs/Penguin.aar and b/android/app/libs/Penguin.aar differ diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar index b498726f..cc5ac36d 100644 Binary files a/android/app/libs/PenguinRenderer.aar and b/android/app/libs/PenguinRenderer.aar differ diff --git a/assets/images/png/lakum_logo_white.png b/assets/images/png/lakum_logo_white.png new file mode 100644 index 00000000..db45e5a9 Binary files /dev/null and b/assets/images/png/lakum_logo_white.png differ diff --git a/assets/images/svg/expired_icon.svg b/assets/images/svg/expired_icon.svg new file mode 100644 index 00000000..490cc412 --- /dev/null +++ b/assets/images/svg/expired_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/hmg_pharmacy_logo.svg b/assets/images/svg/hmg_pharmacy_logo.svg new file mode 100644 index 00000000..e069d513 --- /dev/null +++ b/assets/images/svg/hmg_pharmacy_logo.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/redeem_icon.svg b/assets/images/svg/redeem_icon.svg new file mode 100644 index 00000000..52eb8815 --- /dev/null +++ b/assets/images/svg/redeem_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index dab50baa..e0faf4e5 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -220,7 +220,7 @@ "request": "الطلبات", "memberName": "اسم العضو", "switchLogin": "عرض الملف", - "removeMember": "إزالة", + "removeMember": "حذف", "allowView": "السماح", "rejectView": "رفض", "deleteView": "حذف", @@ -1845,5 +1845,10 @@ "maxOneFileAllowed": "يمكن رفع ملف واحد فقط", "fileSizeExceedsLimit": "يجب ألا يتجاوز حجم الملف 1 ميغابايت", "completedPrescriptionOrder": "تمت الخدمة", - "submitEReferral": "تقديم الطلب" + "submitEReferral": "تقديم الطلب", + "redeem": "استبدال", + "points": "نقاط", + "transactions": "المعاملات", + "pharmacy": "الصيدلية", + "visitsOrders": "الزيارات/الطلبات" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 58d49f30..e8409260 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -576,7 +576,7 @@ "active": "Active", "inactive": "InActive", "balance": "Balance", - "gained": "GAINED", + "gained": "Gained", "consumed": "Consumed", "transferred": "Transferred", "riyal": "RIYAL", @@ -1835,7 +1835,12 @@ "maxOneFileAllowed": "Only 1 file can be uploaded", "fileSizeExceedsLimit": "File size must not exceed 1 MB", "completedPrescriptionOrder": "Completed", - "submitEReferral": "Submit" + "submitEReferral": "Submit", + "redeem": "Redeem", + "points": "Points", + "transactions": "Transactions", + "pharmacy": "Pharmacy", + "visitsOrders": "Visits/Orders" } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index e46de55c..8dbc9ce3 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -281,7 +281,7 @@ class ApiConsts { static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; // ************ static values for Api **************** - static final double appVersionID = 21.1; + static final double appVersionID = 21.2; // static final double appVersionID = 50.7; static final int appChannelId = 3; @@ -872,6 +872,7 @@ var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoct // var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = 'Services/Patients.svc/REST/GetAllInvoices'; +var GET_ALL_PHARMACY_INVOICES = 'Services/Patients.svc/REST/GetAllPharmacyInvoices'; var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo"; var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental"; @@ -995,6 +996,8 @@ const UPLOAD_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_InsertProfileIm const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport'; +const DOWNLOAD_PHARMACY_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 3c80296d..d6e041da 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -380,6 +380,9 @@ class AppAssets { static const String gallery = '$svgBasePath/gallery.svg'; static const String files = '$svgBasePath/files.svg'; static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg'; + static const String hmg_pharmacy_logo = '$svgBasePath/hmg_pharmacy_logo.svg'; + static const String redeem_icon = '$svgBasePath/redeem_icon.svg'; + static const String expired_icon = '$svgBasePath/expired_icon.svg'; //Health Calculators static const String bodyfatcalc = '$svgBasePath/bfc.svg'; @@ -421,6 +424,7 @@ class AppAssets { static const String maleIcon = '$pngBasePath/male_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png'; static const String lakumLogo = '$pngBasePath/lakum_logo.png'; + static const String lakumLogoWhite = '$pngBasePath/lakum_logo_white.png'; static const String fullBodyFrontMale = '$pngBasePath/full_body_front_male.png'; static const String fullBodyBackMale = '$pngBasePath/full_body_back_male.png'; @@ -430,8 +434,6 @@ class AppAssets { static const String bmiFullBodyFemale = '$pngBasePath/female_bmi.png'; static const String defaultMEP = '$pngBasePath/default_MEP.png'; - - static const String ambulance_section = '$pngBasePath/ambulance_section.png'; static const String rrt_section = '$pngBasePath/rrt_section.png'; static const String er_checkin = '$pngBasePath/er_checkin.png'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 21f93eed..5eb7a05d 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -44,6 +44,8 @@ class AppState { AuthenticatedUser? _authenticatedRootUser; AuthenticatedUser? _authenticatedChildUser; + bool isPaytabsEnabled = false; + int? _superUserID; bool isChildLoggedIn = false; bool isGMSAvailable = true; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0380a9a3..a45e9e6d 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -50,6 +50,7 @@ import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_dis import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_picture/profile_picture_view_model.dart'; @@ -342,5 +343,7 @@ class AppDependencies { getIt.registerLazySingleton(() => OffersAndDiscountsViewModel(offersAndDiscountsRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); + + getIt.registerLazySingleton(() => PayTabsViewModel(appState: getIt())); } } diff --git a/lib/core/paytabs_config.dart b/lib/core/paytabs_config.dart new file mode 100644 index 00000000..4d90ee8f --- /dev/null +++ b/lib/core/paytabs_config.dart @@ -0,0 +1,12 @@ +/// Default configuration values for PayTabs Payment SDK +class PaymentSdkDefaultConfig { + static const String profileID = "119730"; + static const String serverKey = "SWJ9LD22MT-JLJZLBKTLK-WGWTLWGTWR"; + static const String clientKey = "C7K2DQ-PDDN6B-69B2K7-P9GB9T"; + static const String defaultCurrency = "SAR"; + static const double defaultAmount = 10.0; + static const String defaultMerchantCountryCode = "SA"; + static const String defaultMerchantName = "Sulaiman Al Habib Medical Group"; + static const String defaultMerchantAppleBundleID = "merchant.com.hmgwebservices"; + static const String defaultMerchantAppleBundleIDUAT = "merchant.com.hmgwebservices.uat"; +} diff --git a/lib/core/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart index c3da7242..939b98a9 100644 --- a/lib/core/utils/penguin_method_channel.dart +++ b/lib/core/utils/penguin_method_channel.dart @@ -22,7 +22,6 @@ class PenguinMethodChannel { "baseURL": "https://eagleiq.hmg.com", "dataURL": "https://eagleiq.hmg.com", "positionURL": "https://eagleiq.hmg.com", - "dataServiceName": "api", "positionServiceName": "pe", "clientID": "HMG", diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 1cf8094d..ef83cb9f 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -99,7 +99,11 @@ class Utils { static bool isArabicText(String inputText) { bool isArabicText = false; - final arabic = RegExp(r'^[\u0621-\u064A]+'); + // Updated regex to include: + // - Arabic letters: \u0621-\u064A + // - Arabic numerals: \u0660-\u0669 (٠-٩) + // - Persian numerals: \u06F0-\u06F9 (۰-۹) + final arabic = RegExp(r'[\u0621-\u064A\u0660-\u0669\u06F0-\u06F9]'); if (arabic.hasMatch(inputText)) { isArabicText = true; } else { @@ -872,25 +876,25 @@ class Utils { ); } - static Widget getPaymentMethods() { + static Widget getPaymentMethods({num spacing = 3, num width = 35, num height = 35}) { return Row( - spacing: 6.w, + spacing: spacing.w, mainAxisSize: MainAxisSize.max, children: [ - Image.asset(AppAssets.mada, width: 35.h, height: 35.h), + Image.asset(AppAssets.mada, width: width.h, height: height.h), Image.asset( AppAssets.tamaraEng, - width: 35.h, - height: 35.h, + width: width.h, + height: height.h, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { debugPrint('Failed to load Tamara PNG in payment methods: $error'); return Utils.buildSvgWithAssets(icon: AppAssets.tamara, width: 35.h, height: 35.h, fit: BoxFit.contain); }, ), - Image.asset(AppAssets.visa, width: 35.h, height: 35.h), - Image.asset(AppAssets.mastercard, width: 35.h, height: 25.h), - Image.asset(AppAssets.applePay, width: 35.h, height: 35.h), + Image.asset(AppAssets.visa, width: width.h, height: height.h), + Image.asset(AppAssets.mastercard, width: width.h, height: height.h), + Platform.isIOS ? Image.asset(AppAssets.applePay, width: width.h, height: height.h) : SizedBox.shrink(), ], ); } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 737df465..41ca087a 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; @@ -451,7 +452,9 @@ class _OTPVerificationScreenState extends State { super.initState(); _otpController = TextEditingController(); _startResendTimer(); - checkSignature(); + if(Platform.isAndroid) { + checkSignature(); + } } @override diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 7ac09686..e6cfa6c2 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -866,6 +866,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { initialSlotDuration = apiResponse.data["InitialSlotDuration"]; freeSlotsResponse = apiResponse.data['FreeTimeSlots']; isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"]; + // isWaitingAppointmentAvailable = true; freeSlotsResponse.forEach((element) { @@ -1479,6 +1480,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { + if(onError != null) { + onError(failure.message); + } onError!(LocaleKeys.noDoctorFound.tr()); }, (apiResponse) { diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 00ef8dbc..648fab6b 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -345,7 +345,7 @@ class ContactUsViewModel extends ChangeNotifier { } } else if (apiResponse.messageStatus == 1) { cocItemsList = apiResponse.data!; - cocItemsList.sort((a, b) => a.date!.compareTo(b.date!)); + cocItemsList.sort((a, b) => b.date!.compareTo(a.date!)); isCOCItemsListLoading = false; notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/habib_wallet/habib_wallet_repo.dart b/lib/features/habib_wallet/habib_wallet_repo.dart index c89a85be..57d5af20 100644 --- a/lib/features/habib_wallet/habib_wallet_repo.dart +++ b/lib/features/habib_wallet/habib_wallet_repo.dart @@ -18,6 +18,10 @@ abstract class HabibWalletRepo { Future>> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference}); Future>> getPatientInfoByPatientID({required String patientID}); + + Future>> getLakumAccountInformation({required String identificationNumber}); + + Future>> getLakumAccountInquiryInformation({required String lakumAccountNumber}); } class HabibWalletRepoImp implements HabibWalletRepo { @@ -231,4 +235,75 @@ class HabibWalletRepoImp implements HabibWalletRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getLakumAccountInformation({required String identificationNumber}) async { + Map requestBody = {"IdentificationNo": identificationNumber}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_LACUM_ACCOUNT_INFORMATION, + body: requestBody, + 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()); + } + }, + ); + 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> getLakumAccountInquiryInformation({required String lakumAccountNumber}) async { + Map requestBody = { + "AccountNumber": lakumAccountNumber, + "IsDetailsRequired": true, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_LACUM_GROUP_INFORMATION, + body: requestBody, + 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()); + } + }, + ); + 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/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 01dc2357..21139c80 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -1,6 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/lakum_inquiry_information_response_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/models/patient_advance_balance_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -43,6 +47,54 @@ class HabibWalletViewModel extends ChangeNotifier { String? get hospitalError => _hospitalError; String yahalaAccountNumber = ''; + bool isLakumAccountInfoLoading = false; + LakumInquiryInformationResponseModel? lakumAccountInfo; + + // Get all flattened transactions sorted by date descending + List getAllLakumTransactions() { + if (lakumAccountInfo?.gainedPointsAmountPerYear == null) { + return []; + } + + List allTransactions = []; + + // Flatten the nested structure + for (var yearData in lakumAccountInfo!.gainedPointsAmountPerYear!) { + if (yearData.pointsAmountPerMonth != null) { + for (var monthData in yearData.pointsAmountPerMonth!) { + if (monthData.pointsAmountPerday != null) { + for (var dayData in monthData.pointsAmountPerday!) { + if (dayData.pointsDetails != null) { + allTransactions.addAll(dayData.pointsDetails!); + } + } + } + } + } + } + + // Sort by transaction date DESCENDING (latest/most recent transactions first, older transactions last) + allTransactions.sort((a, b) { + // Null dates go to the end + if (a.transactionDate == null && b.transactionDate == null) return 0; + if (a.transactionDate == null) return 1; // a goes after b + if (b.transactionDate == null) return -1; // a goes before b + + try { + DateTime dateA = DateUtil.convertStringToDate(a.transactionDate!); + DateTime dateB = DateUtil.convertStringToDate(b.transactionDate!); + + // Compare dateB to dateA (not dateA to dateB) for DESCENDING order + // This puts newer dates (larger values) at the top of the list + return dateB.compareTo(dateA); + } catch (e) { + // If date parsing fails, maintain current order + return 0; + } + }); + + return allTransactions; + } // Clear amount error void clearAmountError() { @@ -103,6 +155,7 @@ class HabibWalletViewModel extends ChangeNotifier { initHabibWalletProvider() { isWalletAmountLoading = true; isBottomSheetContentLoading = false; + isLakumAccountInfoLoading = true; habibWalletAmount = 0; walletRechargeAmount = 0; selectedRechargeType = 1; @@ -112,6 +165,7 @@ class HabibWalletViewModel extends ChangeNotifier { fileNumber = ''; depositorName = ''; mobileNumber = ''; + yahalaAccountNumber = ""; notifyListeners(); } @@ -148,6 +202,11 @@ class HabibWalletViewModel extends ChangeNotifier { notifyListeners(); } + setYahalaAccountNumber(String accountNumber) { + yahalaAccountNumber = accountNumber; + notifyListeners(); + } + String getSelectedRechargeTypeValue() { switch (selectedRechargeType) { case 1: @@ -221,7 +280,12 @@ class HabibWalletViewModel extends ChangeNotifier { paymentMethodName: paymentMethodName, paidAmount: paidAmount, paymentReference: paymentReference, patientID: patientID, projectID: projectID, depositorName: depositorName); 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) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); @@ -277,4 +341,46 @@ class HabibWalletViewModel extends ChangeNotifier { }, ); } + + Future getLakumAccountInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isLakumAccountInfoLoading = true; + lakumAccountInfo = LakumInquiryInformationResponseModel(); + notifyListeners(); + + final result = await habibWalletRepo.getLakumAccountInformation(identificationNumber: getIt.get().getAuthenticatedUser()!.patientIdentificationNo ?? ""); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + yahalaAccountNumber = "0"; + isLakumAccountInfoLoading = false; + notifyListeners(); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data["YahalaAccountNo"] == 0 || apiResponse.data["YahalaAccountNo"] == "0") { + yahalaAccountNumber = "0"; + } else { + yahalaAccountNumber = apiResponse.data["YahalaAccountNo"].toString(); + + final lakumAccountResult = await habibWalletRepo.getLakumAccountInquiryInformation(lakumAccountNumber: yahalaAccountNumber); + lakumAccountResult.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (lakumApiResponse) async { + if (lakumApiResponse.messageStatus == 2) { + lakumAccountInfo = LakumInquiryInformationResponseModel(); + } else if (lakumApiResponse.messageStatus == 1) { + lakumAccountInfo = LakumInquiryInformationResponseModel.fromJson(lakumApiResponse.data["LakumInquiryInformationObjVersion"]); + } + }, + ); + } + isLakumAccountInfoLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart b/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart new file mode 100644 index 00000000..621434fe --- /dev/null +++ b/lib/features/habib_wallet/models/lakum_inquiry_information_response_model.dart @@ -0,0 +1,318 @@ +class LakumInquiryInformationResponseModel { + int? accountNumber; + String? accountStatus; + String? barCode; + num? consumedPoints; + dynamic consumedPointsAmount; + dynamic consumedPointsAmountPerYear; + + // List? consumedPointsDetails; + String? createdDate; + num? expiredPoints; + String? expiryDate; + num? gainedPoints; + num? gainedPointsAmount; + List? gainedPointsAmountPerYear; + + // List? gainedPointsDetails; + String? lakumMessageStatus; + String? memberName; + String? memberUniversalId; + String? mobileNumber; + num? pointsBalance; + num? pointsBalanceAmount; + num? pointsWillBeExpired; + String? prefLang; + num? statusCode; + num? transferPoints; + dynamic transferPointsAmountPerYear; + + // List? transferPointsDetails; + num? waitingPoints; + num? loyalityAmount; + num? loyalityPoints; + num? purchaseRate; + + LakumInquiryInformationResponseModel( + {this.accountNumber, + this.accountStatus, + this.barCode, + this.consumedPoints, + this.consumedPointsAmount, + this.consumedPointsAmountPerYear, + // this.consumedPointsDetails, + this.createdDate, + this.expiredPoints, + this.expiryDate, + this.gainedPoints, + this.gainedPointsAmount, + this.gainedPointsAmountPerYear, + // this.gainedPointsDetails, + this.lakumMessageStatus, + this.memberName, + this.memberUniversalId, + this.mobileNumber, + this.pointsBalance, + this.pointsBalanceAmount, + this.pointsWillBeExpired, + this.prefLang, + this.statusCode, + this.transferPoints, + this.transferPointsAmountPerYear, + // this.transferPointsDetails, + this.waitingPoints, + this.loyalityAmount, + this.loyalityPoints, + this.purchaseRate}); + + LakumInquiryInformationResponseModel.fromJson(Map json) { + accountNumber = json['AccountNumber']; + accountStatus = json['AccountStatus']; + barCode = json['BarCode']; + consumedPoints = json['ConsumedPoints']; + consumedPointsAmount = json['ConsumedPointsAmount']; + consumedPointsAmountPerYear = json['ConsumedPointsAmountPerYear']; + // if (json['ConsumedPointsDetails'] != null) { + // consumedPointsDetails = []; + // json['ConsumedPointsDetails'].forEach((v) { + // consumedPointsDetails!.add(new Null.fromJson(v)); + // }); + // } + createdDate = json['CreatedDate']; + expiredPoints = json['ExpiredPoints']; + expiryDate = json['ExpiryDate']; + gainedPoints = json['GainedPoints']; + gainedPointsAmount = json['GainedPointsAmount']; + if (json['GainedPointsAmountPerYear'] != null) { + gainedPointsAmountPerYear = []; + json['GainedPointsAmountPerYear'].forEach((v) { + gainedPointsAmountPerYear!.add(new GainedPointsAmountPerYear.fromJson(v)); + }); + } + // if (json['GainedPointsDetails'] != null) { + // gainedPointsDetails = []; + // json['GainedPointsDetails'].forEach((v) { + // gainedPointsDetails!.add(new Null.fromJson(v)); + // }); + // } + lakumMessageStatus = json['LakumMessageStatus']; + memberName = json['MemberName']; + memberUniversalId = json['MemberUniversalId']; + mobileNumber = json['MobileNumber']; + pointsBalance = json['PointsBalance']; + pointsBalanceAmount = json['PointsBalanceAmount']; + pointsWillBeExpired = json['PointsWillBeExpired']; + prefLang = json['PrefLang']; + statusCode = json['StatusCode']; + transferPoints = json['TransferPoints']; + transferPointsAmountPerYear = json['TransferPointsAmountPerYear']; + // if (json['TransferPointsDetails'] != null) { + // transferPointsDetails = []; + // json['TransferPointsDetails'].forEach((v) { + // transferPointsDetails!.add(new Null.fromJson(v)); + // }); + // } + waitingPoints = json['WaitingPoints']; + loyalityAmount = json['loyalityAmount']; + loyalityPoints = json['loyalityPoints']; + purchaseRate = json['purchaseRate']; + } + + Map toJson() { + final Map data = new Map(); + data['AccountNumber'] = this.accountNumber; + data['AccountStatus'] = this.accountStatus; + data['BarCode'] = this.barCode; + data['ConsumedPoints'] = this.consumedPoints; + data['ConsumedPointsAmount'] = this.consumedPointsAmount; + data['ConsumedPointsAmountPerYear'] = this.consumedPointsAmountPerYear; + // if (this.consumedPointsDetails != null) { + // data['ConsumedPointsDetails'] = this.consumedPointsDetails!.map((v) => v.toJson()).toList(); + // } + data['CreatedDate'] = this.createdDate; + data['ExpiredPoints'] = this.expiredPoints; + data['ExpiryDate'] = this.expiryDate; + data['GainedPoints'] = this.gainedPoints; + data['GainedPointsAmount'] = this.gainedPointsAmount; + if (this.gainedPointsAmountPerYear != null) { + data['GainedPointsAmountPerYear'] = this.gainedPointsAmountPerYear!.map((v) => v.toJson()).toList(); + } + // if (this.gainedPointsDetails != null) { + // data['GainedPointsDetails'] = this.gainedPointsDetails!.map((v) => v.toJson()).toList(); + // } + data['LakumMessageStatus'] = this.lakumMessageStatus; + data['MemberName'] = this.memberName; + data['MemberUniversalId'] = this.memberUniversalId; + data['MobileNumber'] = this.mobileNumber; + data['PointsBalance'] = this.pointsBalance; + data['PointsBalanceAmount'] = this.pointsBalanceAmount; + data['PointsWillBeExpired'] = this.pointsWillBeExpired; + data['PrefLang'] = this.prefLang; + data['StatusCode'] = this.statusCode; + data['TransferPoints'] = this.transferPoints; + data['TransferPointsAmountPerYear'] = this.transferPointsAmountPerYear; + // if (this.transferPointsDetails != null) { + // data['TransferPointsDetails'] = this.transferPointsDetails!.map((v) => v.toJson()).toList(); + // } + data['WaitingPoints'] = this.waitingPoints; + data['loyalityAmount'] = this.loyalityAmount; + data['loyalityPoints'] = this.loyalityPoints; + data['purchaseRate'] = this.purchaseRate; + return data; + } +} + +class GainedPointsAmountPerYear { + num? amountPerYear; + List? pointsAmountPerMonth; + num? pointsPerYear; + int? year; + + GainedPointsAmountPerYear({this.amountPerYear, this.pointsAmountPerMonth, this.pointsPerYear, this.year}); + + GainedPointsAmountPerYear.fromJson(Map json) { + amountPerYear = json['AmountPerYear']; + if (json['PointsAmountPerMonth'] != null) { + pointsAmountPerMonth = []; + json['PointsAmountPerMonth'].forEach((v) { + pointsAmountPerMonth!.add(new PointsAmountPerMonth.fromJson(v)); + }); + } + pointsPerYear = json['PointsPerYear']; + year = json['Year']; + } + + Map toJson() { + final Map data = new Map(); + data['AmountPerYear'] = this.amountPerYear; + if (this.pointsAmountPerMonth != null) { + data['PointsAmountPerMonth'] = this.pointsAmountPerMonth!.map((v) => v.toJson()).toList(); + } + data['PointsPerYear'] = this.pointsPerYear; + data['Year'] = this.year; + return data; + } +} + +class PointsAmountPerMonth { + num? amountPerMonth; + String? month; + num? monthNumber; + List? pointsAmountPerday; + num? pointsPerMonth; + + PointsAmountPerMonth({this.amountPerMonth, this.month, this.monthNumber, this.pointsAmountPerday, this.pointsPerMonth}); + + PointsAmountPerMonth.fromJson(Map json) { + amountPerMonth = json['AmountPerMonth']; + month = json['Month']; + monthNumber = json['MonthNumber']; + if (json['PointsAmountPerday'] != null) { + pointsAmountPerday = []; + json['PointsAmountPerday'].forEach((v) { + pointsAmountPerday!.add(new PointsAmountPerday.fromJson(v)); + }); + } + pointsPerMonth = json['PointsPerMonth']; + } + + Map toJson() { + final Map data = new Map(); + data['AmountPerMonth'] = this.amountPerMonth; + data['Month'] = this.month; + data['MonthNumber'] = this.monthNumber; + if (this.pointsAmountPerday != null) { + data['PointsAmountPerday'] = this.pointsAmountPerday!.map((v) => v.toJson()).toList(); + } + data['PointsPerMonth'] = this.pointsPerMonth; + return data; + } +} + +class PointsAmountPerday { + num? amountPerDay; + String? day; + List? pointsDetails; + num? pointsPerDay; + String? transationDate; + + PointsAmountPerday({this.amountPerDay, this.day, this.pointsDetails, this.pointsPerDay, this.transationDate}); + + PointsAmountPerday.fromJson(Map json) { + amountPerDay = json['AmountPerDay']; + day = json['Day']; + if (json['PointsDetails'] != null) { + pointsDetails = []; + json['PointsDetails'].forEach((v) { + pointsDetails!.add(new PointsDetails.fromJson(v)); + }); + } + pointsPerDay = json['PointsPerDay']; + transationDate = json['TransationDate']; + } + + Map toJson() { + final Map data = new Map(); + data['AmountPerDay'] = this.amountPerDay; + data['Day'] = this.day; + if (this.pointsDetails != null) { + data['PointsDetails'] = this.pointsDetails!.map((v) => v.toJson()).toList(); + } + data['PointsPerDay'] = this.pointsPerDay; + data['TransationDate'] = this.transationDate; + return data; + } +} + +class PointsDetails { + int? accNumber; + String? accountStatus; + num? amount; + int? lineItemNo; + String? operationType; + num? points; + num? purchasePoints; + int? subTransactionType; + String? subTransactionTypeDescription; + String? transactionDate; + + PointsDetails( + {this.accNumber, + this.accountStatus, + this.amount, + this.lineItemNo, + this.operationType, + this.points, + this.purchasePoints, + this.subTransactionType, + this.subTransactionTypeDescription, + this.transactionDate}); + + PointsDetails.fromJson(Map json) { + accNumber = json['AccNumber']; + accountStatus = json['AccountStatus']; + amount = json['Amount']; + lineItemNo = json['LineItemNo']; + operationType = json['OperationType']; + points = json['Points']; + purchasePoints = json['PurchasePoints']; + subTransactionType = json['SubTransactionType']; + subTransactionTypeDescription = json['SubTransactionTypeDescription']; + transactionDate = json['TransactionDate']; + } + + Map toJson() { + final Map data = new Map(); + data['AccNumber'] = this.accNumber; + data['AccountStatus'] = this.accountStatus; + data['Amount'] = this.amount; + data['LineItemNo'] = this.lineItemNo; + data['OperationType'] = this.operationType; + data['Points'] = this.points; + data['PurchasePoints'] = this.purchasePoints; + data['SubTransactionType'] = this.subTransactionType; + data['SubTransactionTypeDescription'] = this.subTransactionTypeDescription; + data['TransactionDate'] = this.transactionDate; + return data; + } +} diff --git a/lib/features/my_invoices/models/get_pharmacy_invoices_response_model.dart b/lib/features/my_invoices/models/get_pharmacy_invoices_response_model.dart new file mode 100644 index 00000000..cfa19900 --- /dev/null +++ b/lib/features/my_invoices/models/get_pharmacy_invoices_response_model.dart @@ -0,0 +1,99 @@ +class GetPharmacyInvoicesResponseModel { + String? setupId; + int? projectID; + int? pharmacyProjectID; + int? patientID; + int? appointmentNo; + String? appointmentDate; + dynamic appointmentDateN; + int? clinicID; + int? doctorID; + int? invoiceNo; + int? status; + String? arrivedOn; + String? doctorName; + String? doctorNameN; + String? displayInvoiceNo; + String? clinicName; + num? decimalDoctorRate; + String? doctorImageURL; + num? doctorRate; + num? patientNumber; + String? projectName; + dynamic locationID; + + GetPharmacyInvoicesResponseModel( + {this.setupId, + this.projectID, + this.pharmacyProjectID, + this.patientID, + this.appointmentNo, + this.appointmentDate, + this.appointmentDateN, + this.clinicID, + this.doctorID, + this.invoiceNo, + this.status, + this.arrivedOn, + this.doctorName, + this.doctorNameN, + this.displayInvoiceNo, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorRate, + this.patientNumber, + this.projectName, this.locationID}); + + GetPharmacyInvoicesResponseModel.fromJson(Map json) { + setupId = json['SetupId']; + projectID = json['ProjectID']; + pharmacyProjectID = json['PharmacyProjectID']; + patientID = json['PatientID']; + appointmentNo = json['AppointmentNo']; + appointmentDate = json['AppointmentDate']; + appointmentDateN = json['AppointmentDateN']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + invoiceNo = json['InvoiceNo']; + status = json['Status']; + arrivedOn = json['ArrivedOn']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + displayInvoiceNo = json['DisplayInvoiceNo']; + clinicName = json['ClinicName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorRate = json['DoctorRate']; + patientNumber = json['PatientNumber']; + projectName = json['ProjectName']; + locationID = json['LocationId']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupId'] = this.setupId; + data['ProjectID'] = this.projectID; + data['PharmacyProjectID'] = this.pharmacyProjectID; + data['PatientID'] = this.patientID; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentDateN'] = this.appointmentDateN; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['InvoiceNo'] = this.invoiceNo; + data['Status'] = this.status; + data['ArrivedOn'] = this.arrivedOn; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['DisplayInvoiceNo'] = this.displayInvoiceNo; + data['ClinicName'] = this.clinicName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorRate'] = this.doctorRate; + data['PatientNumber'] = this.patientNumber; + data['ProjectName'] = this.projectName; + data['LocationId'] = this.locationID; + return data; + } +} diff --git a/lib/features/my_invoices/my_invoices_repo.dart b/lib/features/my_invoices/my_invoices_repo.dart index 486fdc2a..23f5d06a 100644 --- a/lib/features/my_invoices/my_invoices_repo.dart +++ b/lib/features/my_invoices/my_invoices_repo.dart @@ -5,16 +5,21 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyInvoicesRepo { Future>>> getAllInvoicesList(); + Future>>> getAllPharmacyInvoices(); + Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}); Future>> sendInvoiceEmail({required num appointmentNo, required int projectID}); Future>> downloadInvoice({required String setupId, required int invoiceNo, required int projectID}); + + Future>> downloadPharmacyInvoice({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID}); } class MyInvoicesRepoImp implements MyInvoicesRepo { @@ -61,6 +66,47 @@ class MyInvoicesRepoImp implements MyInvoicesRepo { } } + @override + Future>>> getAllPharmacyInvoices() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_ALL_PHARMACY_INVOICES, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_AllPharmacyInvoices'] ?? response['PharmacyInvoicesList'] ?? []; + + final invoicesList = list + .map((item) => GetPharmacyInvoicesResponseModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: invoicesList, + ); + } 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())); + } + } + @override Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async { Map mapDevice = { @@ -178,4 +224,43 @@ class MyInvoicesRepoImp implements MyInvoicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> downloadPharmacyInvoice({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID}) async { + Map mapDevice = { + "SetupID": setupId, + "ProjectID": projectID, + "InvoiceNo": invoiceNo, + "LocationID": locationID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + DOWNLOAD_PHARMACY_INVOICE_PDF, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["InvoiceReportPDFContent"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart index e191b24d..49d37927 100644 --- a/lib/features/my_invoices/my_invoices_view_model.dart +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -3,6 +3,7 @@ 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/features/my_invoices/models/get_invoice_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -12,6 +13,7 @@ enum InvoiceFilterType { all, hospital, clinic, doctor } class MyInvoicesViewModel extends ChangeNotifier { bool isInvoicesListLoading = false; bool isInvoiceDetailsLoading = false; + bool isPharmacyInvoicesLoading = false; MyInvoicesRepo myInvoicesRepo; ErrorHandlerService errorHandlerService; @@ -19,6 +21,8 @@ class MyInvoicesViewModel extends ChangeNotifier { List allInvoicesList = []; List _originalInvoicesList = []; + List allPharmacyInvoicesList = []; + List _originalPharmacyInvoicesList = []; InvoiceFilterType currentFilter = InvoiceFilterType.all; late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel; @@ -29,14 +33,20 @@ class MyInvoicesViewModel extends ChangeNotifier { String? selectedFilterItem; String? downloadInvoicePDFBase64 = ""; + String? downloadPharmacyInvoicePDFBase64 = ""; + + int selectedTabIndex = 0; MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices}); setInvoicesListLoading() { isInvoicesListLoading = true; + isPharmacyInvoicesLoading = true; allInvoicesList.clear(); + allPharmacyInvoicesList.clear(); _originalInvoicesList.clear(); currentFilter = InvoiceFilterType.all; + onTabChanged(0); notifyListeners(); } @@ -45,6 +55,11 @@ class MyInvoicesViewModel extends ChangeNotifier { notifyListeners(); } + void onTabChanged(int index) { + selectedTabIndex = index; + notifyListeners(); + } + Future getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myInvoicesRepo.getAllInvoicesList(); @@ -72,6 +87,40 @@ class MyInvoicesViewModel extends ChangeNotifier { ); } + Future getAllPharmacyInvoices({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isPharmacyInvoicesLoading = true; + allPharmacyInvoicesList.clear(); + notifyListeners(); + + final result = await myInvoicesRepo.getAllPharmacyInvoices(); + + result.fold( + (failure) async { + isPharmacyInvoicesLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + _originalPharmacyInvoicesList = []; + allPharmacyInvoicesList = []; + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + _originalPharmacyInvoicesList = apiResponse.data ?? []; + _originalPharmacyInvoicesList.sort((a, b) => (b.appointmentDate ?? '').compareTo(a.appointmentDate ?? '')); + allPharmacyInvoicesList = List.from(_originalPharmacyInvoicesList); + isPharmacyInvoicesLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID); result.fold( @@ -144,6 +193,32 @@ class MyInvoicesViewModel extends ChangeNotifier { ); } + Future downloadPharmacyInvoicePDF({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myInvoicesRepo.downloadPharmacyInvoice(setupId: setupId, invoiceNo: invoiceNo, projectID: projectID, locationID: locationID); + + result.fold( + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + downloadPharmacyInvoicePDFBase64 = apiResponse.data!; + } else { + downloadPharmacyInvoicePDFBase64 = ""; + } + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + void filterInvoices(InvoiceFilterType filterType) { currentFilter = filterType; diff --git a/lib/features/paytabs/models/paytabs_transaction_response_model.dart b/lib/features/paytabs/models/paytabs_transaction_response_model.dart new file mode 100644 index 00000000..57d23349 --- /dev/null +++ b/lib/features/paytabs/models/paytabs_transaction_response_model.dart @@ -0,0 +1,133 @@ +class PaytabsTransactionResponseModel { + int? tranTotal; + String? transactionReference; + String? cartCurrency; + String? cartDescription; + String? cartID; + String? tranCurrency; + String? payResponseReturn; + bool? isPending; + bool? isOnHold; + String? token; + String? transactionType; + bool? isAuthorized; + String? trace; + dynamic cartAmount; + int? merchantId; + int? profileId; + bool? isProcessed; + int? serviceId; + PaymentInfo? paymentInfo; + bool? isSuccess; + + PaytabsTransactionResponseModel( + {this.tranTotal, + this.transactionReference, + this.cartCurrency, + this.cartDescription, + this.cartID, + this.tranCurrency, + this.payResponseReturn, + this.isPending, + this.isOnHold, + this.token, + this.transactionType, + this.isAuthorized, + this.trace, + this.cartAmount, + this.merchantId, + this.profileId, + this.isProcessed, + this.serviceId, + this.paymentInfo, + this.isSuccess}); + + PaytabsTransactionResponseModel.fromJson(Map json) { + tranTotal = json['tran_total']; + transactionReference = json['transactionReference']; + cartCurrency = json['cartCurrency']; + cartDescription = json['cartDescription']; + cartID = json['cartID']; + tranCurrency = json['tran_currency']; + payResponseReturn = json['payResponseReturn']; + isPending = json['isPending']; + isOnHold = json['isOnHold']; + token = json['token']; + transactionType = json['transactionType']; + isAuthorized = json['isAuthorized']; + trace = json['trace']; + cartAmount = json['cartAmount']; + merchantId = json['merchantId']; + profileId = json['profileId']; + isProcessed = json['isProcessed']; + serviceId = json['serviceId']; + paymentInfo = json['paymentInfo'] != null + ? PaymentInfo.fromJson(Map.from(json['paymentInfo'] as Map)) + : null; + isSuccess = json['isSuccess']; + } + + Map toJson() { + final Map data = new Map(); + data['tran_total'] = this.tranTotal; + data['transactionReference'] = this.transactionReference; + data['cartCurrency'] = this.cartCurrency; + data['cartDescription'] = this.cartDescription; + data['cartID'] = this.cartID; + data['tran_currency'] = this.tranCurrency; + data['payResponseReturn'] = this.payResponseReturn; + data['isPending'] = this.isPending; + data['isOnHold'] = this.isOnHold; + data['token'] = this.token; + data['transactionType'] = this.transactionType; + data['isAuthorized'] = this.isAuthorized; + data['trace'] = this.trace; + data['cartAmount'] = this.cartAmount; + data['merchantId'] = this.merchantId; + data['profileId'] = this.profileId; + data['isProcessed'] = this.isProcessed; + data['serviceId'] = this.serviceId; + if (this.paymentInfo != null) { + data['paymentInfo'] = this.paymentInfo!.toJson(); + } + data['isSuccess'] = this.isSuccess; + return data; + } +} + +class PaymentInfo { + String? cardScheme; + String? cardType; + int? expiryMonth; + int? expiryYear; + String? paymentDescription; + String? paymentMethod; + + PaymentInfo( + {this.cardScheme, + this.cardType, + this.expiryMonth, + this.expiryYear, + this.paymentDescription, + this.paymentMethod}); + + PaymentInfo.fromJson(Map json) { + cardScheme = json['cardScheme']; + cardType = json['cardType']; + expiryMonth = json['expiryMonth']; + expiryYear = json['expiryYear']; + paymentDescription = json['paymentDescription']; + paymentMethod = json['payment_method']; + } + + Map toJson() { + final Map data = new Map(); + data['cardScheme'] = this.cardScheme; + data['cardType'] = this.cardType; + data['expiryMonth'] = this.expiryMonth; + data['expiryYear'] = this.expiryYear; + data['paymentDescription'] = this.paymentDescription; + data['payment_method'] = this.paymentMethod; + return data; + } +} diff --git a/lib/features/paytabs/paytabs_service.dart b/lib/features/paytabs/paytabs_service.dart new file mode 100644 index 00000000..6877389f --- /dev/null +++ b/lib/features/paytabs/paytabs_service.dart @@ -0,0 +1,23 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; + +abstract class PaytabsService { + Future> getAskDoctorAppointmentsList(); + + Future> getDoctorResponse(); +} + +class PaytabsServiceImp implements PaytabsService { + @override + Future> getAskDoctorAppointmentsList() { + // TODO: implement getAskDoctorAppointmentsList + throw UnimplementedError(); + } + + @override + Future> getDoctorResponse() { + // TODO: implement getDoctorResponse + throw UnimplementedError(); + } + +} diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart new file mode 100644 index 00000000..c922c752 --- /dev/null +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paytabs_bridge/BaseBillingShippingInfo.dart'; +import 'package:flutter_paytabs_bridge/IOSThemeConfiguration.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkLocale.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkTokeniseType.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart'; +import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.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/enums.dart'; +import 'package:hmg_patient_app_new/core/paytabs_config.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class PayTabsViewModel extends ChangeNotifier { + late PaymentSdkConfigurationDetails paymentConfiguration; + final AppState appState; + late PaytabsTransactionResponseModel paytabsTransactionResponseModel; + + PayTabsViewModel({ + required this.appState, + }); + + setPaymentConfiguration(String paymentDescription, num amount) { + String cartID = DateTime.now().millisecondsSinceEpoch.toString(); + paymentConfiguration = PaymentSdkConfigurationDetails( + profileId: PaymentSdkDefaultConfig.profileID, + serverKey: PaymentSdkDefaultConfig.serverKey, + clientKey: PaymentSdkDefaultConfig.clientKey, + transactionType: PaymentSdkTransactionType.SALE, + cartId: cartID.substring((cartID.length - 5), cartID.length), + cartDescription: paymentDescription, + merchantName: PaymentSdkDefaultConfig.defaultMerchantName, + locale: appState.isArabic() ? PaymentSdkLocale.AR : PaymentSdkLocale.EN, + merchantApplePayIndentifier: + ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT : PaymentSdkDefaultConfig.defaultMerchantAppleBundleID, + // merchantApplePayIndentifier: PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT, + screentTitle: paymentDescription, + amount: amount.toDouble(), + showBillingInfo: false, + forceShippingInfo: false, + currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, + billingDetails: BillingDetails( + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), + shippingDetails: ShippingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), + alternativePaymentMethods: [], + linkBillingNameWithCardHolderName: true, + simplifyApplePayValidation: true); + + paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( + // logoImage: "assets/images/png/hmg_logo.png", + backgroundColor: "ffffff", + backgroundColorDark: "ffffff", + secondaryColor: "191919", + secondaryColorDark: "191919", + primaryColor: "ffffff", + primaryColorDark: "ffffff", + // primaryFont: "Poppins", + titleFontColor: "2E3039", + titleFontColorDark: "2E3039", + // titleFont: "Poppins", + primaryFontColor: "2E3039", + primaryFontColorDark: "2E3039", + inputFieldBackgroundColor: "ffffff", + inputFieldBackgroundColorDark: "ffffff", + placeholderColor: "2E3039", + placeholderColorDark: "2E3039", + buttonColor: "ED1C2B", + buttonColorDark: "ED1C2B", + // buttonFont: "Poppins", + ); + paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY; + + notifyListeners(); + } + + startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { + FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) { + final transactionDetails = event["data"] as Map?; + if (event["status"] == "success") { + // Cast Map to Map + final data = Map.from(event["data"] as Map); + paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); + if (paytabsTransactionResponseModel.isSuccess!) { + onSuccess!(paytabsTransactionResponseModel); + } else { + // Transaction was processed but failed + final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; + final responseCode = transactionDetails?["responseCode"] ?? ""; + final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; + onError!(errorMessage); + } + } else if (event["status"] == "error") { + final errorMessage = event["message"] ?? "An error occurred"; + debugPrint("Error occurred in transaction: $errorMessage"); + debugPrint("Full error event: $event"); + onError!(errorMessage); + } else if (event["status"] == "event") { + final eventMessage = event["message"] ?? "Event occurred"; + debugPrint("Event occurred: $eventMessage"); + } + }); + } + + startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { + FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) { + final transactionDetails = event["data"] as Map?; + if (event["status"] == "success") { + // Cast Map to Map + final data = Map.from(event["data"] as Map); + paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); + if (paytabsTransactionResponseModel.isSuccess!) { + onSuccess!(paytabsTransactionResponseModel); + } else { + // Transaction was processed but failed + final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; + final responseCode = transactionDetails?["responseCode"] ?? ""; + final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; + onError!(errorMessage); + } + } else if (event["status"] == "error") { + final errorMessage = event["message"] ?? "An error occurred"; + debugPrint("Error occurred in transaction: $errorMessage"); + debugPrint("Full error event: $event"); + onError!(errorMessage); + } else if (event["status"] == "event") { + final eventMessage = event["message"] ?? "Event occurred"; + debugPrint("Event occurred: $eventMessage"); + } + }); + } +} diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ad44d33f..475368f0 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -347,8 +347,11 @@ class PrescriptionsViewModel extends ChangeNotifier { result.fold( (failure) async { isPrescriptionsDeliveryOrdersLoading = false; + prescriptionsOrderList.clear(); notifyListeners(); - onError!(failure.message); + if(onError != null) { + onError(failure.message); + } }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/features/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart index 8c983eb7..02cbc10f 100644 --- a/lib/features/profile_settings/profile_settings_view_model.dart +++ b/lib/features/profile_settings/profile_settings_view_model.dart @@ -40,7 +40,7 @@ class ProfileSettingsViewModel extends ChangeNotifier { bool isDeactivateAccountSuccess = false; String? deactivateAccountError; - late GetPatientInfoForUpdate getPatientInfoForUpdate; + GetPatientInfoForUpdate? getPatientInfoForUpdate; bool isPatientProfileLoading = false; // ── Profile image state ────────────────────────────────────────────── diff --git a/lib/features/symptoms_checker/data/organ_mapping_data.dart b/lib/features/symptoms_checker/data/organ_mapping_data.dart index d1a0b83b..d284aeb5 100644 --- a/lib/features/symptoms_checker/data/organ_mapping_data.dart +++ b/lib/features/symptoms_checker/data/organ_mapping_data.dart @@ -1,4 +1,3 @@ - import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; @@ -37,12 +36,65 @@ class OrganData { id: 'nose_mouth', name: 'oral_cavity', nameAr: 'تجويف_الفم', - description: 'Nose/Mouth', - descriptionAr: 'الأنف/الفم', + description: 'Mouth', + descriptionAr: 'الفم', + bodyView: BodyView.front, + position: OrganPosition(x: 0.50, y: 0.135), + ), + + // ========== EARS (NEW) ========== + OrganModel( + id: 'left_ear', + name: 'ears', + nameAr: 'الأذن', + description: 'Left Ear', + descriptionAr: 'الأذن اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.1), + ), + OrganModel( + id: 'right_ear', + name: 'ears', + nameAr: 'الأذن', + description: 'Right Ear', + descriptionAr: 'الأذن اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.64, y: 0.1), + ), + + // ========== NOSE (NEW) ========== + OrganModel( + id: 'nose', + name: 'nose', + nameAr: 'الأنف', + description: 'Nose', + descriptionAr: 'الأنف', bodyView: BodyView.front, - position: OrganPosition(x: 0.50, y: 0.09), + position: OrganPosition(x: 0.50, y: 0.095), ), + // ========== HEAD – SPECIFIC (NEW) ========== + // OrganModel( + // id: 'front_head', + // name: 'head', + // nameAr: 'الرأس', + // description: 'Front Head', + // descriptionAr: 'مقدمة الرأس', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.50, y: 0.04), + // ), + + // // ========== BEHAVIOR / THOUGHTS (NEW) ========== + // OrganModel( + // id: 'behavior_thoughts', + // name: 'problem_concerning_behavior_or_thoughts', + // nameAr: 'مشكلة_سلوكية_أو_نفسية', + // description: 'Behavior / Thoughts', + // descriptionAr: 'سلوك / أفكار', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.50, y: 0.02), + // ), + // ========== NECK ========== OrganModel( id: 'throat', @@ -51,7 +103,27 @@ class OrganData { description: 'Throat', descriptionAr: 'الحلق', bodyView: BodyView.front, - position: OrganPosition(x: 0.50, y: 0.15), + position: OrganPosition(x: 0.50, y: 0.17), + ), + + // ========== HALF NECK (NEW) ========== + OrganModel( + id: 'left_halfneck', + name: 'halfneck', + nameAr: 'نصف_الرقبة', + description: 'Left Half Neck', + descriptionAr: 'نصف الرقبة الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.42, y: 0.15), + ), + OrganModel( + id: 'right_halfneck', + name: 'halfneck', + nameAr: 'نصف_الرقبة', + description: 'Right Half Neck', + descriptionAr: 'نصف الرقبة الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.58, y: 0.15), ), // ========== SHOULDERS ========== @@ -74,6 +146,26 @@ class OrganData { position: OrganPosition(x: 0.8, y: 0.22), ), + // ========== UPPER LIMB (NEW) ========== + OrganModel( + id: 'left_upper_limb', + name: 'upper_limb', + nameAr: 'الطرف_العلوي', + description: 'Left Upper Limb', + descriptionAr: 'الطرف العلوي الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.20, y: 0.30), + ), + OrganModel( + id: 'right_upper_limb', + name: 'upper_limb', + nameAr: 'الطرف_العلوي', + description: 'Right Upper Limb', + descriptionAr: 'الطرف العلوي الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.80, y: 0.30), + ), + // ========== CHEST ========== OrganModel( id: 'left_chest', @@ -152,6 +244,28 @@ class OrganData { position: OrganPosition(x: 0.51, y: 0.44), ), + // ========== ABDOMEN – GENERAL (NEW) ========== + // OrganModel( + // id: 'abdomin', + // name: 'abdomin', + // nameAr: 'البطن_العام', + // description: 'Abdomen', + // descriptionAr: 'البطن', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.51, y: 0.36), + // ), + + // ========== SKIN (NEW) ========== + // OrganModel( + // id: 'skin', + // name: 'skin', + // nameAr: 'الجلد', + // description: 'Skin', + // descriptionAr: 'الجلد', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.50, y: 0.50), + // ), + // ========== PELVIS ========== OrganModel( id: 'left_groin', @@ -173,7 +287,6 @@ class OrganData { ), // ========== LEFT ARM ========== - OrganModel( id: 'left_elbow', name: 'elbow', @@ -203,7 +316,6 @@ class OrganData { ), // ========== RIGHT ARM ========== - OrganModel( id: 'right_elbow', name: 'elbow', @@ -307,7 +419,69 @@ class OrganData { bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.91), ), + + + // ========== UPPER LEG (NEW) ========== + OrganModel( + id: 'left_upperleg', + name: 'upperleg', + nameAr: 'الجزء_العلوي_من_الساق', + description: 'Left Upper Leg', + descriptionAr: 'الجزء العلوي من الساق الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.40, y: 0.63), + ), + OrganModel( + id: 'right_upperleg', + name: 'upperleg', + nameAr: 'الجزء_العلوي_من_الساق', + description: 'Right Upper Leg', + descriptionAr: 'الجزء العلوي من الساق الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.60, y: 0.63), + ), + + // ========== LOWER LEG – lowerleg key (NEW) ========== + OrganModel( + id: 'left_lowerleg', + name: 'lowerleg', + nameAr: 'الجزء_السفلي_من_الساق', + description: 'Left Lower Leg', + descriptionAr: 'الجزء السفلي من الساق الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.41, y: 0.78), + ), + OrganModel( + id: 'right_lowerleg', + name: 'lowerleg', + nameAr: 'الجزء_السفلي_من_الساق', + description: 'Right Lower Leg', + descriptionAr: 'الجزء السفلي من الساق الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.57, y: 0.78), + ), + + // ========== LOWER LIMB (NEW) ========== + OrganModel( + id: 'left_lower_limb', + name: 'lower_limb', + nameAr: 'الطرف_السفلي', + description: 'Left Lower Limb', + descriptionAr: 'الطرف السفلي الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.40, y: 0.67), + ), + OrganModel( + id: 'right_lower_limb', + name: 'lower_limb', + nameAr: 'الطرف_السفلي', + description: 'Right Lower Limb', + descriptionAr: 'الطرف السفلي الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.60, y: 0.67), + ), ]; + static const List frontViewOrgansForFemale = [ // ========== HEAD & FACE ========== OrganModel( @@ -341,12 +515,65 @@ class OrganData { id: 'nose_mouth', name: 'oral_cavity', nameAr: 'تجويف_الفم', - description: 'Nose/Mouth', + description: 'Mouth', descriptionAr: 'الأنف/الفم', bodyView: BodyView.front, - position: OrganPosition(x: 0.54, y: 0.12), + position: OrganPosition(x: 0.54, y: 0.14), ), + // ========== EARS (NEW) ========== + OrganModel( + id: 'left_ear', + name: 'ears', + nameAr: 'الأذن', + description: 'Left Ear', + descriptionAr: 'الأذن اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.43, y: 0.12), + ), + OrganModel( + id: 'right_ear', + name: 'ears', + nameAr: 'الأذن', + description: 'Right Ear', + descriptionAr: 'الأذن اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.656, y: 0.12), + ), + + // ========== NOSE (NEW) ========== + OrganModel( + id: 'nose', + name: 'nose', + nameAr: 'الأنف', + description: 'Nose', + descriptionAr: 'الأنف', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.115), + ), + + // ========== HEAD – SPECIFIC (NEW) ========== + OrganModel( + id: 'front_head', + name: 'head', + nameAr: 'الرأس', + description: 'Front Head', + descriptionAr: 'مقدمة الرأس', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.05), + ), + + // // ========== BEHAVIOR / THOUGHTS (NEW) ========== + // OrganModel( + // id: 'behavior_thoughts', + // name: 'problem_concerning_behavior_or_thoughts', + // nameAr: 'مشكلة_سلوكية_أو_نفسية', + // description: 'Behavior / Thoughts', + // descriptionAr: 'سلوك / أفكار', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.54, y: 0.02), + // ), + // ========== NECK ========== OrganModel( id: 'throat', @@ -358,6 +585,26 @@ class OrganData { position: OrganPosition(x: 0.54, y: 0.17), ), + // ========== HALF NECK (NEW) ========== + OrganModel( + id: 'left_halfneck', + name: 'halfneck', + nameAr: 'نصف_الرقبة', + description: 'Left Half Neck', + descriptionAr: 'نصف الرقبة الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.48, y: 0.175), + ), + OrganModel( + id: 'right_halfneck', + name: 'halfneck', + nameAr: 'نصف_الرقبة', + description: 'Right Half Neck', + descriptionAr: 'نصف الرقبة الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.60, y: 0.175), + ), + // ========== SHOULDERS ========== OrganModel( id: 'left_shoulder', @@ -378,6 +625,26 @@ class OrganData { position: OrganPosition(x: 0.75, y: 0.22), ), + // ========== UPPER LIMB (NEW) ========== + OrganModel( + id: 'left_upper_limb', + name: 'upper_limb', + nameAr: 'الطرف_العلوي', + description: 'Left Upper Limb', + descriptionAr: 'الطرف العلوي الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.26, y: 0.30), + ), + OrganModel( + id: 'right_upper_limb', + name: 'upper_limb', + nameAr: 'الطرف_العلوي', + description: 'Right Upper Limb', + descriptionAr: 'الطرف العلوي الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.76, y: 0.30), + ), + // ========== CHEST ========== OrganModel( id: 'left_chest', @@ -407,6 +674,26 @@ class OrganData { position: OrganPosition(x: 0.65, y: 0.22), ), + // ========== BREASTS (NEW – female only) ========== + OrganModel( + id: 'left_breast', + name: 'breasts', + nameAr: 'الثدي', + description: 'Left Breast', + descriptionAr: 'الثدي الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.27), + ), + OrganModel( + id: 'right_breast', + name: 'breasts', + nameAr: 'الثدي', + description: 'Right Breast', + descriptionAr: 'الثدي الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.62, y: 0.27), + ), + // ========== RIBS ========== OrganModel( id: 'left_ribs', @@ -456,6 +743,28 @@ class OrganData { position: OrganPosition(x: 0.51, y: 0.44), ), + // ========== ABDOMEN – GENERAL (NEW) ========== + // OrganModel( + // id: 'abdomin', + // name: 'abdomin', + // nameAr: 'البطن_العام', + // description: 'Abdomen', + // descriptionAr: 'البطن', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.54, y: 0.36), + // ), + + // ========== SKIN (NEW) ========== + // OrganModel( + // id: 'skin', + // name: 'skin', + // nameAr: 'الجلد', + // description: 'Skin', + // descriptionAr: 'الجلد', + // bodyView: BodyView.front, + // position: OrganPosition(x: 0.54, y: 0.50), + // ), + // ========== PELVIS ========== OrganModel( id: 'left_groin', @@ -477,7 +786,6 @@ class OrganData { ), // ========== LEFT ARM ========== - OrganModel( id: 'left_elbow', name: 'elbow', @@ -507,7 +815,6 @@ class OrganData { ), // ========== RIGHT ARM ========== - OrganModel( id: 'right_elbow', name: 'elbow', @@ -611,6 +918,68 @@ class OrganData { bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.91), ), + + + + // ========== UPPER LEG (NEW) ========== + OrganModel( + id: 'left_upperleg', + name: 'upperleg', + nameAr: 'الجزء_العلوي_من_الساق', + description: 'Left Upper Leg', + descriptionAr: 'الجزء العلوي من الساق الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.40, y: 0.63), + ), + OrganModel( + id: 'right_upperleg', + name: 'upperleg', + nameAr: 'الجزء_العلوي_من_الساق', + description: 'Right Upper Leg', + descriptionAr: 'الجزء العلوي من الساق الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.63), + ), + + // ========== LOWER LEG – lowerleg key (NEW) ========== + OrganModel( + id: 'left_lowerleg', + name: 'lowerleg', + nameAr: 'الجزء_السفلي_من_الساق', + description: 'Left Lower Leg', + descriptionAr: 'الجزء السفلي من الساق الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.41, y: 0.78), + ), + OrganModel( + id: 'right_lowerleg', + name: 'lowerleg', + nameAr: 'الجزء_السفلي_من_الساق', + description: 'Right Lower Leg', + descriptionAr: 'الجزء السفلي من الساق الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.59, y: 0.78), + ), + + // ========== LOWER LIMB (NEW) ========== + OrganModel( + id: 'left_lower_limb', + name: 'lower_limb', + nameAr: 'الطرف_السفلي', + description: 'Left Lower Limb', + descriptionAr: 'الطرف السفلي الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.40, y: 0.67), + ), + OrganModel( + id: 'right_lower_limb', + name: 'lower_limb', + nameAr: 'الطرف_السفلي', + description: 'Right Lower Limb', + descriptionAr: 'الطرف السفلي الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.67), + ), ]; // ==================== BACK VIEW ==================== @@ -742,6 +1111,16 @@ class OrganData { position: OrganPosition(x: 0.60, y: 0.43), ), + // ========== ANUS (NEW) ========== + OrganModel( + id: 'anus', + name: 'anus', + nameAr: 'فتحة_الشرج', + description: 'Anus', + descriptionAr: 'فتحة الشرج', + bodyView: BodyView.back, + position: OrganPosition(x: 0.525, y: 0.5), + ), // ========== GLUTES ========== OrganModel( id: 'sacrum', @@ -750,7 +1129,7 @@ class OrganData { description: 'Sacrum', descriptionAr: 'العجز', bodyView: BodyView.back, - position: OrganPosition(x: 0.50, y: 0.52), + position: OrganPosition(x: 0.50, y: 0.46), ), OrganModel( id: 'left_glute', @@ -809,6 +1188,7 @@ class OrganData { position: OrganPosition(x: 0.57, y: 0.79), ), ]; + static const List backViewOrgansForFemale = [ // ========== HEAD & NECK ========== OrganModel( @@ -879,6 +1259,17 @@ class OrganData { position: OrganPosition(x: 0.62, y: 0.28), ), + // // ========== SKIN – BACK (NEW) ========== + // OrganModel( + // id: 'skin_back', + // name: 'skin', + // nameAr: 'الجلد', + // description: 'Skin', + // descriptionAr: 'الجلد', + // bodyView: BodyView.back, + // position: OrganPosition(x: 0.50, y: 0.30), + // ), + // ========== MID BACK ========== OrganModel( id: 'mid_spine', @@ -945,7 +1336,7 @@ class OrganData { description: 'Sacrum', descriptionAr: 'العجز', bodyView: BodyView.back, - position: OrganPosition(x: 0.50, y: 0.5), + position: OrganPosition(x: 0.50, y: 0.47), ), OrganModel( id: 'left_glute', @@ -1010,7 +1401,6 @@ class OrganData { static List getOrgansForView(BodyView view, {String? genderKey}) { final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2'); - if (view == BodyView.front) { return isFemale ? frontViewOrgansForFemale : frontViewOrgansForMale; } else { @@ -1039,4 +1429,4 @@ class OrganData { return null; } } -} +} \ No newline at end of file diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index b14e4ead..131774d6 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -51,8 +51,12 @@ class TodoSectionViewModel extends ChangeNotifier { // if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // } else if (apiResponse.messageStatus == 1) { - notificationsCount = - apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString(); + if (apiResponse['List_PatientDashboard'] != null && apiResponse['List_PatientDashboard'] is List && (apiResponse['List_PatientDashboard'] as List).isNotEmpty) { + notificationsCount = + apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString(); + } else { + notificationsCount = "0"; + } notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index ae8c6817..34787232 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1838,5 +1838,10 @@ abstract class LocaleKeys { static const fileSizeExceedsLimit = 'fileSizeExceedsLimit'; static const completedPrescriptionOrder = 'completedPrescriptionOrder'; static const submitEReferral = 'submitEReferral'; + static const redeem = 'redeem'; + static const points = 'points'; + static const transactions = 'transactions'; + static const pharmacy = 'pharmacy'; + static const visitsOrders = 'visitsOrders'; } diff --git a/lib/main.dart b/lib/main.dart index 904c2674..e2643438 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.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/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; @@ -270,6 +271,8 @@ void main() async { create: (_) => getIt.get(), ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index c14897d8..2ec8f568 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -838,40 +838,42 @@ class _AppointmentDetailsPageState extends State { iconSize: 36.w, isLocked: false, ).toShimmer2() - : MedicalFileCard( - label: !((myAppointmentsVM.appointmentRatedResponseModel!).isAllowedToRate ?? false) - ? LocaleKeys.ratingSubmitted.tr(context: context) - : LocaleKeys.doctorRatingAppointment.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.appointmentRatingIcon, - isLargeText: true, - iconSize: 36.w, - isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false), - ).onPress(() { - if ((myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? true)) { - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.doctorRating.tr(context: context), - isCloseButtonVisible: true, - child: StatefulBuilder( - builder: (context, setState) { - return AppointmentRatingWidget( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - ); - }, - ), - callBackFunc: () { - myAppointmentsViewModel.getDoctorsRatingCheck( - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); - }, - isFullScreen: false, - ); - } - }), + : myAppointmentsVM.appointmentRatedResponseModel != null + ? MedicalFileCard( + label: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false) + ? LocaleKeys.ratingSubmitted.tr(context: context) + : LocaleKeys.doctorRatingAppointment.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.appointmentRatingIcon, + isLargeText: true, + iconSize: 36.w, + isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false), + ).onPress(() { + if (myAppointmentsVM.appointmentRatedResponseModel?.isAllowedToRate ?? false) { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.doctorRating.tr(context: context), + isCloseButtonVisible: true, + child: StatefulBuilder( + builder: (context, setState) { + return AppointmentRatingWidget( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + ); + }, + ), + callBackFunc: () { + myAppointmentsViewModel.getDoctorsRatingCheck( + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + }, + isFullScreen: false, + ); + } + }) + : SizedBox.shrink(), ], ); }), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 340501c0..2c39b43c 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -17,6 +17,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_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/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -43,6 +45,7 @@ class _AppointmentPaymentPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; late PayfortViewModel payfortViewModel; late AppState appState; + late PayTabsViewModel paytabsViewModel; MyInAppBrowser? browser; String selectedPaymentMethod = ""; @@ -83,6 +86,7 @@ class _AppointmentPaymentPageState extends State { appState = getIt.get(); myAppointmentsViewModel = Provider.of(context); payfortViewModel = Provider.of(context); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, myAppointmentsVM, child) { @@ -130,7 +134,86 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; - openPaymentURL("mada"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + paymentReference: transactionData.transactionReference!, + patientID: appState.getAuthenticatedUser()!.patientId.toString(), + patientType: appState.getAuthenticatedUser()!.patientType!, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("mada"); + } }), SizedBox(height: 16.h), Container( @@ -174,7 +257,86 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; - openPaymentURL("visa"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + paymentReference: transactionData.transactionReference!, + patientID: appState.getAuthenticatedUser()!.patientId.toString(), + patientType: appState.getAuthenticatedUser()!.patientType!, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("visa"); + } }), SizedBox(height: 16.h), isShowTamara @@ -632,37 +794,117 @@ class _AppointmentPaymentPageState extends State { //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { - payfortViewModel.paymentWithApplePay( - customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", - // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, - customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", - orderDescription: "Appointment Payment", - orderAmount: double.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), - merchantReference: transID, - merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, - applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, - applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, - currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", - onFailed: (failureResult) async { - log("failureResult: ${failureResult.message.toString()}"); - LoaderBottomSheet.hideLoader(); + if (appState.isPaytabsEnabled) { + LoaderBottomSheet.hideLoader(); + paytabsViewModel.setPaymentConfiguration( + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ); + paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + paymentReference: transactionData.transactionReference!, + patientID: appState.getAuthenticatedUser()!.patientId.toString(), + patientType: appState.getAuthenticatedUser()!.patientType!, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + }); + }, onError: (err) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - }, - onSucceeded: (successResult) async { - LoaderBottomSheet.hideLoader(); - log("successResult: ${successResult.responseMessage.toString()}"); - selectedPaymentMethod = successResult.paymentOption ?? "VISA"; - checkPaymentStatus(); - }, - // projectId: appo.projectID, - // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, - ); + }); + } else { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "Appointment Payment", + orderAmount: double.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + // projectId: appo.projectID, + // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, + ); + } }); } } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 9b01ce8b..e17d52dd 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -274,9 +274,8 @@ class _AppointmentCardState extends State { children: [ (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), Expanded( - child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") - .toText16(isBold: true, maxlines: 2, - textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(30)}").toText16( + isBold: true, maxlines: 2, textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), ), SizedBox(width: 12.w), (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 04111c8d..21f46bea 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -186,7 +186,6 @@ class LoginScreenState extends State { constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), backgroundColor: AppColors.transparent, child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { - // Helper function to validate phone number with error handling void validatePhoneAndProceed(OTPTypeEnum otpType) { // Use ViewModel validation method diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 137aaa09..044db504 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -170,7 +170,7 @@ class DoctorProfilePage extends StatelessWidget { icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), - NumberFormat.decimalPattern() + NumberFormat.decimalPattern("en_US") .format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) .toText16( isBold: true, 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 2dcdb42a..dd8422ee 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -444,7 +444,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { // cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted; // micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted; notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted; - alertWindowGranted = newStatuses[Permission.systemAlertWindow]?.isGranted ?? alertWindowGranted; + // alertWindowGranted = newStatuses[Permission.systemAlertWindow]?.isGranted ?? alertWindowGranted; + alertWindowGranted = true; // If any requested permission is now permanently denied -> open settings final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList(); 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 031bb0b8..57e17608 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -19,6 +19,8 @@ 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/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -45,6 +47,7 @@ class _ImmediateLiveCarePaymentPageState extends State(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); payfortViewModel = Provider.of(context, listen: false); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, myAppointmentsVM, child) { @@ -121,9 +125,52 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("mada"); + } }), SizedBox(height: 16.h), Container( @@ -165,7 +212,50 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("visa"); + } }), SizedBox(height: 16.h), isShowTamara @@ -357,7 +447,6 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, onError: (err) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - }, - onSucceeded: (successResult) async { - LoaderBottomSheet.hideLoader(); - log("successResult: ${successResult.responseMessage.toString()}"); - selectedPaymentMethod = successResult.paymentOption ?? "VISA"; - checkPaymentStatus(); - }, - // projectId: appo.projectID, - // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, - ); + }); + } else { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "LiveCare Payment", + orderAmount: num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")), + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + // projectId: appo.projectID, + // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, + ); + } }); } } 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 4d136cdd..63e6aa72 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 @@ -19,6 +19,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_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/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -45,6 +47,7 @@ class _WaitingAppointmentPaymentPageState extends State(context); myAppointmentsViewModel = Provider.of(context); payfortViewModel = Provider.of(context); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, bookAppointmentsVM, child) { @@ -133,7 +137,50 @@ class _WaitingAppointmentPaymentPageState extends State(); + + // Get clinic icon path with fallback + String getClinicIconPath() { + final clinicId = clinicsListResponseModel.clinicID ?? 1; + // Try to use specific clinic icon, fallback to default if not found + return "assets/images/clinicIcons/$clinicId.svg"; + } + return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -32,7 +40,19 @@ class ClinicCard extends StatelessWidget { child: Column( children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.buildSvgWithAssets(icon: "assets/images/clinicIcons/${clinicsListResponseModel.clinicID ?? 1}.svg", width: 24.w, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), + // Use Image.asset with error builder for fallback + SizedBox( + width: 24.w, + height: 24.h, + child: Utils.buildSvgWithAssets( + icon: getClinicIconPath(), + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + // Note: Add error handling in the Utils.buildSvgWithAssets if possible + // or use a try-catch wrapper + ), + ).toShimmer2(isShow: isLoading), (clinicsListResponseModel.isLiveCareClinicAndOnline ?? true) ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.w, height: 32.h, fit: BoxFit.contain, applyThemeColor: false).toShimmer2(isShow: isLoading) : SizedBox.shrink(), diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 0fd14b6c..107f5ba4 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -258,7 +258,7 @@ class LiveChatPage extends StatelessWidget { workGroup: workGroup, onSuccess: (response) { debugPrint("Chat Request ID received: ${contactUsVM.chatRequestID}"); - chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${contactUsVM.chatRequestID}"; + chatURL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=${contactUsVM.chatRequestID}"; debugPrint("Chat URL: $chatURL"); }, onError: (error) { diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index b47ccc33..297c9011 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -16,6 +16,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_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/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -39,6 +41,7 @@ class ErOnlineCheckinPaymentPage extends StatefulWidget { class _ErOnlineCheckinPaymentPageState extends State { late PayfortViewModel payfortViewModel; late EmergencyServicesViewModel emergencyServicesViewModel; + late PayTabsViewModel paytabsViewModel; late AppState appState; @@ -75,6 +78,7 @@ class _ErOnlineCheckinPaymentPageState extends State appState = getIt.get(); payfortViewModel = Provider.of(context, listen: false); emergencyServicesViewModel = Provider.of(context, listen: false); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -119,7 +123,54 @@ class _ErOnlineCheckinPaymentPageState extends State ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; - openPaymentURL("mada"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "ER Online Check-In Payment", + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + if (emergencyServicesViewModel.isERBookAppointment) { + await emergencyServicesViewModel.ER_CreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paymentReference: transactionData.transactionReference!, + onSuccess: (value) async { + await emergencyServicesViewModel.addAdvanceNumberRequest( + advanceNumber: value, + paymentReference: transactionData.transactionReference!, + appointmentNo: "0", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (emergencyServicesViewModel.isERBookAppointment) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else {} + }); + }); + } else {} + }, onError: (errMsg) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: errMsg.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("mada"); + } }), SizedBox(height: 16.h), Container( @@ -161,7 +212,54 @@ class _ErOnlineCheckinPaymentPageState extends State ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; - openPaymentURL("visa"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "ER Online Check-In Payment", + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + if (emergencyServicesViewModel.isERBookAppointment) { + await emergencyServicesViewModel.ER_CreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paymentReference: transactionData.transactionReference!, + onSuccess: (value) async { + await emergencyServicesViewModel.addAdvanceNumberRequest( + advanceNumber: value, + paymentReference: transactionData.transactionReference!, + appointmentNo: "0", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (emergencyServicesViewModel.isERBookAppointment) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else {} + }); + }); + } else {} + }, onError: (errMsg) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: errMsg.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("visa"); + } }), SizedBox(height: 16.h), isShowTamara @@ -393,35 +491,84 @@ class _ErOnlineCheckinPaymentPageState extends State //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { - payfortViewModel.paymentWithApplePay( - customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", - // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, - customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", - orderDescription: "Appointment Payment", - orderAmount: double.parse(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString()), - merchantReference: transID, - merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, - applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, - applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, - currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", - onFailed: (failureResult) async { - LoaderBottomSheet.hideLoader(); - log("failureResult: ${failureResult.message.toString()}"); + if (appState.isPaytabsEnabled) { + LoaderBottomSheet.hideLoader(); + paytabsViewModel.setPaymentConfiguration( + "ER Online Check-In Payment", + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + ); + paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + if (emergencyServicesViewModel.isERBookAppointment) { + await emergencyServicesViewModel.ER_CreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paymentReference: transactionData.transactionReference!, + onSuccess: (value) async { + await emergencyServicesViewModel.addAdvanceNumberRequest( + advanceNumber: value, + paymentReference: transactionData.transactionReference!, + appointmentNo: "0", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (emergencyServicesViewModel.isERBookAppointment) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else {} + }); + }); + } else {} + }, onError: (err) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - }, - onSucceeded: (successResult) async { - LoaderBottomSheet.hideLoader(); - log("successResult: ${successResult.responseMessage.toString()}"); - selectedPaymentMethod = successResult.paymentOption ?? "VISA"; - checkPaymentStatus(); - }, - ); + }); + } else { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "ER Online Check-In Payment", + orderAmount: double.parse(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString()), + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + LoaderBottomSheet.hideLoader(); + log("failureResult: ${failureResult.message.toString()}"); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + ); + } }); } diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index c21e7a62..42ad5984 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -33,6 +33,7 @@ class _HabibWalletState extends State { scheduleMicrotask(() async { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); }); super.initState(); @@ -123,6 +124,7 @@ class _HabibWalletState extends State { .then((val) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); }); }, ), diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 151b3f21..1363025e 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -16,7 +16,10 @@ 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/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -34,6 +37,7 @@ class WalletPaymentConfirmPage extends StatefulWidget { class _WalletPaymentConfirmPageState extends State { late PayfortViewModel payfortViewModel; + late PayTabsViewModel paytabsViewModel; late AppState appState; late HabibWalletViewModel habibWalletVM; @@ -55,6 +59,7 @@ class _WalletPaymentConfirmPageState extends State { appState = getIt.get(); habibWalletVM = Provider.of(context, listen: false); payfortViewModel = Provider.of(context, listen: false); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -101,7 +106,66 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; - openPaymentURL("mada"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(); + await habibWalletVM.HISCreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paidAmount: habibWalletVM.walletRechargeAmount, + paymentReference: transactionData.transactionReference!, + patientID: habibWalletVM.fileNumber, + projectID: habibWalletVM.selectedHospital!.iD!, + depositorName: habibWalletVM.depositorName, + onSuccess: (value) async { + await habibWalletVM.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("mada"); + } }), SizedBox(height: 16.h), Container( @@ -143,7 +207,66 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; - openPaymentURL("visa"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(); + await habibWalletVM.HISCreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paidAmount: habibWalletVM.walletRechargeAmount, + paymentReference: transactionData.transactionReference!, + patientID: habibWalletVM.fileNumber, + projectID: habibWalletVM.selectedHospital!.iD!, + depositorName: habibWalletVM.depositorName, + onSuccess: (value) async { + await habibWalletVM.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("visa"); + } }), ], ), @@ -196,9 +319,7 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 12.h), Platform.isIOS - ? Utils.buildSvgWithAssets( - icon: AppAssets.apple_pay_button, - width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() { + ? Utils.buildSvgWithAssets(icon: AppAssets.apple_pay_button, width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() { if (Utils.havePrivilege(103)) { startApplePay(); } else { @@ -267,33 +388,93 @@ class _WalletPaymentConfirmPageState extends State { //TODO: Need to pass dynamic params to the Apple Pay instead of static values await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { - payfortViewModel.paymentWithApplePay( - customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", - // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, - customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", - orderDescription: "Appointment Payment", - orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()), - merchantReference: transID, - merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, - applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, - applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, - currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", - onFailed: (failureResult) async { - log("failureResult: ${failureResult.message.toString()}"); + if(appState.isPaytabsEnabled) { + LoaderBottomSheet.hideLoader(); + paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(); + await habibWalletVM.HISCreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paidAmount: habibWalletVM.walletRechargeAmount, + paymentReference: transactionData.transactionReference!, + patientID: habibWalletVM.fileNumber, + projectID: habibWalletVM.selectedHospital!.iD!, + depositorName: habibWalletVM.depositorName, + onSuccess: (value) async { + await habibWalletVM.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - }, - onSucceeded: (successResult) async { - log("successResult: ${successResult.responseMessage.toString()}"); - selectedPaymentMethod = successResult.paymentOption ?? "VISA"; - checkPaymentStatus(); - }, - ); + }); + } else { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "Appointment Payment", + orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()), + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + ); + } }); } @@ -319,19 +500,13 @@ class _WalletPaymentConfirmPageState extends State { paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, onSuccess: (value) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), - callBackFunc: () { - habibWalletVM.initHabibWalletProvider(); - habibWalletVM.getPatientBalanceAmount(); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - isFullScreen: false, - isCloseButtonVisible: true, - isAutoDismiss: true - ); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); }, onError: (err) { LoaderBottomSheet.hideLoader(); @@ -344,7 +519,16 @@ class _WalletPaymentConfirmPageState extends State { ); }); }, - onError: (err) {}); + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); } else { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( diff --git a/lib/presentation/home/lakum_wallet_details.dart b/lib/presentation/home/lakum_wallet_details.dart new file mode 100644 index 00000000..79b71ec0 --- /dev/null +++ b/lib/presentation/home/lakum_wallet_details.dart @@ -0,0 +1,438 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.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_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +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/habib_wallet/models/lakum_inquiry_information_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class LakumWalletDetails extends StatelessWidget { + const LakumWalletDetails({super.key}); + + @override + Widget build(BuildContext context) { + AppState appState = getIt.get(); + + return CollapsingListView( + title: LocaleKeys.lakumPoints.tr(context: context), + child: Consumer( + builder: (context, viewModel, child) { + // Check if loading + if (viewModel.isLakumAccountInfoLoading) { + return Center( + child: Padding( + padding: EdgeInsets.all(32.w), + child: CircularProgressIndicator( + color: AppColors.primaryRedColor, + ), + ), + ); + } + + // Check if account number is not available + if (viewModel.yahalaAccountNumber == "0" || viewModel.yahalaAccountNumber.isEmpty) { + return Utils.getNoDataWidget(context); + // return Center( + // child: Padding( + // padding: EdgeInsets.all(32.w), + // child: LocaleKeys.lakumMsg.tr().toText14( + // color: AppColors.greyTextColor, + // ), + // ), + // ); + } + + // Get all transactions + List transactions = viewModel.getAllLakumTransactions(); + + // Display main UI + return SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Main Lakum Card with gradient and user info + _buildLakumMainCard(viewModel.lakumAccountInfo, appState), + SizedBox(height: 24.h), + + // Redeem Button + _buildRedeemButton(context), + SizedBox(height: 24.h), + + // Transaction List Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.transactions.tr(context: context).toText16(isBold: true), + // if (transactions.isNotEmpty) "${transactions.length} Records".toText12(color: AppColors.greyTextColor), + ], + ), + SizedBox(height: 12.h), + + // Transaction List Container + if (transactions.isEmpty) _buildEmptyTransactions() else _buildTransactionsList(transactions), + + SizedBox(height: 24.h), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildLakumMainCard(LakumInquiryInformationResponseModel? accountInfo, AppState appState) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.habibPharmacyColor, + AppColors.habibPharmacyColor.withAlpha(220), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(24.r), + boxShadow: [ + BoxShadow( + color: AppColors.habibPharmacyColor.withAlpha(76), + blurRadius: 16, + offset: Offset(0, 8), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Top Row: User Info and Grid Icon + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${appState.getAuthenticatedUser()?.firstName ?? ''} ${appState.getAuthenticatedUser()?.lastName ?? ''}".toText18( + isBold: true, + color: Colors.white, + ), + SizedBox(height: 4.h), + (accountInfo?.accountNumber ?? 0).toString().toText14( + color: Colors.white.withAlpha(230), + ), + ], + ), + ), + Utils.buildImgWithAssets( + icon: AppAssets.lakumLogoWhite, + width: 110.h, + height: 100.h, + ), + ], + ), + // SizedBox(height: 24.h), + LocaleKeys.lakumPoints.tr().toText18(color: Colors.white.withAlpha(230), isBold: true), + SizedBox(height: 8.h), + + // Points Balance + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + (accountInfo?.pointsBalance?.toString() ?? "0").toText44( + isBold: true, + color: Colors.white, + ), + SizedBox(width: 8.w), + LocaleKeys.points + .tr() + .toText18( + color: Colors.white.withAlpha(230), + isBold: true, + ) + .paddingOnly(bottom: 8.h), + ], + ), + SizedBox(height: 4.h), + + // Balance Amount in SAR + "≈ ${accountInfo?.pointsBalanceAmount?.toStringAsFixed(2) ?? "0.00"} SAR".toText18( + color: Colors.white.withAlpha(204), + ), + SizedBox(height: 20.h), + + // Stats Row + Row( + children: [ + Expanded( + child: _buildStatChip( + LocaleKeys.gained.tr(), + accountInfo?.gainedPoints?.toString() ?? "0", + Icons.arrow_circle_up, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: _buildStatChip( + LocaleKeys.consumed.tr(), + accountInfo?.consumedPoints?.toString() ?? "0", + Icons.arrow_circle_down, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: _buildStatChip( + LocaleKeys.expired.tr(), accountInfo?.expiredPoints?.toString() ?? "0", Icons.calendar_month, + isSvg: true + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildStatChip(String label, String value, IconData icon, {bool isSvg = false}) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), + decoration: BoxDecoration( + color: Colors.white.withAlpha(51), + borderRadius: BorderRadius.circular(12.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + isSvg ? Utils.buildSvgWithAssets(icon: AppAssets.expired_icon, width: 15.w, height: 15.h, applyThemeColor: false) : Icon(icon, color: Colors.white, size: 16.f), + SizedBox(width: 6.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + value.toText14(color: Colors.white, isBold: true), + label.toText10(color: Colors.white.withAlpha(204)), + ], + ), + ], + ), + ); + } + + Widget _buildRedeemButton(BuildContext context) { + return CustomButton( + height: 46.h, + icon: AppAssets.redeem_icon, + iconColor: AppColors.whiteColor, + iconSize: 24.h, + backgroundColor: AppColors.habibPharmacyColor, + textColor: AppColors.whiteColor, + text: LocaleKeys.redeem.tr(context: context), + borderWidth: 0.w, + fontWeight: FontWeight.w600, + borderColor: AppColors.habibPharmacyColor.withAlpha(15), + padding: EdgeInsets.fromLTRB(4, 0, 12, 0), + fontSize: 14.f, + onPressed: () { + Uri uri = Uri.parse(PHARMACY_REDIRECT_URL); + launchUrl(uri, mode: LaunchMode.externalApplication); + }, + ); + } + + Widget _buildEmptyTransactions() { + return Container( + padding: EdgeInsets.all(32.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Center( + child: Column( + children: [ + Icon(Icons.receipt_long_outlined, size: 48.f, color: AppColors.greyTextColor), + SizedBox(height: 12.h), + "No transactions found".toText14( + color: AppColors.greyTextColor, + isCenter: true, + ), + ], + ), + ), + ); + } + + Widget _buildTransactionsList(List transactions) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: ListView.separated( + padding: EdgeInsets.all(16.w), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: transactions.length, + separatorBuilder: (context, index) => Divider( + color: AppColors.dividerColor, + height: 24.h, + ), + itemBuilder: (context, index) { + return _buildTransactionItem(transactions[index]); + }, + ), + ); + } + + Widget _buildTransactionItem(PointsDetails transaction) { + // Determine if points were gained or used + bool isGained = transaction.operationType?.toLowerCase() == 'gain' || transaction.operationType?.toLowerCase() == 'credit' || (transaction.points ?? 0) > 0; + + Color transactionColor = isGained ? AppColors.habibPharmacyColor : AppColors.primaryRedColor; + String pointsText = "${isGained ? '+' : '-'}${transaction.points?.abs() ?? 0}"; + + // Format date + String formattedDate = ""; + if (transaction.transactionDate != null) { + try { + formattedDate = DateUtil.formatDateToDate( + DateUtil.convertStringToDate(transaction.transactionDate!), + false, + ); + } catch (e) { + formattedDate = transaction.transactionDate ?? ""; + } + } + + return Row( + children: [ + // Transaction Icon + // Container( + // width: 44.w, + // height: 44.h, + // decoration: BoxDecoration( + // color: transactionColor.withAlpha(25), + // borderRadius: BorderRadius.circular(12.r), + // ), + // child: Icon( + // isGained ? Icons.add_rounded : Icons.remove_rounded, + // color: transactionColor, + // size: 24.f, + // ), + // ), + // SizedBox(width: 12.w), + + // Transaction Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (transaction.subTransactionTypeDescription ?? transaction.operationType ?? "Transaction").toText14( + isBold: true, + maxlines: 2, + ), + SizedBox(height: 2.h), + formattedDate.toText12(color: AppColors.greyTextColor), + ], + ), + ), + + // Points Value + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + pointsText.toText18( + isBold: true, + color: transactionColor, + ), + if (transaction.amount != null && transaction.amount! > 0) + "${transaction.amount?.toStringAsFixed(2) ?? "0.00"} SAR".toText11( + color: AppColors.greyTextColor, + ), + ], + ), + ], + ); + } +} + +// Custom painter for wavy background pattern +class _WavyBackgroundPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + // Base color from AppColors + final baseColor = AppColors.habibPharmacyColor; + + final paint = Paint() + ..color = Colors.white.withAlpha(25) + ..style = PaintingStyle.fill; + + // Draw multiple wave patterns + final path1 = Path(); + path1.moveTo(0, size.height * 0.3); + path1.quadraticBezierTo( + size.width * 0.25, + size.height * 0.2, + size.width * 0.5, + size.height * 0.3, + ); + path1.quadraticBezierTo( + size.width * 0.75, + size.height * 0.4, + size.width, + size.height * 0.3, + ); + path1.lineTo(size.width, 0); + path1.lineTo(0, 0); + path1.close(); + canvas.drawPath(path1, paint); + + // Second wave with darker shade + paint.color = Colors.black.withAlpha(15); + final path2 = Path(); + path2.moveTo(0, size.height * 0.6); + path2.quadraticBezierTo( + size.width * 0.3, + size.height * 0.5, + size.width * 0.6, + size.height * 0.6, + ); + path2.quadraticBezierTo( + size.width * 0.8, + size.height * 0.7, + size.width, + size.height * 0.6, + ); + path2.lineTo(size.width, size.height); + path2.lineTo(0, size.height); + path2.close(); + canvas.drawPath(path2, paint); + + // Decorative circles with white overlay + paint.color = Colors.white.withAlpha(20); + canvas.drawCircle(Offset(size.width * 0.85, size.height * 0.15), 30, paint); + paint.color = Colors.white.withAlpha(10); + canvas.drawCircle(Offset(size.width * 0.15, size.height * 0.75), 40, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 5e12f575..4e0a6135 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -30,6 +30,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ 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'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; @@ -140,6 +141,7 @@ class _LandingPageState extends State { if (appState.isAuthenticated) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); todoSectionViewModel.initializeTodoSectionViewModel(); immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); @@ -203,6 +205,7 @@ class _LandingPageState extends State { // Refresh Appointments Data habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); // Refresh Ancillary Orders todoSectionViewModel.initializeTodoSectionViewModel(); @@ -292,7 +295,7 @@ class _LandingPageState extends State { await authVM.onLoginPressed(); } }), - (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) + (appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0") ? Positioned( // right: appState.isArabic() ? 8.w : -8.w, top: -8.h, @@ -307,7 +310,7 @@ class _LandingPageState extends State { borderRadius: BorderRadius.circular(20.r), ), child: Text( - todoSectionVM.notificationsCount.toString(), + todoSectionVM.notificationsCount ?? "0", style: TextStyle( color: Colors.white, fontFamily: "Poppins", @@ -451,6 +454,9 @@ class _LandingPageState extends State { myAppointmentsViewModel.onTabChange(0); myAppointmentsViewModel.updateListWRTTab(0); Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + + // getIt.get().setPaymentConfiguration("Appointment Payment", 15.5); + // getIt.get().startCardPayment(); }), Consumer3( builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { @@ -864,6 +870,7 @@ class _LandingPageState extends State { children: [ LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox( + // height: 200.h, height: 170.h, child: ListView.separated( scrollDirection: Axis.horizontal, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 15aed11a..1d9bd656 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -86,6 +86,7 @@ class HabibWalletCard extends StatelessWidget { Consumer(builder: (context, habibWalletVM, child) { return Row( mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ @@ -104,8 +105,8 @@ class HabibWalletCard extends StatelessWidget { .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ), - // Spacer(), - Utils.getPaymentMethods(), + Spacer(), + Utils.getPaymentMethods(spacing: 3, width: 30, height: 30), ], ); }), diff --git a/lib/presentation/home/widgets/lakum_wallet_card.dart b/lib/presentation/home/widgets/lakum_wallet_card.dart index e693667c..3645a2d3 100644 --- a/lib/presentation/home/widgets/lakum_wallet_card.dart +++ b/lib/presentation/home/widgets/lakum_wallet_card.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -11,10 +12,12 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/lakum_wallet_details.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class LakumWalletCard extends StatelessWidget { const LakumWalletCard({super.key}); @@ -64,7 +67,7 @@ class LakumWalletCard extends StatelessWidget { // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ // LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - "Lakum Wallet".toText16(isBold: true, letterSpacing: -0.2), + LocaleKeys.lakumPoints.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), // Container( // height: 40.h, // width: 40.h, @@ -84,7 +87,7 @@ class LakumWalletCard extends StatelessWidget { // ), // ], // ), - SizedBox(height: 4.h), + SizedBox(height: 8.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -93,24 +96,23 @@ class LakumWalletCard extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.saudi_riyal_icon, - iconColor: AppColors.inputLabelTextColor, - width: 24.h, - height: 24.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.h), NumberFormat.decimalPattern() - .format(habibWalletVM.habibWalletAmount) + .format(habibWalletVM.isLakumAccountInfoLoading + ? 123 + : habibWalletVM.yahalaAccountNumber == "0" + ? 0 + : (habibWalletVM.lakumAccountInfo?.pointsBalance ?? 0)) .toString() .toText32(isBold: true, isEnglishOnly: true) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + .toShimmer2(isShow: habibWalletVM.isLakumAccountInfoLoading, radius: 12.h, width: 80.h, height: 40.h), + SizedBox(width: 4.h), + LocaleKeys.points.tr().toText12(color: AppColors.inputLabelTextColor, isBold: true) ], ), - // Spacer(), - Utils.getPaymentMethods(), + Spacer(), + Utils.buildSvgWithAssets(icon: AppAssets.hmg_pharmacy_logo, width: 32.h, height: 32.h, fit: BoxFit.contain, applyThemeColor: false), ], ); }), @@ -123,19 +125,21 @@ class LakumWalletCard extends StatelessWidget { flex: 6, child: CustomButton( height: 40.h, - icon: AppAssets.recharge_icon, - iconColor: AppColors.infoColor, + icon: AppAssets.redeem_icon, + iconColor: AppColors.habibPharmacyColor, iconSize: 24.h, - backgroundColor: AppColors.infoColor.withAlpha(15), - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(context: context), + backgroundColor: AppColors.habibPharmacyColor.withAlpha(15), + textColor: AppColors.habibPharmacyColor, + text: LocaleKeys.redeem.tr(context: context), borderWidth: 0.w, fontWeight: FontWeight.w600, - borderColor: AppColors.infoColor.withAlpha(15), + borderColor: AppColors.habibPharmacyColor.withAlpha(15), padding: EdgeInsets.fromLTRB(4, 0, 12, 0), fontSize: 14.f, onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + Uri uri = Uri.parse(PHARMACY_REDIRECT_URL); + launchUrl(uri, mode: LaunchMode.externalApplication); + // Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); }, ), ), @@ -165,7 +169,7 @@ class LakumWalletCard extends StatelessWidget { ).onPress(() { Navigator.of(context).push( CustomPageRoute( - page: HabibWalletPage(), + page: LakumWalletDetails(), ), ); }), @@ -179,7 +183,7 @@ class LakumWalletCard extends StatelessWidget { ).paddingSymmetrical(0.h, 0.h).onPress(() { Navigator.of(context).push( CustomPageRoute( - page: HabibWalletPage(), + page: LakumWalletDetails(), ), ); }), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 51bd94d7..624d0527 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -593,7 +593,7 @@ class _MedicalFilePageState extends State { ) : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.bgRedLightColor, + // color: AppColors.bgRedLightColor, borderRadius: 12.r, hasShadow: false, ), diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 1f2ed8ff..2f48bcbe 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -157,13 +157,14 @@ class _FamilyCardsState extends State { // ), // SizedBox(height: 24.h), CustomExpandableList( - expansionMode: ExpansionMode.exactlyOne, + expansionMode: ExpansionMode.multiple, dividerColor: AppColors.dividerColor, itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), items: [ ExpandableListItem( title: LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, + initiallyExpanded: true, children: [ SizedBox(height: 10.h), manageFamily() @@ -612,6 +613,7 @@ class _FamilyCardsState extends State { : Expanded( child: CustomButton( height: 40.h, + fontSize: 14.f, text: LocaleKeys.acceptLbl.tr(), onPressed: () { navigationService.pop(); @@ -628,6 +630,7 @@ class _FamilyCardsState extends State { Expanded( child: CustomButton( height: 40.h, + fontSize: 14.f, text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.rejectView.tr(), onPressed: () { navigationService.pop(); diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index 5a8e9c70..26f0d1c9 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -5,21 +5,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; 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/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; -import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/pharmacy_invoice_card.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/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; @@ -124,194 +121,302 @@ class _MyInvoicesListState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: LocaleKeys.allInvoices.tr(context: context), - onPressed: () { - myInvoicesViewModel.filterInvoices(InvoiceFilterType.all); - }, - backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w600, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.hospitals.tr(context: context), - onPressed: () { - if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; - _showHospitalFilterBottomSheet(context); - }, - backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w600, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.clinics.tr(context: context), - onPressed: () { - if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; - _showClinicFilterBottomSheet(context); - }, - backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w600, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.doctors.tr(context: context), - onPressed: () { - if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; - _showDoctorFilterBottomSheet(context); - }, - backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w600, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + initialIndex: 0, + tabs: [ + CustomTabBarModel(null, LocaleKeys.visitsOrders.tr(context: context)), + CustomTabBarModel(null, LocaleKeys.pharmacy.tr(context: context)), ], + onTabChange: (index) { + myInvoicesVM.onTabChanged(index); + if (index == 1 && myInvoicesVM.allPharmacyInvoicesList.isEmpty) { + // Load pharmacy invoices when pharmacy tab is selected and list is empty + myInvoicesVM.getAllPharmacyInvoices(); + } + }, ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - ListView.builder( - itemCount: myInvoicesVM.isInvoicesListLoading - ? 4 - : myInvoicesVM.allInvoicesList.isEmpty - ? 1 - : myInvoicesVM.allInvoicesList.length, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: EdgeInsetsGeometry.zero, - itemBuilder: (context, index) { - return myInvoicesVM.isInvoicesListLoading - ? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true) - : myInvoicesVM.allInvoicesList.isNotEmpty - ? AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: InvoiceListCard( - getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], - onTap: () async { - if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); - await myInvoicesViewModel.sendInvoiceEmail( - appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, - projectID: myInvoicesVM.allInvoicesList[index].projectId!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - isAutoDismiss: true); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); - myInvoicesViewModel.downloadInvoicePDF( - setupId: myInvoicesVM.allInvoicesList[index].setupId!, - invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, - projectID: myInvoicesVM.allInvoicesList[index].projectId!, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - onSuccess: (value) async { - LoaderBottomSheet.hideLoader(); - if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { - String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); - try { - OpenFilex.open(path); - } catch (ex) { + myInvoicesVM.selectedTabIndex == 0 ? Column( + children: [ + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: LocaleKeys.allInvoices.tr(context: context), + onPressed: () { + myInvoicesViewModel.filterInvoices(InvoiceFilterType.all); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w600, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.hospitals.tr(context: context), + onPressed: () { + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; + _showHospitalFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w600, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.clinics.tr(context: context), + onPressed: () { + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; + _showClinicFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w600, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.doctors.tr(context: context), + onPressed: () { + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; + _showDoctorFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w600, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ListView.builder( + itemCount: myInvoicesVM.isInvoicesListLoading + ? 4 + : myInvoicesVM.allInvoicesList.isEmpty + ? 1 + : myInvoicesVM.allInvoicesList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return myInvoicesVM.isInvoicesListLoading + ? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true) + : myInvoicesVM.allInvoicesList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: InvoiceListCard( + getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + onTap: () async { + if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); + await myInvoicesViewModel.sendInvoiceEmail( + appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectId!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myInvoicesViewModel.downloadInvoicePDF( + setupId: myInvoicesVM.allInvoicesList[index].setupId!, + invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectId!, + onError: (err) { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Cannot open file"), + child: Utils.getErrorWidget(loadingText: err), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - } - } - }, - ); - } - }, - // onTap: () async { - // myInvoicesVM.setInvoiceDetailLoading(); - // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); - // await myInvoicesVM.getInvoiceDetails( - // appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, - // invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, - // projectID: myInvoicesVM.allInvoicesList[index].projectId!, - // onSuccess: (val) { - // LoaderBottomSheet.hideLoader(); - // Navigator.of(context).push( - // CustomPageRoute( - // page: MyInvoicesDetailsPage( - // getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, - // getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], - // ), - // ), - // ); - // }, - // onError: (err) { - // LoaderBottomSheet.hideLoader(); - // showCommonBottomSheetWithoutHeight( - // context, - // child: Utils.getErrorWidget(loadingText: err), - // callBackFunc: () {}, - // isFullScreen: false, - // isCloseButtonVisible: true, - // ); - // }); - // }, + }, + onSuccess: (value) async { + LoaderBottomSheet.hideLoader(); + if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }, + ); + } + }, + // onTap: () async { + // myInvoicesVM.setInvoiceDetailLoading(); + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); + // await myInvoicesVM.getInvoiceDetails( + // appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + // invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + // projectID: myInvoicesVM.allInvoicesList[index].projectId!, + // onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: MyInvoicesDetailsPage( + // getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, + // getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + // ), + // ), + // ); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // }, + ), + ), ), ), - ), - ), - ) - : Utils.getNoDataWidget(context); - }).paddingSymmetrical(24.w, 0.h), + ) + : Utils.getNoDataWidget(context); + }).paddingSymmetrical(24.w, 0.h), + ], + ) + : Column( + children: [ + SizedBox(height: 16.h), + ListView.builder( + itemCount: myInvoicesVM.isPharmacyInvoicesLoading + ? 4 + : myInvoicesVM.allPharmacyInvoicesList.isEmpty + ? 1 + : myInvoicesVM.allPharmacyInvoicesList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return myInvoicesVM.isPharmacyInvoicesLoading + ? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true) + : myInvoicesVM.allPharmacyInvoicesList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: PharmacyInvoiceCard( + pharmacyInvoice: myInvoicesVM.allPharmacyInvoicesList[index], + onTap: () async { + // if (Utils.isVidaPlusProject(myInvoicesVM.allPharmacyInvoicesList[index].projectID ?? 0)) { + // // For Vida+ projects, send email (if API is available) + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getSuccessWidget( + // loadingText: LocaleKeys.comingSoon.tr(context: context), + // ), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // isAutoDismiss: true, + // ); + // } else { + // Download pharmacy invoice PDF + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myInvoicesViewModel.downloadPharmacyInvoicePDF( + setupId: myInvoicesVM.allPharmacyInvoicesList[index].setupId ?? "", + invoiceNo: myInvoicesVM.allPharmacyInvoicesList[index].invoiceNo ?? 0, + projectID: myInvoicesVM.allPharmacyInvoicesList[index].pharmacyProjectID ?? 0, + locationID: myInvoicesVM.allPharmacyInvoicesList[index].locationID ?? 0, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSuccess: (value) async { + LoaderBottomSheet.hideLoader(); + if (myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!.isNotEmpty) { + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }, + ); + // } + }, + ), + ), + ), + ), + ) + : Utils.getNoDataWidget(context); + }).paddingSymmetrical(24.w, 0.h), + ], + ), ], ); }), diff --git a/lib/presentation/my_invoices/widgets/pharmacy_invoice_card.dart b/lib/presentation/my_invoices/widgets/pharmacy_invoice_card.dart new file mode 100644 index 00000000..6d672b38 --- /dev/null +++ b/lib/presentation/my_invoices/widgets/pharmacy_invoice_card.dart @@ -0,0 +1,186 @@ +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/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +import 'dart:ui' as ui; + +class PharmacyInvoiceCard extends StatelessWidget { + final GetPharmacyInvoicesResponseModel pharmacyInvoice; + final VoidCallback onTap; + + const PharmacyInvoiceCard({ + super.key, + required this.pharmacyInvoice, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final isArabic = getIt().isArabic(); + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Type chips + // Wrap( + // alignment: WrapAlignment.start, + // direction: Axis.horizontal, + // spacing: 6.w, + // runSpacing: 6.h, + // children: [ + // AppCustomChipWidget( + // icon: AppAssets.pharmacy_icon, + // iconColor: AppColors.primaryRedColor, + // labelText: LocaleKeys.pharmacy.tr(context: context), + // textColor: AppColors.primaryRedColor, + // ), + // if (pharmacyInvoice.totalAmount != null) + // AppCustomChipWidget( + // labelText: '${pharmacyInvoice.totalAmount!.toStringAsFixed(2)} ${LocaleKeys.sar.tr(context: context)}', + // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + // textColor: AppColors.primaryRedColor, + // isEnglishOnly: true, + // ), + // ], + // ), + SizedBox(height: 16.h), + + // Main content row + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + Image.network( + pharmacyInvoice.doctorImageURL ?? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png', + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100.r), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, + border: Border.all( + color: AppColors.scaffoldBgColor, + width: 1.5.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false), + SizedBox(height: 2.h), + "${pharmacyInvoice.decimalDoctorRate ?? '0.0'}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100), + ), + ], + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (getIt().isArabic() + ? (pharmacyInvoice.doctorNameN ?? pharmacyInvoice.doctorName ?? LocaleKeys.doctor.tr(context: context)) + : (pharmacyInvoice.doctorName ?? pharmacyInvoice.doctorNameN ?? LocaleKeys.doctor.tr(context: context)) + ).toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${pharmacyInvoice.invoiceNo ?? '-'}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + isEnglishOnly: true, + ), + AppCustomChipWidget( + labelText: ((pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context)).length > 15 + ? '${(pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context)).substring(0, 12)}...' + : (pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context))), + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: pharmacyInvoice.projectName ?? LocaleKeys.hospital.tr(context: context), + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + if (pharmacyInvoice.appointmentDate != null) + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(pharmacyInvoice.appointmentDate), false), + isEnglishOnly: true, + ), + ), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.h), + + // Action button + CustomButton( + text: Utils.isVidaPlusProject(pharmacyInvoice.projectID ?? 0) + ? LocaleKeys.sendEmail.tr(context: context) + : LocaleKeys.downloadInvoice.tr(context: context), + onPressed: onTap, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconSize: 14.h, + ), + ], + ), + ), + ).paddingOnly(bottom: 16.h); + } + + String _formatDate(String? dateString) { + if (dateString == null || dateString.isEmpty) return '-'; + try { + final date = DateUtil.convertStringToDate(dateString); + return DateUtil.formatDateToDate(date, false); + } catch (e) { + return dateString; + } + } +} + diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index c581bcd5..bf83a905 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -53,11 +53,17 @@ class ProfileSettingsState extends State { @override void initState() { - scheduleMicrotask(() { - insuranceViewModel.initInsuranceProvider(); - }); - _loadPermissions(); super.initState(); + _loadPermissions(); + + // Load data after first frame + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + insuranceViewModel.initInsuranceProvider(); + // Load profile settings for preferred language + profileSettingsViewModel.getProfileSettings(); + } + }); } @override @@ -96,12 +102,14 @@ class ProfileSettingsState extends State { final SwiperController _controller = SwiperController(); late InsuranceViewModel insuranceViewModel; late ContactUsViewModel contactUsViewModel; + late ProfileSettingsViewModel profileSettingsViewModel; String _permissionsLabel = ""; @override Widget build(BuildContext context) { insuranceViewModel = Provider.of(context, listen: false); contactUsViewModel = Provider.of(context, listen: false); + profileSettingsViewModel = Provider.of(context, listen: false); return CollapsingListView( title: LocaleKeys.profileAndSettings.tr(context: context), logout: () { @@ -268,9 +276,9 @@ class ProfileSettingsState extends State { actionItem(AppAssets.language, LocaleKeys.communicationLanguage.tr(context: context), () { profileVm.openPreferredLanguageBottomSheet(); }, - trailingLabel: profileVm.isPatientProfileLoading + trailingLabel: profileVm.isPatientProfileLoading || profileVm.getPatientInfoForUpdate == null ? "English" - : (profileVm.getPatientInfoForUpdate.preferredLanguage! == "1" ? LocaleKeys.arabic.tr(context: context) : LocaleKeys.english.tr(context: context)), + : (profileVm.getPatientInfoForUpdate!.preferredLanguage! == "1" ? LocaleKeys.arabic.tr(context: context) : LocaleKeys.english.tr(context: context)), isShowLoading: profileVm.isPatientProfileLoading), 1.divider, actionItem(AppAssets.smart_phone_fill, LocaleKeys.emrgNo.tr(context: context), () { diff --git a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart index 4cecc651..32dcc57b 100644 --- a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart +++ b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart @@ -29,7 +29,7 @@ class _PreferredLanguageWidgetState extends State { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { - selectedValue = profileSettingsViewModel.getPatientInfoForUpdate.preferredLanguage!; + selectedValue = profileSettingsViewModel.getPatientInfoForUpdate?.preferredLanguage!; }); }); } @@ -60,9 +60,9 @@ class _PreferredLanguageWidgetState extends State { profileSettingsViewModel!.updatePatientInfo( patientInfo: { "PreferredLanguage": selectedValue, - "EmailAddress": profileSettingsViewModel.getPatientInfoForUpdate.emailAddress, - "EmergencyContactName": profileSettingsViewModel.getPatientInfoForUpdate.emergencyContactName, - "EmergencyContactNo": profileSettingsViewModel.getPatientInfoForUpdate.emergencyContactNo, + "EmailAddress": profileSettingsViewModel.getPatientInfoForUpdate!.emailAddress, + "EmergencyContactName": profileSettingsViewModel.getPatientInfoForUpdate!.emergencyContactName, + "EmergencyContactNo": profileSettingsViewModel.getPatientInfoForUpdate!.emergencyContactNo, "IsEmailAlertRequired": true, "IsSMSAlertRequired": true, }, diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart index e8e313f4..4f13726a 100644 --- a/lib/presentation/profile_settings/widgets/update_email_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -39,7 +39,7 @@ class _UpdateEmailDialogState extends State { // Set the text setState(() { - textController!.text = viewModel.getPatientInfoForUpdate.emailAddress ?? ""; + textController!.text = viewModel.getPatientInfoForUpdate!.emailAddress ?? ""; }); }); @@ -118,10 +118,10 @@ class _UpdateEmailDialogState extends State { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingEmailAddress.tr(context: context)); profileSettingsViewModel!.updatePatientInfo( patientInfo: { - "PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate.preferredLanguage, + "PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate!.preferredLanguage, "EmailAddress": textController!.text, - "EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate.emergencyContactName, - "EmergencyContactNo": profileSettingsViewModel!.getPatientInfoForUpdate.emergencyContactNo, + "EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate!.emergencyContactName, + "EmergencyContactNo": profileSettingsViewModel!.getPatientInfoForUpdate!.emergencyContactNo, "IsEmailAlertRequired": true, "IsSMSAlertRequired": true, }, diff --git a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart index b9a81afd..e1faadca 100644 --- a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart @@ -1,11 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -39,7 +41,7 @@ class _UpdateEmergencyContactDialogState extends State().navigatorKey.currentContext!).pop(); profileSettingsViewModel!.getProfileSettings(); }, isFullScreen: false, isAutoDismiss: true); }, diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 3a113b1e..db60b7df 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -302,6 +302,18 @@ class _PossibleConditionsPageState extends State { // Set flag to indicate booking is from symptoms checker symptomsCheckerViewModel.setBookingFromSymptomsChecker(true); + // Check if clinicDetailsList is not empty before accessing + if (symptomsCheckerViewModel.clinicDetailsList.isEmpty) { + // Show error if no clinic details available + final dialogService = getIt(); + dialogService.showErrorBottomSheet( + message: appState.isArabic() + ? "لا توجد تفاصيل عيادة متاحة" + : "No clinic details available", + ); + return; + } + // bookAppointmentsViewModel.getLocation(); bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel( clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID, @@ -395,9 +407,17 @@ class _PossibleConditionsPageState extends State { //if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api var id = ""; if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { - id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""; + // Check if hospitalList is not empty before accessing first + if (regionalViewModel.selectedHospital?.hospitalList != null && + regionalViewModel.selectedHospital!.hospitalList.isNotEmpty) { + id = regionalViewModel.selectedHospital!.hospitalList.first.iD?.toString() ?? ""; + } } else { - id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; + // Check if patientDoctorAppointmentList is not empty before accessing first + if (regionalViewModel.selectedHospital?.patientDoctorAppointmentList != null && + regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.isNotEmpty) { + id = regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.first.projectID?.toString() ?? ""; + } } bookAppointmentsViewModel.setProjectID(id); return SizedBox.shrink(); diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index a8de2708..24ed9fc5 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -16,6 +16,8 @@ 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/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -55,6 +57,7 @@ class _AncillaryOrderPaymentPageState extends State { late PayfortViewModel payfortViewModel; late AppState appState; late TodoSectionViewModel todoSectionViewModel; + late PayTabsViewModel paytabsViewModel; MyInAppBrowser? browser; String selectedPaymentMethod = ""; @@ -74,6 +77,7 @@ class _AncillaryOrderPaymentPageState extends State { appState = getIt.get(); todoSectionViewModel = Provider.of(context); payfortViewModel = Provider.of(context); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -125,7 +129,34 @@ class _AncillaryOrderPaymentPageState extends State { ).paddingSymmetrical(24.h, 0.h).onPress(() { if (!todoVM.isProcessingPayment) { selectedPaymentMethod = "MADA"; - _openPaymentURL("mada"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "Ancillary Orders Payment", + widget.totalAmount, + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + final paymentAmount = widget.totalAmount; + final fortId = transactionData.transactionReference; + final paymentMethod = selectedPaymentMethod; + + // Call createAdvancePayment with the payment details + _createAdvancePayment( + paymentAmount: paymentAmount.toDouble(), + paymentReference: fortId!, + paymentMethod: paymentMethod, + ); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + _openPaymentURL("mada"); + } } }), @@ -172,7 +203,34 @@ class _AncillaryOrderPaymentPageState extends State { ).paddingSymmetrical(24.h, 0.h).onPress(() { if (!todoVM.isProcessingPayment) { selectedPaymentMethod = "VISA"; - _openPaymentURL("visa"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration( + "Ancillary Orders Payment", + widget.totalAmount, + ); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + final paymentAmount = widget.totalAmount; + final fortId = transactionData.transactionReference; + final paymentMethod = selectedPaymentMethod; + + // Call createAdvancePayment with the payment details + _createAdvancePayment( + paymentAmount: paymentAmount.toDouble(), + paymentReference: fortId!, + paymentMethod: paymentMethod, + ); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + _openPaymentURL("visa"); + } } }), ], @@ -573,33 +631,61 @@ class _AncillaryOrderPaymentPageState extends State { return; } // Only proceed with Apple Pay if insert was successful - payfortViewModel.paymentWithApplePay( - customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", - customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", - orderDescription: "Ancillary Order Payment", - orderAmount: widget.totalAmount, - merchantReference: transID, - merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, - applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, - applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, - currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", - onFailed: (failureResult) async { - log("failureResult: ${failureResult.message.toString()}"); - LoaderBottomSheet.hideLoader(); + if(appState.isPaytabsEnabled) { + LoaderBottomSheet.hideLoader(); + paytabsViewModel.setPaymentConfiguration( + "Ancillary Orders Payment", + widget.totalAmount, + ); + paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + final paymentAmount = widget.totalAmount; + final fortId = transactionData.transactionReference; + final paymentMethod = selectedPaymentMethod; + + // Call createAdvancePayment with the payment details + _createAdvancePayment( + paymentAmount: paymentAmount.toDouble(), + paymentReference: fortId!, + paymentMethod: paymentMethod, + ); + }, onError: (err) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - }, - onSucceeded: (successResult) async { - LoaderBottomSheet.hideLoader(); - log("successResult: ${successResult.responseMessage.toString()}"); - selectedPaymentMethod = successResult.paymentOption ?? "VISA"; - _checkPaymentStatus(); - }, - ); + }); + } else { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "Ancillary Order Payment", + orderAmount: widget.totalAmount, + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + _checkPaymentStatus(); + }, + ); + } } } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 1e39eac8..1ecbcb61 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -40,6 +40,7 @@ class AppColors { static Color get lightGreenColor => isDarkMode ? dark.lightGreenColor : const Color(0xFF0ccedde); static Color get ratingColorYellow => isDarkMode ? dark.ratingColorYellow : const Color(0xFFFFAF15); static Color get spacerLineColor => isDarkMode ? dark.spacerLineColor : const Color(0x2E30391A); + static Color get habibPharmacyColor => const Color(0xFF2d9137); // ── Doctor Rating Colors ────────────────────────────────────────────────── static const Color ratingStarIconColor = Color(0xFFFFA726); // Orange-amber for star icons diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index f041cd85..b084870b 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; @@ -29,6 +30,7 @@ class _AppLanguageChangeState extends State { late ImmediateLiveCareViewModel immediateLiveCareViewModel; late EmergencyServicesViewModel emergencyServicesViewModel; late TodoSectionViewModel todoSectionViewModel; + late HabibWalletViewModel habibWalletVM; @override void initState() { @@ -48,6 +50,7 @@ class _AppLanguageChangeState extends State { immediateLiveCareViewModel = Provider.of(context, listen: false); emergencyServicesViewModel = Provider.of(context, listen: false); todoSectionViewModel = Provider.of(context, listen: false); + habibWalletVM = context.read(); return Column( spacing: 24.h, @@ -72,6 +75,10 @@ class _AppLanguageChangeState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); + //Reload Immediate LiveCare Data immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index dfc1584c..d232f207 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -150,6 +150,9 @@ class GenericBottomSheetState extends State { keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, onChange: (value) { if (widget.onChange != null) { + if (Utils.isArabicText(value!)) { + value = Utils.toEnglishNumbers(value); + } widget.onChange!(value); } }, diff --git a/pubspec.yaml b/pubspec.yaml index 99310e51..3d087f6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,8 @@ name: hmg_patient_app_new description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -#version: 0.0.30+29 -version: 0.0.4+1 +#version: 0.0.32+31 +version: 0.0.5+2 environment: sdk: ">=3.6.0 <4.0.0" @@ -101,11 +101,11 @@ dependencies: flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301 flutter_image_compress: ^2.3.0 - scrollable_positioned_list: ^0.3.8 - in_app_review: ^2.0.11 + flutter_paytabs_bridge: ^2.7.13 + dev_dependencies: flutter_test: sdk: flutter