From 0e26505692b093f9be41bb9b3bdcc0bdb38ba1cf Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 31 Dec 2025 10:04:05 +0300 Subject: [PATCH] covid page inprogress --- lib/core/utils/utils.dart | 11 +- .../hmg_services/hmg_services_repo.dart | 98 +++ .../hmg_services/hmg_services_view_model.dart | 76 ++- .../covid_get_test_proceedure_resp.dart | 33 + .../get_covid_payment_info_resp.dart | 105 ++++ .../laser/laser_appointment.dart | 1 + .../covid19test/covid_19_questionnaire.dart | 156 ++--- .../covid19test/covid_payment_screen.dart | 565 ++++++++++++++++++ .../covid19test/covid_review_screen.dart | 441 ++++++++++++++ 9 files changed, 1416 insertions(+), 70 deletions(-) create mode 100644 lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart create mode 100644 lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart create mode 100644 lib/presentation/covid19test/covid_payment_screen.dart create mode 100644 lib/presentation/covid19test/covid_review_screen.dart diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 38d04b9..7e1e5d4 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -722,7 +722,16 @@ class Utils { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Image.asset(AppAssets.mada, width: 25.h, height: 25.h), - Image.asset(AppAssets.tamaraEng, width: 25.h, height: 25.h), + Image.asset( + AppAssets.tamaraEng, + width: 25.h, + height: 25.h, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + debugPrint('Failed to load Tamara PNG in payment methods: $error'); + return Utils.buildSvgWithAssets(icon: AppAssets.tamara, width: 25.h, height: 25.h, fit: BoxFit.contain); + }, + ), Image.asset(AppAssets.visa, width: 25.h, height: 25.h), Image.asset(AppAssets.mastercard, width: 25.h, height: 25.h), Image.asset(AppAssets.applePay, width: 25.h, height: 25.h), diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index b2b3709..851ac93 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -17,6 +17,8 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'models/req_models/create_e_referral_model.dart'; import 'models/req_models/send_activation_code_ereferral_req_model.dart'; +import 'models/resq_models/covid_get_test_proceedure_resp.dart'; +import 'models/resq_models/get_covid_payment_info_resp.dart'; import 'models/resq_models/relationship_type_resp_mode.dart'; import 'models/resq_models/search_e_referral_resp_model.dart'; @@ -60,7 +62,9 @@ abstract class HmgServicesRepo { Future>>> searchEReferral(SearchEReferralRequestModel requestModel); + Future>>> getCovidTestProcedures(); + Future>> getCovidPaymentInfo(String procedureID, int projectID); } class HmgServicesRepoImp implements HmgServicesRepo { @@ -816,4 +820,98 @@ class HmgServicesRepoImp implements HmgServicesRepo { } } + + @override + Future>>> getCovidTestProcedures() async { + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + GET_COVID_DRIVETHRU_PROCEDURES_LIST, + body: {"TestTypeEnum":2,"TestProcedureEnum":3,}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Covid Test Procedure : $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List covidTestProcedure = []; + + if (response['COVID19_TestProceduresList'] != null && response['COVID19_TestProceduresList'] is List) { + final servicesList = response['COVID19_TestProceduresList'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + covidTestProcedure.add(Covid19GetTestProceduresResp.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: covidTestProcedure, + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in Search Referral: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getCovidPaymentInfo(String procedureID, int projectID) async { + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + GET_COVID_DRIVETHRU_PAYMENT_INFO, + body: {"TestTypeEnum":2,"TestProcedureEnum":3, "ProcedureId":procedureID, "ProjectID":projectID,}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Covid Test Procedure : $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + + Covid19GetPaymentInfo covidPaymentInfo = Covid19GetPaymentInfo.fromJson(response["COVID19_PatientShare"]); + + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: covidPaymentInfo, + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in Search Referral: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + } diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 5da0860..d702cab 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/crea import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/search_e_referral_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; @@ -19,6 +20,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'models/req_models/check_activation_e_referral_req_model.dart'; +import 'models/resq_models/get_covid_payment_info_resp.dart'; import 'models/resq_models/relationship_type_resp_mode.dart'; import 'models/ui_models/covid_questionnare_model.dart'; @@ -60,7 +62,8 @@ class HmgServicesViewModel extends ChangeNotifier { List relationTypes = []; List getAllCitiesList = []; List searchReferralList = []; - + List covidTestProcedureList = []; + Covid19GetPaymentInfo? covidPaymentInfo; Future getOrdersList() async {} @@ -783,4 +786,75 @@ class HmgServicesViewModel extends ChangeNotifier { return []; } } + + + Future getCovidProcedureList({ + + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notifyListeners(); + + final result = await hmgServicesRepo.getCovidTestProcedures(); + + result.fold( + (failure) async { + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1) { + covidTestProcedureList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + + Future getPaymentInfo({ + String? procedureID, + int? projectID, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notifyListeners(); + + final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!); + + result.fold( + (failure) async { + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1) { + covidPaymentInfo = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } } diff --git a/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart b/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart new file mode 100644 index 0000000..c27f1e7 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; + +class Covid19GetTestProceduresResp { + String? procedureId; + String? procedureName; + String? procedureNameN; + String? setupId; + + Covid19GetTestProceduresResp({ + this.procedureId, + this.procedureName, + this.procedureNameN, + this.setupId, + }); + + factory Covid19GetTestProceduresResp.fromRawJson(String str) => Covid19GetTestProceduresResp.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Covid19GetTestProceduresResp.fromJson(Map json) => Covid19GetTestProceduresResp( + procedureId: json["ProcedureID"], + procedureName: json["ProcedureName"], + procedureNameN: json["ProcedureNameN"], + setupId: json["SetupID"], + ); + + Map toJson() => { + "ProcedureID": procedureId, + "ProcedureName": procedureName, + "ProcedureNameN": procedureNameN, + "SetupID": setupId, + }; +} diff --git a/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart b/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart new file mode 100644 index 0000000..5f95263 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; + +class Covid19GetPaymentInfo { + dynamic propertyChanged; + int? cashPriceField; + int? cashPriceTaxField; + int? cashPriceWithTaxField; + int? companyIdField; + String? companyNameField; + int? companyShareWithTaxField; + dynamic errCodeField; + int? groupIdField; + dynamic insurancePolicyNoField; + String? messageField; + dynamic patientCardIdField; + int? patientShareField; + double? patientShareWithTaxField; + double? patientTaxAmountField; + int? policyIdField; + dynamic policyNameField; + dynamic procedureIdField; + String? procedureNameField; + dynamic setupIdField; + int? statusCodeField; + dynamic subPolicyNoField; + + Covid19GetPaymentInfo({ + this.propertyChanged, + this.cashPriceField, + this.cashPriceTaxField, + this.cashPriceWithTaxField, + this.companyIdField, + this.companyNameField, + this.companyShareWithTaxField, + this.errCodeField, + this.groupIdField, + this.insurancePolicyNoField, + this.messageField, + this.patientCardIdField, + this.patientShareField, + this.patientShareWithTaxField, + this.patientTaxAmountField, + this.policyIdField, + this.policyNameField, + this.procedureIdField, + this.procedureNameField, + this.setupIdField, + this.statusCodeField, + this.subPolicyNoField, + }); + + factory Covid19GetPaymentInfo.fromRawJson(String str) => Covid19GetPaymentInfo.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Covid19GetPaymentInfo.fromJson(Map json) => Covid19GetPaymentInfo( + propertyChanged: json["PropertyChanged"], + cashPriceField: json["cashPriceField"], + cashPriceTaxField: json["cashPriceTaxField"], + cashPriceWithTaxField: json["cashPriceWithTaxField"], + companyIdField: json["companyIdField"], + companyNameField: json["companyNameField"], + companyShareWithTaxField: json["companyShareWithTaxField"], + errCodeField: json["errCodeField"], + groupIdField: json["groupIDField"], + insurancePolicyNoField: json["insurancePolicyNoField"], + messageField: json["messageField"], + patientCardIdField: json["patientCardIDField"], + patientShareField: json["patientShareField"], + patientShareWithTaxField: json["patientShareWithTaxField"]?.toDouble(), + patientTaxAmountField: json["patientTaxAmountField"]?.toDouble(), + policyIdField: json["policyIdField"], + policyNameField: json["policyNameField"], + procedureIdField: json["procedureIdField"], + procedureNameField: json["procedureNameField"], + setupIdField: json["setupIDField"], + statusCodeField: json["statusCodeField"], + subPolicyNoField: json["subPolicyNoField"], + ); + + Map toJson() => { + "PropertyChanged": propertyChanged, + "cashPriceField": cashPriceField, + "cashPriceTaxField": cashPriceTaxField, + "cashPriceWithTaxField": cashPriceWithTaxField, + "companyIdField": companyIdField, + "companyNameField": companyNameField, + "companyShareWithTaxField": companyShareWithTaxField, + "errCodeField": errCodeField, + "groupIDField": groupIdField, + "insurancePolicyNoField": insurancePolicyNoField, + "messageField": messageField, + "patientCardIDField": patientCardIdField, + "patientShareField": patientShareField, + "patientShareWithTaxField": patientShareWithTaxField, + "patientTaxAmountField": patientTaxAmountField, + "policyIdField": policyIdField, + "policyNameField": policyNameField, + "procedureIdField": procedureIdField, + "procedureNameField": procedureNameField, + "setupIDField": setupIdField, + "statusCodeField": statusCodeField, + "subPolicyNoField": subPolicyNoField, + }; +} diff --git a/lib/presentation/book_appointment/laser/laser_appointment.dart b/lib/presentation/book_appointment/laser/laser_appointment.dart index 28f0f2d..d14e5dd 100644 --- a/lib/presentation/book_appointment/laser/laser_appointment.dart +++ b/lib/presentation/book_appointment/laser/laser_appointment.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart' show CapExtension; 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/book_appointments/models/resp_models/laser_body_parts.dart'; diff --git a/lib/presentation/covid19test/covid_19_questionnaire.dart b/lib/presentation/covid19test/covid_19_questionnaire.dart index 8d7e8bd..b9ac008 100644 --- a/lib/presentation/covid19test/covid_19_questionnaire.dart +++ b/lib/presentation/covid19test/covid_19_questionnaire.dart @@ -7,10 +7,13 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/covid_questionnare_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/presentation/covid19test/covid_review_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/CustomSwitch.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.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'; @@ -46,87 +49,104 @@ class _Covid19QuestionnaireState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: Column(children: [ - Expanded( - child: CollapsingListView( + return CollapsingListView( title: "COVID-19", - child: Padding( + bottomChild: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + hasShadow: true, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.r), + topRight: Radius.circular(24.r), + ), + ),child: CustomButton( + text: "Next".needTranslation, + onPressed: () { + moveToNextPage(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 56.h, + )), + child: SingleChildScrollView( + child: Padding( padding: EdgeInsets.all(24.w), child: Column( children: [ - Expanded( - child: SingleChildScrollView( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(20.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Please answer below questionnaire:".toText14( - color: AppColors.textColor, - weight: FontWeight.w500, - ), - SizedBox(height: 20.h), - // Question list - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: qaList.length, - separatorBuilder: (context, index) => SizedBox(height: 16.h), - itemBuilder: (context, index) { - final question = qaList[index]; - final isAnswerYes = question.ans == 1; + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(20.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Please answer below questionnaire:".toText14( + color: AppColors.textColor, + weight: FontWeight.w500, + ), + SizedBox(height: 20.h), + // Question list + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: qaList.length, + separatorBuilder: (context, index) => SizedBox(height: 16.h), + itemBuilder: (context, index) { + final question = qaList[index]; + final isAnswerYes = question.ans == 1; - return Row( - children: [ - Expanded( - child: (question.questionEn ?? '').toText14( - color: AppColors.textColor, - weight: FontWeight.w400, - ), - ), - SizedBox(width: 12.w), - CustomSwitch( - value: isAnswerYes, - onChanged: (value) => _toggleAnswer(index, value), - ), - ], - ); - }, - ), - ], + return Row( + children: [ + Expanded( + child: (question.questionEn ?? '').toText14( + color: AppColors.textColor, + weight: FontWeight.w400, + ), + ), + SizedBox(width: 12.w), + CustomSwitch( + value: isAnswerYes, + onChanged: (value) => _toggleAnswer(index, value), + ), + ], + ); + }, ), - ), + ], ), ), ), SizedBox(height: 16.h), // Next button - CustomButton( - text: "Next".needTranslation, - onPressed: () { - // Handle next action - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - height: 56.h, - ), + ], ), + ), + ), - ), - ), - ])); + ); + + + } + moveToNextPage(BuildContext context) async{ + LoaderBottomSheet.showLoader(); + await hmgServicesViewModel.getCovidProcedureList(); + await hmgServicesViewModel.getPaymentInfo(procedureID: hmgServicesViewModel.covidTestProcedureList[0].procedureId!); + LoaderBottomSheet.hideLoader(); + Navigator.of(context) + .push( + CustomPageRoute( + page: CovidReviewScreen(selectedHospital: widget.selectedHospital, qaList: qaList), + ), + ); } } diff --git a/lib/presentation/covid19test/covid_payment_screen.dart b/lib/presentation/covid19test/covid_payment_screen.dart new file mode 100644 index 0000000..42e7736 --- /dev/null +++ b/lib/presentation/covid19test/covid_payment_screen.dart @@ -0,0 +1,565 @@ +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/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_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/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'; + +// Added imports required by this file +import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; + +/// A reusable payment screen for COVID-related payments. +/// +/// This screen re-uses the same UI pattern and payment flow used by +/// `AppointmentPaymentPage` (in-app browser, Apple Pay and payfort status checks), +/// but it keeps the post-payment handling generic (shows success / failure) +/// so it can be safely used for COVID test purchases without appointment-specific APIs. +class CovidPaymentScreen extends StatefulWidget { + final double amount; + final int projectID; + final int clinicID; + final String procedureId; + final double taxAmount; + final String title; + + const CovidPaymentScreen({ + super.key, + required this.amount, + required this.projectID, + required this.clinicID, + required this.procedureId, + this.taxAmount = 0.0, + this.title = "COVID Payment", + }); + + @override + State createState() => _CovidPaymentScreenState(); +} + +class _CovidPaymentScreenState extends State { + late PayfortViewModel payfortViewModel; + late AppState appState; + + MyInAppBrowser? browser; + String selectedPaymentMethod = ""; + String transID = ""; + + bool isShowTamara = false; // placeholder: could be enabled based on remote config + + @override + void initState() { + super.initState(); + // initialize payfort view model when the widget is ready + scheduleMicrotask(() { + payfortViewModel = Provider.of(context, listen: false); + payfortViewModel.initPayfortViewModel(); + payfortViewModel.setIsApplePayConfigurationLoading(false); + // Optionally compute if Tamara should be shown by calling a remote config API. + // For now keep it false (unless the app provides an API for it). + // Enable Tamara payment option for COVID screen + setState(() { + isShowTamara = true; + }); + }); + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + payfortViewModel = Provider.of(context); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: widget.title.needTranslation, + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(( (widget.amount - widget.taxAmount).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), + // Show VAT amount passed from review screen + Utils.getPaymentAmountWithSymbol((widget.taxAmount.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(widget.amount.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + + // Apple Pay (iOS) + Platform.isIOS + ? Utils.buildSvgWithAssets( + icon: AppAssets.apple_pay_button, + width: 200.h, + height: 80.h, + fit: BoxFit.contain, + ).paddingSymmetrical(24.h, 0.h).onPress(() { + if (Utils.havePrivilege(103)) { + startApplePay(); + } else { + openPaymentURL("ApplePay"); + } + }) + : SizedBox(height: 12.h), + SizedBox(height: 12.h), + + // Action buttons: Cancel + Next (Next opens default payment flow - e.g. Visa) + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 24.h), + // child: Row( + // children: [ + // Expanded( + // child: CustomButton( + // height: 56.h, + // text: LocaleKeys.cancel.tr(), + // onPressed: () { + // Navigator.of(context).pop(); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // icon: AppAssets.cancel, + // iconColor: AppColors.primaryRedColor, + // borderRadius: 12.r, + // ), + // ), + // SizedBox(width: 8.h), + // Expanded( + // child: CustomButton( + // height: 56.h, + // text: "Next".needTranslation, + // onPressed: () { + // // Default to Visa for Next + // selectedPaymentMethod = "VISA"; + // openPaymentURL("visa"); + // }, + // backgroundColor: AppColors.primaryRedColor, + // borderColor: AppColors.primaryRedColor, + // textColor: AppColors.whiteColor, + // fontSize: 16.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // icon: AppAssets.add_icon, + // iconColor: AppColors.whiteColor, + // iconSize: 18.h, + // ), + // ), + // ], + // ), + // ), + ], + ), + ).paddingSymmetrical(0.h, 0.h), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + + // MADA tile + 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_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "MADA"; + openPaymentURL("MADA"); + }), + + SizedBox(height: 16.h), + + // Visa / Mastercard tile + 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_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "VISA"; + openPaymentURL("VISA"); + }), + + SizedBox(height: 16.h), + + // Optional Tamara tile (shown only if enabled) + 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.tamaraEng, + width: 72.h, + height: 25.h, + fit: BoxFit.contain, + // If PNG fails to load for any reason, log and fallback to SVG asset + errorBuilder: (context, error, stackTrace) { + debugPrint('Failed to load Tamara PNG asset: $error'); + return Utils.buildSvgWithAssets( + icon: AppAssets.tamara, + width: 72.h, + height: 25.h, + fit: BoxFit.contain, + ); + }, + ), + 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_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + openPaymentURL("TAMARA"); + }) + : SizedBox.shrink(), + + SizedBox(height: 24.h), + + // Bottom payment summary + + SizedBox(height: 24.h), + ], + ), + ), + + ), + ); + } + + void onBrowserLoadStart(String url) { + // Generic loader hook: detect success / error patterns from the in-app browser. + // Keep parsing defensive: use Uri.parse where possible. + try { + final uri = Uri.tryParse(url); + if (selectedPaymentMethod == "TAMARA" && uri != null) { + // tamara returns different query param names depending on platform; defensive checks + final params = uri.queryParameters; + if (params.isNotEmpty) { + // example keys: 'status', 'AuthorizePaymentId' (android) or 'paymentStatus', 'orderId' (iOS) + } + } + } catch (e) { + debugPrint('onBrowserLoadStart parse error: $e'); + } + + MyInAppBrowser.successURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = true; + return; + } + }); + + MyInAppBrowser.errorURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = false; + return; + } + }); + } + + void onBrowserExit(bool isPaymentMade) async { + // When browser closes, check payment status using payfort view model + await checkPaymentStatus(); + } + + Future checkPaymentStatus() async { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + try { + await payfortViewModel.checkPaymentStatus(transactionID: transID, onSuccess: (apiResponse) async { + // treat any successful responseMessage as success; otherwise show generic error + final success = payfortViewModel.payfortCheckPaymentStatusResponseModel?.responseMessage?.toLowerCase() == 'success'; + LoaderBottomSheet.hideLoader(); + if (success) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Payment successful".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } catch (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + + void openPaymentURL(String paymentMethod) { + browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); + transID = Utils.getAppointmentTransID(widget.projectID, widget.clinicID, DateTime.now().millisecondsSinceEpoch); + + // Open payment browser with essential parameters; many fields are simplified here + browser?.openPaymentBrowser( + widget.amount, + "COVID Test Payment", + transID, + widget.projectID.toString(), + "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", + selectedPaymentMethod, + appState.getAuthenticatedUser()?.patientType.toString() ?? "", + appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", + appState.getAuthenticatedUser()?.patientId.toString() ?? "", + appState.getAuthenticatedUser() ?? (null as dynamic), + browser!, + false, + "2", + "", + context, + DateTime.now().toString(), + "", + 0, + 0, + "3", + ); + } + + void startApplePay() async { + showCommonBottomSheet( + context, + child: Utils.getLoadingWidget(), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + + transID = Utils.getAppointmentTransID(widget.projectID, widget.clinicID, DateTime.now().millisecondsSinceEpoch); + + // Prepare a minimal apple pay request using payfortViewModel's configuration + try { + await payfortViewModel.getPayfortConfigurations(serviceId: 0, projectId: widget.projectID, integrationId: 2); + + // Build minimal apple pay request (model omitted here to keep things generic) + ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest( + clientRequestID: transID, + clinicID: widget.clinicID, + currency: appState.getAuthenticatedUser() != null && appState.getAuthenticatedUser()!.outSa == 0 ? "SAR" : "AED", + customerEmail: "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", + customerID: appState.getAuthenticatedUser()?.patientId, + customerName: appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", + deviceToken: await Utils.getStringFromPrefs(CacheConst.pushToken), + voipToken: await Utils.getStringFromPrefs(CacheConst.voipToken), + doctorID: 0, + projectID: widget.projectID.toString(), + serviceID: "0", + channelID: 3, + patientID: appState.getAuthenticatedUser()?.patientId, + patientTypeID: appState.getAuthenticatedUser()?.patientType, + patientOutSA: appState.getAuthenticatedUser()?.outSa, + appointmentDate: DateTime.now().toString(), + appointmentNo: 0, + orderDescription: "COVID Test Payment", + liveServiceID: "0", + latitude: "0.0", + longitude: "0.0", + amount: widget.amount.toString(), + isSchedule: "0", + language: appState.isArabic() ? 'ar' : 'en', + languageID: appState.isArabic() ? 1 : 2, + userName: appState.getAuthenticatedUser()?.patientId, + responseContinueURL: "http://hmg.com/Documents/success.html", + backClickUrl: "http://hmg.com/Documents/success.html", + paymentOption: "ApplePay", + isMobSDK: true, + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel?.merchantIdentifier, + commandType: "PURCHASE", + signature: payfortViewModel.payfortProjectDetailsRespModel?.signature, + accessCode: payfortViewModel.payfortProjectDetailsRespModel?.accessCode, + shaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaRequest, + shaResponsePhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaResponse, + returnURL: "", + ); + + await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { + // Start apple pay flow + payfortViewModel.paymentWithApplePay( + customerName: appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", + customerEmail: "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", + orderDescription: "COVID Test Payment", + orderAmount: widget.amount, + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel?.merchantIdentifier ?? "", + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel?.accessCode ?? "", + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaRequest ?? "", + currency: appState.getAuthenticatedUser() != null && appState.getAuthenticatedUser()!.outSa == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + Navigator.of(context).pop(); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + await checkPaymentStatus(); + }, + ); + }).catchError((e) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: e.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } catch (e) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: e.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + } + diff --git a/lib/presentation/covid19test/covid_review_screen.dart b/lib/presentation/covid19test/covid_review_screen.dart new file mode 100644 index 0000000..7ccbb02 --- /dev/null +++ b/lib/presentation/covid19test/covid_review_screen.dart @@ -0,0 +1,441 @@ +import 'dart:async'; + +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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/covid_questionnare_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +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/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/presentation/covid19test/covid_payment_screen.dart'; + +class CovidReviewScreen extends StatefulWidget { + + final HospitalsModel selectedHospital; + final List qaList; + + const CovidReviewScreen({super.key, required this.selectedHospital, required this.qaList}); + + @override + State createState() => _CovidReviewScreenState(); +} + +class _CovidReviewScreenState extends State { + + late HmgServicesViewModel hmgServicesViewModel; + bool _acceptedTerms =false; + Covid19GetTestProceduresResp? _selectedProcedure; + @override + void initState() { + super.initState(); + hmgServicesViewModel = Provider.of(context, listen: false); + scheduleMicrotask(() { + + }); + + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "COVID-19", + bottomChild: Consumer(builder: (context, vm, _) { + final info = vm.covidPaymentInfo; + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (info == null) ...[ + // show a placeholder/loading while payment info is null + SizedBox(height: 24.h), + Center(child: CircularProgressIndicator()), + SizedBox(height: 24.h), + ] else ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText18(isBold: true), + Utils.getPaymentAmountWithSymbol( + (info.patientShareField ?? 0).toString().toText16(isBold: true), + AppColors.blackColor, + 13, + isSaudiCurrency: true), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Tax Amount".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + (info.patientTaxAmountField ?? 0).toString().toText16(isBold: true), + AppColors.blackColor, + 13, + isSaudiCurrency: true) + ], + ), + SizedBox(height: 18.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 150.h, + child: Utils.getPaymentMethods(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Utils.getPaymentAmountWithSymbol( + (info.patientShareWithTaxField ?? 0).toString().toText24(isBold: true), + AppColors.blackColor, + 17, + isSaudiCurrency: true), + ], + ), + ], + ) + ], + ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), + + GestureDetector( + onTap: () { + setState(() { + _acceptedTerms = !_acceptedTerms; + }); + }, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + child: Row( + children: [ + Container( + width: 20.w, + height: 20.h, + decoration: BoxDecoration( + color: _acceptedTerms + ? AppColors.primaryRedColor + : AppColors.whiteColor, + border: Border.all( + color: _acceptedTerms + ? AppColors.primaryRedColor + : AppColors.greyTextColor.withValues(alpha: 0.3), + width: 2, + ), + borderRadius: BorderRadius.circular(4.r), + ), + child: _acceptedTerms + ? Icon( + Icons.check, + size: 14.h, + color: AppColors.whiteColor, + ) + : null, + ), + SizedBox(width: 12.w), + Expanded( + child: RichText( + text: TextSpan( + style: TextStyle( + fontSize: 14.f, + color: AppColors.textColor, + fontWeight: FontWeight.w400, + ), + children: [ + const TextSpan(text: "I agree to the "), + TextSpan( + text: "terms and conditions", + style: TextStyle( + color: AppColors.primaryRedColor, + fontWeight: FontWeight.w600, + decoration: TextDecoration.underline, + decorationColor: AppColors.primaryRedColor, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + // Two-button layout: Cancel (left) and Next (right) + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 24.h), + child: Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + // icon: AppAssets.cancel, + // iconColor: AppColors.primaryRedColor, + borderRadius: 12.r, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + height: 56.h, + text: "Next".needTranslation, + onPressed: () async { + // Validate selection and payment info + if (_selectedProcedure == null) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Please select a procedure".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + return; + } + + if (info == null) { + // If payment info missing, attempt to fetch + LoaderBottomSheet.showLoader(); + try { + await hmgServicesViewModel.getPaymentInfo(procedureID: _selectedProcedure!.procedureId!); + } catch (e) { + debugPrint('getPaymentInfo error: $e'); + } finally { + LoaderBottomSheet.hideLoader(); + } + + if (hmgServicesViewModel.covidPaymentInfo == null) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment information not available".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + return; + } + } + + // Compute amount and project/clinic IDs defensively + final paymentInfo = hmgServicesViewModel.covidPaymentInfo ?? info!; + final double amount = paymentInfo.patientShareWithTaxField ?? (paymentInfo.patientShareField?.toDouble() ?? 0.0); + + // projectID may be int or string; parse defensively + int projectID = 0; + try { + final p = widget.selectedHospital.mainProjectID; + if (p is int) projectID = p; + else if (p is String) projectID = int.tryParse(p) ?? 0; + } catch (_) {} + + int clinicID = 0; + try { + clinicID = int.tryParse(widget.selectedHospital.setupID ?? '') ?? int.tryParse(widget.selectedHospital.iD?.toString() ?? '') ?? 0; + } catch (_) {} + + // Navigate to payment screen + Navigator.of(context).push( + CustomPageRoute( + page: CovidPaymentScreen( + amount: amount, + projectID: projectID, + clinicID: clinicID, + procedureId: _selectedProcedure!.procedureId ?? '', + taxAmount: paymentInfo.patientTaxAmountField?.toDouble() ?? 0.0, + ), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // icon: AppAssets.add_icon, + // iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + ), + ], + ), + ) + ] + ], + ), + ), + ); + }), + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + "Please select the procedure:".toText14( + color: AppColors.textColor, + weight: FontWeight.w500, + ), + SizedBox(height: 16.h), + + Consumer( + builder: (context, vm, _) { + final procedures = vm.covidTestProcedureList; + + // ensure default selection is first item (once available) + if (_selectedProcedure == null && procedures.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _selectedProcedure = procedures[0]; + }); + + // also fetch payment info for default selection and show loader while loading + if (procedures[0].procedureId != null) { + LoaderBottomSheet.showLoader(); + hmgServicesViewModel + .getPaymentInfo(procedureID: procedures[0].procedureId!) + .whenComplete(() { + LoaderBottomSheet.hideLoader(); + }); + } + } + }); + } + + // Use a shrink-wrapped ListView.separated and match prescription styling + return Container( + + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: procedures.length, + separatorBuilder: (context, index) => Divider( + height: 1.h, + thickness: 1.h, + color: AppColors.borderOnlyColor.withValues(alpha: 0.05), + ).paddingOnly(top:8,bottom:16.h), + itemBuilder: (context, index) { + final item = procedures[index]; + // Let the radio option widget manage its own padding to avoid doubled spacing + return _buildRadioOption(value: item, title: item.procedureName ?? ''); + }, + ).paddingOnly(bottom:16.h) + ), + ); + }, + ), + ], + ), + ), + ), + ); + + } + + Widget _buildRadioOption({ + required Covid19GetTestProceduresResp value, + required String title, + }) { + final bool isSelected = _selectedProcedure?.procedureId == value.procedureId; + + return GestureDetector( + onTap: () async { + setState(() { + _selectedProcedure = value; + }); + + // show bottomsheet loader while fetching payment info + if (value.procedureId != null) { + LoaderBottomSheet.showLoader(); + try { + await hmgServicesViewModel.getPaymentInfo(procedureID: value.procedureId!, projectID: widget.selectedHospital.mainProjectID); + } catch (e) { + debugPrint('getPaymentInfo error: $e'); + } finally { + LoaderBottomSheet.hideLoader(); + } + } + }, + + + child: Row( + children: [ + Container( + width: 20.h, + height: 20.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? AppColors.primaryRedColor + : AppColors.greyTextColor.withValues(alpha: 0.3), + width: 2, + ), + ), + child: isSelected + ? Center( + child: Container( + width: 10.h, + height: 10.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.primaryRedColor, + ), + ), + ) + : null, + ), + SizedBox(width: 12.h), + Expanded( + child: title.toText14( + color: AppColors.textColor, + weight: FontWeight.w400, + ), + ), + ], + ) + // Keep only bottom padding here and rely on the surrounding container's left/right inset + .paddingOnly(left: 0.h, right: 0.h, top: 0.h, bottom: 12.h), + ); + + } + }