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 index b05c90a2..db45e5a9 100644 Binary files a/assets/images/png/lakum_logo_white.png 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/langs/ar-SA.json b/assets/langs/ar-SA.json index 3c54f367..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": "حذف", diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index b5d62899..64074ff8 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -208,8 +208,8 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; - // body['PatientID'] = 5787639; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 1018977; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 301cb31c..e8e53a9a 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -867,6 +867,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"; @@ -990,6 +991,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 f11de07f..57f91017 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -379,6 +379,7 @@ class AppAssets { 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'; 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 1de302b7..0c014232 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -48,6 +48,7 @@ import 'package:hmg_patient_app_new/features/notifications/notifications_repo.da import 'package:hmg_patient_app_new/features/notifications/notifications_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_settings/profile_settings_repo.dart'; @@ -327,5 +328,7 @@ class AppDependencies { getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: 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 210970b1..ef83cb9f 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -876,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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index c2f35308..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) { 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_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 16997369..21139c80 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -48,18 +48,18 @@ class HabibWalletViewModel extends ChangeNotifier { String yahalaAccountNumber = ''; bool isLakumAccountInfoLoading = false; - late LakumInquiryInformationResponseModel lakumAccountInfo; + LakumInquiryInformationResponseModel? lakumAccountInfo; // Get all flattened transactions sorted by date descending List getAllLakumTransactions() { - if (lakumAccountInfo.gainedPointsAmountPerYear == null) { + if (lakumAccountInfo?.gainedPointsAmountPerYear == null) { return []; } List allTransactions = []; // Flatten the nested structure - for (var yearData in lakumAccountInfo.gainedPointsAmountPerYear!) { + for (var yearData in lakumAccountInfo!.gainedPointsAmountPerYear!) { if (yearData.pointsAmountPerMonth != null) { for (var monthData in yearData.pointsAmountPerMonth!) { if (monthData.pointsAmountPerday != null) { @@ -280,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: () {}); 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 a5d1e0b1..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,6 +33,7 @@ class MyInvoicesViewModel extends ChangeNotifier { String? selectedFilterItem; String? downloadInvoicePDFBase64 = ""; + String? downloadPharmacyInvoicePDFBase64 = ""; int selectedTabIndex = 0; @@ -36,9 +41,12 @@ class MyInvoicesViewModel extends ChangeNotifier { setInvoicesListLoading() { isInvoicesListLoading = true; + isPharmacyInvoicesLoading = true; allInvoicesList.clear(); + allPharmacyInvoicesList.clear(); _originalInvoicesList.clear(); currentFilter = InvoiceFilterType.all; + onTabChanged(0); notifyListeners(); } @@ -79,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( @@ -151,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/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/main.dart b/lib/main.dart index 25639e91..1ebcf0d6 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'; @@ -98,7 +99,7 @@ Future callAppStateInitializations() async { PlatformDispatcher.instance.onError = (error, stack) { if (!kDebugMode) { FirebaseCrashlytics.instance.recordError(error, stack, - fatal: false, + fatal: true, printDetails: true, reason: "${appState.isAuthenticated ? "Authenticated User ID: ${appState.getAuthenticatedUser()!.patientId} - ${appState.getAuthenticatedUser()!.mobileNumber}" : "Unauthenticated User"} - Uncaught asynchronous error"); @@ -262,6 +263,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_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/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 { 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/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 3bd95a98..1363025e 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -16,6 +16,8 @@ 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'; @@ -35,6 +37,7 @@ class WalletPaymentConfirmPage extends StatefulWidget { class _WalletPaymentConfirmPageState extends State { late PayfortViewModel payfortViewModel; + late PayTabsViewModel paytabsViewModel; late AppState appState; late HabibWalletViewModel habibWalletVM; @@ -56,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( @@ -102,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( @@ -144,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"); + } }), ], ), @@ -197,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 { @@ -268,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(); + }, + ); + } }); } @@ -321,16 +501,12 @@ class _WalletPaymentConfirmPageState extends State { onSuccess: (value) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), - callBackFunc: () { - habibWalletVM.initHabibWalletProvider(); - habibWalletVM.getPatientBalanceAmount(); + 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 - ); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); }, onError: (err) { LoaderBottomSheet.hideLoader(); @@ -343,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 index a3f9f9f9..79b71ec0 100644 --- a/lib/presentation/home/lakum_wallet_details.dart +++ b/lib/presentation/home/lakum_wallet_details.dart @@ -77,7 +77,7 @@ class LakumWalletDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.transactions.tr(context: context).toText16(isBold: true), - if (transactions.isNotEmpty) "${transactions.length} Records".toText12(color: AppColors.greyTextColor), + // if (transactions.isNotEmpty) "${transactions.length} Records".toText12(color: AppColors.greyTextColor), ], ), SizedBox(height: 12.h), @@ -95,7 +95,7 @@ class LakumWalletDetails extends StatelessWidget { ); } - Widget _buildLakumMainCard(LakumInquiryInformationResponseModel accountInfo, AppState appState) { + Widget _buildLakumMainCard(LakumInquiryInformationResponseModel? accountInfo, AppState appState) { return Container( decoration: BoxDecoration( gradient: LinearGradient( @@ -134,7 +134,7 @@ class LakumWalletDetails extends StatelessWidget { color: Colors.white, ), SizedBox(height: 4.h), - (accountInfo.accountNumber ?? 0).toString().toText14( + (accountInfo?.accountNumber ?? 0).toString().toText14( color: Colors.white.withAlpha(230), ), ], @@ -142,8 +142,8 @@ class LakumWalletDetails extends StatelessWidget { ), Utils.buildImgWithAssets( icon: AppAssets.lakumLogoWhite, - width: 80.h, - height: 80.h, + width: 110.h, + height: 100.h, ), ], ), @@ -155,7 +155,7 @@ class LakumWalletDetails extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - (accountInfo.pointsBalance?.toString() ?? "0").toText44( + (accountInfo?.pointsBalance?.toString() ?? "0").toText44( isBold: true, color: Colors.white, ), @@ -172,7 +172,7 @@ class LakumWalletDetails extends StatelessWidget { SizedBox(height: 4.h), // Balance Amount in SAR - "≈ ${accountInfo.pointsBalanceAmount?.toStringAsFixed(2) ?? "0.00"} SAR".toText18( + "≈ ${accountInfo?.pointsBalanceAmount?.toStringAsFixed(2) ?? "0.00"} SAR".toText18( color: Colors.white.withAlpha(204), ), SizedBox(height: 20.h), @@ -183,7 +183,7 @@ class LakumWalletDetails extends StatelessWidget { Expanded( child: _buildStatChip( LocaleKeys.gained.tr(), - accountInfo.gainedPoints?.toString() ?? "0", + accountInfo?.gainedPoints?.toString() ?? "0", Icons.arrow_circle_up, ), ), @@ -191,10 +191,17 @@ class LakumWalletDetails extends StatelessWidget { Expanded( child: _buildStatChip( LocaleKeys.consumed.tr(), - accountInfo.consumedPoints?.toString() ?? "0", + 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 + ), + ), ], ), ], @@ -203,7 +210,7 @@ class LakumWalletDetails extends StatelessWidget { ); } - Widget _buildStatChip(String label, String value, IconData icon) { + Widget _buildStatChip(String label, String value, IconData icon, {bool isSvg = false}) { return Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), decoration: BoxDecoration( @@ -213,7 +220,7 @@ class LakumWalletDetails extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, color: Colors.white, size: 16.f), + 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, @@ -318,20 +325,20 @@ class LakumWalletDetails extends StatelessWidget { 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), + // 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( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b6d32f72..f31a04cc 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_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'; @@ -148,11 +149,14 @@ class _LandingPageState extends State { notificationsViewModel.initNotificationsViewModel(); insuranceViewModel.initInsuranceProvider(); - if(appState.isRatedVisible) { - appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { - if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, - onSuccess: ((response) { + if (!appState.isRatedVisible) { + appointmentRatingViewModel.getLastRatingAppointment( + onSuccess: (response) { + if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + appointmentRatingViewModel.getAppointmentDetails( + appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, + appointmentRatingViewModel.appointmentRatedList.last.projectID!, + onSuccess: ((response) { appointmentRatingViewModel.setClinicOrDoctor(false); appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); @@ -212,10 +216,7 @@ class _LandingPageState extends State { controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( - top: (appState.isAuthenticated && - !insuranceVM.isInsuranceLoading && - insuranceVM.isInsuranceExpired && - insuranceVM.isInsuranceExpiryBannerShown) + top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) ? (MediaQuery.paddingOf(context).top + 70.h) : kToolbarHeight + 0.h, bottom: 24), @@ -338,9 +339,7 @@ class _LandingPageState extends State { SizedBox( width: 24.w, ), - Utils.buildSvgWithAssets( - icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h) - .onPress(() { + Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); }) ]) @@ -415,6 +414,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) { @@ -484,9 +486,7 @@ class _LandingPageState extends State { ), ) : SizedBox( - height: 255.h + - 20 + - 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space child: Builder( builder: (context) { final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading @@ -531,8 +531,7 @@ class _LandingPageState extends State { ? _buildLiveCareRequestCard().paddingSymmetrical(24.h, 16.h) : Container( width: double.infinity, - decoration: RoundedRectangleBorder() - .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( padding: EdgeInsets.all(16.h), child: Column( @@ -632,9 +631,7 @@ class _LandingPageState extends State { LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), Row( children: [ - LocaleKeys.viewMedicalFileLandingPage - .tr(context: context) - .toText14(color: AppColors.primaryRedColor, isBold: true), + LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], @@ -662,10 +659,7 @@ class _LandingPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.pendingAncillaryOrders - .tr(context: context) - .toText14(color: AppColors.eReferralCardColor, isBold: true) - .paddingSymmetrical(24.h, 0.h), + LocaleKeys.pendingAncillaryOrders.tr(context: context).toText14(color: AppColors.eReferralCardColor, isBold: true).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.view.tr(context: context), onPressed: () { @@ -696,10 +690,7 @@ class _LandingPageState extends State { trackColor: Color(0xffD9D9D9), trackBorderColor: Colors.transparent, trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only( - top: 92.h + 32.h, - left: MediaQuery.sizeOf(context).width / 2.5 - 10, - right: MediaQuery.sizeOf(context).width / 2.5 - 10), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData().getLoggedInServiceCardsList.length, @@ -753,10 +744,7 @@ class _LandingPageState extends State { trackColor: Color(0xffD9D9D9), trackBorderColor: Colors.transparent, trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only( - top: 92.h + 32.h, - left: MediaQuery.sizeOf(context).width / 2.5 - 10, - right: MediaQuery.sizeOf(context).width / 2.5 - 10), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, @@ -842,6 +830,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, @@ -879,10 +868,7 @@ class _LandingPageState extends State { ), ), ), - (appState.isAuthenticated && - !insuranceVM.isInsuranceLoading && - insuranceVM.isInsuranceExpired && - insuranceVM.isInsuranceExpiryBannerShown) + (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) ? Container( height: MediaQuery.paddingOf(context).top + 50.h, decoration: ShapeDecoration( @@ -899,24 +885,17 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive - .tr(context: context) - .toText14(color: AppColors.primaryRedColor, isBold: true) - .paddingSymmetrical(0.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h), Row( children: [ CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { insuranceVM.setIsInsuranceUpdateDetailsLoading(true); - insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), - appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + insuranceVM.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); showCommonBottomSheetWithoutHeight(context, - child: PatientInsuranceCardUpdateCard(), - callBackFunc: () {}, - title: "", - isCloseButtonVisible: false, - isFullScreen: false); + child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, @@ -1066,8 +1045,7 @@ class _LandingPageState extends State { SizedBox(height: 6.h), _buildServingNowSection(), SizedBox(height: 5.h), - _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "") - .toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), ], ), ), @@ -1096,8 +1074,7 @@ class _LandingPageState extends State { hasShadow: false, ), padding: EdgeInsets.all(6.h), - child: Lottie.asset(AppAnimations.hourGlass, - repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), ], ); } @@ -1276,8 +1253,7 @@ class _LandingPageState extends State { hasShadow: false, ), padding: EdgeInsets.all(6.h), - child: Lottie.asset(AppAnimations.hourGlass, - repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), // Utils.buildSvgWithAssets( // icon: AppAssets.waiting_icon, // width: 24.h, @@ -1325,8 +1301,7 @@ class _LandingPageState extends State { // Appointment Card Wrapper (reusable) Widget _buildAppointmentCardWrapper(appointment) { return Container( - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), child: AppointmentCard( patientAppointmentHistoryResponseModel: appointment, myAppointmentsViewModel: myAppointmentsViewModel, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 5ee07f15..1d9bd656 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -105,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 599dc2e8..3645a2d3 100644 --- a/lib/presentation/home/widgets/lakum_wallet_card.dart +++ b/lib/presentation/home/widgets/lakum_wallet_card.dart @@ -99,7 +99,11 @@ class LakumWalletCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ NumberFormat.decimalPattern() - .format(habibWalletVM.isLakumAccountInfoLoading ? 123 : habibWalletVM.yahalaAccountNumber == "0" ? 0 : habibWalletVM.lakumAccountInfo.pointsBalance) + .format(habibWalletVM.isLakumAccountInfoLoading + ? 123 + : habibWalletVM.yahalaAccountNumber == "0" + ? 0 + : (habibWalletVM.lakumAccountInfo?.pointsBalance ?? 0)) .toString() .toText32(isBold: true, isEnglishOnly: true) .toShimmer2(isShow: habibWalletVM.isLakumAccountInfoLoading, radius: 12.h, width: 80.h, height: 40.h), @@ -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 b60cd8bc..c22e261d 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -584,7 +584,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 2ebb82b9..d2706732 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -83,27 +83,19 @@ class _FamilyCardsState extends State { false, // Don't show files option, only camera and gallery (base64String, file) async { try { - print('=== Starting image processing ==='); - print('File path: ${file.path}'); - print('File exists: ${await file.exists()}'); - print('Original file size: ${await file.length() / 1024} KB'); - // Compress and resize the image - print('Calling compressAndResizeImage...'); final compressedFile = await ImageCompressionHelper.compressAndResizeImage(file); File finalFile; String finalBase64; if (compressedFile == null) { - print('⚠️ Compression failed - using original file as fallback'); // Fallback: use original image if compression fails final originalSize = await file.length(); final maxSize = 1048576; // 1MB if (originalSize > maxSize) { - print('❌ Original file is too large: ${originalSize / 1024} KB'); if (mounted) { Utils.showToast( LocaleKeys.imageSizeTooLarge.tr(context: context), @@ -111,8 +103,6 @@ class _FamilyCardsState extends State { } return; } - - print('✅ Using original file (${originalSize / 1024} KB)'); finalFile = file; var bytes = await file.readAsBytes(); finalBase64 = base64.encode(bytes); @@ -120,10 +110,8 @@ class _FamilyCardsState extends State { // Check compressed file size final fileSize = await compressedFile.length(); final maxSize = 1048576; // 1MB - print('✅ Compression successful: ${fileSize / 1024} KB'); if (fileSize > maxSize) { - print('❌ Compressed file still too large'); if (mounted) { Utils.showToast( LocaleKeys.imageSizeTooLarge.tr(context: context), @@ -137,22 +125,16 @@ class _FamilyCardsState extends State { finalBase64 = base64.encode(bytes); } - print('Converting to base64... Length: ${finalBase64.length}'); - if (mounted) { setState(() { _selectedImage = finalFile; }); - print('📤 Starting upload...'); // Upload the image _uploadImage(finalBase64); } - print('=== Image processing complete ==='); } catch (e, stackTrace) { - print('❌ Error in _pickImage: $e'); - print('Stack trace: $stackTrace'); if (mounted) { Utils.showToast( LocaleKeys.failedToProcessImage.tr(context: context), @@ -450,13 +432,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() @@ -901,6 +884,7 @@ class _FamilyCardsState extends State { : Expanded( child: CustomButton( height: 40.h, + fontSize: 14.f, text: LocaleKeys.acceptLbl.tr(), onPressed: () { navigationService.pop(); @@ -917,6 +901,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 ae191d89..26f0d1c9 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -5,22 +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'; @@ -135,6 +131,10 @@ class _MyInvoicesListState extends State { ], 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), myInvoicesVM.selectedTabIndex == 0 ? Column( @@ -148,7 +148,7 @@ class _MyInvoicesListState extends State { 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), + 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, @@ -164,7 +164,7 @@ class _MyInvoicesListState extends State { _showHospitalFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + 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, @@ -180,7 +180,7 @@ class _MyInvoicesListState extends State { _showClinicFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + 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, @@ -196,7 +196,7 @@ class _MyInvoicesListState extends State { _showDoctorFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + 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, @@ -329,7 +329,94 @@ class _MyInvoicesListState extends State { : Utils.getNoDataWidget(context); }).paddingSymmetrical(24.w, 0.h), ], - ) : Container(), + ) + : 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/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/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/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