From 60bd9ee55a2254ec5dca20940bfa7ddaea0b3e5a Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 9 Nov 2025 16:34:07 +0300 Subject: [PATCH 1/3] ER Online CheckIn implementation contd. --- lib/core/utils/utils.dart | 18 +++ .../emergency_services_repo.dart | 79 +++++++++++ .../emergency_services_view_model.dart | 123 +++++++++++++++++- .../call_ambulance/call_ambulance_page.dart | 21 ++- .../widgets/HospitalBottomSheetBody.dart | 58 ++++++--- .../widgets/type_selection_widget.dart | 105 +++++++++------ .../emergency_services_page.dart | 97 +++++++++++++- .../er_online_checkin_home.dart | 114 ++++++++++++++++ 8 files changed, 545 insertions(+), 70 deletions(-) create mode 100644 lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 491aa49..3420efc 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -18,6 +18,8 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/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/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -804,6 +806,22 @@ class Utils { return file.path; } + ///method to be used to get the text as per the langauge of the application + static String getTextWRTCurrentLanguage(String? englishText, String? arabicText) { + String? text = appState.isArabic() ? arabicText : englishText; + return text ?? ''; + } + + static String formatNumberToInternationalFormat(num number, {String? currencySymbol, int decimalDigit = 0}) { + return NumberFormat.currency(locale: 'en_US', symbol: currencySymbol ?? "", decimalDigits: decimalDigit).format(number); + } + + static PatientDoctorAppointmentList? convertToPatientDoctorAppointmentList(HospitalsModel? hospital) { + if (hospital == null) return null; + return PatientDoctorAppointmentList( + filterName: hospital.name, distanceInKMs: hospital.distanceInKilometers?.toString(), projectTopName: hospital.name, projectBottomName: hospital.name, model: hospital, isHMC: hospital.isHMC); + } + static bool havePrivilege(int id) { bool isHavePrivilege = false; try { diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index b238602..5c63f5b 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -5,12 +5,17 @@ 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/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class EmergencyServicesRepo { Future>>> getRRTProcedures(); Future>>> getNearestEr({int? id, int? projectID}); + + Future>> checkPatientERAdvanceBalance(); + + Future>>> getProjectList(); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -91,4 +96,78 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> checkPatientERAdvanceBalance() async { + Map mapDevice = {"ClinicID": 10}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + CHECK_PATIENT_ER_ADVANCE_BALANCE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final bool patientHasERBalance = response['BalanceAmount'] > 0; + print(patientHasERBalance); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: patientHasERBalance, + ); + } 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>>> getProjectList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_LIST, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListProject']; + + final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } 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/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 77e5823..8d37760 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -8,7 +8,10 @@ import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -30,8 +33,20 @@ class EmergencyServicesViewModel extends ChangeNotifier { List RRTProceduresList = []; + List? hospitalList; + List? hmgHospitalList; + List? hmcHospitalList; + List? displayList; + HospitalsModel? selectedHospital; + FacilitySelection selectedFacility = FacilitySelection.ALL; + int hmgCount = 0; + int hmcCount = 0; + bool pickupFromInsideTheLocation = true; + late RRTProceduresResponseModel selectedRRTProcedure; + bool patientHasAdvanceERBalance = false; + BottomSheetType bottomSheetType = BottomSheetType.FIXED; setSelectedRRTProcedure(RRTProceduresResponseModel procedure) { @@ -47,10 +62,9 @@ class EmergencyServicesViewModel extends ChangeNotifier { required this.appState, }); - get isGMSAvailable - { - return appState.isGMSAvailable; - } + bool get isArabic => appState.isArabic(); + + get isGMSAvailable => appState.isGMSAvailable; Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -167,6 +181,75 @@ class EmergencyServicesViewModel extends ChangeNotifier { //todo handle the camera moved position for HMS devices } + FutureOr getProjects() async { + // if (hospitalList.isNotEmpty) return; + var response = await emergencyServicesRepo.getProjectList(); + + response.fold( + (failure) async {}, + (apiResponse) async { + List? data = apiResponse.data; + if (data == null) return; + hospitalList = data; + hmgHospitalList = data.where((e) => e.isHMC == false).toList(); + hmcHospitalList = data.where((e) => e.isHMC == true).toList(); + hmgCount = hmgHospitalList?.length ?? 0; + hmcCount = hmcHospitalList?.length ?? 0; + notifyListeners(); + }, + ); + } + + setSelectedFacility(FacilitySelection selection) { + selectedFacility = selection; + notifyListeners(); + } + + searchHospitals(String query) { + if (query.isEmpty) { + getDisplayList(); + return; + } + List? sourceList; + switch (selectedFacility) { + case FacilitySelection.ALL: + sourceList = hospitalList; + break; + case FacilitySelection.HMG: + sourceList = hmgHospitalList; + break; + case FacilitySelection.HMC: + sourceList = hmcHospitalList; + break; + } + displayList = sourceList?.where((hospital) => hospital.name != null && hospital.name!.toLowerCase().contains(query.toLowerCase())).toList(); + notifyListeners(); + } + + getDisplayList() { + switch (selectedFacility) { + case FacilitySelection.ALL: + displayList = hospitalList; + break; + case FacilitySelection.HMG: + displayList = hmgHospitalList; + break; + case FacilitySelection.HMC: + displayList = hmcHospitalList; + break; + } + notifyListeners(); + } + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + String? getSelectedHospitalName() { + return selectedHospital?.getName(isArabic); + } + void navigateTOAmbulancePage() { locationUtils!.getLocation( isShowConfirmDialog: true, @@ -181,14 +264,44 @@ class EmergencyServicesViewModel extends ChangeNotifier { }); } + void navigateToEROnlineCheckIn() { + navServices.push( + CustomPageRoute(page: ErOnlineCheckinHome()), + ); + } + void updateBottomSheetState(BottomSheetType sheetType) { bottomSheetType = sheetType; notifyListeners(); } void setIsGMSAvailable(bool value) { - notifyListeners(); + } + Future checkPatientERAdvanceBalance({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.checkPatientERAdvanceBalance(); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) { + patientHasAdvanceERBalance = false; + if (onSuccess != null) { + onSuccess(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + patientHasAdvanceERBalance = false; + } else if (apiResponse.messageStatus == 1) { + patientHasAdvanceERBalance = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); } } diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index eb36f92..9bd6c5d 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -441,7 +441,26 @@ class CallAmbulancePage extends StatelessWidget { title: LocaleKeys.selectHospital.tr(), context, - child: HospitalBottomSheetBody(), + child: Consumer( + builder:(_,vm,__)=> HospitalBottomSheetBody( + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: + vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), isFullScreen: false, isCloseButtonVisible: true, hasBottomPadding: false, diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart index c594888..e264488 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart @@ -2,12 +2,17 @@ import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; @@ -20,8 +25,25 @@ import 'package:provider/provider.dart'; class HospitalBottomSheetBody extends StatelessWidget { final TextEditingController searchText = TextEditingController(); + final Debouncer debouncer = Debouncer(milliseconds: 500); - HospitalBottomSheetBody({super.key}); + final int hmcCount; + final int hmgCount; + final List? displayList; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onFacilityClicked; + final Function(HospitalsModel) onHospitalClicked; + final Function(String) onHospitalSearch; + + HospitalBottomSheetBody( + {super.key, + required this.hmcCount, + required this.hmgCount, + this.displayList, + required this.selectedFacility, + required this.onFacilityClicked, + required this.onHospitalClicked, + required this.onHospitalSearch}); @override Widget build(BuildContext context) { @@ -29,13 +51,14 @@ class HospitalBottomSheetBody extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextInputWidget( labelText: LocaleKeys.search.tr(), hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - + debouncer.run(() { + onHospitalSearch(value ?? ""); + }); }, isEnable: true, prefix: null, @@ -51,30 +74,31 @@ class HospitalBottomSheetBody extends StatelessWidget { ), SizedBox(height: 24.h), TypeSelectionWidget( - hmcCount: "0", - hmgCount: "0", + selectedFacility: selectedFacility, + hmcCount: hmcCount.toString(), + hmgCount: hmgCount.toString(), onitemClicked: (selectedValue){ - + onFacilityClicked(selectedValue); }, ), SizedBox(height: 21.h), SizedBox( - height: MediaQuery.sizeOf(context).height * .4, - child: ListView.separated( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( itemBuilder: (_, index) { - var hospital = null; + var hospital = displayList?[index]; return HospitalListItem( - hospitalData: hospital, - isLocationEnabled: false, - ).onPress(() { - + hospitalData: Utils.convertToPatientDoctorAppointmentList(hospital), + isLocationEnabled: true, + ).onPress(() { + onHospitalClicked(hospital!); });}, separatorBuilder: (_, __) => SizedBox( - height: 16.h, - ), - itemCount: 0, - )) + height: 16.h, + ), + itemCount: displayList?.length ?? 0, + )) ], ); } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart index c1ab8a8..17f3d9c 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart @@ -12,10 +12,15 @@ import 'package:provider/provider.dart' show Consumer; class TypeSelectionWidget extends StatelessWidget { final String hmcCount; final String hmgCount; - final Function(String) onitemClicked; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onitemClicked; const TypeSelectionWidget( - {super.key, required this.hmcCount, required this.hmgCount, required this.onitemClicked}); + {super.key, + required this.hmcCount, + required this.hmgCount, + required this.onitemClicked, + required this.selectedFacility}); @override Widget build(BuildContext context) { @@ -28,51 +33,69 @@ class TypeSelectionWidget extends StatelessWidget { labelText: "All Facilities".needTranslation, shape: RoundedRectangleBorder( side: BorderSide( - color: AppColors.errorColor - , + color: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), backgroundColor: - AppColors.secondaryLightRedColor - , - textColor: AppColors.errorColor - , + selectedFacility == FacilitySelection.ALL + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor:AppColors.blackColor + , ).onPress((){ - onitemClicked(FacilitySelection.ALL.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmg, - iconHasColor: false, - labelText: "Hospitals".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color: AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMG.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmc, - iconHasColor: false, - labelText: "Medical Centers".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color:AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMC.name); + onitemClicked(FacilitySelection.ALL); }), + Visibility( + visible: hmgCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmg, + iconHasColor: false, + labelText: "Hospitals".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMG + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMG); + }), + ), + Visibility( + visible: hmcCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmc, + iconHasColor: false, + labelText: "Medical Centers".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMC + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMC); + }), + ), ], ); } diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 90a8551..d9a5c6a 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -34,7 +34,7 @@ class EmergencyServicesPage extends StatelessWidget { locationUtils = getIt.get(); locationUtils!.isShowConfirmDialog = true; return CollapsingListView( - title: "Emergency Services".needTranslation, + title: LocaleKeys.emergencyServices.tr(), requests: () {}, child: Padding( padding: EdgeInsets.all(24.h), @@ -57,7 +57,7 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Request and ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Request an ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -101,8 +101,7 @@ class EmergencyServicesPage extends StatelessWidget { height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28( - color: AppColors.whiteColor, isBold: true), + LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), "Are you sure you want to call an ambulance?" .needTranslation @@ -234,7 +233,7 @@ class EmergencyServicesPage extends StatelessWidget { ), Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), "Are you sure you want to call Rapid Response Team (RRT)?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), @@ -257,7 +256,93 @@ class EmergencyServicesPage extends StatelessWidget { callBackFunc: () {}, ); }); - + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.whiteColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + ), + SizedBox(height: 8.h), + ], + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: false, + hasBottomPadding: false, + backgroundColor: AppColors.primaryRedColor, + callBackFunc: () {}, + ); + }), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rrt_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Emergency Check-In".needTranslation.toText16(isBold: true, color: AppColors.blackColor), + "Prior ER Check-In to skip the line & payment at the reception.".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ], + ), + ), + SizedBox(width: 12.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor, + borderRadius: 24.h, + ), + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".toText14(), + Utils.buildSvgWithAssets( + icon: AppAssets.cancel_circle_icon, + iconColor: AppColors.whiteColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ).onPress(() { + Navigator.of(context).pop(); + }), + ], + ), + Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + SizedBox(height: 8.h), + LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), + SizedBox(height: 8.h), + "Are you sure you want to make ER Check-In?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async { + Navigator.of(context).pop(); + LoaderBottomSheet.showLoader(loadingText: "Checking your ER Appointment status...".needTranslation); + await context.read().checkPatientERAdvanceBalance(onSuccess: (dynamic response) { + LoaderBottomSheet.hideLoader(); + context.read().navigateToEROnlineCheckIn(); + }); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart new file mode 100644 index 0000000..222fced --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -0,0 +1,114 @@ +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/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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +import '../call_ambulance/widgets/HospitalBottomSheetBody.dart'; + +class ErOnlineCheckinHome extends StatelessWidget { + const ErOnlineCheckinHome({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Online Check-In".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "This service lets patients to register their ER appointment prior to arrival.".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + CustomButton( + text: "Book Appointment".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: "Fetching hospitals list...".needTranslation); + await context.read().getProjects(); + LoaderBottomSheet.hideLoader(); + //Project Selection Dropdown + showHospitalBottomSheet(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.bookAppoBottom, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ], + ), + ); + } + + showHospitalBottomSheet(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + context, + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } +} From cc1e073f6d86fd6eaa213f496a82713fe3e6c8be Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Sun, 9 Nov 2025 20:59:14 +0300 Subject: [PATCH 2/3] ER Online CheckIn implementation contd. --- .../emergency_services_repo.dart | 38 +++ .../emergency_services_view_model.dart | 46 ++- ...EROnlineCheckInPaymentDetailsResponse.dart | 108 +++++++ lib/features/payfort/payfort_repo.dart | 41 +++ lib/features/payfort/payfort_view_model.dart | 21 ++ .../appointment_details_page.dart | 2 +- .../er_online_checkin_home.dart | 7 +- ...r_online_checkin_payment_details_page.dart | 177 ++++++++++ .../er_online_checkin_payment_page.dart | 302 ++++++++++++++++++ 9 files changed, 729 insertions(+), 13 deletions(-) create mode 100644 lib/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart create mode 100644 lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart create mode 100644 lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 5c63f5b..1b26cd9 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; @@ -16,6 +17,8 @@ abstract class EmergencyServicesRepo { Future>> checkPatientERAdvanceBalance(); Future>>> getProjectList(); + + Future>> checkPatientERPaymentInformation({int projectID}); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -170,4 +173,39 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> checkPatientERPaymentInformation({int? projectID}) async { + Map mapDevice = {"ClinicID": 10, "ProjectID": projectID ?? 0}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_ER_ONLINE_PAYMENT_DETAILS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final erOnlineCheckInPaymentDetailsResponse = EROnlineCheckInPaymentDetailsResponse.fromJson(response["ResponsePatientShare"]); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: erOnlineCheckInPaymentDetailsResponse, + ); + } 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/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 8d37760..98a3d33 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -6,12 +6,14 @@ import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -46,6 +48,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { late RRTProceduresResponseModel selectedRRTProcedure; bool patientHasAdvanceERBalance = false; + late EROnlineCheckInPaymentDetailsResponse erOnlineCheckInPaymentDetailsResponse; BottomSheetType bottomSheetType = BottomSheetType.FIXED; @@ -64,8 +67,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool get isArabic => appState.isArabic(); - get isGMSAvailable => appState.isGMSAvailable; - + get isGMSAvailable => appState.isGMSAvailable; Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { RRTProceduresList.clear(); @@ -163,8 +165,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } GMSMapServices.CameraPosition getGMSLocation() { - return GMSMapServices.CameraPosition( - target: GMSMapServices.LatLng(appState.userLat, appState.userLong), zoom: 18); + return GMSMapServices.CameraPosition(target: GMSMapServices.LatLng(appState.userLat, appState.userLong), zoom: 18); } handleGMSMapCameraMoved(GMSMapServices.CameraPosition value) { @@ -172,9 +173,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } HMSCameraServices.CameraPosition getHMSLocation() { - return HMSCameraServices.CameraPosition( - target: HMSCameraServices.LatLng(appState.userLat, appState.userLong),zoom: 18); - + return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(appState.userLat, appState.userLong), zoom: 18); } handleHMSMapCameraMoved(HMSCameraServices.CameraPosition value) { @@ -256,11 +255,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { onSuccess: (position) { updateBottomSheetState(BottomSheetType.FIXED); navServices.push( - CustomPageRoute( - page: CallAmbulancePage(), direction: AxisDirection.down - ), + CustomPageRoute(page: CallAmbulancePage(), direction: AxisDirection.down), ); - }); } @@ -270,6 +266,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { ); } + void navigateToEROnlineCheckInPaymentPage() { + navServices.push( + CustomPageRoute(page: ErOnlineCheckinPaymentDetailsPage()), + ); + } + void updateBottomSheetState(BottomSheetType sheetType) { bottomSheetType = sheetType; notifyListeners(); @@ -304,4 +306,26 @@ class EmergencyServicesViewModel extends ChangeNotifier { }, ); } + + Future getPatientERPaymentInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.checkPatientERPaymentInformation(projectID: selectedHospital!.iD); + + result.fold( + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart b/lib/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart new file mode 100644 index 0000000..5cac2cc --- /dev/null +++ b/lib/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart @@ -0,0 +1,108 @@ +class EROnlineCheckInPaymentDetailsResponse { + num? cashPrice; + num? cashPriceTax; + num? cashPriceWithTax; + int? companyId; + String? companyName; + num? companyShareWithTax; + dynamic errCode; + int? groupID; + String? insurancePolicyNo; + String? message; + String? patientCardID; + num? patientShare; + num? patientShareWithTax; + num? patientTaxAmount; + int? policyId; + String? policyName; + String? procedureId; + String? procedureName; + dynamic setupID; + int? statusCode; + String? subPolicyNo; + bool? isCash; + bool? isEligible; + bool? isInsured; + + EROnlineCheckInPaymentDetailsResponse( + {this.cashPrice, + this.cashPriceTax, + this.cashPriceWithTax, + this.companyId, + this.companyName, + this.companyShareWithTax, + this.errCode, + this.groupID, + this.insurancePolicyNo, + this.message, + this.patientCardID, + this.patientShare, + this.patientShareWithTax, + this.patientTaxAmount, + this.policyId, + this.policyName, + this.procedureId, + this.procedureName, + this.setupID, + this.statusCode, + this.subPolicyNo, + this.isCash, + this.isEligible, + this.isInsured}); + + EROnlineCheckInPaymentDetailsResponse.fromJson(Map json) { + cashPrice = json['CashPrice']; + cashPriceTax = json['CashPriceTax']; + cashPriceWithTax = json['CashPriceWithTax']; + companyId = json['CompanyId']; + companyName = json['CompanyName']; + companyShareWithTax = json['CompanyShareWithTax']; + errCode = json['ErrCode']; + groupID = json['GroupID']; + insurancePolicyNo = json['InsurancePolicyNo']; + message = json['Message']; + patientCardID = json['PatientCardID']; + patientShare = json['PatientShare']; + patientShareWithTax = json['PatientShareWithTax']; + patientTaxAmount = json['PatientTaxAmount']; + policyId = json['PolicyId']; + policyName = json['PolicyName']; + procedureId = json['ProcedureId']; + procedureName = json['ProcedureName']; + setupID = json['SetupID']; + statusCode = json['StatusCode']; + subPolicyNo = json['SubPolicyNo']; + isCash = json['IsCash']; + isEligible = json['IsEligible']; + isInsured = json['IsInsured']; + } + + Map toJson() { + final Map data = new Map(); + data['CashPrice'] = this.cashPrice; + data['CashPriceTax'] = this.cashPriceTax; + data['CashPriceWithTax'] = this.cashPriceWithTax; + data['CompanyId'] = this.companyId; + data['CompanyName'] = this.companyName; + data['CompanyShareWithTax'] = this.companyShareWithTax; + data['ErrCode'] = this.errCode; + data['GroupID'] = this.groupID; + data['InsurancePolicyNo'] = this.insurancePolicyNo; + data['Message'] = this.message; + data['PatientCardID'] = this.patientCardID; + data['PatientShare'] = this.patientShare; + data['PatientShareWithTax'] = this.patientShareWithTax; + data['PatientTaxAmount'] = this.patientTaxAmount; + data['PolicyId'] = this.policyId; + data['PolicyName'] = this.policyName; + data['ProcedureId'] = this.procedureId; + data['ProcedureName'] = this.procedureName; + data['SetupID'] = this.setupID; + data['StatusCode'] = this.statusCode; + data['SubPolicyNo'] = this.subPolicyNo; + data['IsCash'] = this.isCash; + data['IsEligible'] = this.isEligible; + data['IsInsured'] = this.isInsured; + return data; + } +} diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index 74a323a..c00b01c 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/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_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; @@ -25,6 +26,8 @@ abstract class PayfortRepo { Future>> updateTamaraRequestStatus( {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}); + + Future>> getTamaraInstallmentsDetails(); } class PayfortRepoImp implements PayfortRepo { @@ -250,4 +253,42 @@ class PayfortRepoImp implements PayfortRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getTamaraInstallmentsDetails() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.get( + ApiConsts.GET_TAMARA_INSTALLMENTS_URL, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response; + + final tamaraInstallmentsList = GetTamaraInstallmentsDetailsResponseModel.fromJson(list.first); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: tamaraInstallmentsList, + ); + } 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/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 89effcd..5910473 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -1,6 +1,7 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; @@ -21,6 +22,9 @@ class PayfortViewModel extends ChangeNotifier { late AmazonPayfort _payfort; final NetworkInfo _info = NetworkInfo(); + GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; + bool isTamaraDetailsLoading = false; + PayfortViewModel({required this.payfortRepo, required this.errorHandlerService}); setIsApplePayConfigurationLoading(bool value) { @@ -249,4 +253,21 @@ class PayfortViewModel extends ChangeNotifier { }, ); } + + Future getTamaraInstallmentsDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.getTamaraInstallmentsDetails(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + getTamaraInstallmentsDetailsResponseModel = apiResponse.data!; + isTamaraDetailsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + }, + ); + } + } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index bac44dd..c772c80 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -219,7 +219,7 @@ class _AppointmentDetailsPageState extends State { ), const Spacer(), Switch( - activeThumbColor: AppColors.successColor, + // activeThumbColor: AppColors.successColor, activeTrackColor: AppColors.successColor.withValues(alpha: .15), value: widget.patientAppointmentHistoryResponseModel.hasReminder!, onChanged: (newValue) { diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index 222fced..a20a81d 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -92,9 +92,14 @@ class ErOnlineCheckinHome extends StatelessWidget { vm.setSelectedFacility(value); vm.getDisplayList(); }, - onHospitalClicked: (hospital) { + onHospitalClicked: (hospital) async { Navigator.pop(context); vm.setSelectedHospital(hospital); + LoaderBottomSheet.showLoader(loadingText: "Fetching payment information...".needTranslation); + await vm.getPatientERPaymentInformation(onSuccess: (response) { + LoaderBottomSheet.hideLoader(); + vm.navigateToEROnlineCheckInPaymentPage(); + }); }, onHospitalSearch: (value) { vm.searchHospitals(value ?? ""); diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart new file mode 100644 index 0000000..3aa975d --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart @@ -0,0 +1,177 @@ +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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { + ErOnlineCheckinPaymentDetailsPage({super.key}); + + late AppState appState; + late EmergencyServicesViewModel emergencyServicesViewModel; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + emergencyServicesViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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: [ + "ER Visit Details".needTranslation.toText18(color: AppColors.textColor, isBold: true), + SizedBox(height: 24.h), + Row( + children: [ + "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}".toText14(color: AppColors.textColor, isBold: true), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "File No.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: "ER Clinic".needTranslation, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: emergencyServicesViewModel.selectedHospital!.name, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + icon: AppAssets.calendar, + labelText: DateUtil.formatDateToDate(DateTime.now(), false), + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + ], + ), + SizedBox(height: 12.h), + ], + ), + ), + ) + ], + ), + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText18(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: true), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: "".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + "VAT 15% (${emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount})" + .needTranslation + .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), + ], + ), + SizedBox(height: 18.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 150.h, + child: Utils.getPaymentMethods(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Utils.getPaymentAmountWithSymbol( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax.toString().toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: true), + ], + ), + ], + ) + ], + ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), + CustomButton( + text: LocaleKeys.payNow.tr(), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute(page: ErOnlineCheckinPaymentPage()), + ); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor.withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 56.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart new file mode 100644 index 0000000..cd1ba27 --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -0,0 +1,302 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_corner/smooth_corner.dart'; + +class ErOnlineCheckinPaymentPage extends StatefulWidget { + ErOnlineCheckinPaymentPage({super.key}); + + @override + State createState() => _ErOnlineCheckinPaymentPageState(); +} + +class _ErOnlineCheckinPaymentPageState extends State { + late PayfortViewModel payfortViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; + + late AppState appState; + + MyInAppBrowser? browser; + + String selectedPaymentMethod = ""; + + String transID = ""; + + bool isShowTamara = false; + + String tamaraPaymentStatus = ""; + + String tamaraOrderID = ""; + + @override + void initState() { + scheduleMicrotask(() { + payfortViewModel.initPayfortViewModel(); + payfortViewModel.getTamaraInstallmentsDetails().then((val) { + if (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! >= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! <= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + payfortViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.mada, width: 72.h, height: 25.h), + SizedBox(height: 16.h), + "Mada".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "MADA"; + // openPaymentURL("mada"); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset(AppAssets.visa, width: 50.h, height: 50.h), + SizedBox(width: 8.h), + Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h), + ], + ), + SizedBox(height: 16.h), + "Visa or Mastercard".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "VISA"; + // openPaymentURL("visa"); + }), + SizedBox(height: 16.h), + isShowTamara + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h), + SizedBox(height: 16.h), + "Tamara".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + // openPaymentURL("tamara"); + }) + : SizedBox.shrink(), + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Consumer(builder: (context, payfortVM, child) { + //TODO: Need to add loading state & animation for Apple Pay Configuration + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.isCash ?? true) + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.secondaryLightRedBorderColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.updateInsurance.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.secondaryLightRedBorderColor, + textColor: AppColors.whiteColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], + ), + ) + : const SizedBox(), + SizedBox(height: 24.h), + "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax.toString().toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Platform.isIOS + ? Utils.buildSvgWithAssets( + icon: AppAssets.apple_pay_button, + width: 200.h, + height: 80.h, + fit: BoxFit.contain, + ).paddingSymmetrical(24.h, 0.h).onPress(() { + // payfortVM.setIsApplePayConfigurationLoading(true); + if (Utils.havePrivilege(103)) { + // startApplePay(); + } else { + // openPaymentURL("ApplePay"); + } + }) + : SizedBox(height: 12.h), + SizedBox(height: 12.h), + ], + ); + }), + ), + ], + ), + ); + } +} From 9a48421271e833b03340e7bd97e03b4a451b0a7f Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 10 Nov 2025 15:23:38 +0300 Subject: [PATCH 3/3] ER Online Check-In implemented --- .../emergency_services_repo.dart | 177 +++++++++++++ .../emergency_services_view_model.dart | 106 ++++++++ .../appointment_payment_page.dart | 4 +- .../er_online_checkin_home.dart | 81 +++++- .../er_online_checkin_payment_page.dart | 242 ++++++++++++++++-- ...e_checkin_select_checkin_bottom_sheet.dart | 169 ++++++++++++ lib/presentation/home/landing_page.dart | 67 +++++ 7 files changed, 816 insertions(+), 30 deletions(-) create mode 100644 lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 1b26cd9..d8ba717 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -3,6 +3,8 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; @@ -19,6 +21,15 @@ abstract class EmergencyServicesRepo { Future>>> getProjectList(); Future>> checkPatientERPaymentInformation({int projectID}); + + Future>> ER_CreateAdvancePayment( + {required int projectID, required AuthenticatedUser authUser, required num paymentAmount, required String paymentMethodName, required String paymentReference}); + + Future>> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, required String appointmentNo}); + + Future>> getProjectIDFromNFC({required String nfcCode}); + + Future>> autoGenerateInvoiceERClinic({required int projectID}); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -208,4 +219,170 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> ER_CreateAdvancePayment( + {required int projectID, required AuthenticatedUser authUser, required num paymentAmount, required String paymentMethodName, required String paymentReference}) async { + Map mapDevice = { + "LanguageID": 1, + "ERAdvanceAmount": { + "ProjectId": projectID, + "PatientId": authUser.patientId, + "ClinicId": 10, + "DepositorName": "${authUser.firstName!} ${authUser.lastName!}", + "MemberId": authUser.patientId, + "NationalityID": authUser.nationalityId, + "PaymentAmount": paymentAmount, + "PaymentDate": DateUtil.convertDateToString(DateTime.now()), + "PaymentMethodName": paymentMethodName, + "PaymentReferenceNumber": paymentReference, + "SourceType": 2 + } + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ER_CREATE_ADVANCE_PAYMENT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final vidaAdvanceNumber = response['ER_AdvancePaymentResponse']['AdvanceNumber'].toString(); + print(vidaAdvanceNumber); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vidaAdvanceNumber, + ); + } 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> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, required String appointmentNo}) async { + Map requestBody = { + "AdvanceNumber": advanceNumber, + "AdvanceNumber_VP": advanceNumber, + "PaymentReferenceNumber": paymentReference, + "AppointmentID": appointmentNo, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ADD_ADVANCE_NUMBER_REQUEST, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getProjectIDFromNFC({required String nfcCode}) async { + Map mapDevice = {"nFC_Code": nfcCode}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_FROM_NFC, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final projectID = response['GetProjectByNFC'][0]["ProjectID"]; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: projectID, + ); + } 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>> autoGenerateInvoiceERClinic({required int projectID}) async { + Map mapDevice = { + "ProjectID": projectID, + "ClinicID": "10", + "IsAdvanceAvailable": true, + "MemberID": 102, + "PaymentMethod": "VISA", + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + AUTO_GENERATE_INVOICE_ER, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } 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/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 98a3d33..2103810 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -48,6 +48,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { late RRTProceduresResponseModel selectedRRTProcedure; bool patientHasAdvanceERBalance = false; + bool isERBookAppointment = false; late EROnlineCheckInPaymentDetailsResponse erOnlineCheckInPaymentDetailsResponse; BottomSheetType bottomSheetType = BottomSheetType.FIXED; @@ -69,6 +70,11 @@ class EmergencyServicesViewModel extends ChangeNotifier { get isGMSAvailable => appState.isGMSAvailable; + void setIsERBookAppointment(bool value) { + isERBookAppointment = value; + notifyListeners(); + } + Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { RRTProceduresList.clear(); notifyListeners(); @@ -288,6 +294,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { // (failure) async => await errorHandlerService.handleError(failure: failure), (failure) { patientHasAdvanceERBalance = false; + isERBookAppointment = true; if (onSuccess != null) { onSuccess(failure.message); } @@ -296,8 +303,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); patientHasAdvanceERBalance = false; + isERBookAppointment = true; } else if (apiResponse.messageStatus == 1) { patientHasAdvanceERBalance = apiResponse.data; + isERBookAppointment = !patientHasAdvanceERBalance; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -328,4 +337,101 @@ class EmergencyServicesViewModel extends ChangeNotifier { }, ); } + + Future ER_CreateAdvancePayment({required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.ER_CreateAdvancePayment( + projectID: selectedHospital!.iD, + authUser: appState.getAuthenticatedUser()!, + paymentAmount: erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + paymentMethodName: paymentMethodName, + paymentReference: paymentReference); + + result.fold( + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + // erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future addAdvanceNumberRequest( + {required String advanceNumber, required String paymentReference, required String appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getProjectIDFromNFC({required String nfcCode, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.getProjectIDFromNFC(nfcCode: nfcCode); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage!); + } + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future autoGenerateInvoiceERClinic({required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.autoGenerateInvoiceERClinic(projectID: projectID); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.data["InvoiceResponse"]["Message"]); + } + } else if (apiResponse.messageStatus == 1) { + patientHasAdvanceERBalance = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 58e5ef5..e259233 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -545,8 +545,7 @@ class _AppointmentPaymentPageState extends State { } startApplePay() async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -625,7 +624,6 @@ class _AppointmentPaymentPageState extends State { ); }, onSucceeded: (successResult) async { - Navigator.of(context).pop(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index a20a81d..d389414 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -7,20 +8,31 @@ 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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import '../call_ambulance/widgets/HospitalBottomSheetBody.dart'; class ErOnlineCheckinHome extends StatelessWidget { - const ErOnlineCheckinHome({super.key}); + ErOnlineCheckinHome({super.key}); + + late EmergencyServicesViewModel emergencyServicesViewModel; + bool _supportsNFC = false; @override Widget build(BuildContext context) { + emergencyServicesViewModel = Provider.of(context, listen: false); + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -56,23 +68,72 @@ class ErOnlineCheckinHome extends StatelessWidget { ), ), CustomButton( - text: "Book Appointment".needTranslation, + text: emergencyServicesViewModel.patientHasAdvanceERBalance ? LocaleKeys.checkinOption.tr() : LocaleKeys.bookAppo.tr(), onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching hospitals list...".needTranslation); - await context.read().getProjects(); - LoaderBottomSheet.hideLoader(); - //Project Selection Dropdown - showHospitalBottomSheet(context); + if (emergencyServicesViewModel.patientHasAdvanceERBalance) { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () async { + print(nfcId); + LoaderBottomSheet.showLoader(loadingText: "Processing check-in...".needTranslation); + await emergencyServicesViewModel.getProjectIDFromNFC( + nfcCode: nfcId, + onSuccess: (value) async { + await emergencyServicesViewModel.autoGenerateInvoiceERClinic( + projectID: value, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.onlineCheckIn.tr(), + child: Utils.getSuccessWidget(loadingText: "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.".needTranslation), + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false); + }, + onError: (errMessage) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unexpected error occurred during check-in. Please contact support.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) {}); + }); + }, onCancel: () {}); + }); + // showCommonBottomSheetWithoutHeight(context, + // title: LocaleKeys.onlineCheckIn.tr(), + // child: ErOnlineCheckinSelectCheckinBottomSheet( + // projectID: 15, + // ), + // callBackFunc: () {}, + // isFullScreen: false); + } else { + LoaderBottomSheet.showLoader(loadingText: "Fetching hospitals list...".needTranslation); + await context.read().getProjects(); + LoaderBottomSheet.hideLoader(); + //Project Selection Dropdown + showHospitalBottomSheet(context); + } }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + backgroundColor: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppColors.alertColor : AppColors.primaryRedColor, + borderColor: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppColors.alertColor : AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, fontWeight: FontWeight.w500, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, - icon: AppAssets.bookAppoBottom, + icon: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppAssets.appointment_checkin_icon : AppAssets.bookAppoBottom, iconColor: AppColors.whiteColor, iconSize: 18.h, ).paddingSymmetrical(24.h, 24.h), diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index cd1ba27..a3524fd 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -1,23 +1,29 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; 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/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.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:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -51,14 +57,14 @@ class _ErOnlineCheckinPaymentPageState extends State void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); - payfortViewModel.getTamaraInstallmentsDetails().then((val) { - if (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! >= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! <= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - setState(() { - isShowTamara = true; - }); - } - }); + // payfortViewModel.getTamaraInstallmentsDetails().then((val) { + // if (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! >= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + // emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! <= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + // setState(() { + // isShowTamara = true; + // }); + // } + // }); }); super.initState(); } @@ -112,7 +118,7 @@ class _ErOnlineCheckinPaymentPageState extends State ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; - // openPaymentURL("mada"); + openPaymentURL("mada"); }), SizedBox(height: 16.h), Container( @@ -154,7 +160,7 @@ class _ErOnlineCheckinPaymentPageState extends State ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; - // openPaymentURL("visa"); + openPaymentURL("visa"); }), SizedBox(height: 16.h), isShowTamara @@ -191,7 +197,7 @@ class _ErOnlineCheckinPaymentPageState extends State ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; - // openPaymentURL("tamara"); + openPaymentURL("tamara"); }) : SizedBox.shrink(), ], @@ -282,13 +288,12 @@ class _ErOnlineCheckinPaymentPageState extends State height: 80.h, fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { - // payfortVM.setIsApplePayConfigurationLoading(true); if (Utils.havePrivilege(103)) { - // startApplePay(); - } else { - // openPaymentURL("ApplePay"); - } - }) + startApplePay(); + } else { + openPaymentURL("ApplePay"); + } + }) : SizedBox(height: 12.h), SizedBox(height: 12.h), ], @@ -299,4 +304,207 @@ class _ErOnlineCheckinPaymentPageState extends State ), ); } + + openPaymentURL(String paymentMethod) { + browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); + transID = Utils.getAdvancePaymentTransID( + emergencyServicesViewModel.selectedHospital!.iD, + appState.getAuthenticatedUser()!.patientId!, + ); + + //TODO: Need to pass dynamic params to the payment request instead of static values + browser?.openPaymentBrowser( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + "ER Online Check-In Payment", + transID, + emergencyServicesViewModel.selectedHospital!.iD.toString(), + "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + selectedPaymentMethod, + appState.getAuthenticatedUser()!.patientType.toString(), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!, + browser!, + false, + "3", + "", + context, + null, + 0, + 10, + 0, + "3"); + } + + startApplePay() async { + // showCommonBottomSheet(context, + // child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(); + transID = Utils.getAdvancePaymentTransID( + emergencyServicesViewModel.selectedHospital!.iD, + appState.getAuthenticatedUser()!.patientId!, + ); + + ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); + + await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), projectId: emergencyServicesViewModel.selectedHospital!.iD); + + applePayInsertRequest.clientRequestID = transID; + applePayInsertRequest.clinicID = 10; + + applePayInsertRequest.currency = appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; + applePayInsertRequest.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com"; + applePayInsertRequest.customerID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.customerName = "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"; + + applePayInsertRequest.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken); + applePayInsertRequest.doctorID = 0; + applePayInsertRequest.projectID = emergencyServicesViewModel.selectedHospital!.iD.toString(); + applePayInsertRequest.serviceID = ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(); + applePayInsertRequest.channelID = 3; + applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType; + applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa; + applePayInsertRequest.appointmentDate = null; + applePayInsertRequest.appointmentNo = 0; + applePayInsertRequest.orderDescription = "ER Online Check-In Payment"; + applePayInsertRequest.liveServiceID = "0"; + applePayInsertRequest.latitude = "0.0"; + applePayInsertRequest.longitude = "0.0"; + applePayInsertRequest.amount = emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString(); + applePayInsertRequest.isSchedule = "0"; + applePayInsertRequest.language = appState.isArabic() ? 'ar' : 'en'; + applePayInsertRequest.languageID = appState.isArabic() ? 1 : 2; + applePayInsertRequest.userName = appState.getAuthenticatedUser()!.patientId; + applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html"; + applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html"; + applePayInsertRequest.paymentOption = "ApplePay"; + + applePayInsertRequest.isMobSDK = true; + applePayInsertRequest.merchantReference = transID; + applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; + applePayInsertRequest.commandType = "PURCHASE"; + applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature; + applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode; + applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; + applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; + applePayInsertRequest.returnURL = ""; + + //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 { + log("failureResult: ${failureResult.message.toString()}"); + Navigator.of(context).pop(); + 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(); + }, + ); + }); + } + + void checkPaymentStatus() async { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + print(apiResponse.data); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + if (emergencyServicesViewModel.isERBookAppointment) { + await emergencyServicesViewModel.ER_CreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + onSuccess: (value) async { + await emergencyServicesViewModel.addAdvanceNumberRequest( + advanceNumber: value, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + appointmentNo: "0", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (emergencyServicesViewModel.isERBookAppointment) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else {} + }); + }); + } else {} + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } + + onBrowserLoadStart(String url) { + print("onBrowserLoadStart"); + print(url); + + if (selectedPaymentMethod == "tamara") { + if (Platform.isAndroid) { + Uri uri = new Uri.dataFromString(url); + tamaraPaymentStatus = uri.queryParameters['status']!; + tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!; + } else { + Uri uri = new Uri.dataFromString(url); + tamaraPaymentStatus = uri.queryParameters['paymentStatus']!; + tamaraOrderID = uri.queryParameters['orderId']!; + } + } + + // if(selectedPaymentMethod != "TAMARA") { + MyInAppBrowser.successURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = true; + return; + } + }); + // } + + // if(selectedPaymentMethod != "TAMARA") { + MyInAppBrowser.errorURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = false; + return; + } + }); + // } + } + + onBrowserExit(bool isPaymentMade) async { + checkPaymentStatus(); + } } diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart new file mode 100644 index 0000000..0b22417 --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart @@ -0,0 +1,169 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_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_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { + ErOnlineCheckinSelectCheckinBottomSheet({super.key, required this.projectID}); + + bool _supportsNFC = false; + int projectID = 0; + + late LocationUtils locationUtils; + late AppState appState; + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + checkInOptionCard( + AppAssets.checkin_location_icon, + "Live Location".needTranslation, + "Verify your location to be at hospital to check in".needTranslation, + ).onPress(() { + // locationUtils = LocationUtils( + // isShowConfirmDialog: false, + // navigationService: myAppointmentsViewModel.navigationService, + // appState: myAppointmentsViewModel.appState, + // ); + locationUtils.getCurrentLocation(onSuccess: (value) { + projectDetailListModel = Utils.getProjectDetailObj(appState, projectID); + double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; + print(dist); + if (dist <= projectDetailListModel.geofenceRadius!) { + sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); + } else { + showCommonBottomSheetWithoutHeight(context, + title: "Error".needTranslation, + child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_nfc_icon, + "NFC (Near Field Communication)".needTranslation, + "Scan your phone via NFC board to check in".needTranslation, + ).onPress(() { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () { + sendCheckInRequest(nfcId, context); + }); + }, onCancel: () {}); + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_qr_icon, + "QR Code".needTranslation, + "Scan QR code with your camera to check in".needTranslation, + ).onPress(() async { + String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); + if (onlineCheckInQRCode != "") { + sendCheckInRequest(onlineCheckInQRCode, context); + } else {} + }), + ], + ); + } + + Widget checkInOptionCard(String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } + + void sendCheckInRequest(String scannedCode, BuildContext context) async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + // await myAppointmentsViewModel.sendCheckInNfcRequest( + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // scannedCode: scannedCode, + // checkInType: 2, + // onSuccess: (apiResponse) { + // Navigator.of(context).pop(); + // showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + // Navigator.of(context).pop(); + // Navigator.pushAndRemoveUntil( + // context, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // Navigator.of(context).push( + // CustomPageRoute(page: MyAppointmentsPage()), + // ); + // }, isFullScreen: false); + // }, + // onError: (error) { + // Navigator.of(context).pop(); + // showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + // Navigator.of(context).pop(); + // }, isFullScreen: false); + // }, + // ); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index c023bd3..51bd872 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.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/insurance/insurance_view_model.dart'; @@ -28,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -64,6 +66,7 @@ class _LandingPageState extends State { late InsuranceViewModel insuranceViewModel; late ImmediateLiveCareViewModel immediateLiveCareViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; final SwiperController _controller = SwiperController(); @@ -93,6 +96,7 @@ class _LandingPageState extends State { insuranceViewModel.initInsuranceProvider(); immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); + emergencyServicesViewModel.checkPatientERAdvanceBalance(); } }); super.initState(); @@ -105,6 +109,7 @@ class _LandingPageState extends State { prescriptionsViewModel = Provider.of(context, listen: false); insuranceViewModel = Provider.of(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); appState = getIt.get(); return PopScope( canPop: false, @@ -296,6 +301,8 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h); }, ), + + // Consumer for LiveCare pending request Consumer( builder: (context, immediateLiveCareVM, child) { return immediateLiveCareVM.patientHasPendingLiveCareRequest @@ -353,6 +360,66 @@ class _LandingPageState extends State { : SizedBox(height: 12.h); }, ), + + // Consumer for ER Online Check-In pending request + Consumer( + builder: (context, emergencyServicesVM, child) { + return emergencyServicesVM.patientHasAdvanceERBalance + ? Column( + children: [ + SizedBox(height: 4.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + ), + width: double.infinity, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: "ER Online Check-In Request", + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + textColor: AppColors.primaryRedColor, + ), + Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // context.read().navigateToEROnlineCheckIn(); + }), + SizedBox(height: 12.h), + ], + ) + : SizedBox(height: 12.h); + }, + ), + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [