From 1af2db5c098b826bb71216aa15b5fddd110d3fcd Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 29 Sep 2025 15:45:54 +0300 Subject: [PATCH 1/9] patient profile settings page integration implementation contd. --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- .../insurance/insurance_view_model.dart | 18 ++++- lib/presentation/home/landing_page.dart | 5 ++ .../widgets/patient_insurance_card.dart | 2 +- .../profile_settings/profile_settings.dart | 69 +++++++++++++++---- 6 files changed, 78 insertions(+), 20 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index a4e76fc..29b41fd 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -177,7 +177,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4767477; + // body['PatientID'] = 1018977; } body.removeWhere((key, value) => value == null); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fa02b2e..5886dfa 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 8319634..3129fbc 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -12,6 +12,8 @@ class InsuranceViewModel extends ChangeNotifier { bool isInsuranceDetailsLoading = false; bool isInsuranceUpdateDetailsLoading = false; + bool isInsuranceDataToBeLoaded = true; + InsuranceRepo insuranceRepo; ErrorHandlerService errorHandlerService; @@ -23,13 +25,15 @@ class InsuranceViewModel extends ChangeNotifier { InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService}); initInsuranceProvider() { - patientInsuranceList.clear(); + if (isInsuranceDataToBeLoaded) { + patientInsuranceList.clear(); + isInsuranceLoading = true; + getPatientInsuranceDetails(); + } patientInsuranceCardHistoryList.clear(); - isInsuranceLoading = true; isInsuranceHistoryLoading = true; isInsuranceDetailsLoading = true; isInsuranceUpdateDetailsLoading = true; - getPatientInsuranceDetails(); notifyListeners(); } @@ -48,7 +52,14 @@ class InsuranceViewModel extends ChangeNotifier { notifyListeners(); } + setIsInsuranceDataToBeLoaded(bool val) { + isInsuranceDataToBeLoaded = val; + notifyListeners(); + } + Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isInsuranceDataToBeLoaded) return; + final result = await insuranceRepo.getPatientInsuranceDetails(); result.fold( @@ -62,6 +73,7 @@ class InsuranceViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientInsuranceList = apiResponse.data!; isInsuranceLoading = false; + isInsuranceDataToBeLoaded = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index cb2f76f..e78d7bd 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/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; @@ -61,6 +62,8 @@ class _LandingPageState extends State { late PrescriptionsViewModel prescriptionsViewModel; final CacheService cacheService = GetIt.instance(); + late InsuranceViewModel insuranceViewModel; + final SwiperController _controller = SwiperController(); @override @@ -81,6 +84,7 @@ class _LandingPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); + insuranceViewModel.initInsuranceProvider(); } }); super.initState(); @@ -92,6 +96,7 @@ class _LandingPageState extends State { NavigationService navigationService = getIt.get(); myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); + insuranceViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index cf8b7d7..06cfd04 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -55,7 +55,7 @@ class PatientInsuranceCard extends StatelessWidget { icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 13.h, - text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active", + text: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, onPressed: () {}, backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 58273fc..70f20b1 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -3,12 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_swiper_view/flutter_swiper_view.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -21,6 +25,8 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import '../../core/dependencies.dart' show getIt; + class ProfileSettings extends StatefulWidget { ProfileSettings({Key? key}) : super(key: key); @@ -75,7 +81,7 @@ class _ProfileSettingsState extends State { builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return FamilyCardWidget().paddingOnly(right: 16); + return FamilyCardWidget(isRootUser: true).paddingOnly(right: 16); }, ), GridView( @@ -224,10 +230,14 @@ class _ProfileSettingsState extends State { } class FamilyCardWidget extends StatelessWidget { - FamilyCardWidget(); + FamilyCardWidget({this.isRootUser = true, Key? key}) : super(key: key); + + bool isRootUser; + late AppState appState; @override Widget build(BuildContext context) { + appState = getIt.get(); return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -243,17 +253,18 @@ class FamilyCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - Image.asset(true ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), + Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 0.h, mainAxisSize: MainAxisSize.min, children: [ - "Mahmoud Shrouf Shrouf".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" + .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "File no: 3423443", - iconSize: 14, + labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + iconSize: 12, ), ], ).expanded, @@ -266,22 +277,52 @@ class FamilyCardWidget extends StatelessWidget { alignment: WrapAlignment.start, spacing: 8.h, children: [ - AppCustomChipWidget(labelText: "35 Years Old"), - AppCustomChipWidget(labelText: "Blood: A+"), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), AppCustomChipWidget( - icon: AppAssets.insurance_active_icon, - labelText: "Insurance Active", - iconColor: AppColors.bgGreenColor, - iconSize: 14, - backgroundColor: AppColors.bgGreenColor.withValues(alpha: 0.15), + icon: AppAssets.blood_icon, + labelText: "${LocaleKeys.bloodType.tr(context: context)}: ${appState.getUserBloodGroup}", + iconColor: AppColors.primaryRedColor, ), + Consumer(builder: (context, insuranceVM, child) { + return AppCustomChipWidget( + icon: insuranceVM.isInsuranceLoading + ? AppAssets.cancel_circle_icon + : (DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + )) + ? AppAssets.cancel_circle_icon + : AppAssets.insurance_active_icon, + labelText: insuranceVM.isInsuranceLoading + ? "Insurance" + : (DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + ) + ? "Insurance Expired".needTranslation + : "Insurance Active".needTranslation), + iconColor: insuranceVM.isInsuranceLoading + ? AppColors.primaryRedColor + : (DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + )) + ? AppColors.primaryRedColor + : AppColors.successColor, + iconSize: 14, + backgroundColor: insuranceVM.isInsuranceLoading + ? AppColors.primaryRedColor + : (DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + )) + ? AppColors.primaryRedColor.withValues(alpha: 0.15) + : AppColors.successColor.withValues(alpha: 0.15), + ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); + }), ], ), ), ], ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded, 1.divider, - CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, height: 40.h, fontSize: 14, onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), ], ), ); From 2de02102c26571b014d686c7863b363b49d3b50b Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 29 Sep 2025 17:30:13 +0300 Subject: [PATCH 2/9] profile settings update --- .../lab/lab_results/lab_result_details.dart | 2 +- .../profile_settings/profile_settings.dart | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index e3ffaea..9935c0d 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -13,10 +13,10 @@ import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.d import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart' show LabRangeViewModel; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; import 'package:provider/provider.dart' show Consumer, Provider; diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 70f20b1..e5ec389 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -322,7 +322,21 @@ class FamilyCardWidget extends StatelessWidget { ], ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded, 1.divider, - CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, height: 40.h, fontSize: 14, onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + //TODO: Add family file switch logic here + isRootUser + ? CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, height: 40.h, fontSize: 14, onPressed: () {}) + .paddingOnly(top: 12, right: 16, left: 16, bottom: 16) + : CustomButton( + icon: AppAssets.add_family, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + text: "Switch to this medical file".needTranslation, + height: 40.h, + fontSize: 14, + onPressed: () {}) + .paddingOnly(top: 12, right: 16, left: 16, bottom: 16), ], ), ); From 8bb0e495cc8feada8f26fca6e799824e3dd3373a Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Mon, 29 Sep 2025 22:59:10 +0300 Subject: [PATCH 3/9] request medical report implementation contd. --- lib/core/api_consts.dart | 2 +- lib/features/insurance/insurance_repo.dart | 4 +- .../insurance/insurance_view_model.dart | 13 +- .../medical_file/medical_file_repo.dart | 50 +++++ .../medical_file/medical_file_view_model.dart | 35 +++- .../my_appointments/my_appointments_repo.dart | 5 +- .../widgets/appointment_card.dart | 163 +++++++++------- .../insurance/insurance_home_page.dart | 81 +++++--- .../insurance/widgets/insurance_history.dart | 131 +++++++------ .../insurance_update_details_card.dart | 101 +++++----- .../widgets/patient_insurance_card.dart | 6 +- .../medical_file/medical_file_page.dart | 87 ++++++--- .../medical_file/medical_reports_page.dart | 94 ---------- .../medical_report_request_page.dart | 61 ++++++ .../medical_report/medical_reports_page.dart | 176 ++++++++++++++++++ .../widgets/patient_medical_report_card.dart | 0 pubspec.yaml | 2 +- 17 files changed, 674 insertions(+), 337 deletions(-) delete mode 100644 lib/presentation/medical_file/medical_reports_page.dart create mode 100644 lib/presentation/medical_report/medical_report_request_page.dart create mode 100644 lib/presentation/medical_report/medical_reports_page.dart rename lib/presentation/{medical_file => medical_report}/widgets/patient_medical_report_card.dart (100%) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5886dfa..fa02b2e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 65f97ae..d367509 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class InsuranceRepo { Future>>> getPatientInsuranceDetails(); - Future>>> getPatientInsuranceCardHistory({required String patientId}); + Future>>> getPatientInsuranceCardHistory(); Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}); } @@ -64,7 +64,7 @@ class InsuranceRepoImp implements InsuranceRepo { } @override - Future>>> getPatientInsuranceCardHistory({required String patientId}) async { + Future>>> getPatientInsuranceCardHistory() async { Map mapDevice = {}; try { diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 3129fbc..0bcf30f 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -66,6 +66,7 @@ class InsuranceViewModel extends ChangeNotifier { // (failure) async => await errorHandlerService.handleError(failure: failure), (failure) async { isInsuranceLoading = false; + notifyListeners(); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -84,10 +85,13 @@ class InsuranceViewModel extends ChangeNotifier { } Future getPatientInsuranceCardHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await insuranceRepo.getPatientInsuranceCardHistory(patientId: "1231755"); + final result = await insuranceRepo.getPatientInsuranceCardHistory(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isInsuranceHistoryLoading = false; + notifyListeners(); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); @@ -108,7 +112,10 @@ class InsuranceViewModel extends ChangeNotifier { final result = await insuranceRepo.getPatientInsuranceDetailsForUpdate(patientId: patientID, identificationNo: identificationNo); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isInsuranceUpdateDetailsLoading = false; + notifyListeners(); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 6b83f33..658d045 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import '../authentication/models/resp_models/authenticated_user_resp_model.dart'; @@ -27,6 +28,8 @@ abstract class MedicalFileRepo { Future>>> getPatientFamilyFiles(); Future>>> addFamilyFile({required dynamic request}); + + Future>>> getPatientAppointmentsForMedicalReport(); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -348,4 +351,51 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientAppointmentsForMedicalReport() async { + Map mapDevice = { + "IsActiveAppointment": false, + "IsComingFromCOC": false, + "isForUpcomming": false, + "IsForMedicalReport": true, + "IsForArrived": false, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_APPOINTMENT_HISTORY, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppoimentAllHistoryResultList']; + // if (list == null || list.isEmpty) { + // throw Exception("Appointments list is empty"); + // } + + final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.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/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 2e528de..061392e 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_res import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -30,6 +31,9 @@ class MedicalFileViewModel extends ChangeNotifier { List patientMedicalReportReadyList = []; List patientMedicalReportCancelledList = []; + List patientMedicalReportAppointmentHistoryList = []; + PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment; + List patientFamilyFiles = []; String patientSickLeavePDFBase64 = ""; @@ -42,6 +46,7 @@ class MedicalFileViewModel extends ChangeNotifier { MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); initMedicalFileProvider() { + patientMedicalReportAppointmentHistoryList.clear(); isPatientVaccineListLoading = true; isPatientMedicalReportsListLoading = true; notifyListeners(); @@ -87,6 +92,11 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedMedicalReportAppointment(PatientAppointmentHistoryResponseModel? val) { + patientMedicalReportSelectedAppointment = val; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); @@ -293,9 +303,32 @@ class MedicalFileViewModel extends ChangeNotifier { ); } + Future getPatientMedicalReportAppointmentsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientMedicalReportAppointmentHistoryList.clear(); + notifyListeners(); + + final result = await medicalFileRepo.getPatientAppointmentsForMedicalReport(); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientMedicalReportAppointmentHistoryList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future addFamilyFile() async { final resultEither = await medicalFileRepo.addFamilyFile(request: {}); resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async {}); } - } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 99f7c7d..eb8611d 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -56,13 +56,10 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, - "isDentalAllowedBackend": false, - "PatientTypeID": 1, "IsComingFromCOC": false, - "PatientType": 1, "isForUpcomming": false, + "IsForMedicalReport": false, "IsForArrived": isArrivedAppointments, - "PatientOutSA": 0 }; try { diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 152436a..d287795 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; @@ -21,12 +22,21 @@ import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:smooth_corner/smooth_corner.dart'; class AppointmentCard extends StatefulWidget { - AppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel, this.isLoading = false, this.isFromHomePage = false}); + AppointmentCard( + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.myAppointmentsViewModel, + this.isLoading = false, + this.isFromHomePage = false, + this.isFromMedicalReport = false, + this.medicalFileViewModel}); PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; MyAppointmentsViewModel myAppointmentsViewModel; bool isLoading; bool isFromHomePage; + bool isFromMedicalReport; + MedicalFileViewModel? medicalFileViewModel; @override State createState() => _AppointmentCardState(); @@ -171,75 +181,94 @@ class _AppointmentCardState extends State { ], ), SizedBox(height: 16.h), - Row( - children: [ - Expanded( - flex: 6, - child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton().toShimmer2(isShow: widget.isLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - Navigator.of(context) - .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 15.h, - ).toShimmer2(isShow: widget.isLoading), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( + widget.isFromMedicalReport + ? CustomButton( + text: "Select appointment".needTranslation, + onPressed: () { + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); + Navigator.pop(context, false); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.h, - ), - child: Padding( - padding: EdgeInsets.all(10.h), - child: Transform.flip( - flipX: appState.isArabic() ? true : false, - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, - ), + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) + : Row( + children: [ + Expanded( + flex: 6, + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton().toShimmer2(isShow: widget.isLoading) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 15.h, + ).toShimmer2(isShow: widget.isLoading), ), - ), - ).toShimmer2(isShow: widget.isLoading).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + ), + child: Padding( + padding: EdgeInsets.all(10.h), + child: Transform.flip( + flipX: appState.isArabic() ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.whiteColor, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), + ), + ), + ).toShimmer2(isShow: widget.isLoading).onPress(() { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }), ), - ) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }), - ), - ], - ), + ], + ), ], ), ), diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 41640b7..bc56473 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -3,12 +3,17 @@ 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/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/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -30,6 +35,8 @@ class InsuranceHomePage extends StatefulWidget { class _InsuranceHomePageState extends State { late InsuranceViewModel insuranceViewModel; + late AppState appState; + @override void initState() { scheduleMicrotask(() { @@ -40,6 +47,7 @@ class _InsuranceHomePageState extends State { @override Widget build(BuildContext context) { + appState = getIt.get(); insuranceViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -48,44 +56,55 @@ class _InsuranceHomePageState extends State { history: () { insuranceViewModel.setIsInsuranceHistoryLoading(true); insuranceViewModel.getPatientInsuranceCardHistory(); - showCommonBottomSheet(context, - child: InsuranceHistory(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); + showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); }, child: SingleChildScrollView( child: Consumer(builder: (context, insuranceVM, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), - // CustomButton( - // icon: AppAssets.insurance_history_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 21.h, - // text: LocaleKeys.history.tr(context: context), - // onPressed: () { - // }, - // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - // borderColor: AppColors.primaryRedColor.withOpacity(0.0), - // textColor: AppColors.primaryRedColor, - // fontSize: 14, - // fontWeight: FontWeight.w600, - // borderRadius: 12, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 40.h, - // ), - // ], - // ).paddingSymmetrical(24.h, 24.h), insuranceVM.isInsuranceLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) - : Padding( - padding: EdgeInsets.only(top: 24.h), - child: PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), - ), + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ).paddingSymmetrical(24.h, 24.h) + : insuranceVM.patientInsuranceList.isNotEmpty + ? Padding( + padding: EdgeInsets.only(top: 24.h), + child: PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ) + : Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have insurance registered with HMG.".needTranslation, + callToActionButton: CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () { + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ).paddingSymmetrical(64.h, 0.h), + ), + ), ], ); }), diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index de5581c..1c7b1b9 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -8,6 +8,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/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -25,6 +26,7 @@ class InsuranceHistory extends StatelessWidget { return Consumer(builder: (context, insuranceVM, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -36,72 +38,85 @@ class InsuranceHistory extends StatelessWidget { ], ).paddingSymmetrical(24.h, 24.h), insuranceVM.isInsuranceHistoryLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) - : ListView.separated( - itemCount: insuranceVM.patientInsuranceCardHistoryList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Container( - // height: 120.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - CustomButton( - text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription!, - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ).paddingSymmetrical(24.h, 24.h) + : insuranceVM.patientInsuranceCardHistoryList.isNotEmpty + ? ListView.separated( + itemCount: insuranceVM.patientInsuranceCardHistoryList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Container( + // height: 120.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, ), - SizedBox(height: 8.h), - // "Haroon Amjad".toText16(weight: FontWeight.w600), - SizedBox(height: 8.h), - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, + Row( children: [ - AppCustomChipWidget( - labelText: "File No.: ${insuranceVM.patientInsuranceCardHistoryList[index].patientID}", + CustomButton( + text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription!, + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, ), - AppCustomChipWidget( - labelText: insuranceVM.patientInsuranceCardHistoryList[index].createdOn!, + ], + ), + SizedBox(height: 8.h), + // "Haroon Amjad".toText16(weight: FontWeight.w600), + SizedBox(height: 8.h), + Row( + children: [ + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "File No.: ${insuranceVM.patientInsuranceCardHistoryList[index].patientID}", + ), + AppCustomChipWidget( + labelText: insuranceVM.patientInsuranceCardHistoryList[index].createdOn!, + ), + ], ), ], ), ], - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ) + : Utils.getNoDataWidget( + context, + noDataText: "No insurance update requests found.".needTranslation, + // isSmallWidget: true, + // width: 62, + // height: 62, + ), ], ); }); diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index aa04d8c..08e252b 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -8,6 +8,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/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -23,6 +24,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { Widget build(BuildContext context) { insuranceViewModel = Provider.of(context); return Column( + mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -34,57 +36,63 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { ], ).paddingSymmetrical(24.h, 24.h), insuranceViewModel.isInsuranceUpdateDetailsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) - : Container( - // height: 120.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Haroon Amjad".toText16(weight: FontWeight.w600), - "Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor), - SizedBox(height: 8.h), - Row( - children: [ - insuranceViewModel.patientInsuranceUpdateResponseModel!.companyName!.toText12(isBold: true), - SizedBox( - width: 6.h, - ), - Container( - padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 3.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.infoColor, - borderRadius: 50, - ), - child: insuranceViewModel.patientInsuranceUpdateResponseModel!.subCategory!.toText8(isBold: true, color: AppColors.whiteColor), - ), - ], + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ).paddingSymmetrical(24.h, 24.h) + : insuranceViewModel.patientInsuranceUpdateResponseModel != null + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, ), - SizedBox(height: 8.h), - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, + "Haroon Amjad".toText16(weight: FontWeight.w600), + "Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor), + SizedBox(height: 8.h), + Row( children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", + insuranceViewModel.patientInsuranceUpdateResponseModel!.companyName!.toText12(isBold: true), + SizedBox( + width: 6.h, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 3.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.infoColor, + borderRadius: 50, + ), + child: insuranceViewModel.patientInsuranceUpdateResponseModel!.subCategory!.toText8(isBold: true, color: AppColors.whiteColor), ), - AppCustomChipWidget( - labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", + ), + AppCustomChipWidget( + labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", + ), + ], ), ], ), ], - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h) + : Utils.getNoDataWidget(context, noDataText: "No insurance data found...".needTranslation), SizedBox( height: 24.h, ), @@ -92,9 +100,9 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { icon: AppAssets.insurance_active_icon, iconColor: AppColors.whiteColor, iconSize: 20.h, - text: "Update Insurance", + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", onPressed: () {}, - backgroundColor: AppColors.successColor, + backgroundColor: insuranceViewModel.patientInsuranceUpdateResponseModel != null ? AppColors.successColor : AppColors.lightGrayBGColor, borderColor: AppColors.successColor.withOpacity(0.01), textColor: AppColors.whiteColor, fontSize: 16, @@ -103,9 +111,6 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 56.h, ).paddingSymmetrical(24.h, 0.h), - SizedBox( - height: 24.h, - ), ], ); } diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 06cfd04..b89d3e8 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -94,11 +94,11 @@ class PatientInsuranceCard extends StatelessWidget { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); insuranceViewModel.getPatientInsuranceDetailsForUpdate( appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheet(context, + showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), - callBackFunc: (str) {}, + callBackFunc: () {}, title: "", - height: ResponsiveExtension.screenHeight * 0.42, + // height: ResponsiveExtension.screenHeight * 0.42, isCloseButtonVisible: false, isFullScreen: false); }, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 28045fb..2a9fcfe 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -28,9 +28,11 @@ import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.da import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:hmg_patient_app_new/presentation/medical_file/medical_reports_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; @@ -566,28 +568,28 @@ class _MedicalFilePageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: SizedBox( - width: 80.h, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.network( - myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, - width: 64.h, - height: 64.h, - fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: false, radius: 50.h), - SizedBox(height: 8.h), - Expanded( - child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: false), + width: 80.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], ), - ], + ), ), ), - ), - ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62) .paddingSymmetrical(24.h, 0.h); @@ -646,7 +648,12 @@ class _MedicalFilePageState extends State { children: [ Consumer(builder: (context, insuranceVM, child) { return insuranceVM.isInsuranceLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0) + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ).paddingSymmetrical(24.h, 0.0) : insuranceVM.patientInsuranceList.isNotEmpty ? PatientInsuranceCard( insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, @@ -654,7 +661,33 @@ class _MedicalFilePageState extends State { DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), ), ) - : SizedBox.shrink(); + : Utils.getNoDataWidget( + context, + noDataText: "You don't have insurance registered with HMG.".needTranslation, + isSmallWidget: true, + width: 62, + height: 62, + callToActionButton: CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () { + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ).paddingSymmetrical(64.h, 0.h), + ); }), SizedBox(height: 10.h), GridView( @@ -718,11 +751,17 @@ class _MedicalFilePageState extends State { patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, isLoading: false, ).paddingSymmetrical(24.h, 0.0) - : SizedBox.shrink(); + : Utils.getNoDataWidget( + context, + noDataText: "You don't have any sick leaves yet.".needTranslation, + isSmallWidget: true, + width: 62, + height: 62, + ); }), SizedBox(height: 16.h), GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 6.5, mainAxisSpacing: 6.5), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, shrinkWrap: true, diff --git a/lib/presentation/medical_file/medical_reports_page.dart b/lib/presentation/medical_file/medical_reports_page.dart deleted file mode 100644 index 9baf538..0000000 --- a/lib/presentation/medical_file/medical_reports_page.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; -import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_medical_report_card.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; -import 'package:provider/provider.dart'; - -class MedicalReportsPage extends StatefulWidget { - const MedicalReportsPage({super.key}); - - @override - State createState() => _MedicalReportsPageState(); -} - -class _MedicalReportsPageState extends State { - late MedicalFileViewModel medicalFileViewModel; - - @override - Widget build(BuildContext context) { - medicalFileViewModel = Provider.of(context, listen: false); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "Medical Reports".needTranslation, - child: SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: 16.h), - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "Requested".needTranslation), - CustomTabBarModel(null, "Ready".needTranslation), - CustomTabBarModel(null, "Cancelled".needTranslation), - ], - onTabChange: (index) { - medicalFileViewModel.onMedicalReportTabChange(index); - }, - ).paddingSymmetrical(24.h, 0.h), - Consumer(builder: (context, medicalFileVM, child) { - return ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: medicalFileViewModel.isPatientMedicalReportsListLoading ? 3 : medicalFileViewModel.patientMedicalReportList.length, - // medicalFileViewModel.patientMedicalReportList.isNotEmpty - // ? medicalFileViewModel.patientMedicalReportList.length - // : 1, - itemBuilder: (context, index) { - return medicalFileViewModel.isPatientMedicalReportsListLoading - ? PatientMedicalReportCard( - patientMedicalReportResponseModel: PatientMedicalReportResponseModel(), - medicalFileViewModel: medicalFileVM, - isLoading: true, - ).paddingSymmetrical(24.h, 0.h) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: PatientMedicalReportCard( - patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index], - medicalFileViewModel: medicalFileVM, - - isLoading: false, - ), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ); - }), - SizedBox(height: 24.h), - ], - ), - ), - ), - ); - } -} diff --git a/lib/presentation/medical_report/medical_report_request_page.dart b/lib/presentation/medical_report/medical_report_request_page.dart new file mode 100644 index 0000000..8f891e2 --- /dev/null +++ b/lib/presentation/medical_report/medical_report_request_page.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class MedicalReportRequestPage extends StatelessWidget { + MedicalReportRequestPage({super.key}); + + late MedicalFileViewModel medicalFileViewModel; + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return CollapsingListView( + title: "Medical Reports".needTranslation, + isClose: true, + child: Column( + children: [ + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: medicalFileViewModel.patientMedicalReportAppointmentHistoryList.length, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: medicalFileViewModel.patientMedicalReportAppointmentHistoryList[index], + myAppointmentsViewModel: Provider.of(context, listen: false), + medicalFileViewModel: medicalFileViewModel, + isLoading: false, + isFromHomePage: false, + isFromMedicalReport: true, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart new file mode 100644 index 0000000..c08c750 --- /dev/null +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -0,0 +1,176 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.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/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/medical_report/medical_report_request_page.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_report/widgets/patient_medical_report_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class MedicalReportsPage extends StatefulWidget { + const MedicalReportsPage({super.key}); + + @override + State createState() => _MedicalReportsPageState(); +} + +class _MedicalReportsPageState extends State { + late MedicalFileViewModel medicalFileViewModel; + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Medical Reports".needTranslation, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Requested".needTranslation), + CustomTabBarModel(null, "Ready".needTranslation), + CustomTabBarModel(null, "Cancelled".needTranslation), + ], + onTabChange: (index) { + medicalFileViewModel.onMedicalReportTabChange(index); + }, + ).paddingSymmetrical(24.h, 0.h), + Consumer(builder: (context, medicalFileVM, child) { + return ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: medicalFileViewModel.isPatientMedicalReportsListLoading + ? 3 + : medicalFileViewModel.patientMedicalReportList.isNotEmpty + ? medicalFileViewModel.patientMedicalReportList.length + : 1, + itemBuilder: (context, index) { + return medicalFileViewModel.isPatientMedicalReportsListLoading + ? PatientMedicalReportCard( + patientMedicalReportResponseModel: PatientMedicalReportResponseModel(), + medicalFileViewModel: medicalFileVM, + isLoading: true, + ).paddingSymmetrical(24.h, 0.h) + : medicalFileViewModel.patientMedicalReportList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: PatientMedicalReportCard( + patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index], + medicalFileViewModel: medicalFileVM, + isLoading: false, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation).paddingSymmetrical(24.h, 0.h); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ); + }), + SizedBox(height: 24.h), + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: "Request medical report".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(); + await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { + LoaderBottomSheet.hideLoader(); + bool? value = await Navigator.of(context).push( + CustomPageRoute( + page: MedicalReportRequestPage(), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + showConfirmRequestMedicalReportBottomSheet(); + } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "You do not have any appointments to request a medical report.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 45.h, + icon: AppAssets.requests, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); + } + + showConfirmRequestMedicalReportBottomSheet() { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to request a medical report for this appointment?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart similarity index 100% rename from lib/presentation/medical_file/widgets/patient_medical_report_card.dart rename to lib/presentation/medical_report/widgets/patient_medical_report_card.dart diff --git a/pubspec.yaml b/pubspec.yaml index 3bdaa4a..72e75f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,7 @@ dependencies: uuid: ^4.5.1 health: ^13.1.3 # health: 12.0.1 - fl_chart: ^1.0.0 + fl_chart: 1.0.0 geolocator: ^14.0.2 dropdown_search: ^6.0.2 google_maps_flutter: ^2.12.3 From 093e645f6009184af50315aec3259fa03f1a9264 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 30 Sep 2025 11:57:37 +0300 Subject: [PATCH 4/9] request medical report implementation completed. --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- .../medical_file/medical_file_repo.dart | 55 ++++++- .../medical_file/medical_file_view_model.dart | 23 ++- .../widgets/appointment_card.dart | 8 +- .../medical_file/medical_file_page.dart | 134 +++++++++++------- .../widgets/medical_file_card.dart | 4 +- .../widgets/patient_sick_leave_card.dart | 8 +- .../medical_report_request_page.dart | 1 + .../medical_report/medical_reports_page.dart | 110 +++++++++++--- lib/theme/colors.dart | 2 +- lib/widgets/buttons/custom_button.dart | 2 +- lib/widgets/chip/app_custom_chip_widget.dart | 4 +- lib/widgets/input_widget.dart | 4 +- 14 files changed, 263 insertions(+), 96 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 29b41fd..5d4da2b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -177,7 +177,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 1018977; + // body['PatientID'] = 4770714; } body.removeWhere((key, value) => value == null); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fa02b2e..1c6cec1 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -119,7 +119,7 @@ var GET_STATUS_FOR_COCO = 'Services/COCWS.svc/REST/GetStatusforCOC'; // var GET_PATIENT_AppointmentHistory = 'Services' // '/Doctors.svc/REST/PateintHasAppoimentHistory'; -var GET_PATIENT_AppointmentHistory = 'Services' +var GET_PATIENT_APPOINTMENT_HISTORY_ASYNC = 'Services' '/Doctors.svc/REST/PateintHasAppoimentHistory_Async'; ///VITAL SIGN diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 658d045..7077fd0 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -30,6 +30,8 @@ abstract class MedicalFileRepo { Future>>> addFamilyFile({required dynamic request}); Future>>> getPatientAppointmentsForMedicalReport(); + + Future>> insertRequestForMedicalReport({required PatientAppointmentHistoryResponseModel appointmentHistoryResponseModel}); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -366,7 +368,7 @@ class MedicalFileRepoImp implements MedicalFileRepo { GenericApiModel>? apiResponse; Failure? failure; await apiClient.post( - GET_PATIENT_APPOINTMENT_HISTORY, + GET_PATIENT_APPOINTMENT_HISTORY_ASYNC, body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; @@ -374,9 +376,6 @@ class MedicalFileRepoImp implements MedicalFileRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['AppoimentAllHistoryResultList']; - // if (list == null || list.isEmpty) { - // throw Exception("Appointments list is empty"); - // } final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); @@ -398,4 +397,52 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> insertRequestForMedicalReport({required PatientAppointmentHistoryResponseModel appointmentHistoryResponseModel}) async { + Map mapDevice = { + "ClinicID": appointmentHistoryResponseModel.clinicID, + "DoctorID": appointmentHistoryResponseModel.doctorID, + "SetupID": appointmentHistoryResponseModel.setupID, + "EncounterNo": appointmentHistoryResponseModel.appointmentNo, + "EncounterType": 1, + "IsActive": appointmentHistoryResponseModel.isActiveDoctor, + "ProjectID": appointmentHistoryResponseModel.projectID, + "Remarks": "", + "ProcedureId": "", + "RequestType": 1, + "Source": 2, + "Status": 1, + "CreatedBy": 102 + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + INSERT_REQUEST_FOR_MEDICAL_REPORT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } 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/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 061392e..95c5d63 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; @@ -54,6 +53,7 @@ class MedicalFileViewModel extends ChangeNotifier { void onMedicalReportTabChange(int index) { selectedMedicalReportsTabIndex = index; + print("Selected Medical Report Tab Index: $selectedMedicalReportsTabIndex"); if (index == 0) { patientMedicalReportList = patientMedicalReportRequestedList; } else if (index == 1) { @@ -85,6 +85,7 @@ class MedicalFileViewModel extends ChangeNotifier { setIsPatientMedicalReportsLoading(bool val) { if (val) { + onMedicalReportTabChange(0); patientMedicalReportList.clear(); patientMedicalReportPDFBase64 = ""; } @@ -327,6 +328,26 @@ class MedicalFileViewModel extends ChangeNotifier { ); } + Future insertRequestForMedicalReport({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.insertRequestForMedicalReport(appointmentHistoryResponseModel: patientMedicalReportSelectedAppointment!); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future addFamilyFile() async { final resultEither = await medicalFileRepo.addFamilyFile(request: {}); resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async {}); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index d287795..6d157a5 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -167,9 +167,11 @@ class _AppointmentCardState extends State { labelText: widget.isLoading ? "Cardiology" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)) .toShimmer2(isShow: widget.isLoading), - AppCustomChipWidget( - icon: AppAssets.appointment_time_icon, - labelText: widget.isLoading + widget.isFromMedicalReport + ? SizedBox.shrink() + : AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, + labelText: widget.isLoading ? "Cardiology" : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)) .toShimmer2(isShow: widget.isLoading), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 2a9fcfe..7fd9138 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -38,8 +38,6 @@ import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page. import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; -import 'package:hmg_patient_app_new/presentation/my_family/my_Family.dart'; -import 'package:hmg_patient_app_new/presentation/my_family/widget/my_family_sheet.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,7 +49,6 @@ import 'package:hmg_patient_app_new/widgets/input_widget.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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; -import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import '../prescriptions/prescription_detail_page.dart'; @@ -502,7 +499,14 @@ class _MedicalFilePageState extends State { ), ), ).paddingSymmetrical(24.h, 0.h) - : Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation, isSmallWidget: true, width: 62, height: 62).paddingSymmetrical(24.h, 0.h); + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation, isSmallWidget: true, width: 62, height: 62)) + .paddingSymmetrical(24.h, 0.h); }), SizedBox(height: 24.h), //My Doctor Section @@ -551,7 +555,7 @@ class _MedicalFilePageState extends State { "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 64.h, height: 64.h, - fit: BoxFit.fill, + fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.h), SizedBox(height: 8.h), Expanded( @@ -591,8 +595,14 @@ class _MedicalFilePageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62) - .paddingSymmetrical(24.h, 0.h); + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62), + ).paddingSymmetrical(24.h, 0.h); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), @@ -602,7 +612,12 @@ class _MedicalFilePageState extends State { "Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + mainAxisExtent: 130, + ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, shrinkWrap: true, @@ -613,15 +628,15 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, isLargeText: true, - iconSize: 40.h, + iconSize: 36.h, ), MedicalFileCard( - label: "Allergy Info".needTranslation, + label: "Allergy Info".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.allergy_info_icon, isLargeText: true, - iconSize: 40.h, + iconSize: 36.h, ), MedicalFileCard( label: "Vaccine Info".needTranslation, @@ -629,7 +644,7 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.vaccine_info_icon, isLargeText: true, - iconSize: 40.h, + iconSize: 36.h, ).onPress(() { Navigator.of(context).push( CustomPageRoute( @@ -661,37 +676,49 @@ class _MedicalFilePageState extends State { DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), ), ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have insurance registered with HMG.".needTranslation, - isSmallWidget: true, - width: 62, - height: 62, - callToActionButton: CustomButton( - icon: AppAssets.update_insurance_card_icon, - iconColor: AppColors.successColor, - iconSize: 15.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () { - insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); - }, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).paddingSymmetrical(64.h, 0.h), - ); + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have insurance registered with HMG.".needTranslation, + isSmallWidget: true, + width: 62, + height: 62, + callToActionButton: CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () { + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ).paddingOnly(left: 12.h, right: 12.h, bottom: 12.h), + ), + ).paddingSymmetrical(24.h, 0.h); }), SizedBox(height: 10.h), GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + mainAxisExtent: 140, + ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.only(top: 12), shrinkWrap: true, @@ -701,7 +728,7 @@ class _MedicalFilePageState extends State { textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, - isLargeText: false, + isLargeText: true, iconSize: 36.h) .onPress(() { Navigator.of(context).push( @@ -715,21 +742,21 @@ class _MedicalFilePageState extends State { textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, - isLargeText: false, + isLargeText: true, iconSize: 36.h), MedicalFileCard( label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, - isLargeText: false, + isLargeText: true, iconSize: 36.h), MedicalFileCard( label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, - isLargeText: false, + isLargeText: true, iconSize: 36.h), ], ).paddingSymmetrical(24.h, 0.0), @@ -761,7 +788,12 @@ class _MedicalFilePageState extends State { }), SizedBox(height: 16.h), GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 6.5, mainAxisSpacing: 6.5), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + mainAxisExtent: 140, + ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, shrinkWrap: true, @@ -771,16 +803,16 @@ class _MedicalFilePageState extends State { textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon, - isLargeText: false, - iconSize: 40.h, + isLargeText: true, + iconSize: 36.h, ), MedicalFileCard( label: "Medical Reports".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.allergy_info_icon, - isLargeText: false, - iconSize: 40.h, + isLargeText: true, + iconSize: 36.h, ).onPress(() { medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.getPatientMedicalReportList(); @@ -795,8 +827,8 @@ class _MedicalFilePageState extends State { textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.vaccine_info_icon, - isLargeText: false, - iconSize: 40.h, + isLargeText: true, + iconSize: 36.h, ).onPress(() { Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index c4980d9..e8cff72 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -29,14 +29,14 @@ class MedicalFileCard extends StatelessWidget { borderRadius: 20, ), child: Padding( - padding: EdgeInsets.all(8.h), + padding: EdgeInsets.all(12.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - isLargeText ? label.toText14(color: textColor, isBold: true, maxlines: 1) : label.toText11(color: textColor, isBold: true, maxLine: 2), + isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), ], ), ), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index 5cd2547..fed7d15 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -59,7 +59,7 @@ class PatientSickLeaveCard extends StatelessWidget { isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : patientSickLeavesResponseModel.doctorImageURL!, width: 30.h, height: 30.h, - fit: BoxFit.fill, + fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: isLoading), SizedBox(width: 16.h), Expanded( @@ -182,11 +182,11 @@ class PatientSickLeaveCard extends StatelessWidget { Color getStatusColor() { Color statusColor = Colors.white; if (patientSickLeavesResponseModel.status == 1) { - statusColor = Color(0xffCC9B14); + statusColor = Color(0xffCC9B14); // TODO change color as per In Queue design } else if (patientSickLeavesResponseModel.status == 2) { - statusColor = Color(0xff359846); + statusColor = AppColors.successColor; } else if (patientSickLeavesResponseModel.status == 3) { - statusColor = Color(0xffD02127); + statusColor = AppColors.primaryRedColor; } else { statusColor = Colors.white; } diff --git a/lib/presentation/medical_report/medical_report_request_page.dart b/lib/presentation/medical_report/medical_report_request_page.dart index 8f891e2..9d1bcb1 100644 --- a/lib/presentation/medical_report/medical_report_request_page.dart +++ b/lib/presentation/medical_report/medical_report_request_page.dart @@ -54,6 +54,7 @@ class MedicalReportRequestPage extends StatelessWidget { }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), + SizedBox(height: 24.h), ], ), ); diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index c08c750..71abcb7 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -41,23 +41,71 @@ class _MedicalReportsPageState extends State { child: CollapsingListView( title: "Medical Reports".needTranslation, child: SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: 16.h), - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "Requested".needTranslation), - CustomTabBarModel(null, "Ready".needTranslation), - CustomTabBarModel(null, "Cancelled".needTranslation), - ], - onTabChange: (index) { - medicalFileViewModel.onMedicalReportTabChange(index); - }, - ).paddingSymmetrical(24.h, 0.h), - Consumer(builder: (context, medicalFileVM, child) { - return ListView.separated( + child: Consumer(builder: (context, medicalFileVM, child) { + return Column( + children: [ + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "Requested".needTranslation, + onPressed: () { + medicalFileViewModel.onMedicalReportTabChange(0); + }, + backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.ready.tr(context: context), + onPressed: () { + medicalFileViewModel.onMedicalReportTabChange(1); + }, + backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.cancelled.tr(context: context), + onPressed: () { + medicalFileViewModel.onMedicalReportTabChange(2); + }, + backgroundColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + // CustomTabBar( + // activeTextColor: Color(0xffED1C2B), + // activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + // tabs: [ + // CustomTabBarModel(null, "Requested".needTranslation), + // CustomTabBarModel(null, "Ready".needTranslation), + // CustomTabBarModel(null, "Cancelled".needTranslation), + // ], + // onTabChange: (index) { + // medicalFileViewModel.onMedicalReportTabChange(index); + // }, + // ).paddingSymmetrical(24.h, 0.h), + ListView.separated( padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), @@ -93,14 +141,14 @@ class _MedicalReportsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation).paddingSymmetrical(24.h, 0.h); + : Utils.getNoDataWidget(context, noDataText: "You don't have any medical reports yet.".needTranslation).paddingSymmetrical(24.h, 24.h); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ); - }), - SizedBox(height: 24.h), - ], - ), + ), + SizedBox(height: 24.h), + ], + ); + }), ), ), ), @@ -167,6 +215,22 @@ class _MedicalReportsPageState extends State { }, onConfirmTap: () async { Navigator.pop(context); + LoaderBottomSheet.showLoader(); + await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: "Your medical report request has been successfully submitted.".needTranslation), callBackFunc: () { + medicalFileViewModel.setIsPatientMedicalReportsLoading(true); + medicalFileViewModel.onMedicalReportTabChange(0); + medicalFileViewModel.getPatientMedicalReportList(); + }); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, child: Utils.getErrorWidget(loadingText: err), callBackFunc: () { + medicalFileViewModel.setIsPatientMedicalReportsLoading(true); + medicalFileViewModel.onMedicalReportTabChange(0); + medicalFileViewModel.getPatientMedicalReportList(); + }); + }); }), callBackFunc: () {}, isFullScreen: false, diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 4015249..291f81d 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -32,7 +32,7 @@ class AppColors { static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color chipBorderColorOpacity20 = Color(0x332E3039); - static const Color dividerColor = Color(0xFFD2D2D2); + static const Color dividerColor = Color(0x40D2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); static const Color blackBgColor = Color(0xFF2E3039); static const blackColor = textColor; diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 2f8a9ec..a4bec4c 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -78,7 +78,7 @@ class CustomButton extends StatelessWidget { style: context.dynamicTextStyle( fontSize: fontSize.fSize, color: isDisabled ? textColor.withOpacity(0.5) : textColor, - letterSpacing: -0.4, + letterSpacing: 0, fontWeight: fontWeight, ), ), diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index a4db172..16db798 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -58,7 +58,7 @@ class AppCustomChipWidget extends StatelessWidget { child: icon.isNotEmpty ? Chip( avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconHasColor ? iconColor : null) : SizedBox.shrink(), - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), // padding: EdgeInsets.all(0.0), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -77,7 +77,7 @@ class AppCustomChipWidget extends StatelessWidget { ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), padding: EdgeInsets.all(0.0), backgroundColor: backgroundColor, shape: shape ?? SmoothRectangleBorder( diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 4a2322c..38e5829 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -217,7 +217,7 @@ class TextInputWidget extends StatelessWidget { fontSize: 12.fSize, fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor, - letterSpacing: -0.2, + letterSpacing: -0, height: 18 / 12, ), ); @@ -246,7 +246,7 @@ class TextInputWidget extends StatelessWidget { decoration: InputDecoration( isDense: true, hintText: hintText, - hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -1), + hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.75), prefixIconConstraints: BoxConstraints(minWidth: 30.h), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), contentPadding: EdgeInsets.zero, From 2f4d6f5553f976f3d5ad10fa2ec8a49229a2a512 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 30 Sep 2025 17:02:05 +0300 Subject: [PATCH 5/9] immediate livecare consultation implementation contd. --- assets/animations/lottie/Ripple.json | 1 + assets/images/svg/livecare_online_icon.svg | 4 + lib/core/app_assets.dart | 2 + .../book_appointments_repo.dart | 46 +++++ .../book_appointments_view_model.dart | 43 ++++- ...care_immediate_clinics_response_model.dart | 97 +++++++++++ .../book_appointment_page.dart | 16 +- ...select_immediate_livecare_clinic_page.dart | 158 ++++++++++++++++++ .../widgets/select_livecare_call_type.dart | 65 +++++++ .../widgets/livecare_clinic_card.dart | 66 ++++++++ 10 files changed, 489 insertions(+), 9 deletions(-) create mode 100644 assets/animations/lottie/Ripple.json create mode 100644 assets/images/svg/livecare_online_icon.svg create mode 100644 lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart create mode 100644 lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart create mode 100644 lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart create mode 100644 lib/presentation/book_appointment/widgets/livecare_clinic_card.dart diff --git a/assets/animations/lottie/Ripple.json b/assets/animations/lottie/Ripple.json new file mode 100644 index 0000000..058b4ca --- /dev/null +++ b/assets/animations/lottie/Ripple.json @@ -0,0 +1 @@ +{"nm":"Comp 1","ddd":0,"h":100,"w":100,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[5.277,-32.723,0],"ix":1},"s":{"a":0,"k":[4.91,4.91,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50.2,50.18,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[102.555,102.555],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0941,0.7608,0.451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[3.277,-34.527],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[102.555,102.555],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0941,0.7608,0.451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100,100],"t":0},{"s":[295,295],"t":60}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[3.277,-34.527],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100],"t":0},{"s":[0],"t":60}],"ix":7}}]}],"ind":1}],"v":"5.5.7","fr":60,"op":61,"ip":0,"assets":[]} \ No newline at end of file diff --git a/assets/images/svg/livecare_online_icon.svg b/assets/images/svg/livecare_online_icon.svg new file mode 100644 index 0000000..e063de8 --- /dev/null +++ b/assets/images/svg/livecare_online_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 31b4f32..446bbc2 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -145,6 +145,7 @@ class AppAssets { static const String ic_normal_result = '$svgBasePath/normal_result.svg'; static const String ic_low_result = '$svgBasePath/low_result.svg'; static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg'; + static const String livecare_online_icon = '$svgBasePath/livecare_online_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -184,4 +185,5 @@ class AppAnimations { static const String warningAnimation = '$lottieBasePath/warningAnimation.json'; static const String splashLaunching = '$lottieBasePath/splash_launching.json'; static const String noData = '$lottieBasePath/Nodata.json'; + static const String ripple = '$lottieBasePath/Ripple.json'; } diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 5e99585..18b5330 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -66,6 +67,8 @@ abstract class BookAppointmentsRepo { required int serviceID, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -657,4 +660,47 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getLiveCareImmediateClinicsList(int age, int genderID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "Age": age, + "Gender": genderID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_CLINICS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['PatientER_GetClinicsList']; + + final clinicsList = list.map((item) => GetLiveCareClinicListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } 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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 5ef653a..ab4ca56 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot. import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.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'; @@ -60,6 +61,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); GetLiveCareClinicsResponseModel selectedLiveCareClinic = GetLiveCareClinicsResponseModel(); + //Immediate LiveCare + List immediateLiveCareClinicsList = []; + bool isImmediateLiveCareClinicsLoading = false; + int liveCareSelectedCallType = 1; + late DoctorsProfileResponseModel doctorsProfileResponseModel; List slotsList = []; @@ -89,7 +95,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { BookAppointmentsViewModel( {required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils}) { - ; initBookAppointmentViewModel(); } @@ -117,6 +122,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { clinicsList.clear(); doctorsList.clear(); liveCareClinicsList.clear(); + + immediateLiveCareClinicsList.clear(); + isImmediateLiveCareClinicsLoading = true; // getLocation(); notifyListeners(); } @@ -183,6 +191,12 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + + setLiveCareSelectedCallType(int value) { + liveCareSelectedCallType = value; + notifyListeners(); + } + /// this function will decide which clinic api to be called /// either api for region flow or the select clinic api Future getClinics() async { @@ -757,4 +771,31 @@ class BookAppointmentsViewModel extends ChangeNotifier { void getLocation() { locationUtils.getLocation(); } + + Future getLiveCareImmediateClinicsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + immediateLiveCareClinicsList.clear(); + isImmediateLiveCareClinicsLoading = true; + notifyListeners(); + + final result = await bookAppointmentsRepo.getLiveCareImmediateClinicsList(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); + + 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) { + immediateLiveCareClinicsList = apiResponse.data!; + + immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); + + isImmediateLiveCareClinicsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart b/lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart new file mode 100644 index 0000000..5c5b900 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart @@ -0,0 +1,97 @@ +class GetLiveCareClinicListResponseModel { + int? iD; + int? serviceID; + String? serviceName; + String? serviceNameN; + int? clinicID; + int? age; + bool? isCheckAgeBelow; + int? gender; + bool? isActive; + String? createdOn; + String? createdBy; + int? isOnline; + bool? projectOutSA; + List? shiftTimings; + + GetLiveCareClinicListResponseModel( + {this.iD, + this.serviceID, + this.serviceName, + this.serviceNameN, + this.clinicID, + this.age, + this.isCheckAgeBelow, + this.gender, + this.isActive, + this.createdOn, + this.createdBy, + this.isOnline, + this.projectOutSA, + this.shiftTimings}); + + GetLiveCareClinicListResponseModel.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + serviceName = json['ServiceName']; + serviceNameN = json['ServiceNameN']; + clinicID = json['ClinicID']; + age = json['Age']; + isCheckAgeBelow = json['IsCheckAgeBelow']; + gender = json['Gender']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + isOnline = json['IsOnline']; + projectOutSA = json['ProjectOutSA']; + if (json['ShiftTimings'] != null) { + shiftTimings = []; + json['ShiftTimings'].forEach((v) { + shiftTimings!.add(new ShiftTimings.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = Map(); + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['ServiceName'] = this.serviceName; + data['ServiceNameN'] = this.serviceNameN; + data['ClinicID'] = this.clinicID; + data['Age'] = this.age; + data['IsCheckAgeBelow'] = this.isCheckAgeBelow; + data['Gender'] = this.gender; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['IsOnline'] = this.isOnline; + data['ProjectOutSA'] = this.projectOutSA; + if (this.shiftTimings != null) { + data['ShiftTimings'] = this.shiftTimings!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ShiftTimings { + String? endTime; + int? shiftID; + String? startTime; + + ShiftTimings({this.endTime, this.shiftID, this.startTime}); + + ShiftTimings.fromJson(Map json) { + endTime = json['EndTime']; + shiftID = json['ShiftID']; + startTime = json['StartTime']; + } + + Map toJson() { + final Map data = Map(); + data['EndTime'] = this.endTime; + data['ShiftID'] = this.shiftID; + data['StartTime'] = this.startTime; + return data; + } +} diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 6ba14f5..012a8db 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' show RegionBottomSheetBody; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -230,14 +231,13 @@ class _BookAppointmentPageState extends State { flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), ], ).onPress(() { - // bookAppointmentsViewModel.setIsClinicsListLoading(true); - // bookAppointmentsViewModel.setLoadSpecificClinic(false); - // bookAppointmentsViewModel.setProjectID(null); - // Navigator.of(context).push( - // CustomPageRoute( - // page: SelectClinicPage(), - // ), - // ); + //TODO Implement API to check for existing LiveCare Requests + + Navigator.of(context).push( + CustomPageRoute( + page: SelectImmediateLiveCareClinicPage(), + ), + ); }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), diff --git a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart new file mode 100644 index 0000000..8ec6e4b --- /dev/null +++ b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart @@ -0,0 +1,158 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/livecare_clinic_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +class SelectImmediateLiveCareClinicPage extends StatefulWidget { + const SelectImmediateLiveCareClinicPage({super.key}); + + @override + State createState() => _SelectImmediateLiveCareClinicPageState(); +} + +class _SelectImmediateLiveCareClinicPageState extends State { + TextEditingController searchEditingController = TextEditingController(); + FocusNode textFocusNode = FocusNode(); + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + bookAppointmentsViewModel.getLiveCareImmediateClinicsList(); + }); + super.initState(); + } + + @override + void dispose() { + textFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Select LiveCare Clinic".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + children: [ + // SizedBox(height: 16.h), + // TextInputWidget( + // labelText: LocaleKeys.search.tr(context: context), + // hintText: LocaleKeys.clinicName.tr(context: context), + // controller: searchEditingController, + // isEnable: true, + // prefix: null, + // autoFocus: false, + // isBorderAllowed: false, + // keyboardType: TextInputType.text, + // focusNode: textFocusNode, + // suffix: searchEditingController.text.isNotEmpty + // ? GestureDetector( + // onTap: () { + // searchEditingController.clear(); + // bookAppointmentsViewModel.filterClinics(""); + // textFocusNode.unfocus(); + // }, + // child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + // ) + // : null, + // onChange: (value) { + // bookAppointmentsViewModel.filterClinics(value!); + // }, + // padding: EdgeInsets.symmetric( + // vertical: ResponsiveExtension(10).h, + // horizontal: ResponsiveExtension(15).h, + // ), + // ), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isImmediateLiveCareClinicsLoading ? 5 : bookAppointmentsVM.immediateLiveCareClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isImmediateLiveCareClinicsLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isImmediateLiveCareClinicsLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: LiveCareClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicListResponseModel: bookAppointmentsVM.immediateLiveCareClinicsList[index], + isLoading: bookAppointmentsVM.isImmediateLiveCareClinicsLoading, + ).onPress(() { + onImmediateLiveCareClinicSelected(bookAppointmentsVM.immediateLiveCareClinicsList[index]); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + }), + ), + ), + ), + ); + } + + onImmediateLiveCareClinicSelected(GetLiveCareClinicListResponseModel liveCareClinic) { + //TODO: add implementation to show clinic schedule + if (liveCareClinic.isOnline == 1) { + showCommonBottomSheetWithoutHeight(context, + child: SelectLiveCareCallType(bookAppointmentsViewModel: bookAppointmentsViewModel), + callBackFunc: () {}, + title: "Select LiveCare call type".needTranslation, + isCloseButtonVisible: true, + isFullScreen: false); + } else { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getErrorWidget( + loadingText: "The selected clinic is only available between ${liveCareClinic.shiftTimings!.first.startTime} & ${liveCareClinic.shiftTimings!.first.endTime} hours.".needTranslation), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: true, + isFullScreen: false); + } + } +} diff --git a/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart new file mode 100644 index 0000000..689dc9b --- /dev/null +++ b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart @@ -0,0 +1,65 @@ +import 'package:flutter/cupertino.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/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/presentation/medical_file/widgets/medical_file_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class SelectLiveCareCallType extends StatelessWidget { + SelectLiveCareCallType({super.key, required this.bookAppointmentsViewModel}); + + BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + //TODO: Replace with actual icons + return GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + mainAxisExtent: 130, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Video Call".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: true, + iconSize: 36.h, + ).onPress(() { + Navigator.of(context).pop(); + bookAppointmentsViewModel.setLiveCareSelectedCallType(1); + }), + MedicalFileCard( + label: "Audio Call".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 36.h, + ).onPress(() { + Navigator.of(context).pop(); + bookAppointmentsViewModel.setLiveCareSelectedCallType(2); + }), + MedicalFileCard( + label: "Phone Call".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: true, + iconSize: 36.h, + ).onPress(() { + Navigator.of(context).pop(); + bookAppointmentsViewModel.setLiveCareSelectedCallType(3); + }), + ], + ); + } +} diff --git a/lib/presentation/book_appointment/widgets/livecare_clinic_card.dart b/lib/presentation/book_appointment/widgets/livecare_clinic_card.dart new file mode 100644 index 0000000..1642d60 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/livecare_clinic_card.dart @@ -0,0 +1,66 @@ +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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class LiveCareClinicCard extends StatelessWidget { + LiveCareClinicCard({super.key, required this.liveCareClinicListResponseModel, required this.isLoading, required this.bookAppointmentsVM}); + + GetLiveCareClinicListResponseModel liveCareClinicListResponseModel; + bool isLoading; + BookAppointmentsViewModel bookAppointmentsVM; + + @override + Widget build(BuildContext context) { + AppState appState = getIt.get(); + return Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 24.h, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), + Column( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.livecare_online_icon, + width: 16.h, + height: 16.h, + fit: BoxFit.contain, + iconColor: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor) + .toShimmer2(isShow: isLoading), + SizedBox(height: 4.h), + liveCareClinicListResponseModel.isOnline == 1 + ? LocaleKeys.online.tr(context: context).toText10(isBold: true, color: AppColors.successColor).toShimmer2(isShow: isLoading) + : "Offline".toText10(isBold: true, color: AppColors.primaryRedColor).toShimmer2(isShow: isLoading), + ], + ), + ]), + SizedBox(height: 8.h), + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded( + child: (isLoading ? "Cardiology" : (appState.isArabic() ? liveCareClinicListResponseModel.serviceNameN : liveCareClinicListResponseModel.serviceName))! + .toText16(isBold: true) + .toShimmer2(isShow: isLoading)), + Transform.flip( + flipX: appState.isArabic() ? true : false, + child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)), + ]), + ], + ), + ); + } +} From 10b77206f6060940f08d758061fac5e09043f346 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 1 Oct 2025 17:29:59 +0300 Subject: [PATCH 6/9] Immediate LiveCare consultation implementation contd. --- assets/images/svg/edit_icon.svg | 5 + lib/core/app_assets.dart | 1 + lib/core/utils/push_notification_handler.dart | 104 ++-- lib/core/utils/utils.dart | 1 - .../book_appointments_repo.dart | 107 ++++ .../book_appointments_view_model.dart | 59 +- ...ivecare_immediate_fees_response_model.dart | 37 ++ .../appointment_details_page.dart | 2 +- .../appointment_payment_page.dart | 4 +- .../immediate_livecare_payment_details.dart | 301 +++++++++++ .../immediate_livecare_payment_page.dart | 510 ++++++++++++++++++ ...select_immediate_livecare_clinic_page.dart | 26 +- .../review_appointment_page.dart | 1 + lib/widgets/loader/bottomsheet_loader.dart | 5 +- 14 files changed, 1096 insertions(+), 67 deletions(-) create mode 100644 assets/images/svg/edit_icon.svg create mode 100644 lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart create mode 100644 lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart create mode 100644 lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart diff --git a/assets/images/svg/edit_icon.svg b/assets/images/svg/edit_icon.svg new file mode 100644 index 0000000..3f82a2e --- /dev/null +++ b/assets/images/svg/edit_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 446bbc2..016e42d 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -146,6 +146,7 @@ class AppAssets { static const String ic_low_result = '$svgBasePath/low_result.svg'; static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg'; static const String livecare_online_icon = '$svgBasePath/livecare_online_icon.svg'; + static const String edit_icon = '$svgBasePath/edit_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 4568a15..f59d657 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -8,11 +8,14 @@ import 'package:firebase_messaging/firebase_messaging.dart' as fir; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart'; +import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart'; // import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hmg_patient_app_new/core/utils/local_notifications.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:permission_handler/permission_handler.dart'; import '../cache_consts.dart'; @@ -139,7 +142,7 @@ class PushNotificationHandler { // late HmsApiAvailability hmsApiAvailability; - // final voIPKit = FlutterIOSVoIPKit.instance; + final voIPKit = FlutterIOSVoIPKit.instance; late Timer timeOutTimer; bool isTalking = false; @@ -188,55 +191,56 @@ class PushNotificationHandler { this.context = context; if (Platform.isIOS) { - // voIPKit.getVoIPToken().then((value) { - // print("APNS VOIP KIT TOKEN: $value"); - // AppSharedPreferences().setString(APNS_TOKEN, value!); - // }); - // - // voIPKit.onDidUpdatePushToken = (String token) { - // print('🎈 example: onDidUpdatePushToken: $token'); - // }; - // - // voIPKit.onDidReceiveIncomingPush = ( - // Map payload, - // ) async { - // print('🎈 example: onDidReceiveIncomingPush $payload'); - // _timeOut(); - // }; - // - // voIPKit.onDidRejectIncomingCall = ( - // String uuid, - // String callerId, - // ) async { - // try { - // print('🎈 example: onDidRejectIncomingCall $uuid - $callerId'); - // timeOutTimer.cancel(); - // } catch (err) {} - // }; - // - // voIPKit.onDidAcceptIncomingCall = ( - // String uuid, - // String callerId, - // ) async { - // print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId'); - // await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); - // await voIPKit.callConnected(); - // await Future.delayed(Duration(seconds: 1)); - // - // Navigator.pushNamed( - // locator().navigatorKey.currentContext!, - // "zoom_call_page", - // arguments: CallArguments("hoover-dam", "123", "Patient", "40", "1", false), - // ); - // - // await voIPKit.endCall(); - // - // // Navigator.pushNamed(navigatorKey.currentContext!, VIDEO_CALL_SCREEN, - // // arguments: VideoArgus( - // // reservationId: int.parse(callerId), token: null, isVideo: true)); - // - // timeOutTimer.cancel(); - // }; + voIPKit.getVoIPToken().then((value) { + print("🎈 APNS VOIP KIT TOKEN: $value"); + Utils.saveStringFromPrefs(CacheConst.voipToken, value ?? ""); + // AppSharedPreferences().setString(APNS_TOKEN, value!); + }); + + voIPKit.onDidUpdatePushToken = (String token) { + print('🎈 example: onDidUpdatePushToken: $token'); + }; + + voIPKit.onDidReceiveIncomingPush = ( + Map payload, + ) async { + print('🎈 example: onDidReceiveIncomingPush $payload'); + // _timeOut(); + }; + + voIPKit.onDidRejectIncomingCall = ( + String uuid, + String callerId, + ) async { + try { + print('🎈 example: onDidRejectIncomingCall $uuid - $callerId'); + timeOutTimer.cancel(); + } catch (err) {} + }; + + voIPKit.onDidAcceptIncomingCall = ( + String uuid, + String callerId, + ) async { + print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId'); + await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); + await voIPKit.callConnected(); + await Future.delayed(Duration(seconds: 1)); + + // Navigator.pushNamed( + // locator().navigatorKey.currentContext!, + // "zoom_call_page", + // arguments: CallArguments("hoover-dam", "123", "Patient", "40", "1", false), + // ); + + await voIPKit.endCall(); + + // Navigator.pushNamed(navigatorKey.currentContext!, VIDEO_CALL_SCREEN, + // arguments: VideoArgus( + // reservationId: int.parse(callerId), token: null, isVideo: true)); + + // timeOutTimer.cancel(); + }; } if (Platform.isAndroid) { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index efa01d0..8830b78 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -658,7 +658,6 @@ class Utils { static Widget getPaymentAmountWithSymbol(Widget paymentAmountWidget, Color iconColor, double iconSize, {bool isSaudiCurrency = true, bool isExpanded = true}) { return Row( mainAxisAlignment: isExpanded ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.end, children: [ appState.isArabic() ? Container() diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 18b5330..0a137ce 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -9,6 +11,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -69,6 +72,13 @@ abstract class BookAppointmentsRepo { Function(String)? onError}); Future>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, + {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> addNewCallRequestForImmediateLiveCare( + int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, + {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -703,4 +713,101 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "Age": age, + "Gender": genderID, + "ServiceID": serviceID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_ER_APPOINTMENT_FEES, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final respObject = response['GetERAppointmentFeesList']; + + final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: liveCareFeesObj, + ); + } 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> addNewCallRequestForImmediateLiveCare( + int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, + {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async { + Map mapDevice = { + "IsPharmacy": isPharma, + "ErServiceID": serviceID, + "ClientRequestID": clientRequestID, + "DeviceToken": deviceToken, + "VoipToken": voipToken, + "IsFlutter": true, + "DeviceType": Platform.isIOS ? 'iOS' : 'Android', + "Age": age, + "Gender": gender, + "IsVoip": Platform.isIOS ? true : false, + "CallTypeID": callTypeID + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ADD_NEW_CALL_FOR_PATIENT_ER, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final respObject = response['GetERAppointmentFeesList']; + + // final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); + + 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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index ab4ca56..120aa3b 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.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'; @@ -27,8 +28,6 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_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'; -import 'package:location/location.dart' show Location; import 'models/resp_models/get_livecare_clinics_response_model.dart'; @@ -64,7 +63,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { //Immediate LiveCare List immediateLiveCareClinicsList = []; bool isImmediateLiveCareClinicsLoading = false; - int liveCareSelectedCallType = 1; + int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone + late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic; + late LiveCareImmediateAppointmentFeesList liveCareImmediateAppointmentFeesList; late DoctorsProfileResponseModel doctorsProfileResponseModel; @@ -125,6 +126,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { immediateLiveCareClinicsList.clear(); isImmediateLiveCareClinicsLoading = true; + liveCareSelectedCallType = 0; // getLocation(); notifyListeners(); } @@ -191,12 +193,16 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - setLiveCareSelectedCallType(int value) { liveCareSelectedCallType = value; notifyListeners(); } + setImmediateLiveCareSelectedClinic(GetLiveCareClinicListResponseModel clinic) { + immediateLiveCareSelectedClinic = clinic; + notifyListeners(); + } + /// this function will decide which clinic api to be called /// either api for region flow or the select clinic api Future getClinics() async { @@ -798,4 +804,49 @@ class BookAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = + await bookAppointmentsRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + liveCareImmediateAppointmentFeesList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future addNewCallRequestForImmediateLiveCare(String transID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await bookAppointmentsRepo.addNewCallRequestForImmediateLiveCare(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, + immediateLiveCareSelectedClinic.serviceID!, transID, liveCareSelectedCallType, false, _appState.deviceToken, await Utils.getStringFromPrefs(CacheConst.voipToken)); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart b/lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart new file mode 100644 index 0000000..9a5861e --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart @@ -0,0 +1,37 @@ +class LiveCareImmediateAppointmentFeesList { + String? amount; + String? companyName; + bool? isInsured; + bool? isShowInsuranceUpdateModule; + bool? isCash; + bool? isEligible; + String? tax; + String? total; + String? currency; + + LiveCareImmediateAppointmentFeesList({this.amount, this.companyName, this.isInsured, this.isShowInsuranceUpdateModule, this.tax, this.total, this.currency}); + + LiveCareImmediateAppointmentFeesList.fromJson(Map json) { + amount = json['Amount']; + companyName = json['CompanyName']; + isInsured = json['IsInsured']; + isCash = json['IsCash']; + isEligible = json['IsEligible']; + isShowInsuranceUpdateModule = json['IsShowInsuranceUpdateModule']; + tax = json['Tax']; + total = json['Total']; + currency = json['currency']; + } + + Map toJson() { + final Map data = new Map(); + data['Amount'] = this.amount; + data['CompanyName'] = this.companyName; + data['IsInsured'] = this.isInsured; + data['IsShowInsuranceUpdateModule'] = this.isShowInsuranceUpdateModule; + data['Tax'] = this.tax; + data['Total'] = this.total; + data['currency'] = this.currency; + return data; + } +} diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 6976188..54353a4 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -419,7 +419,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Total amount to pay".needTranslation.toText18(isBold: true), + "Amount before tax".needTranslation.toText18(isBold: true), Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 9d6b557..7334369 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -257,7 +257,7 @@ class _AppointmentPaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Total amount to pay".needTranslation.toText14(isBold: true), + "Amount before tax".needTranslation.toText14(isBold: true), Utils.getPaymentAmountWithSymbol(myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], @@ -293,7 +293,7 @@ class _AppointmentPaymentPageState extends State { if (Utils.havePrivilege(103)) { startApplePay(); } else { - openPaymentURL(selectedPaymentMethod); + openPaymentURL("ApplePay"); } }) : SizedBox(height: 12.h), diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart new file mode 100644 index 0000000..76f6ad1 --- /dev/null +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -0,0 +1,301 @@ +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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_corner/smooth_corner.dart'; + +class ImmediateLiveCarePaymentDetails extends StatelessWidget { + ImmediateLiveCarePaymentDetails({super.key}); + + late BookAppointmentsViewModel bookAppointmentsViewModel; + late AppState appState; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Review LiveCare Request".needTranslation, + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + width: 52.h, + height: 52.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + SizedBox(height: 8.h), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + ], + ), + ], + ), + ), + ), + SizedBox(height: 24.h), + "Clinic Information".needTranslation.toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (appState.isArabic() ? bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceNameN : bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceName)! + .toText16(isBold: true), + // SizedBox(height: 8.h), + // AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + ], + ), + ], + ), + ), + ), + SizedBox(height: 24.h), + "Selected LiveCare Type".needTranslation.toText16(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, bookAppointmentsVM, child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), + SizedBox(width: 8.h), + getLiveCareType(bookAppointmentsViewModel.liveCareSelectedCallType).toText16(isBold: true), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), + ], + ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(bookAppointmentsViewModel: bookAppointmentsViewModel), callBackFunc: () async { + debugPrint("Selected Call Type: ${bookAppointmentsViewModel.liveCareSelectedCallType}"); + }, title: "Select LiveCare call type".needTranslation, isCloseButtonVisible: true, isFullScreen: false); + }); + }), + SizedBox(height: 24.h) + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.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(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 150.h, child: Utils.getPaymentMethods()), + Utils.getPaymentAmountWithSymbol(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + ], + ).paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.payNow.tr(context: context), + onPressed: () async { + await askVideoCallPermission().then((val) { + if (val) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePaymentPage(), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: + "LiveCare requires Camera, Microphone & Location permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ], + ), + ), + ], + ), + ); + } + + Future askVideoCallPermission() async { + Map statuses = await [ + Permission.camera, + Permission.microphone, + ].request(); + + if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) { + // Camera permission granted + return true; + } else { + return false; + } + + // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { + // return false; + // } + } + + String getLiveCareType(int callType) { + switch (callType) { + case 1: + return "Video Call".needTranslation; + case 2: + return "Audio Call".needTranslation; + case 3: + return "Phone Call".needTranslation; + default: + return "Video Call".needTranslation; + } + } +} diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart new file mode 100644 index 0000000..d4a108c --- /dev/null +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -0,0 +1,510 @@ +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/api_consts.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/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/date_util.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; +import 'package:hmg_patient_app_new/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/features/payfort/payfort_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/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +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:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_corner/smooth_corner.dart'; + +class ImmediateLiveCarePaymentPage extends StatefulWidget { + ImmediateLiveCarePaymentPage({super.key}); + + @override + State createState() => _ImmediateLiveCarePaymentPageState(); +} + +class _ImmediateLiveCarePaymentPageState extends State { + late PayfortViewModel payfortViewModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + late MyAppointmentsViewModel myAppointmentsViewModel; + late AppState appState; + + MyInAppBrowser? browser; + String selectedPaymentMethod = ""; + + String transID = ""; + + @override + void initState() { + scheduleMicrotask(() { + payfortViewModel.initPayfortViewModel(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + myAppointmentsViewModel = Provider.of(context); + bookAppointmentsViewModel = Provider.of(context, listen: false); + payfortViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Appointment Payment".needTranslation, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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() ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + 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), + 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() ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + 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), + 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() ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + 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"); + }), + ], + ), + ), + ), + ), + 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: [ + (bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.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(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.amount!.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( + bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.tax!.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(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.total!.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), + ], + ); + }), + ), + ], + ); + }), + ); + } + + 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 { + print("onBrowserExit Called!!!!"); + if (selectedPaymentMethod == "TAMARA") { + // checkTamaraPaymentStatus(transID!, appo); + // if (tamaraPaymentStatus != null && tamaraPaymentStatus.toLowerCase() == "approved") { + // updateTamaraRequestStatus("success", "14", Utils.getAppointmentTransID(appo.projectID, appo.clinicID, appo.appointmentNo), tamaraOrderID, num.parse(selectedInstallments), appo); + // } else { + // updateTamaraRequestStatus("Failed", "00", Utils.getAppointmentTransID(appo.projectID, appo.clinicID, appo.appointmentNo), tamaraOrderID, num.parse(selectedInstallments), appo); + // } + } else { + checkPaymentStatus(); + // checkPaymentStatus(appo); + } + } + + void checkPaymentStatus() async { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + debugPrint(apiResponse.data.toString()); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + await bookAppointmentsViewModel.addNewCallRequestForImmediateLiveCare(transID); + LoaderBottomSheet.hideLoader(); + + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getSuccessWidget(loadingText: "Payment Successful!".needTranslation), + // callBackFunc: () { + // Navigator.of(context).pop(); + // Navigator.of(context).pop(); + // }, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } + + openPaymentURL(String paymentMethod) { + browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); + transID = Utils.getAppointmentTransID( + bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceID!, + ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, + DateTime.now().millisecondsSinceEpoch, + ); + + //TODO: Need to pass dynamic params to the payment request instead of static values + browser?.openPaymentBrowser( + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!, + "LiveCare Payment", + transID, + "12", + "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, + "4", + bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceID.toString(), + context, + myAppointmentsViewModel.patientAppointmentShareResponseModel!.appointmentDate, + myAppointmentsViewModel.patientAppointmentShareResponseModel!.appointmentNo, + myAppointmentsViewModel.patientAppointmentShareResponseModel!.clinicID, + myAppointmentsViewModel.patientAppointmentShareResponseModel!.doctorID, + "3"); + } + + startApplePay() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Apple Pay details, Please wait...".needTranslation); + transID = Utils.getAppointmentTransID( + bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceID!, + ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, + DateTime.now().millisecondsSinceEpoch, + ); + + ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); + + await payfortViewModel.getPayfortConfigurations( + serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, integrationId: 2); + + applePayInsertRequest.clientRequestID = transID; + applePayInsertRequest.clinicID = bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceID!; + + 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 = "12"; + applePayInsertRequest.serviceID = ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(); + applePayInsertRequest.channelID = 3; + applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType; + applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa; + applePayInsertRequest.appointmentDate = DateUtil.convertDateToString(DateTime.now()); + applePayInsertRequest.appointmentNo = 0; + applePayInsertRequest.orderDescription = "LiveCare Payment"; + applePayInsertRequest.liveServiceID = bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceID!.toString(); + applePayInsertRequest.latitude = "0.0"; + applePayInsertRequest.longitude = "0.0"; + applePayInsertRequest.amount = bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.total.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: "LiveCare Payment", + orderAmount: double.parse(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.total!), + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + // projectId: appo.projectID, + // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, + ); + }); + } +} diff --git a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart index 8ec6e4b..6910113 100644 --- a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart @@ -12,12 +12,15 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/livecare_clinic_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class SelectImmediateLiveCareClinicPage extends StatefulWidget { @@ -37,6 +40,7 @@ class _SelectImmediateLiveCareClinicPageState extends State { ), SizedBox(width: 8.h), Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), SizedBox(height: 8.h), diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index aa97fdf..41d2b15 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api_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/utils.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -11,7 +10,7 @@ class LoaderBottomSheet { static final NavigationService _navService = GetIt.I(); static bool _isVisible = false; - static void showLoader() { + static void showLoader({String? loadingText}) { if (_isVisible) return; _isVisible = true; @@ -29,7 +28,7 @@ class LoaderBottomSheet { borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: Center( - child: Utils.getLoadingWidget(), + child: Utils.getLoadingWidget(loadingText: loadingText), ), ); }, From 34184801220d7635e2e649fa46cd2e8382d16640 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 2 Oct 2025 14:05:07 +0300 Subject: [PATCH 7/9] Immediate LiveCare implementation completed --- assets/images/svg/forward_arrow_icon.svg | 4 +- .../images/svg/forward_arrow_icon_small.svg | 4 + assets/images/svg/waiting_icon.svg | 3 + ios/Runner.xcodeproj/project.pbxproj | 4 +- lib/core/app_assets.dart | 2 + lib/core/dependencies.dart | 12 + lib/core/utils/utils.dart | 2 +- .../book_appointments_repo.dart | 154 --------- .../book_appointments_view_model.dart | 95 ------ .../immediate_livecare_repo.dart | 206 ++++++++++++ .../immediate_livecare_view_model.dart | 162 ++++++++++ ...care_immediate_clinics_response_model.dart | 0 ...ivecare_immediate_fees_response_model.dart | 0 ...tient_livecare_history_response_model.dart | 84 +++++ lib/main.dart | 9 + .../widgets/appointment_card.dart | 19 +- .../book_appointment_page.dart | 42 ++- .../immediate_livecare_payment_details.dart | 29 +- .../immediate_livecare_payment_page.dart | 83 ++--- ...mediate_livecare_pending_request_page.dart | 303 ++++++++++++++++++ ...select_immediate_livecare_clinic_page.dart | 39 +-- .../widgets/livecare_clinic_card.dart | 9 +- .../widgets/select_livecare_call_type.dart | 11 +- .../book_appointment/widgets/clinic_card.dart | 2 +- lib/presentation/home/landing_page.dart | 70 +++- 25 files changed, 990 insertions(+), 358 deletions(-) create mode 100644 assets/images/svg/forward_arrow_icon_small.svg create mode 100644 assets/images/svg/waiting_icon.svg create mode 100644 lib/features/immediate_livecare/immediate_livecare_repo.dart create mode 100644 lib/features/immediate_livecare/immediate_livecare_view_model.dart rename lib/features/{book_appointments => immediate_livecare}/models/resp_models/get_livecare_immediate_clinics_response_model.dart (100%) rename lib/features/{book_appointments => immediate_livecare}/models/resp_models/get_livecare_immediate_fees_response_model.dart (100%) create mode 100644 lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart create mode 100644 lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart rename lib/presentation/book_appointment/{ => livecare}/widgets/livecare_clinic_card.dart (87%) diff --git a/assets/images/svg/forward_arrow_icon.svg b/assets/images/svg/forward_arrow_icon.svg index d8a3d51..e4fd254 100644 --- a/assets/images/svg/forward_arrow_icon.svg +++ b/assets/images/svg/forward_arrow_icon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/images/svg/forward_arrow_icon_small.svg b/assets/images/svg/forward_arrow_icon_small.svg new file mode 100644 index 0000000..c97ff8b --- /dev/null +++ b/assets/images/svg/forward_arrow_icon_small.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/waiting_icon.svg b/assets/images/svg/waiting_icon.svg new file mode 100644 index 0000000..40e8645 --- /dev/null +++ b/assets/images/svg/waiting_icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a982556..be64c53 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -130,6 +131,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */, 478CFA952E6E20A60064F3D7 /* Runner.entitlements */, 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, @@ -635,7 +637,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 016e42d..44f979c 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -147,6 +147,8 @@ class AppAssets { static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg'; static const String livecare_online_icon = '$svgBasePath/livecare_online_icon.svg'; static const String edit_icon = '$svgBasePath/edit_icon.svg'; + static const String waiting_icon = '$svgBasePath/waiting_icon.svg'; + static const String forward_arrow_icon_small = '$svgBasePath/forward_arrow_icon_small.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index b42706f..33fa0df 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -10,6 +10,8 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.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_repo.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; @@ -92,6 +94,7 @@ class AppDependencies { getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -164,6 +167,15 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => ImmediateLiveCareViewModel( + immediateLiveCareRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt(), + myAppointmentsViewModel: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 8830b78..f3a53b9 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -360,7 +360,7 @@ class Utils { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.warningAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 0a137ce..43a7eb3 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -10,8 +10,6 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -70,15 +68,6 @@ abstract class BookAppointmentsRepo { required int serviceID, Function(dynamic)? onSuccess, Function(String)? onError}); - - Future>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); - - Future>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, - {Function(dynamic)? onSuccess, Function(String)? onError}); - - Future>> addNewCallRequestForImmediateLiveCare( - int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, - {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -162,9 +151,6 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['DoctorList']; - // if (list == null || list.isEmpty) { - // throw Exception("lab list is empty"); - // } final doctorsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map)).toList().cast(); @@ -670,144 +656,4 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } - - @override - Future>>> getLiveCareImmediateClinicsList(int age, int genderID, - {Function(dynamic)? onSuccess, Function(String)? onError}) async { - Map mapDevice = { - "Age": age, - "Gender": genderID, - }; - - try { - GenericApiModel>? apiResponse; - Failure? failure; - await apiClient.post( - GET_LIVECARE_CLINICS, - body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; - onError!(error); - }, - onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - try { - final list = response['PatientER_GetClinicsList']; - - final clinicsList = list.map((item) => GetLiveCareClinicListResponseModel.fromJson(item as Map)).toList().cast(); - - apiResponse = GenericApiModel>( - messageStatus: messageStatus, - statusCode: statusCode, - errorMessage: null, - data: clinicsList, - ); - } 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>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, - {Function(dynamic)? onSuccess, Function(String)? onError}) async { - Map mapDevice = { - "Age": age, - "Gender": genderID, - "ServiceID": serviceID, - }; - - try { - GenericApiModel? apiResponse; - Failure? failure; - await apiClient.post( - GET_ER_APPOINTMENT_FEES, - body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; - onError!(error); - }, - onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - try { - final respObject = response['GetERAppointmentFeesList']; - - final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); - - apiResponse = GenericApiModel( - messageStatus: messageStatus, - statusCode: statusCode, - errorMessage: null, - data: liveCareFeesObj, - ); - } 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> addNewCallRequestForImmediateLiveCare( - int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, - {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async { - Map mapDevice = { - "IsPharmacy": isPharma, - "ErServiceID": serviceID, - "ClientRequestID": clientRequestID, - "DeviceToken": deviceToken, - "VoipToken": voipToken, - "IsFlutter": true, - "DeviceType": Platform.isIOS ? 'iOS' : 'Android', - "Age": age, - "Gender": gender, - "IsVoip": Platform.isIOS ? true : false, - "CallTypeID": callTypeID - }; - - try { - GenericApiModel? apiResponse; - Failure? failure; - await apiClient.post( - ADD_NEW_CALL_FOR_PATIENT_ER, - body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; - onError!(error); - }, - onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - try { - // final respObject = response['GetERAppointmentFeesList']; - - // final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); - - 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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 120aa3b..8a46f91 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -15,8 +15,6 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot. import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.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'; @@ -60,13 +58,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); GetLiveCareClinicsResponseModel selectedLiveCareClinic = GetLiveCareClinicsResponseModel(); - //Immediate LiveCare - List immediateLiveCareClinicsList = []; - bool isImmediateLiveCareClinicsLoading = false; - int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone - late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic; - late LiveCareImmediateAppointmentFeesList liveCareImmediateAppointmentFeesList; - late DoctorsProfileResponseModel doctorsProfileResponseModel; List slotsList = []; @@ -123,10 +114,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { clinicsList.clear(); doctorsList.clear(); liveCareClinicsList.clear(); - - immediateLiveCareClinicsList.clear(); - isImmediateLiveCareClinicsLoading = true; - liveCareSelectedCallType = 0; // getLocation(); notifyListeners(); } @@ -193,16 +180,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - setLiveCareSelectedCallType(int value) { - liveCareSelectedCallType = value; - notifyListeners(); - } - - setImmediateLiveCareSelectedClinic(GetLiveCareClinicListResponseModel clinic) { - immediateLiveCareSelectedClinic = clinic; - notifyListeners(); - } - /// this function will decide which clinic api to be called /// either api for region flow or the select clinic api Future getClinics() async { @@ -777,76 +754,4 @@ class BookAppointmentsViewModel extends ChangeNotifier { void getLocation() { locationUtils.getLocation(); } - - Future getLiveCareImmediateClinicsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { - immediateLiveCareClinicsList.clear(); - isImmediateLiveCareClinicsLoading = true; - notifyListeners(); - - final result = await bookAppointmentsRepo.getLiveCareImmediateClinicsList(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); - - 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) { - immediateLiveCareClinicsList = apiResponse.data!; - - immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); - - isImmediateLiveCareClinicsLoading = false; - notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); - } - } - }, - ); - } - - Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = - await bookAppointmentsRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!); - - result.fold( - (failure) async { - onError!(failure.message); - }, - (apiResponse) { - if (apiResponse.messageStatus == 2) { - onError!(apiResponse.errorMessage ?? "Unknown error occurred"); - // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); - } else if (apiResponse.messageStatus == 1) { - liveCareImmediateAppointmentFeesList = apiResponse.data!; - notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); - } - } - }, - ); - } - - Future addNewCallRequestForImmediateLiveCare(String transID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await bookAppointmentsRepo.addNewCallRequestForImmediateLiveCare(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, - immediateLiveCareSelectedClinic.serviceID!, transID, liveCareSelectedCallType, false, _appState.deviceToken, await Utils.getStringFromPrefs(CacheConst.voipToken)); - - result.fold( - (failure) async { - onError!(failure.message); - }, - (apiResponse) { - if (apiResponse.messageStatus == 2) { - onError!(apiResponse.errorMessage ?? "Unknown error occurred"); - // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); - } else if (apiResponse.messageStatus == 1) { - notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); - } - } - }, - ); - } } diff --git a/lib/features/immediate_livecare/immediate_livecare_repo.dart b/lib/features/immediate_livecare/immediate_livecare_repo.dart new file mode 100644 index 0000000..eee4adb --- /dev/null +++ b/lib/features/immediate_livecare/immediate_livecare_repo.dart @@ -0,0 +1,206 @@ +import 'dart:io'; + +import 'package:dartz/dartz.dart'; +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/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ImmediateLiveCareRepo { + Future>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, + {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> addNewCallRequestForImmediateLiveCare( + int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, + {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}); +} + +class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ImmediateLiveCareRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getLiveCareImmediateClinicsList(int age, int genderID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "Age": age, + "Gender": genderID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_CLINICS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['PatientER_GetClinicsList']; + + final clinicsList = list.map((item) => GetLiveCareClinicListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } 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>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "Age": age, + "Gender": genderID, + "ServiceID": serviceID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_ER_APPOINTMENT_FEES, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final respObject = response['GetERAppointmentFeesList']; + + final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: liveCareFeesObj, + ); + } 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> addNewCallRequestForImmediateLiveCare( + int age, int gender, int serviceID, String clientRequestID, int callTypeID, bool isPharma, String deviceToken, String voipToken, + {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async { + Map mapDevice = { + "IsPharmacy": isPharma, + "ErServiceID": serviceID, + "ClientRequestID": clientRequestID, + "DeviceToken": deviceToken, + "VoipToken": voipToken, + "IsFlutter": true, + "DeviceType": Platform.isIOS ? 'iOS' : 'Android', + "Age": age, + "Gender": gender, + "IsVoip": Platform.isIOS ? true : false, + "CallTypeID": callTypeID + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ADD_NEW_CALL_FOR_PATIENT_ER, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: 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())); + } + } + + @override + Future>>> getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_HISTORY, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ErRequestHistoryList']; + + final liveCareHistoryList = list.map((item) => PatientLiveCareHistory.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: liveCareHistoryList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart new file mode 100644 index 0000000..599bc39 --- /dev/null +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -0,0 +1,162 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +import '../../services/navigation_service.dart'; + +class ImmediateLiveCareViewModel extends ChangeNotifier { + ImmediateLiveCareViewModel({ + required this.immediateLiveCareRepo, + required this.errorHandlerService, + required this.navigationService, + required this.myAppointmentsViewModel, + }); + + ImmediateLiveCareRepo immediateLiveCareRepo; + ErrorHandlerService errorHandlerService; + final NavigationService navigationService; + MyAppointmentsViewModel myAppointmentsViewModel; + + List immediateLiveCareClinicsList = []; + bool isImmediateLiveCareClinicsLoading = false; + int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone + late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic; + late LiveCareImmediateAppointmentFeesList liveCareImmediateAppointmentFeesList; + + List patientLiveCareHistoryList = []; + bool patientHasPendingLiveCareRequest = false; + + late AppState _appState; + + initImmediateLiveCare() { + _appState = getIt(); + immediateLiveCareClinicsList = []; + patientLiveCareHistoryList = []; + isImmediateLiveCareClinicsLoading = true; + patientHasPendingLiveCareRequest = false; + liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone + immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel(); + liveCareImmediateAppointmentFeesList = LiveCareImmediateAppointmentFeesList(); + } + + setLiveCareSelectedCallType(int value) { + liveCareSelectedCallType = value; + notifyListeners(); + } + + setImmediateLiveCareSelectedClinic(GetLiveCareClinicListResponseModel clinic) { + immediateLiveCareSelectedClinic = clinic; + notifyListeners(); + } + + Future getLiveCareImmediateClinicsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + immediateLiveCareClinicsList.clear(); + isImmediateLiveCareClinicsLoading = true; + notifyListeners(); + + final result = await immediateLiveCareRepo.getLiveCareImmediateClinicsList(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); + + 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) { + immediateLiveCareClinicsList = apiResponse.data!; + + immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); + + isImmediateLiveCareClinicsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = + await immediateLiveCareRepo.getLiveCareImmediateAppointmentFees(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, immediateLiveCareSelectedClinic.serviceID!); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + liveCareImmediateAppointmentFeesList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future addNewCallRequestForImmediateLiveCare(String transID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await immediateLiveCareRepo.addNewCallRequestForImmediateLiveCare(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, + immediateLiveCareSelectedClinic.serviceID!, transID, liveCareSelectedCallType, false, _appState.deviceToken, await Utils.getStringFromPrefs(CacheConst.voipToken)); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getPatientLiveCareHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await immediateLiveCareRepo.getPatientLiveCareHistory(); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientLiveCareHistoryList = apiResponse.data!; + if (patientLiveCareHistoryList.isNotEmpty) { + if (patientLiveCareHistoryList[0].callStatus! < 4) { + patientHasPendingLiveCareRequest = true; + } else { + patientHasPendingLiveCareRequest = false; + } + } else { + patientHasPendingLiveCareRequest = false; + } + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart b/lib/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart similarity index 100% rename from lib/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart rename to lib/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart diff --git a/lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart b/lib/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart similarity index 100% rename from lib/features/book_appointments/models/resp_models/get_livecare_immediate_fees_response_model.dart rename to lib/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart diff --git a/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart b/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart new file mode 100644 index 0000000..0077d83 --- /dev/null +++ b/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart @@ -0,0 +1,84 @@ +class PatientLiveCareHistory { + String? appointmentNo; + String? arrivalTime; + num? callDuration; + int? callStatus; + String? clientRequestID; + String? doctorID; + String? doctorName; + String? doctorNameN; + String? doctorTitle; + String? exWaitingTime; + bool? isAppointmentHaveRating; + int? patCount; + int? projectID; + String? sArrivalTime; + int? serviceID; + String? stringCallStatus; + int? vCID; + int? watingtimeInteger; + + PatientLiveCareHistory( + {this.appointmentNo, + this.arrivalTime, + this.callDuration, + this.callStatus, + this.clientRequestID, + this.doctorID, + this.doctorName, + this.doctorNameN, + this.doctorTitle, + this.exWaitingTime, + this.isAppointmentHaveRating, + this.patCount, + this.projectID, + this.sArrivalTime, + this.serviceID, + this.stringCallStatus, + this.vCID, + this.watingtimeInteger}); + + PatientLiveCareHistory.fromJson(Map json) { + appointmentNo = json['AppointmentNo']; + arrivalTime = json['ArrivalTime']; + callDuration = json['CallDuration']; + callStatus = json['CallStatus']; + clientRequestID = json['ClientRequestID']; + doctorID = json['DoctorID']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + doctorTitle = json['DoctorTitle']; + exWaitingTime = json['Ex_WaitingTime']; + isAppointmentHaveRating = json['IsAppointmentHaveRating']; + patCount = json['Pat_Count']; + projectID = json['ProjectID']; + sArrivalTime = json['SArrivalTime']; + serviceID = json['ServiceID']; + stringCallStatus = json['StringCallStatus']; + vCID = json['VC_ID']; + watingtimeInteger = json['WatingtimeInteger']; + } + + Map toJson() { + final Map data = new Map(); + data['AppointmentNo'] = this.appointmentNo; + data['ArrivalTime'] = this.arrivalTime; + data['CallDuration'] = this.callDuration; + data['CallStatus'] = this.callStatus; + data['ClientRequestID'] = this.clientRequestID; + data['DoctorID'] = this.doctorID; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['DoctorTitle'] = this.doctorTitle; + data['Ex_WaitingTime'] = this.exWaitingTime; + data['IsAppointmentHaveRating'] = this.isAppointmentHaveRating; + data['Pat_Count'] = this.patCount; + data['ProjectID'] = this.projectID; + data['SArrivalTime'] = this.sArrivalTime; + data['ServiceID'] = this.serviceID; + data['StringCallStatus'] = this.stringCallStatus; + data['VC_ID'] = this.vCID; + data['WatingtimeInteger'] = this.watingtimeInteger; + return data; + } +} diff --git a/lib/main.dart b/lib/main.dart index 3690c72..eff60cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.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/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'; import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; @@ -137,6 +138,14 @@ void main() async { locationUtils: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => ImmediateLiveCareViewModel( + immediateLiveCareRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt(), + myAppointmentsViewModel: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 6d157a5..3a65124 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -243,17 +243,14 @@ class _AppointmentCardState extends State { color: AppColors.textColor, borderRadius: 10.h, ), - child: Padding( - padding: EdgeInsets.all(10.h), - child: Transform.flip( - flipX: appState.isArabic() ? true : false, - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, - ), + child: Transform.flip( + flipX: appState.isArabic() ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.whiteColor, + width: 40.h, + height: 40.h, + fit: BoxFit.cover, ), ), ).toShimmer2(isShow: widget.isLoading).onPress(() { diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 012a8db..516aff9 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -10,11 +10,13 @@ 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/immediate_livecare/immediate_livecare_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/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' show RegionBottomSheetBody; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; @@ -22,6 +24,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -39,6 +42,7 @@ class _BookAppointmentPageState extends State { late AppState appState; late AppointmentViaRegionViewmodel regionalViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; @override void initState() { @@ -46,6 +50,7 @@ class _BookAppointmentPageState extends State { bookAppointmentsViewModel.selectedTabIndex = 0; bookAppointmentsViewModel.initBookAppointmentViewModel(); bookAppointmentsViewModel.getLocation(); + immediateLiveCareViewModel.initImmediateLiveCare(); }); super.initState(); } @@ -53,6 +58,7 @@ class _BookAppointmentPageState extends State { @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); regionalViewModel = Provider.of(context, listen: true); return Scaffold( @@ -120,7 +126,7 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); @@ -153,7 +159,7 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false); @@ -184,7 +190,7 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setProjectID(null); @@ -228,16 +234,28 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], - ).onPress(() { + ).onPress(() async { //TODO Implement API to check for existing LiveCare Requests - Navigator.of(context).push( - CustomPageRoute( - page: SelectImmediateLiveCareClinicPage(), - ), - ); + LoaderBottomSheet.showLoader(); + await immediateLiveCareViewModel.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); + + if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + Navigator.of(context).push( + CustomPageRoute( + page: SelectImmediateLiveCareClinicPage(), + ), + ); + } }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), @@ -259,7 +277,7 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); @@ -290,7 +308,7 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 76f6ad1..af7d053 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -10,6 +10,7 @@ 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/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; @@ -27,12 +28,12 @@ import 'package:smooth_corner/smooth_corner.dart'; class ImmediateLiveCarePaymentDetails extends StatelessWidget { ImmediateLiveCarePaymentDetails({super.key}); - late BookAppointmentsViewModel bookAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; late AppState appState; @override Widget build(BuildContext context) { - bookAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, @@ -95,7 +96,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (appState.isArabic() ? bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceNameN : bookAppointmentsViewModel.immediateLiveCareSelectedClinic.serviceName)! + (appState.isArabic() + ? immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceNameN + : immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceName)! .toText16(isBold: true), // SizedBox(height: 8.h), // AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), @@ -124,7 +127,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), SizedBox(width: 8.h), - getLiveCareType(bookAppointmentsViewModel.liveCareSelectedCallType).toText16(isBold: true), + getLiveCareType(immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true), ], ), Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), @@ -132,8 +135,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ), ), ).onPress(() { - showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(bookAppointmentsViewModel: bookAppointmentsViewModel), callBackFunc: () async { - debugPrint("Selected Call Type: ${bookAppointmentsViewModel.liveCareSelectedCallType}"); + showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async { + debugPrint("Selected Call Type: ${immediateLiveCareViewModel.liveCareSelectedCallType}"); }, title: "Select LiveCare call type".needTranslation, isCloseButtonVisible: true, isFullScreen: false); }); }), @@ -152,7 +155,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true) + (immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true) ? Container( height: 50.h, decoration: ShapeDecoration( @@ -195,8 +198,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "Amount before tax".needTranslation.toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, - isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), ], ).paddingSymmetrical(24.h, 0.h), Row( @@ -204,8 +207,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( - bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, - isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), @@ -213,8 +216,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox(width: 150.h, child: Utils.getPaymentMethods()), - Utils.getPaymentAmountWithSymbol(bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, - isSaudiCurrency: bookAppointmentsViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), ], ).paddingSymmetrical(24.h, 0.h), CustomButton( diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index d4a108c..d89ef33 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -12,6 +12,7 @@ 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/date_util.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -22,6 +23,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie 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/appointments/my_appointments_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/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -45,7 +47,7 @@ class ImmediateLiveCarePaymentPage extends StatefulWidget { class _ImmediateLiveCarePaymentPageState extends State { late PayfortViewModel payfortViewModel; - late BookAppointmentsViewModel bookAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; late MyAppointmentsViewModel myAppointmentsViewModel; late AppState appState; @@ -66,7 +68,7 @@ class _ImmediateLiveCarePaymentPageState extends State(); myAppointmentsViewModel = Provider.of(context); - bookAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); payfortViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -75,7 +77,7 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred...".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } else { showCommonBottomSheetWithoutHeight( context, @@ -388,14 +401,14 @@ class _ImmediateLiveCarePaymentPageState extends State createState() => _ImmediateLiveCarePendingRequestPageState(); +} + +class _ImmediateLiveCarePendingRequestPageState extends State { + late ImmediateLiveCareViewModel immediateLiveCareViewModel; + + late AppState appState; + + static Duration countdownDuration = Duration(minutes: 1, seconds: 0); + ValueNotifier durationNotifier = ValueNotifier(countdownDuration); + Timer? timer; + + @override + void initState() { + super.initState(); + scheduleMicrotask(() { + countdownDuration = Duration(minutes: immediateLiveCareViewModel.patientLiveCareHistoryList[0].watingtimeInteger!, seconds: 0); + durationNotifier = ValueNotifier(countdownDuration); + startTimer(); + }); + } + + @override + void dispose() { + timer?.cancel(); + durationNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + immediateLiveCareViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer(builder: (context, immediateLiveCareVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "LiveCare Pending Request".needTranslation, + child: Padding( + padding: EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Expected waiting time: ".toText16(isBold: true), + SizedBox(height: 8.h), + ValueListenableBuilder( + valueListenable: durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), + SizedBox(height: 8.h), + ], + ), + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + ], + ), + SizedBox(height: 8.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)), + SizedBox(height: 8.h), + "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".toText16(isBold: true), + SizedBox(height: 8.h), + ], + ), + ), + ) + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: "Call LiveCare Support".needTranslation, + onPressed: () async { + launchUrl(Uri.parse("tel://" + "011 525 9553")); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.call_fill, + iconColor: AppColors.whiteColor, + iconSize: 21.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ); + }), + ); + } + + void startTimer() { + timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime()); + setState(() {}); + } + + void addTime() { + final seconds = durationNotifier.value.inSeconds - 1; + if (seconds < 0) { + timer?.cancel(); + // Handle end of timer here + // showEndMessage(); + } else { + durationNotifier.value = Duration(seconds: seconds); + } + } + + Future _onWillPop() async { + timer?.cancel(); + Navigator.of(context).pop(); + return true; + } + + Widget buildTime(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, '0'); + final hours = twoDigits(duration.inHours); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTimeColumn(hours, "Hours".needTranslation), + buildTimeColumn(minutes, "Mins".needTranslation), + buildTimeColumn(seconds, "Secs".needTranslation, isLast: true), + ], + ); + } + + Widget buildTimeColumn(String time, String label, {bool isLast = false}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + buildDigit(time[0]), + buildDigit(time[1]), + if (!isLast) buildTimeSeparator(), + ], + ), + buildLabel(label), + ], + ); + } + + Widget buildDigit(String digit) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + // margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: ClipRect( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 600), + switchInCurve: Curves.easeOutExpo, + switchOutCurve: Curves.easeInExpo, + transitionBuilder: (Widget child, Animation animation) { + return Stack( + children: [ + SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: const Offset(0, 1), + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + )), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: const Offset(0, 0), + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.bounceIn, + )), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + ], + ); + }, + child: Text( + digit, + key: ValueKey(digit), + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + fontSize: 20.fSize, + ), + ), + ), + ), + ); + } + + Widget buildLabel(String label) { + return label.toText14(isBold: true); + } + + Widget buildTimeSeparator() { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 2.0), + child: Text( + ":", + style: TextStyle( + color: Colors.black, + fontSize: 20, + ), + ), + ); + } +} diff --git a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart index 6910113..8181d25 100644 --- a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart @@ -11,11 +11,12 @@ 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/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; -import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/livecare_clinic_card.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/livecare_clinic_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -34,13 +35,13 @@ class _SelectImmediateLiveCareClinicPageState extends State(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -62,7 +63,7 @@ class _SelectImmediateLiveCareClinicPageState extends State(builder: (context, bookAppointmentsVM, child) { + child: Consumer(builder: (context, immediateLiveCareVM, child) { return Column( children: [ // SizedBox(height: 16.h), @@ -98,14 +99,14 @@ class _SelectImmediateLiveCareClinicPageState extends State(), liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isImmediateLiveCareClinicsLoading, + isLoading: immediateLiveCareVM.isImmediateLiveCareClinicsLoading, ) : AnimationConfiguration.staggeredList( position: index, @@ -118,11 +119,11 @@ class _SelectImmediateLiveCareClinicPageState extends State { final CacheService cacheService = GetIt.instance(); late InsuranceViewModel insuranceViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; final SwiperController _controller = SwiperController(); @@ -85,6 +89,8 @@ class _LandingPageState extends State { myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); insuranceViewModel.initInsuranceProvider(); + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); } }); super.initState(); @@ -97,6 +103,7 @@ class _LandingPageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); insuranceViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( @@ -124,11 +131,11 @@ class _LandingPageState extends State { backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), - fontSize: 16, + fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, + height: 40, ), Row( mainAxisSize: MainAxisSize.min, @@ -274,7 +281,64 @@ class _LandingPageState extends State { ), ).paddingSymmetrical(24.h, 0.h); }), - SizedBox(height: 12.h), + Consumer(builder: (context, immediateLiveCareVM, child) { + return immediateLiveCareVM.patientHasPendingLiveCareRequest + ? Column( + children: [ + SizedBox(height: 12.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, 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: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "You have a pending LiveCare 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: ImmediateLiveCarePendingRequestPage(), + ), + ); + }), + SizedBox(height: 12.h), + ], + ) + : SizedBox.shrink(); + }), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ From 8a7f46441c11cefbb93eb4b6d84322339f5ddc01 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 2 Oct 2025 15:31:18 +0300 Subject: [PATCH 8/9] updates --- assets/animations/lottie/pending_loading_animation.json | 1 + lib/core/app_assets.dart | 1 + lib/presentation/home/landing_page.dart | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 assets/animations/lottie/pending_loading_animation.json diff --git a/assets/animations/lottie/pending_loading_animation.json b/assets/animations/lottie/pending_loading_animation.json new file mode 100644 index 0000000..177ef65 --- /dev/null +++ b/assets/animations/lottie/pending_loading_animation.json @@ -0,0 +1 @@ +{"nm":"loader","ddd":0,"h":720,"w":720,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"bubble","sr":1,"st":30,"op":50,"ip":27,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-23.672,-43.28,0],"ix":1},"s":{"a":0,"k":[150,150,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[3.698,50.595,0],"t":27,"ti":[-3.16098022460938,1.98855590820312,0],"to":[4.02357578277588,0.35728123784065,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.833,"y":0.833},"s":[27.839,52.739,0],"t":35,"ti":[12.7697801589966,16.6396007537842,0],"to":[3.16098022460938,-1.98855590820312,0]},{"s":[11.589,25.489,0],"t":50}],"ix":2},"r":{"a":0,"k":-155.955,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Rectangle 1","ix":1,"cix":2,"np":3,"it":[{"ty":"rc","bm":0,"hd":false,"mn":"ADBE Vector Shape - Rect","nm":"Rectangle Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"s":{"a":0,"k":[9.485,9.485],"ix":2}},{"ty":"st","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.6863,0.0824],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-23.672,-43.28],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1,"parent":6},{"ty":4,"nm":"timeline","sr":1,"st":35,"op":60,"ip":35,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[144,144,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[360,347.04,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.25,1],[1.25,0.5]]}],"t":35},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.236,3.52],[1,56.5]]}],"t":52},{"s":[{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.07,46.291],[1,56.75]]}],"t":59}],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"c":{"a":0,"k":[1,0.6863,0.0824],"ix":3}},{"ty":"fl","bm":0,"hd":true,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":2,"cix":2,"np":1,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.07,44.041],[1,54.25]]},"ix":2}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"top","sr":1,"st":0,"op":60,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-0.237,-75.448,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"top","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-74.495,-9.31],[74.495,-9.31],[74.495,9.31],[-74.495,9.31]]},"ix":2}},{"ty":"rd","bm":0,"hd":false,"mn":"ADBE Vector Filter - RC","nm":"Round Corners 1","ix":2,"r":{"a":0,"k":9.31,"ix":1}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3,"parent":6},{"ty":4,"nm":"time-down","sr":1,"st":7,"op":60,"ip":53,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,-100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-1.53,-33,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"time 2","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[{"c":true,"i":[[-6.69,0],[0,0],[-0.111,5.244],[1.215,8.14],[7.97,0.125],[2.32,-2.28],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[-0.5,-6.85],[-0.598,-4.008],[-7.97,-0.125],[0,0],[0,0],[0,6.71]],"v":[[-0.89,25.5],[3.223,25.5],[6.75,20.35],[5.815,7.985],[-0.355,9],[-3.245,12.395],[-5,15.93],[-6,20.35]]}],"t":53},{"s":[{"c":true,"i":[[-6.69,0],[0,0],[-0.111,5.244],[9.57,9.95],[7.97,0.125],[2.32,-2.28],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[-0.5,-6.85],[-2.81,-2.92],[-7.97,-0.125],[0,0],[0,0],[0,6.71]],"v":[[-18.39,25.5],[20.723,25.5],[30.5,13.35],[16.39,-6.14],[-0.28,-14.5],[-16.795,-5.98],[-30.5,13.18],[-30.5,13.35]]}],"t":59}],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.6863,0.0824],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"time","ix":2,"cix":2,"np":1,"it":[{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.6863,0.0824],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4,"parent":6},{"ty":4,"nm":"time-up","sr":1,"st":0,"op":53,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-1.53,32.5,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"time 2","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[-6.69,0],[0,0],[-0.125,5.9],[9.57,9.95],[0,0],[2.32,-2.28],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[0.125,-5.6],[-2.81,-2.92],[-5.98,0],[0,0],[0,0],[0,6.71]],"v":[[-18.39,25.5],[21.015,25.5],[30.5,13.35],[10.41,-21.82],[-0.19,-25.5],[-10.65,-21.99],[-30.5,13.18],[-30.5,13.35]]}],"t":-5},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[-6.69,0],[0,0],[-0.111,5.244],[9.57,9.95],[7.97,0.125],[2.32,-2.28],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[-0.5,-6.85],[-2.81,-2.92],[-7.97,-0.125],[0,0],[0,0],[0,6.71]],"v":[[-18.39,25.5],[20.723,25.5],[30.5,13.35],[16.39,-6.14],[-0.28,-14.5],[-16.795,-5.98],[-30.5,13.18],[-30.5,13.35]]}],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":1},"s":[{"c":true,"i":[[-6.69,0],[0,0],[2.905,4.906],[7.706,11.409],[7.334,-3.123],[3.795,-7.031],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[-3.838,-8.41],[-6.823,-6.658],[-5.759,2.452],[0,0],[0,0],[0,6.71]],"v":[[-18.39,25.5],[16.753,25.987],[26.53,13.837],[12.972,-9.051],[-7.23,-22.451],[-20.652,-6.377],[-30.5,13.18],[-30.5,13.35]]}],"t":20},{"o":{"x":0.167,"y":0},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[-6.69,0],[0,0],[2.905,4.906],[7.706,11.409],[7.742,-1.897],[3.795,-7.031],[0.22,-5.32],[0,0]],"o":[[0,0],[6.59,0],[-3.838,-8.41],[-6.823,-6.658],[-8.432,2.066],[0,0],[0,0],[0,6.71]],"v":[[-15.709,24.889],[20.723,25.5],[30.5,13.35],[18.526,-10.751],[-0.661,-21.798],[-17.97,-6.988],[-27.819,12.569],[-27.819,12.739]]}],"t":25},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[-8.78,-0.135],[-6.861,2.709],[-3.043,7.511],[6.829,11.691],[9.846,-0.973],[3.076,-4.716],[0.22,-5.32],[2.646,-6.27]],"o":[[6.522,0.135],[6.397,-2.699],[2.728,-8.693],[-4.868,-4.837],[-10.263,1.059],[0,0],[0,0],[-2.646,6.27]],"v":[[-26.717,26.233],[2.736,24.462],[21.011,11.794],[17.963,-13.07],[-0.928,-23.139],[-18.387,-11.491],[-29.14,7.995],[-29.14,8.165]]}],"t":35},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[{"c":true,"i":[[-8.78,-0.135],[-4.173,2.699],[-3.043,7.511],[6.829,11.691],[9.846,-0.973],[3.076,-4.716],[3.17,-6.745],[0,0]],"o":[[6.522,0.135],[6.397,-2.699],[2.728,-8.693],[-4.868,-4.837],[-10.263,1.059],[0,0],[0,0],[0,6.71]],"v":[[-23.11,18.54],[6.222,16.368],[20.511,4.794],[17.963,-13.07],[-0.928,-23.139],[-18.387,-11.491],[-26.765,0.995],[-28.89,5.665]]}],"t":40},{"o":{"x":1,"y":0},"i":{"x":0.971,"y":1},"s":[{"c":true,"i":[[-10.45,-3.5],[-5.337,1.625],[0.065,4.325],[5.045,5.015],[12.06,0],[2.32,-2.28],[0.22,-5.32],[0,0]],"o":[[10.45,3.5],[5.337,-1.625],[0.435,-3.575],[-2.81,-2.92],[-12.19,0],[0,0],[0,0],[-2.69,7.15]],"v":[[-23.12,12.5],[8.857,7.25],[24.72,-4.05],[18.36,-16.14],[-0.31,-24.5],[-18.825,-16.23],[-25.78,-3.32],[-24.28,-7.15]]}],"t":46},{"s":[{"c":true,"i":[[0.17,-0.125],[0,0],[-0.095,0.4],[0.046,-0.11],[0.81,-0.125],[0.325,0.105],[0.28,-0.305],[0,0]],"o":[[0,0],[0.182,0.125],[-0.22,0.15],[-0.11,-0.36],[0.185,0],[0,0],[0,0],[-0.095,0.275]],"v":[[-1.67,-24.25],[-0.807,-24.375],[-0.28,-23.525],[0.391,-24.39],[-0.31,-24.5],[-0.294,-24.543],[-1.03,-24.32],[-1.405,-24.4]]}],"t":53}],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.6863,0.0824],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"time","ix":2,"cix":2,"np":1,"it":[{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5,"parent":6},{"ty":4,"nm":"outline-shape","sr":1,"st":0,"op":60,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[144,144,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[360,360,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.185,"y":4.757},"i":{"x":0.575,"y":-11.69},"s":[0],"t":0},{"o":{"x":0.341,"y":0.192},"i":{"x":0.703,"y":1},"s":[7],"t":20},{"o":{"x":0.167,"y":0},"i":{"x":0.703,"y":1},"s":[193],"t":30},{"o":{"x":0.167,"y":0},"i":{"x":0.703,"y":1},"s":[176],"t":39},{"s":[180],"t":43}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"shape","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,7.99],[0,0],[0,0],[0,0],[19.09,-27.84],[0,0],[-0.19,-8.53],[0,0],[0,0],[0,0],[0,0],[-17.24,26.68],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,8.5],[0,0],[18.52,27.1],[0,0],[0,0],[0,0],[0,0],[0,-7.99],[0,0],[0,0],[-17.24,-26.68]],"v":[[-50.5,-54.51],[-50.5,-71],[50.5,-71],[50.5,-54.51],[21.86,0],[22.43,0.83],[50.5,54.26],[50.5,54.51],[50.5,71],[-50.5,71],[-50.5,54.51],[-24.64,2.5],[-23.02,0],[-24.64,-2.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":6},{"ty":4,"nm":"bottom","sr":1,"st":0,"op":60,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-0.237,75.839,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"bottom","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-74.495,-9.31],[74.495,-9.31],[74.495,9.31],[-74.495,9.31]]},"ix":2}},{"ty":"rd","bm":0,"hd":false,"mn":"ADBE Vector Filter - RC","nm":"Round Corners 1","ix":2,"r":{"a":0,"k":9.31,"ix":1}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.0627,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":7,"parent":6}],"v":"5.2.1","fr":60,"op":60,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 44f979c..8ca73b5 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -189,4 +189,5 @@ class AppAnimations { static const String splashLaunching = '$lottieBasePath/splash_launching.json'; static const String noData = '$lottieBasePath/Nodata.json'; static const String ripple = '$lottieBasePath/Ripple.json'; + static const String pending_loading_animation = '$lottieBasePath/pending_loading_animation.json'; } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index fafcafe..8d69eb4 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -46,6 +46,7 @@ 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:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; class LandingPage extends StatefulWidget { @@ -308,6 +309,7 @@ class _LandingPageState extends State { textColor: AppColors.alertColor, ), Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain), ], ), SizedBox(height: 8.h), From 5e000db55674f1bd16de4ed76b3351281c228574 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 5 Oct 2025 08:56:41 +0300 Subject: [PATCH 9/9] updates --- .../immediate_livecare_pending_request_page.dart | 2 ++ lib/presentation/home/landing_page.dart | 2 +- .../widgets/medical_file_appointment_card.dart | 9 +++++---- .../medical_file/widgets/patient_sick_leave_card.dart | 7 ++++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart index 90b6cb1..2d671cf 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart @@ -16,6 +16,7 @@ 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:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -121,6 +122,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State { SizedBox(height: 12.h), ], ) - : SizedBox.shrink(); + : SizedBox(height: 12.h); }), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 3e7b7c9..434bad2 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -121,11 +121,12 @@ class MedicalFileAppointmentCard extends StatelessWidget { child: Padding( padding: EdgeInsets.all(10.h), child: Transform.flip( - flipX: appState.isArabic() ? true : false, + flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - width: 10.h, - height: 10.h, + iconColor: AppColors.whiteColor, + icon: AppAssets.forward_arrow_icon_small, + width: 40.h, + height: 40.h, fit: BoxFit.contain, ), ), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index fed7d15..1f6c0bf 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -142,9 +142,10 @@ class PatientSickLeaveCard extends StatelessWidget { child: Transform.flip( flipX: _appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - width: 10.h, - height: 10.h, + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.whiteColor, + width: 40.h, + height: 40.h, fit: BoxFit.contain, ), ),