From 1af2db5c098b826bb71216aa15b5fddd110d3fcd Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 29 Sep 2025 15:45:54 +0300 Subject: [PATCH 01/29] 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 a4e76fc9..29b41fd3 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 fa02b2e2..5886dfa9 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 8319634f..3129fbc3 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 cb2f76f4..e78d7bdb 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 cf8b7d7f..06cfd045 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 58273fc9..70f20b10 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 02/29] 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 e3ffaea2..9935c0de 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 70f20b10..e5ec389a 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 03/29] 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 5886dfa9..fa02b2e2 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 65f97ae7..d3675095 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 3129fbc3..0bcf30f3 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 6b83f335..658d045a 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 2e528dec..061392e6 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 99f7c7d6..eb8611dd 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 152436a1..d2877957 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 41640b73..bc56473e 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 de5581cb..1c7b1b96 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 aa04d8c9..08e252ba 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 06cfd045..b89d3e87 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 28045fb8..2a9fcfe0 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 9baf538c..00000000 --- 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 00000000..8f891e2a --- /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 00000000..c08c750b --- /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 3bdaa4af..72e75f82 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 04/29] 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 29b41fd3..5d4da2b9 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 fa02b2e2..1c6cec17 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 658d045a..7077fd05 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 061392e6..95c5d63f 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 d2877957..6d157a56 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 2a9fcfe0..7fd9138e 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 c4980d98..e8cff72b 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 5cd25470..fed7d152 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 8f891e2a..9d1bcb19 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 c08c750b..71abcb78 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 40152494..291f81db 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 2f8a9ecd..a4bec4cb 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 a4db1721..16db798a 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 4a2322c6..38e5829c 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 05/29] 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 00000000..058b4cae --- /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 00000000..e063de89 --- /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 31b4f329..446bbc24 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 5e995853..18b53309 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 5ef653ae..ab4ca56f 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 00000000..5c5b9006 --- /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 6ba14f53..012a8dbb 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 00000000..8ec6e4b3 --- /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 00000000..689dc9b7 --- /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 00000000..1642d603 --- /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 da89c419255de64e192720ae2661f3bc374d16ea Mon Sep 17 00:00:00 2001 From: tahaalam Date: Wed, 1 Oct 2025 09:23:43 +0300 Subject: [PATCH 06/29] lab result by hospital and translations are added --- assets/langs/ar-SA.json | 11 +- assets/langs/en-US.json | 13 +- lib/core/api/api_client.dart | 10 +- lib/core/api_consts.dart | 2 +- lib/features/lab/lab_repo.dart | 115 ++++++++- lib/features/lab/lab_view_model.dart | 85 +++++++ .../lab/models/resp_models/lab_result.dart | 2 + .../patient_lab_special_result.dart | 32 +++ lib/generated/locale_keys.g.dart | 15 ++ lib/presentation/lab/lab_orders_page.dart | 230 +----------------- .../LabResultByHospital.dart | 69 ++++++ .../LabResultList.dart | 40 +++ .../lab_order_result_item.dart | 96 ++++++++ .../lab_order_specialResult.dart | 120 +++++++++ .../lab/lab_results/lab_result_calender.dart | 6 +- .../lab/lab_results/lab_result_details.dart | 15 +- lib/splashPage.dart | 12 +- lib/widgets/graph/custom_graph.dart | 4 +- pubspec.yaml | 1 + 19 files changed, 627 insertions(+), 251 deletions(-) create mode 100644 lib/features/lab/models/resp_models/patient_lab_special_result.dart create mode 100644 lib/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart create mode 100644 lib/presentation/lab/lab_result_via_hospital/LabResultList.dart create mode 100644 lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart create mode 100644 lib/presentation/lab/lab_result_via_hospital/lab_order_specialResult.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 8945fee5..fc9d2559 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -856,5 +856,14 @@ "onboardingHeading1": "حجز المواعيد لم يكن أسهل من قبل", "onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.", "onboardingHeading2": "الوصول إلى السجل الطبي بين يديك", - "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها." + "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.", + "normal": "عادي", + "attention": "انتباه", + "monitor": "مراقبة", + "noSpecialResult": "لا توجد نتائج خاصة", + "setTheDateRange": "تعيين النطاق الزمني", + "historyFlowchart": "مخطط تدفق التاريخ", + "to": "إلى", + "startDate": "تاريخ البدء", + "endDate": "تاريخ الانتهاء" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 843daf89..146d96f9 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -852,5 +852,16 @@ "onboardingHeading1": "Booking appointment has never been easy", "onboardingBody1": "In few clicks find yourself having consultation with the doctor of your choice.", "onboardingHeading2": "Access the medical history on finger tips", - "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc" + "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc", + "normal": "Normal", + "attention": "Attention", + "monitor": "Monitor", + "noSpecialResult": "No Special Results", + "setTheDateRange": "Set The Date Range", + "historyFlowchart": "History FlowChart", + "to": "to", + "startDate" : "Start Date", + "endDate": "End Date" + + } \ No newline at end of file diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index a4e76fc9..59256412 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -176,8 +176,12 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4767477; + body['TokenID'] = "@dm!n"; + body['PatientID'] = 1018977; + body['PatientTypeID'] = 1; + + body['PatientOutSA'] = 0; + body['SessionID'] = "45786230487560q"; } body.removeWhere((key, value) => value == null); @@ -198,7 +202,7 @@ class ApiClientImp implements ApiClient { final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); final int statusCode = response.statusCode; - log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { var parsed = json.decode(utf8.decode(response.bodyBytes)); onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fa02b2e2..5886dfa9 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/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 36f97675..2618ab69 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_special_result.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'models/resp_models/lab_result.dart' show LabResult; @@ -11,6 +12,14 @@ import 'models/resp_models/lab_result.dart' show LabResult; abstract class LabRepo { Future>>> getPatientLabOrders(); Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); + + Future>>> + getPatientLabResultsByHospitals( + PatientLabOrdersResponseModel laborder, bool isVidaPlus); + + Future>>> + getSpecialLabResult( + PatientLabOrdersResponseModel laborder, bool isVidaPlus); } class LabRepoImp implements LabRepo { @@ -73,7 +82,6 @@ class LabRepoImp implements LabRepo { request['ProjectID'] = laborder.projectID; request['ClinicID'] = laborder.clinicID; request['Procedure'] = procedureName; - request['LanguageID'] = 1; try { GenericApiModel>? apiResponse; Failure? failure; @@ -90,6 +98,58 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } + final labOrders = list + .map((item) => LabResult.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } 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>>> + getPatientLabResultsByHospitals( + PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Map request = Map(); + request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; + request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; + request['OrderNo'] = laborder!.orderNo; + request['isDentalAllowedBackend'] = false; + request['SetupID'] = laborder!.setupID; + request['ProjectID'] = laborder.projectID; + request['ClinicID'] = laborder.clinicID; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_Patient_LAB_RESULT, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListPLR']; + if (list == null || list.isEmpty) { + throw Exception("lab list is empty"); + } + final labOrders = list.map((item) => LabResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( @@ -110,4 +170,57 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> + getSpecialLabResult( + PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Map request = Map(); + request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; + request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; + request['OrderNo'] = laborder!.orderNo; + request['isDentalAllowedBackend'] = false; + request['SetupID'] = laborder!.setupID; + request['ProjectID'] = laborder.projectID; + request['ClinicID'] = laborder.clinicID; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_Patient_LAB_SPECIAL_RESULT, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListPLSR']; + if (list == null || list.isEmpty) { + throw Exception("lab list is empty"); + } + + final labOrders = list + .map((item) => PatientLabSpecialResult.fromJson( + item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } 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/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 5e89cd3b..1bbc27c7 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -20,6 +20,8 @@ import 'package:logger/logger.dart'; class LabViewModel extends ChangeNotifier { bool isLabOrdersLoading = false; bool isLabResultsLoading = false; + bool isLabResultByHospitalLoading = false; + bool isSpecialResultsLoading = false; LabRepo labRepo; ErrorHandlerService errorHandlerService; @@ -28,6 +30,11 @@ class LabViewModel extends ChangeNotifier { List patientLabOrders = []; List filteredLabOrders = []; List tempLabOrdersList = []; + String labSpecialResult = ""; + + PatientLabOrdersResponseModel? currentlySelectedPatientOrder; + + List mainLabResultsByHospitals = []; List mainLabResults = []; List mainGraphPoints = []; @@ -138,6 +145,31 @@ class LabViewModel extends ChangeNotifier { }; } + Future getPatientLabResultByHospital( + PatientLabOrdersResponseModel laborder) async { + isLabResultByHospitalLoading = true; + notifyListeners(); + mainLabResultsByHospitals.clear; + + final result = await labRepo.getPatientLabResultsByHospitals(laborder, + Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); + + result.fold( + (failure) async { + isLabResultByHospitalLoading = false; + // await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + isLabResultByHospitalLoading = false; + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + mainLabResultsByHospitals = apiResponse.data ?? []; + notifyListeners(); + } + }, + ); + } + Future getPatientLabResult( PatientLabOrdersResponseModel laborder, String procedureName) async { LoaderBottomSheet.showLoader(); @@ -199,6 +231,42 @@ class LabViewModel extends ChangeNotifier { ); } + Future getPatientSpecialResult( + PatientLabOrdersResponseModel laborder) async { + isSpecialResultsLoading = true; + labSpecialResult = ""; + notifyListeners(); + final result = await labRepo.getSpecialLabResult( + laborder, + Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), + ); + + result.fold( + (failure) async { + isSpecialResultsLoading = false; + notifyListeners(); + // await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + isSpecialResultsLoading = false; + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + StringBuffer htmlbuffer = StringBuffer(""); + + apiResponse.data?.forEach((element) { + if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) + htmlbuffer.write("${element.resultDataHTML}

"); + }); + + labSpecialResult = htmlbuffer.toString(); + + notifyListeners(); + } + notifyListeners(); + }, + ); + } + String resultDate(DateTime date){ @@ -460,4 +528,21 @@ class LabViewModel extends ChangeNotifier { return true; } } + + String getSeverityText(String refernceValue) { + switch (refernceValue) { + case 'N': + return "normal"; + case 'L': + case 'H': + return "monitor"; + case 'CL': + case 'LCL': + case 'CH': + case 'HCH': + return "attention"; + default: + return "normal"; + } + } } diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart index d4e92234..3f14d542 100644 --- a/lib/features/lab/models/resp_models/lab_result.dart +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -24,6 +24,7 @@ class LabResult { String? referenceHigh; String? criticalLow; String? referenceLow; + String? packageShortDescription; LabResult( {this.description, @@ -78,6 +79,7 @@ class LabResult { referenceHigh = json['ReferenceHigh']; criticalLow = json['CriticalLow']; referenceLow = json['ReferenceLow']; + packageShortDescription = json['PackageShortDescription']; } Map toJson() { diff --git a/lib/features/lab/models/resp_models/patient_lab_special_result.dart b/lib/features/lab/models/resp_models/patient_lab_special_result.dart new file mode 100644 index 00000000..87301f94 --- /dev/null +++ b/lib/features/lab/models/resp_models/patient_lab_special_result.dart @@ -0,0 +1,32 @@ +class PatientLabSpecialResult { + String? invoiceNo; + String? moduleID; + String? resultData; + String? resultDataHTML; + dynamic resultDataTxt; + + PatientLabSpecialResult( + {this.invoiceNo, + this.moduleID, + this.resultData, + this.resultDataHTML, + this.resultDataTxt}); + + PatientLabSpecialResult.fromJson(Map json) { + invoiceNo = json['InvoiceNo']; + moduleID = json['ModuleID']; + resultData = json['ResultData']; + resultDataHTML = json['ResultDataHTML']; + resultDataTxt = json['ResultDataTxt']; + } + + Map toJson() { + final Map data = new Map(); + data['InvoiceNo'] = this.invoiceNo; + data['ModuleID'] = this.moduleID; + data['ResultData'] = this.resultData; + data['ResultDataHTML'] = this.resultDataHTML; + data['ResultDataTxt'] = this.resultDataTxt; + return data; + } +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3d12e5bb..3c382f76 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -849,5 +849,20 @@ abstract class LocaleKeys { static const selectCountry = 'selectCountry'; static const forLoginVerification = 'forLoginVerification'; static const searchHospital = 'searchHospital'; + static const skip = 'skip'; + static const getStarted = 'getStarted'; + static const onboardingHeading1 = 'onboardingHeading1'; + static const onboardingBody1 = 'onboardingBody1'; + static const onboardingHeading2 = 'onboardingHeading2'; + static const onboardingBody2 = 'onboardingBody2'; + static const normal = 'normal'; + static const attention = 'attention'; + static const monitor = 'monitor'; + static const noSpecialResult = 'noSpecialResult'; + static const setTheDateRange = 'setTheDateRange'; + static const historyFlowchart = 'historyFlowchart'; + static const to = 'to'; + static const startDate = 'startDate'; + static const endDate = 'endDate'; } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6fd82347..1c78bbdf 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1,229 +1 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; -import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; -import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; -import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:provider/provider.dart'; -import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; -import '../../widgets/appbar/collapsing_list_view.dart'; - -class LabOrdersPage extends StatefulWidget { - const LabOrdersPage({super.key}); - - @override - State createState() => _LabOrdersPageState(); -} - -class _LabOrdersPageState extends State { - late LabViewModel labProvider; - late LabRangeViewModel rangeViewModel; - - List?> labSuggestions = []; - int? expandedIndex; - String? selectedFilterText = ''; - int activeIndex = 0; - - @override - void initState() { - scheduleMicrotask(() { - labProvider.initLabProvider(); - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - labProvider = Provider.of(context); - rangeViewModel = Provider.of(context); - - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.labResults.tr(), - search: () async { - final lavVM = Provider.of(context, listen: false); - if (lavVM.isLabOrdersLoading) { - return; - } else { - String? value = await Navigator.of(context).push( - CustomPageRoute( - page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), - fullScreenDialog: true, - direction: AxisDirection.down, - ), - ); - if (value != null) { - selectedFilterText = value; - lavVM.filterLabReports(value); - } - } - }, - child: SingleChildScrollView( - padding: EdgeInsets.all(24.h), - physics: NeverScrollableScrollPhysics(), - child: Consumer( - builder: (context, model, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "By Visit".needTranslation), - CustomTabBarModel(null, "By Test".needTranslation), - // CustomTabBarModel(null, "Completed".needTranslation), - ], - onTabChange: (index) { - activeIndex = index; - setState(() {}); - }, - ), - SizedBox(height: 16.h), - selectedFilterText!.isNotEmpty - ? CustomChipWidget( - chipText: selectedFilterText!, - chipType: ChipTypeEnum.alert, - isSelected: true, - ) - : SizedBox(), - activeIndex == 0 - ? ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: model.isLabOrdersLoading - ? 5 - : model.patientLabOrders.isNotEmpty - ? model.patientLabOrders.length - : 1, - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isLabOrdersLoading - ? LabResultItemView( - onTap: () {}, - labOrder: null, - index: index, - isLoading: true, - ) - : model.patientLabOrders.isNotEmpty - ? AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: LabResultItemView( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - labOrder: model.patientLabOrders[index], - index: index, - isExpanded: isExpanded)), - ), - ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); - }, - ) - : ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().length, - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isLabOrdersLoading - ? LabResultItemView( - onTap: () {}, - labOrder: null, - index: index, - isLoading: true, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: LabOrderByTest( - onTap: () { - if(model.uniqueTests.toList()[index].model != null) { - rangeViewModel.flush(); - model.getPatientLabResult( - model.uniqueTests - .toList()[index] - .model!, model.uniqueTests - .toList()[index].description!); - } - }, - tests: model.uniqueTests.toList()[index], - index: index, - isExpanded: isExpanded)), - ), - ); - }, - ) - ], - ); - }, - ), - ), - )); - } - - Color getLabOrderStatusColor(num status) { - switch (status) { - case 44: - return AppColors.warningColorYellow; - case 45: - return AppColors.warningColorYellow; - case 16: - return AppColors.successColor; - case 17: - return AppColors.successColor; - default: - return AppColors.greyColor; - } - } - - String getLabOrderStatusText(num status) { - switch (status) { - case 44: - return LocaleKeys.resultsPending.tr(context: context); - case 45: - return LocaleKeys.resultsPending.tr(context: context); - case 16: - return LocaleKeys.resultsAvailable.tr(context: context); - case 17: - return LocaleKeys.resultsAvailable.tr(context: context); - default: - return ""; - } - } - - getLabSuggestions(LabViewModel model) { - if (model.patientLabOrders.isEmpty) { - return []; - } - return model.patientLabOrders.map((m) => m.testDetails).toList(); - } -} +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late LabRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; scheduleMicrotask(() { labProvider .getPatientLabResultByHospital( model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); }); Navigator.push(context,FadePage( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().length, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( onTap: () { if(model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult( model.uniqueTests .toList()[index] .model!, model.uniqueTests .toList()[index].description!); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart b/lib/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart new file mode 100644 index 00000000..fb889084 --- /dev/null +++ b/lib/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart @@ -0,0 +1,69 @@ +import 'package:easy_localization/easy_localization.dart' + show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.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/lab/lab_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/presentation/lab/lab_result_via_hospital/LabResultList.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/lab_order_specialResult.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 LabResultByHospitals extends StatelessWidget { + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: LocaleKeys.labResults.tr(), + child: SingleChildScrollView( + child: Column( + spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Selector( + selector: (_, model) => model.isLabResultByHospitalLoading, + builder: (_, isLoading, __) { + if (isLoading) { + return Column( + children: [ + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + ], + ); + } else { + return LabResultList(); + } + }, + ), + LabOrderSpecialResult() + ], + ).paddingAll(24.h), + )); + } +} diff --git a/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart new file mode 100644 index 00000000..9e1f08cc --- /dev/null +++ b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.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/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/lab_result_via_hospital/lab_order_result_item.dart'; +import 'package:provider/provider.dart' show Selector, Provider; + +class LabResultList extends StatelessWidget { + late LabViewModel model; + + @override + Widget build(BuildContext context) { + model = Provider.of(context); + return Selector>( + selector: (_, model) => model.mainLabResultsByHospitals, + builder: (__, list, ___) { + if (list.isEmpty) { + return Utils.getNoDataWidget(context, + noDataText: "You don't have any lab results yet." + .needTranslation); + } else { + return ListView.builder( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true,itemCount: list.length,itemBuilder: (____, index) { + var labItem = list[index]; + return LabOrderResultItem(onTap: () { + model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description??""); + }, + tests: labItem, + index: index, + iconColor: model.getColor(labItem.calculatedResultFlag ?? "N"), + severityText: model.getSeverityText(labItem.calculatedResultFlag ?? "N")); + }); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart new file mode 100644 index 00000000..59638de8 --- /dev/null +++ b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart @@ -0,0 +1,96 @@ +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_export.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/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/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; + +class LabOrderResultItem extends StatelessWidget { + final VoidCallback onTap; + final int index; + final LabResult? tests; + final String severityText; + final bool isLoading; + final bool isExpanded; + final Color iconColor; + + const LabOrderResultItem({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false,required this.iconColor, required this.severityText}); + + @override + build(BuildContext context) { + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ...labOrder!.testDetails!.map((detail) { + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '${tests!.description}'.toText14(weight: FontWeight.w500), + ), + '${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // + SizedBox(height: 24.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + spacing: 6.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + Text(severityText.tr(), + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 10.fSize, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor + )), + Utils.buildSvgWithAssets( + icon: AppAssets.lab_result_indicator, + width: 21, + height: 23, + iconColor: iconColor + ), + ], + ), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () { + onTap(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + )); + } +} diff --git a/lib/presentation/lab/lab_result_via_hospital/lab_order_specialResult.dart b/lib/presentation/lab/lab_result_via_hospital/lab_order_specialResult.dart new file mode 100644 index 00000000..5f71a7ea --- /dev/null +++ b/lib/presentation/lab/lab_result_via_hospital/lab_order_specialResult.dart @@ -0,0 +1,120 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.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/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +class LabOrderSpecialResult extends StatelessWidget { + const LabOrderSpecialResult({super.key}); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (_, model) => model.isSpecialResultsLoading, + builder: (_, isLoading, __) { + return Selector( + selector: (_, model) => model.labSpecialResult, + builder: (_, data, __) { + if(isLoading){ + return Container( + margin: EdgeInsets.symmetric(vertical: 8.h), + padding: EdgeInsets.symmetric( + horizontal: 16.h, vertical: 16.h), + width: MediaQuery.sizeOf(context).width - 24, + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true), + child:Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + "loading".toText14().toShimmer2(isShow: isLoading), + "loading".toText14().toShimmer2(isShow: isLoading), + ], + ) + ); + } + if(data.isNotEmpty ) { + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16.h, vertical: 16.h), + width: MediaQuery.sizeOf(context).width - 24, + child: Column( + spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ...labOrder!.testDetails!.map((detail) { + LocaleKeys.specialResult + .tr() + .toText14(weight: FontWeight.w500) + .toShimmer2(isShow: isLoading), + + data.isEmpty + ? LocaleKeys.noSpecialResult + .tr() + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.textColorLight) + .toShimmer2(isShow: isLoading) + : HtmlWidget(data).toShimmer2(isShow: isLoading) + + // + ], + ), + )); + } return SizedBox.shrink(); + }); + + }); + } +} + +/*Text( + "Special Results", + style: TextStyle( + fontSize: 18.fSize, + fontWeight: FontWeight.w600, + color: AppColors.blackColor), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true + ), + padding: EdgeInsets.all(16.h), + width: MediaQuery.sizeOf(context).width-24, + child: Selector( + selector: (_, model) => + model.isLabResultByHospitalLoading, + builder: (_, isLoading, __) { + return Selector( + selector: (_, model) => model.labSpecialResult, + builder: (_, data, __) { + return (data.isEmpty) + ? Text("No result available".needTranslation, + style: TextStyle( + fontSize: 12.fSize, + fontWeight: FontWeight.w500, + color: AppColors.textColorLight)) + .toShimmer2(isShow: isLoading) + : HtmlWidget(data) + .toShimmer2(isShow: isLoading); + }); + }))*/ diff --git a/lib/presentation/lab/lab_results/lab_result_calender.dart b/lib/presentation/lab/lab_results/lab_result_calender.dart index b118293d..5750fddd 100644 --- a/lib/presentation/lab/lab_results/lab_result_calender.dart +++ b/lib/presentation/lab/lab_results/lab_result_calender.dart @@ -68,7 +68,7 @@ class _LabResultCalenderState extends State { children: [ fromDateComponent(), Text( - "to".needTranslation, + LocaleKeys.to.tr(), style: TextStyle( color: AppColors.calenderTextColor, fontSize: 14.h, @@ -205,7 +205,7 @@ class _LabResultCalenderState extends State { fromDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate("Start Date".needTranslation, + return displayDate(LocaleKeys.startDate.tr(), model.getDateString(model.fromDate), model.fromDate == null); }, ); @@ -214,7 +214,7 @@ class _LabResultCalenderState extends State { toDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate("End Date".needTranslation, + return displayDate(LocaleKeys.endDate.tr(), model.getDateString(model.toDate), model.toDate == null); }, ); diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index e3ffaea2..382a8465 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -13,10 +13,11 @@ 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/generated/locale_keys.g.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; @@ -157,7 +158,7 @@ class LabResultDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation, + model.isGraphVisible?LocaleKeys.historyFlowchart.tr(): LocaleKeys.history.tr(), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -183,7 +184,7 @@ class LabResultDetails extends StatelessWidget { height: 24) .onPress(() { showCommonBottomSheetWithoutHeight( - title: "Set The Date Range".needTranslation, + title: LocaleKeys.setTheDateRange.tr(), context, child: LabResultCalender( onRangeSelected: (start, end) { @@ -235,15 +236,17 @@ class LabResultDetails extends StatelessWidget { Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) { if(model.isGraphVisible){ + print("the labmodel.filteredGraphValues.length is ${labmodel.filteredGraphValues.length}"); var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, // maxY: 100, + makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, leftLabelInterval: getInterval(labmodel), - maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, - + maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/5, + maxX: labmodel.filteredGraphValues.length.toDouble()-.75, leftLabelFormatter: (value) { return leftLabels(value.toStringAsFixed(2).tr()); // switch (value.toInt()) { @@ -263,7 +266,7 @@ class LabResultDetails extends StatelessWidget { // } }, graphColor:graphColor , - graphShadowColor: graphColor.withOpacity(.4), + graphShadowColor: graphColor.withOpacity(.1), graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { if(data.isEmpty) return SizedBox.shrink(); diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 3e707420..117db614 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/onboarding/onboarding_screen.dart'; import 'package:hmg_patient_app_new/presentation/onboarding/splash_animation_screen.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -48,11 +49,12 @@ class _SplashScreenState extends State { Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); - if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { - Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); - } else { - Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); - } + // if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { + // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); + // } else { + // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + // } + Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LabOrdersPage()))); }); var zoom = ZoomVideoSdk(); InitConfig initConfig = InitConfig( diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index fa72b19a..1e0bd604 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -236,7 +236,7 @@ class CustomGraph extends StatelessWidget { gradient: LinearGradient( colors: [ graphShadowColor, - Colors.transparent, + Colors.white, ], begin: Alignment.topCenter, end: Alignment.bottomCenter, @@ -244,6 +244,8 @@ class CustomGraph extends StatelessWidget { ), ) ]; + var max = LineChartHelper().calculateMaxAxisValues(data); + print("the maxX is the -------> ${max.$2}"); return data; } diff --git a/pubspec.yaml b/pubspec.yaml index 3bdaa4af..fe114dd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,6 +85,7 @@ dependencies: gms_check: ^1.0.4 huawei_location: ^6.14.2+301 intl: ^0.20.2 + flutter_widget_from_html: ^0.17.1 dev_dependencies: flutter_test: From 10b77206f6060940f08d758061fac5e09043f346 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 1 Oct 2025 17:29:59 +0300 Subject: [PATCH 07/29] 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 00000000..3f82a2e3 --- /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 446bbc24..016e42df 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 4568a15f..f59d6571 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 efa01d05..8830b78c 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 18b53309..0a137cee 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 ab4ca56f..120aa3bf 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 00000000..9a5861e2 --- /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 6976188a..54353a44 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 9d6b5572..7334369e 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 00000000..76f6ad1d --- /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 00000000..d4a108c5 --- /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 8ec6e4b3..69101137 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 aa97fdfa..41d2b15d 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 08/29] 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 d8a3d51d..e4fd2543 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 00000000..c97ff8be --- /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 00000000..40e86453 --- /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 a982556b..be64c530 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 016e42df..44f979c2 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 b42706f4..33fa0dfe 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 8830b78c..f3a53b91 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 0a137cee..43a7eb3b 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 120aa3bf..8a46f91e 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 00000000..eee4adba --- /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 00000000..599bc39c --- /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 00000000..0077d830 --- /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 3690c724..eff60cca 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 6d157a56..3a651245 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 012a8dbb..516aff9e 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 76f6ad1d..af7d053d 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 d4a108c5..d89ef33b 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 69101137..8181d25d 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 6ef9ae813de469af6fd80671ef620b954f62c8b8 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Thu, 2 Oct 2025 15:14:44 +0300 Subject: [PATCH 09/29] doctors search filter added --- assets/images/svg/cross_circle.svg | 3 + assets/images/svg/filters.svg | 8 + assets/images/svg/ic_close.svg | 3 + assets/langs/ar-SA.json | 10 +- assets/langs/en-US.json | 10 +- lib/core/app_assets.dart | 3 + .../book_appointments_view_model.dart | 147 +++++++++++ .../doctor_filter_view_model.dart | 110 ++++++++ lib/generated/locale_keys.g.dart | 14 + lib/main.dart | 5 +- ...l_bottom_sheet_body_for_doctor_filter.dart | 66 +++++ .../doctor_filter/RegionChips.dart | 60 +++++ .../doctor_filter/clinic_bottomsheet.dart | 85 ++++++ .../doctor_filter/clinic_item.dart | 52 ++++ .../doctor_filter/doctors_filter.dart | 249 ++++++++++++++++++ .../doctor_filter/facility_Chips.dart | 65 +++++ .../search_doctor_by_name.dart | 122 ++++++--- .../book_appointment/select_doctor_page.dart | 84 ++++-- lib/widgets/chip/app_custom_chip_widget.dart | 2 +- lib/widgets/input_widget.dart | 23 +- 20 files changed, 1050 insertions(+), 71 deletions(-) create mode 100644 assets/images/svg/cross_circle.svg create mode 100644 assets/images/svg/filters.svg create mode 100644 assets/images/svg/ic_close.svg create mode 100644 lib/features/doctor_filter/doctor_filter_view_model.dart create mode 100644 lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart create mode 100644 lib/presentation/book_appointment/doctor_filter/RegionChips.dart create mode 100644 lib/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart create mode 100644 lib/presentation/book_appointment/doctor_filter/clinic_item.dart create mode 100644 lib/presentation/book_appointment/doctor_filter/doctors_filter.dart create mode 100644 lib/presentation/book_appointment/doctor_filter/facility_Chips.dart diff --git a/assets/images/svg/cross_circle.svg b/assets/images/svg/cross_circle.svg new file mode 100644 index 00000000..bcf9f909 --- /dev/null +++ b/assets/images/svg/cross_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/filters.svg b/assets/images/svg/filters.svg new file mode 100644 index 00000000..521f6fac --- /dev/null +++ b/assets/images/svg/filters.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/ic_close.svg b/assets/images/svg/ic_close.svg new file mode 100644 index 00000000..615b42d9 --- /dev/null +++ b/assets/images/svg/ic_close.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 8945fee5..b08e7830 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -856,5 +856,13 @@ "onboardingHeading1": "حجز المواعيد لم يكن أسهل من قبل", "onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.", "onboardingHeading2": "الوصول إلى السجل الطبي بين يديك", - "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها." + "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.", + "hmgHospitals": "مستشفيات HMG", + "hmcMedicalClinic": "مراكز HMC الطبية", + "applyFilter": "تطبيق الفلتر", + "facilityAndLocation": "المرفق والموقع", + "regionAndLocation": "المنطقة والمواقع", + "clearAllFilters": "مسح جميع الفلاتر", + "filters": "فلاتر", + "searchClinic": "بحث عن عيادة" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 843daf89..adab1d5c 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -852,5 +852,13 @@ "onboardingHeading1": "Booking appointment has never been easy", "onboardingBody1": "In few clicks find yourself having consultation with the doctor of your choice.", "onboardingHeading2": "Access the medical history on finger tips", - "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc" + "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc", + "hmgHospitals": "HMG Hospitals", + "hmcMedicalClinic": "HMC Medical Centers", + "applyFilter": "AppLy Filter", + "facilityAndLocation": "Facility and Location", + "regionAndLocation": "Region And Locations", + "clearAllFilters": "Clear all filters", + "filters": "Filters", + "searchClinic": "Search Clinic" } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 31b4f329..716448db 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -145,6 +145,9 @@ 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 ic_filters = '$svgBasePath/filters.svg'; + static const String ic_close = '$svgBasePath/ic_close.svg'; + static const String ic_cross_circle = '$svgBasePath/cross_circle.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 5ef653ae..bd292ecd 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -53,6 +53,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { List get filteredClinicsList => _filteredClinicsList; List doctorsList = []; + List filteredDoctorList = []; List liveCareDoctorsList = []; @@ -87,6 +88,19 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool shouldLoadSpecificClinic = false; String? currentlySelectedHospitalFromRegionFlow; + ///variables for doctor filter + List searchedRegionList = []; + List facilityList = ["hmgHospitals", "hmcMedicalClinic"]; + List searchedHospitalList = []; + List + searchedPatientDoctorAppointmentHospitalsList = []; + List searchedClinicList = []; + + PatientDoctorAppointmentList? selectedHospitalForFilters; + List? selectedFacilityForFilters = [], selectedRegionForFilters = []; + String? selectedClinicForFilters; + bool applyFilters = false; + BookAppointmentsViewModel( {required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils}) { ; @@ -279,8 +293,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { doctorsList = apiResponse.data!; + filteredDoctorList = doctorsList; isDoctorsListLoading = false; initializeFilteredList(); + clearSearchFilters(); + getFiltersFromDoctorList(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -757,4 +774,134 @@ class BookAppointmentsViewModel extends ChangeNotifier { void getLocation() { locationUtils.getLocation(); } + + void clearSearchFilters() { + searchedRegionList.clear(); + searchedHospitalList.clear(); + searchedPatientDoctorAppointmentHospitalsList.clear(); + searchedClinicList.clear(); + notifyListeners(); + } + + void clearSelection() { + selectedFacilityForFilters = []; + selectedClinicForFilters = null; + selectedHospitalForFilters = null; + selectedRegionForFilters = []; + applyFilters = false; + notifyListeners(); + } + + void setSelections( + List? selectedFacilityForFilters, + List? selectedRegionForFilters, + String? selectedClinicForFilters, + PatientDoctorAppointmentList? selectedHospitalForFilters, + bool applyFilters) { + this.selectedFacilityForFilters = selectedFacilityForFilters; + this.selectedClinicForFilters = selectedClinicForFilters; + this.selectedHospitalForFilters = selectedHospitalForFilters; + this.selectedRegionForFilters = selectedRegionForFilters; + this.applyFilters = applyFilters; + notifyListeners(); + } + + void getFiltersFromDoctorList() { + doctorsList.forEach((element) { + if (!searchedRegionList + .contains(element.getRegionName(_appState.isArabic()))) { + searchedRegionList + .add(element.getRegionName(_appState.isArabic()) ?? ""); + } + if (!searchedHospitalList.contains(element.projectName)) { + searchedPatientDoctorAppointmentHospitalsList + .add(PatientDoctorAppointmentList() + ..filterName = element.projectName + ..isHMC = element.isHMC + ..distanceInKMs = "0"); + searchedHospitalList.add(element.projectName ?? ""); + } + if (!searchedClinicList.contains(element.clinicName)) { + searchedClinicList.add(element.clinicName ?? ""); + } + }); + } + + void updateApplyFilters(bool applyFilters) { + this.applyFilters = applyFilters; + notifyListeners(); + } + + void setSelectedRegion(String region) { + if (selectedRegionForFilters?.contains(region) == true) { + selectedRegionForFilters?.remove(region); + } else { + selectedRegionForFilters?.add(region); + } + notifyListeners(); + } + + void setSelectedHospital(PatientDoctorAppointmentList? hospital) { + selectedHospitalForFilters = hospital; + notifyListeners(); + } + + void setSelectedFacilityForFilter(String facility) { + if (selectedFacilityForFilters?.contains(facility) == true) { + selectedFacilityForFilters?.remove(facility); + } else { + selectedFacilityForFilters?.add(facility); + } + + notifyListeners(); + } + + void setSelectedClinicForFilter(String? clinic) { + selectedClinicForFilters = clinic; + notifyListeners(); + } + + bool isArabic() { + return _appState.isArabic(); + } + + List getDoctorListAsPerSelection() { + if (!applyFilters) return doctorsList; + + if ((selectedRegionForFilters?.isEmpty == true) && + (selectedFacilityForFilters?.isEmpty == true) && + selectedClinicForFilters == null && + selectedHospitalForFilters == null) { + return doctorsList; + } + var list = doctorsList.where((element) { + var isInSelectedRegion = (selectedRegionForFilters?.isEmpty == true) + ? true + : selectedRegionForFilters + ?.any((region) => region == element.getRegionName(isArabic())); + var shouldApplyFacilityFilter = + (selectedFacilityForFilters?.isEmpty == true) ? false : true; + var isHMC = (selectedFacilityForFilters?.isEmpty == true) + ? true + : selectedFacilityForFilters?.any((item) => item.contains("hmc")); + var isInSelectedClinic = (selectedClinicForFilters == null) + ? true + : selectedClinicForFilters == element.clinicName; + var isInSelectedHospital = (selectedHospitalForFilters == null) + ? true + : element.projectName == selectedHospitalForFilters?.filterName; + var facilityFilter = ((shouldApplyFacilityFilter == true) ? isHMC : true); + + return (isInSelectedRegion ?? true) && + (facilityFilter ?? true) && + isInSelectedClinic && + isInSelectedHospital; + }).toList(); + return list; + } + + void updateList() { + filteredDoctorList = getDoctorListAsPerSelection(); + notifyListeners(); + } } diff --git a/lib/features/doctor_filter/doctor_filter_view_model.dart b/lib/features/doctor_filter/doctor_filter_view_model.dart new file mode 100644 index 00000000..889ab5a2 --- /dev/null +++ b/lib/features/doctor_filter/doctor_filter_view_model.dart @@ -0,0 +1,110 @@ +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/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart' show PatientDoctorAppointmentList; + +class DoctorFilterViewModel extends ChangeNotifier{ + + late AppState appState; + DoctorFilterViewModel(){ + appState = getIt(); + } + List searchedRegionList = []; + List facilityList = ["hmgHospitals", "hmcMedicalClinic"]; + List searchedHospitalList = []; + List + searchedPatientDoctorAppointmentHospitalsList = []; + List searchedClinicList = []; + + PatientDoctorAppointmentList? selectedHospitalForFilters; + List? selectedFacilityForFilters = [] , selectedRegionForFilters = []; + String? selectedClinicForFilters; + bool applyFilters = false; + + void clearSearchFilters() { + searchedRegionList.clear(); + searchedHospitalList.clear(); + searchedPatientDoctorAppointmentHospitalsList.clear(); + searchedClinicList.clear(); + notifyListeners(); + } + + void clearSelection() { + selectedFacilityForFilters = []; + selectedClinicForFilters = null; + selectedHospitalForFilters = null; + selectedRegionForFilters = []; + applyFilters = false; + notifyListeners(); + } + + void getFiltersFromDoctorList(List doctorsList) { + doctorsList.forEach((element) { + if (!searchedRegionList + .contains(element.getRegionName(appState.isArabic()))) { + searchedRegionList + .add(element.getRegionName(appState.isArabic()) ?? ""); + } + if (!searchedHospitalList.contains(element.projectName)) { + searchedPatientDoctorAppointmentHospitalsList + .add(PatientDoctorAppointmentList() + ..filterName = element.projectName + ..isHMC = element.isHMC + ..distanceInKMs = "0"); + searchedHospitalList.add(element.projectName ?? ""); + } + if (!searchedClinicList.contains(element.clinicName)) { + searchedClinicList.add(element.clinicName ?? ""); + } + }); + } + + void updateApplyFilters(bool applyFilters) { + this.applyFilters = applyFilters; + notifyListeners(); + } + + void setSelectedRegion(String region) { + if (selectedRegionForFilters?.contains(region) == true) { + selectedRegionForFilters?.remove(region); + } else { + selectedRegionForFilters?.add(region); + } + notifyListeners(); + } + + void setSelectedHospital(PatientDoctorAppointmentList? hospital) { + selectedHospitalForFilters = hospital; + notifyListeners(); + } + + void setSelectedFacilityForFilter(String facility) { + if (selectedFacilityForFilters?.contains(facility) == true) { + selectedFacilityForFilters?.remove(facility); + } else { + selectedFacilityForFilters?.add(facility); + } + + notifyListeners(); + } + + void setSelectedClinicForFilter(String? clinic) { + selectedClinicForFilters = clinic; + notifyListeners(); + } + + void setSelections( + List? selectedFacilityForFilters, + List? selectedRegionForFilters, + String? selectedClinicForFilters, + PatientDoctorAppointmentList? selectedHospitalForFilters, + bool applyFilters) { + this.selectedFacilityForFilters = selectedFacilityForFilters; + this.selectedClinicForFilters = selectedClinicForFilters; + this.selectedHospitalForFilters = selectedHospitalForFilters; + this.selectedRegionForFilters = selectedRegionForFilters; + this.applyFilters = applyFilters; + notifyListeners(); + } +} \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3d12e5bb..9447bd11 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -849,5 +849,19 @@ abstract class LocaleKeys { static const selectCountry = 'selectCountry'; static const forLoginVerification = 'forLoginVerification'; static const searchHospital = 'searchHospital'; + static const skip = 'skip'; + static const getStarted = 'getStarted'; + static const onboardingHeading1 = 'onboardingHeading1'; + static const onboardingBody1 = 'onboardingBody1'; + static const onboardingHeading2 = 'onboardingHeading2'; + static const onboardingBody2 = 'onboardingBody2'; + static const hmgHospitals = 'hmgHospitals'; + static const hmcMedicalClinic = 'hmcMedicalClinic'; + static const applyFilter = 'applyFilter'; + static const facilityAndLocation = 'facilityAndLocation'; + static const regionAndLocation = 'regionAndLocation'; + static const clearAllFilters = 'clearAllFilters'; + static const filters = 'filters'; + static const searchClinic = 'searchClinic'; } diff --git a/lib/main.dart b/lib/main.dart index 3690c724..4dfba4be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ 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/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/doctor_filter/doctor_filter_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/lab/history/lab_history_viewmodel.dart'; @@ -154,7 +155,9 @@ void main() async { ChangeNotifierProvider( create: (_) => LabHistoryViewModel()), ChangeNotifierProvider( - create: (_) => LabRangeViewModel()) + create: (_) => LabRangeViewModel()) , + ChangeNotifierProvider( + create: (_) => DoctorFilterViewModel()) ], child: MyApp()), ), ); diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart new file mode 100644 index 00000000..58a0d009 --- /dev/null +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart @@ -0,0 +1,66 @@ +import 'package:easy_localization/easy_localization.dart' + show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/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/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +class HospitalBottomSheetBodyForDoctorFilter extends StatelessWidget { + late BookAppointmentsViewModel appointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; + final TextEditingController searchText = TextEditingController(); + + HospitalBottomSheetBodyForDoctorFilter({super.key}); + + @override + Widget build(BuildContext context) { + appointmentsViewModel = Provider.of(context); + regionalViewModel = Provider.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.selectHospital.tr(), + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + SizedBox(height: 24.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) + { + var hospital = appointmentsViewModel.searchedPatientDoctorAppointmentHospitalsList[index]; + return HospitalListItem( + hospitalData: hospital, + isLocationEnabled: appointmentsViewModel.isLocationEnabled(), + ).onPress(() { + regionalViewModel.setHospitalModel(hospital); + context.read().setSelectedHospital(hospital); + Navigator.pop(context); + });}, + separatorBuilder: (_, __) => SizedBox( + height: 16.h, + ), + itemCount: appointmentsViewModel.searchedPatientDoctorAppointmentHospitalsList?.length ?? 0), + ) + ], + ); + } +} diff --git a/lib/presentation/book_appointment/doctor_filter/RegionChips.dart b/lib/presentation/book_appointment/doctor_filter/RegionChips.dart new file mode 100644 index 00000000..c81e5485 --- /dev/null +++ b/lib/presentation/book_appointment/doctor_filter/RegionChips.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.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' + show BookAppointmentsViewModel; +import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +class RegionChips extends StatelessWidget { + const RegionChips({super.key}); + + @override + Widget build(BuildContext context) { + return Selector>( + selector: (_, model) => model.searchedRegionList, + builder: (__, data, ___) => + Selector?>( + selector: (_, model) => model.selectedRegionForFilters, + builder: (context, selectRegion, ___) => Row( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: data.length, + separatorBuilder: (_, __)=>SizedBox(width: 8.h,), + + itemBuilder: (_, index) => AppCustomChipWidget( + labelText: data[index], + textColor: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true + ? AppColors.primaryRedColor + + : AppColors.textColor, + backgroundColor: + selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true + ? AppColors.primaryRedColor + .withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true + ? AppColors + .primaryRedBorderColor + : AppColors + .chipBorderColorOpacity20, + width: 1, + ), + borderRadius: + BorderRadius.circular(10))) + .onPress(() { + context + .read() + .setSelectedRegion(data[index]); + }))) + ], + ), + )); + } +} diff --git a/lib/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart b/lib/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart new file mode 100644 index 00000000..155d8e36 --- /dev/null +++ b/lib/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart @@ -0,0 +1,85 @@ +import 'package:easy_localization/easy_localization.dart' + show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/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/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/clinic_item.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../features/book_appointments/models/resp_models/get_clinic_list_response_model.dart' show GetClinicsListResponseModel; + +class ClinicBottomSheet extends StatelessWidget { + late BookAppointmentsViewModel appointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; + final TextEditingController searchText = TextEditingController(); + + ClinicBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + appointmentsViewModel = Provider.of(context); + regionalViewModel = Provider.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.selectClinic.tr(), + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + SizedBox(height: 24.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, 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: ClinicItem( + isArabic: appointmentsViewModel.isArabic(), + clinicName: appointmentsViewModel.searchedClinicList[index], + ), + ), + ), + ), + + ).onPress(() { + context.read() + .setSelectedClinicForFilter(appointmentsViewModel.searchedClinicList[index]); + Navigator.pop(context); + });}, + separatorBuilder: (_, __) => SizedBox( + height: 16.h, + ), + itemCount: appointmentsViewModel.searchedClinicList.length ?? 0), + ) + ], + ); + } +} diff --git a/lib/presentation/book_appointment/doctor_filter/clinic_item.dart b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart new file mode 100644 index 00000000..0d5ba764 --- /dev/null +++ b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; + +class ClinicItem extends StatelessWidget { + final String clinicName; + final bool isArabic; + + ClinicItem({super.key, required this.clinicName, required this.isArabic}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.generic_clinic_icon, + width: 24.h, + height: 24.h, + fit: BoxFit.contain), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: clinicName.toText16(isBold: true)), + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 15.h, + height: 15.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart new file mode 100644 index 00000000..1a772f76 --- /dev/null +++ b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart @@ -0,0 +1,249 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body_for_doctor_filter.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/RegionChips.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/clinic_bottomsheet.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/facility_Chips.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../widgets/buttons/custom_button.dart'; + +class DoctorsFilters extends StatelessWidget{ + + + TextEditingController hospitalController = TextEditingController(); + TextEditingController clinicController = TextEditingController(); + DoctorsFilters({super.key,}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + backgroundColor: AppColors.bgScaffoldColor, + + automaticallyImplyLeading: false, + centerTitle: false, + title: Utils.buildSvgWithAssets(icon: AppAssets.ic_close, height: 32.h, width: 32.h).onPress((){ + context.read() + .clearSelection() + ; + Navigator.pop(context); + }) + + + + ), + + body: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + LocaleKeys.filters.tr(), + style:TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + fontSize: 27.fSize, + color: AppColors.textColor, + letterSpacing: -1 + ) + ), + Text( + LocaleKeys.clearAllFilters.tr(), + style:TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + fontSize: 14.fSize, + color: AppColors.errorColor + ) + ).onPress((){ + context.read().clearSelection(); + context.read().updateApplyFilters(false); + // context.read().setSelections( + // context.read().selectedFacilityForFilters, + // context.read().selectedRegionForFilters, + // context.read().selectedClinicForFilters, + // context.read().selectedHospitalForFilters, + // context.read().applyFilters); + context.read().updateList(); + }) + ], + ), + titleWidget(LocaleKeys.regionAndLocation.tr()), + SizedBox( + height: 42.h, + child: RegionChips()), + titleWidget(LocaleKeys.facilityAndLocation.tr()), + SizedBox( + height: 42.h, + child: FacilityChip()), + titleWidget(LocaleKeys.hospital.tr()), + TextInputWidget( + controller: TextEditingController()..text =context.watch().selectedHospitalForFilters?.filterName??'', + labelText: LocaleKeys.hospital.tr(context: context), + hintText: LocaleKeys.searchHospital.tr(context: context), + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + suffix:context.watch().selectedHospitalForFilters != null + ? GestureDetector( + onTap: () { + context.read().setSelectedHospital(null); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.ic_cross_circle, width: 24.h, height: 24.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + // DoctorFilterViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(8).h, + horizontal: ResponsiveExtension(10).h, + ), + ).onPress((){ + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); + }), + + titleWidget(LocaleKeys.clinic.tr()), + TextInputWidget( + controller: TextEditingController()..text =context.watch().selectedClinicForFilters ??'', + labelText: LocaleKeys.clinicName.tr(context: context), + hintText: LocaleKeys.searchClinic.tr().needTranslation, + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + suffix:context.read().selectedClinicForFilters?.isNotEmpty == true + ? GestureDetector( + onTap: () { + context.read().setSelectedClinicForFilter(null); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.ic_cross_circle, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + // DoctorFilterViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: 8.h, + horizontal: 10.h, + ), + ).onPress((){ + openClinicListBottomSheet(context,); + }), + + + ], + ).paddingSymmetrical(24.h, 0.h), + Spacer(), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + customBorder: BorderRadius.only(topLeft: Radius.circular(24.h), topRight: Radius.circular(24.h)) , + + ), + child: CustomButton( + text: LocaleKeys.applyFilter.tr(), + onPressed: () { + context.read().updateApplyFilters(true); + context.read().setSelections( + context.read().selectedFacilityForFilters?.toList()??[], + context.read().selectedRegionForFilters?.toList()??[], + context.read().selectedClinicForFilters, + context.read().selectedHospitalForFilters, + context.read().applyFilters); + context.read().updateList(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 16, + padding: EdgeInsets.zero, + fontWeight: FontWeight.w500, + borderRadius: 12, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ).paddingAll(24.h), + ), + ], + ), + ); + } + + Widget titleWidget(String title){ + return Column( + children: [ + SizedBox(height: 24.h,), + Text( + title, + style:TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + fontSize: 16.fSize, + color: AppColors.textColor, + letterSpacing:-1 + ) + ), + SizedBox(height: 8.h,), + ], + ); + } + + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { + context.read().flush(); + context.read().setBottomSheetType(type); + context.read().setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION); + // AppointmentViaRegionViewmodel? viewmodel = null; + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, + child: Consumer(builder: (_, data, __) { + return getRegionalSelectionWidget(data); + }), callBackFunc: () {}); + } + + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { + + if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { + return HospitalBottomSheetBodyForDoctorFilter(); + } + if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) { + } else { + SizedBox.shrink(); + } + return SizedBox.shrink(); + } + + getTitle(AppointmentViaRegionViewmodel data) { + return SizedBox.shrink(); + } + + void openClinicListBottomSheet(BuildContext context) { + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, + child: Consumer(builder: (_, data, __) { + return ClinicBottomSheet(); + }), callBackFunc: () {}); + } + +} \ No newline at end of file diff --git a/lib/presentation/book_appointment/doctor_filter/facility_Chips.dart b/lib/presentation/book_appointment/doctor_filter/facility_Chips.dart new file mode 100644 index 00000000..f416ed4d --- /dev/null +++ b/lib/presentation/book_appointment/doctor_filter/facility_Chips.dart @@ -0,0 +1,65 @@ + +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart' + show BookAppointmentsViewModel; +import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +class FacilityChip extends StatelessWidget { + const FacilityChip({super.key}); + + @override + Widget build(BuildContext context) { + return Selector>( + selector: (_, model) => model.facilityList, + builder: (__, data, ___) => + Selector?>( + selector: (_, model) => model.selectedFacilityForFilters, + builder: (context, selectRegion, ___) => Row( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + + itemCount: data.length, + separatorBuilder: (_, __)=>SizedBox(width: 8.h,), + itemBuilder: (_, index) => AppCustomChipWidget( + icon: data[index].contains("hmg")?AppAssets.hmg: AppAssets.hmc, + iconHasColor: false, + iconSize: 18, + labelText: data[index].tr(), + textColor: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true + ? AppColors.primaryRedColor + : AppColors.textColor, + backgroundColor: + data[index] == selectRegion + ? AppColors.primaryRedColor + .withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectRegion?.any((selectedRegion)=>data[index] == selectedRegion) == true + ? AppColors + .primaryRedBorderColor + : AppColors + .chipBorderColorOpacity20, + width: 1, + ), + borderRadius: + BorderRadius.circular(10))) + .onPress(() { + context + .read() + .setSelectedFacilityForFilter(data[index]); + }))) + ], + ), + )); + } +} diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 8f611dcf..233aff94 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -9,7 +9,9 @@ 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/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/doctors_filter.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -33,16 +35,12 @@ class SearchDoctorByName extends StatefulWidget { class _SearchDoctorByNameState extends State { TextEditingController searchEditingController = TextEditingController(); - FocusNode textFocusNode = FocusNode(); - - late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); - appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -56,33 +54,85 @@ class _SearchDoctorByNameState extends State { child: Column( children: [ SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: LocaleKeys.doctorName.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, - ), + Row( + spacing: 8.h, + children: [ + Expanded( + child: TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.doctorName.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, + ), + ), + ), + Visibility( + visible: bookAppointmentsViewModel.doctorsList.isNotEmpty, + child: SizedBox( + height: 56.h, + width: 56.h, + child: DecoratedBox(decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 10.h, + hasShadow: false, + ), + child: Utils.buildSvgWithAssets(icon: AppAssets.ic_filters, + height: 24.h, + width: 24.h, ).paddingAll(16.h).onPress((){ + context.read() + ..clearSelection() + ..clearSearchFilters() + ..getFiltersFromDoctorList( + bookAppointmentsViewModel.doctorsList + )..setSelections( + bookAppointmentsViewModel.selectedFacilityForFilters?.toList(), + bookAppointmentsViewModel.selectedRegionForFilters?.toList(), + bookAppointmentsViewModel.selectedClinicForFilters, + bookAppointmentsViewModel.selectedHospitalForFilters, + bookAppointmentsViewModel.applyFilters) ; + Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => DoctorsFilters(), // Replace YourNewPage with your actual page widget + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); // Start from the bottom (y=1.0) + const end = Offset.zero; // End at the original position (y=0.0) + final tween = Tween(begin: begin, end: end); + final offsetAnimation = animation.drive(tween); + + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + transitionDuration: Duration(milliseconds: 200), // Adjust duration as needed + ), + ); + }), + ), + ), + ) + ], ), SizedBox(height: 16.h), Consumer(builder: (context, bookAppointmentsVM, child) { @@ -94,7 +144,7 @@ class _SearchDoctorByNameState extends State { padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.doctorsList.length, + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.filteredDoctorList.length, itemBuilder: (context, index) { return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( @@ -113,11 +163,11 @@ class _SearchDoctorByNameState extends State { curve: Curves.easeInOut, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: DoctorCard( - doctorsListResponseModel: bookAppointmentsVM.doctorsList[index], + doctorsListResponseModel: bookAppointmentsVM.filteredDoctorList[index], isLoading: false, bookAppointmentsViewModel: bookAppointmentsViewModel, ).onPress(() async { - bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]); + bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]); // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); LoaderBottomSheet.showLoader(); await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { @@ -166,7 +216,11 @@ class _SearchDoctorByNameState extends State { text: LocaleKeys.search.tr(context: context), onPressed: () async { textFocusNode.unfocus(); + print("the value is empty ${searchEditingController.text.isNotEmpty}"); if (searchEditingController.text.isNotEmpty) { + bookAppointmentsViewModel.updateApplyFilters(false); + bookAppointmentsViewModel.clearSelection(); + bookAppointmentsViewModel.updateList(); bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(true); bookAppointmentsViewModel.setIsDoctorsListLoading(true); // LoaderBottomSheet.showLoader(); diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index fd6e6918..ec17c618 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -68,33 +68,63 @@ class _SelectDoctorPageState extends State { children: [ // TODO: Implement doctor filter functionality SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: LocaleKeys.doctorName.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, - ), + Row( + spacing: 8.h, + children: [ + Expanded( + child: TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.doctorName.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, + ), + ), + ), + SizedBox( + height: 56.h, + width: 56.h, + child: DecoratedBox( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 10.h, + hasShadow: false, + ), + child: Utils.buildSvgWithAssets( + icon: AppAssets.ic_filters, + height: 24.h, + width: 24.h, + ).paddingAll(16.h).onPress((){ + + }), + ), + ) + ], ), ListView.separated( padding: EdgeInsets.only(top: 24.h), diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index a4db1721..084f85b9 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -62,7 +62,7 @@ class AppCustomChipWidget extends StatelessWidget { // padding: EdgeInsets.all(0.0), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + labelPadding: EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, shape: shape ?? SmoothRectangleBorder( diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 4a2322c6..4b060964 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -114,6 +114,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, + height: 58.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -134,12 +135,23 @@ class TextInputWidget extends StatelessWidget { // textField: _buildTextField(context), ) : Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - _buildLabelText(labelColor).paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)), - _buildTextField(context), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabelText(labelColor).paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)), + Row( + children: [ + Expanded(child: _buildTextField(context)), + ], + ), + ], + ), + ), + (suffix!= null )?suffix!:SizedBox.shrink() ], ), ), @@ -218,7 +230,6 @@ class TextInputWidget extends StatelessWidget { fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor, letterSpacing: -0.2, - height: 18 / 12, ), ); } From 8a7f46441c11cefbb93eb4b6d84322339f5ddc01 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 2 Oct 2025 15:31:18 +0300 Subject: [PATCH 10/29] 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 00000000..177ef651 --- /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 44f979c2..8ca73b56 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 fafcafe4..8d69eb4d 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 a4a32cca447a325e6ae5c954cf33086df135b1c7 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Thu, 2 Oct 2025 16:31:50 +0300 Subject: [PATCH 11/29] doctor filter icon removed from the doctor listing from clininc --- .../book_appointment/select_doctor_page.dart | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index ec17c618..59756317 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -105,25 +105,6 @@ class _SelectDoctorPageState extends State { ), ), ), - SizedBox( - height: 56.h, - width: 56.h, - child: DecoratedBox( - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 10.h, - hasShadow: false, - ), - child: Utils.buildSvgWithAssets( - icon: AppAssets.ic_filters, - height: 24.h, - width: 24.h, - ).paddingAll(16.h).onPress((){ - - }), - ), - ) ], ), ListView.separated( From 786aa05b19514ce7ac9246ce7e9da0321695366d Mon Sep 17 00:00:00 2001 From: tahaalam Date: Thu, 2 Oct 2025 16:38:46 +0300 Subject: [PATCH 12/29] doctor list handled to be cleared when going back --- .../book_appointment/search_doctor_by_name.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 233aff94..5949ef58 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -88,7 +88,7 @@ class _SearchDoctorByNameState extends State { ), ), Visibility( - visible: bookAppointmentsViewModel.doctorsList.isNotEmpty, + visible: context.watch().doctorsList.isNotEmpty, child: SizedBox( height: 56.h, width: 56.h, @@ -264,4 +264,9 @@ class _SearchDoctorByNameState extends State { ), ); } + @override + void dispose() { + bookAppointmentsViewModel.doctorsList.clear(); + super.dispose(); + } } From 5e000db55674f1bd16de4ed76b3351281c228574 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 5 Oct 2025 08:56:41 +0300 Subject: [PATCH 13/29] 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 90b6cb1d..2d671cfa 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 3e7b7c99..434bad24 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 fed7d152..1f6c0bfc 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, ), ), From 2e9a462a1cbafa507f6bfb7416251444fd2f7ad0 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 5 Oct 2025 09:42:29 +0300 Subject: [PATCH 14/29] listing resizing if the data is in small numbers. --- lib/presentation/lab/lab_results/lab_result_details.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 382a8465..74557ace 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -148,7 +148,7 @@ class LabResultDetails extends StatelessWidget { borderRadius: 24.h, hasShadow: true, ), - height: 260.h, + height: model.isGraphVisible?260.h:(labmodel.filteredGraphValues.length<3)?(labmodel.filteredGraphValues.length*64)+80.h:260.h, padding: EdgeInsets.all(16.h), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, @@ -173,8 +173,8 @@ class LabResultDetails extends StatelessWidget { //todo handle when the graph icon is being displayed Utils.buildSvgWithAssets( icon: model.isGraphVisible?AppAssets.ic_list:AppAssets.ic_graph, - width: 24, - height: 24) + width: 24.h, + height: 24.h) .onPress(() { model.alterGraphVisibility(); }), @@ -291,7 +291,7 @@ class LabResultDetails extends StatelessWidget { Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) { return SizedBox( - height: 180.h, + height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, child: ListView.separated( padding: EdgeInsets.zero, itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ @@ -313,6 +313,7 @@ class LabResultDetails extends StatelessWidget { double? getInterval(LabViewModel labmodel) { var maxX = labmodel.maxY; + if(maxX<1) return .5; if(maxX >1 && maxX < 5) return 1; if(maxX >5 && maxX < 10) return 5; if(maxX >10 && maxX < 50) return 10; From 42abf9ca1b3fcd8be04a112d964f47149dbf0736 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 5 Oct 2025 09:45:13 +0300 Subject: [PATCH 15/29] login bypass removed --- lib/core/api/api_client.dart | 12 ++++++------ lib/splashPage.dart | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 59256412..799942ac 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -176,12 +176,12 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 1018977; - body['PatientTypeID'] = 1; - - body['PatientOutSA'] = 0; - body['SessionID'] = "45786230487560q"; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 1018977; + // body['PatientTypeID'] = 1; + // + // body['PatientOutSA'] = 0; + // body['SessionID'] = "45786230487560q"; } body.removeWhere((key, value) => value == null); diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 117db614..ba7cefd9 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -49,12 +49,11 @@ class _SplashScreenState extends State { Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); - // if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { - // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); - // } else { - // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); - // } - Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LabOrdersPage()))); + if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { + Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); + } else { + Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + } }); var zoom = ZoomVideoSdk(); InitConfig initConfig = InitConfig( From 641df8b51b986f8c83c83ceca5913ab07ebf7cf9 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 5 Oct 2025 09:50:33 +0300 Subject: [PATCH 16/29] logs removed --- assets/langs/ar-SA.json | 2 +- lib/generated/locale_keys.g.dart | 8 ++++++++ lib/widgets/graph/custom_graph.dart | 2 -- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 7b073f46..c434a9e5 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -864,7 +864,7 @@ "regionAndLocation": "المنطقة والمواقع", "clearAllFilters": "مسح جميع الفلاتر", "filters": "فلاتر", - "searchClinic": "بحث عن عيادة" + "searchClinic": "بحث عن عيادة", "normal": "عادي", "attention": "انتباه", "monitor": "مراقبة", diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3c382f76..3ae958c4 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -855,6 +855,14 @@ abstract class LocaleKeys { static const onboardingBody1 = 'onboardingBody1'; static const onboardingHeading2 = 'onboardingHeading2'; static const onboardingBody2 = 'onboardingBody2'; + static const hmgHospitals = 'hmgHospitals'; + static const hmcMedicalClinic = 'hmcMedicalClinic'; + static const applyFilter = 'applyFilter'; + static const facilityAndLocation = 'facilityAndLocation'; + static const regionAndLocation = 'regionAndLocation'; + static const clearAllFilters = 'clearAllFilters'; + static const filters = 'filters'; + static const searchClinic = 'searchClinic'; static const normal = 'normal'; static const attention = 'attention'; static const monitor = 'monitor'; diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index 1e0bd604..02ad987e 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -244,8 +244,6 @@ class CustomGraph extends StatelessWidget { ), ) ]; - var max = LineChartHelper().calculateMaxAxisValues(data); - print("the maxX is the -------> ${max.$2}"); return data; } From db979977ca9ad6db0cfab4fcab2c6a51f4950b66 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 5 Oct 2025 12:22:33 +0300 Subject: [PATCH 17/29] Tamara implementation & enhancements --- lib/core/api/api_client.dart | 2 + lib/core/api_consts.dart | 16 +++ .../medical_file/medical_file_view_model.dart | 33 +++-- ...a_installments_details_response_model.dart | 83 +++++++++++ .../my_appointments/my_appointments_repo.dart | 40 ++++++ .../my_appointments_view_model.dart | 47 +++++- .../appointment_payment_page.dart | 31 ++-- .../immediate_livecare_payment_page.dart | 25 +++- lib/presentation/home/landing_page.dart | 4 +- .../lab/lab_result_item_view.dart | 135 ++++++++++-------- .../medical_file/medical_file_page.dart | 4 +- .../patient_sickleaves_list_page.dart | 6 +- .../medical_file/vaccine_list_page.dart | 20 +-- .../medical_file/widgets/lab_rad_card.dart | 2 +- .../widgets/patient_sick_leave_card.dart | 5 +- lib/widgets/in_app_browser/InAppBrowser.dart | 18 +-- 16 files changed, 352 insertions(+), 119 deletions(-) create mode 100644 lib/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index a064f11f..9f0d2309 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -34,6 +34,7 @@ abstract class ApiClient { required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + bool isAllowAny, bool isExternal, bool isRCService, }); @@ -324,6 +325,7 @@ class ApiClientImp implements ApiClient { {required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + bool isAllowAny = false, bool isExternal = false, bool isRCService = false}) async { String url; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 1c6cec17..8225796c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -743,6 +743,9 @@ class ApiConsts { static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE // static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT + static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; + static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + // var payFortEnvironment = FortEnvironment.test; // var applePayMerchantId = "merchant.com.hmgwebservices.uat"; @@ -753,18 +756,25 @@ class ApiConsts { payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; + TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; + // GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; break; case AppEnvironmentTypeEnum.preProd: @@ -772,18 +782,24 @@ class ApiConsts { payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; + TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; break; } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 95c5d63f..3f0573f0 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -108,12 +108,17 @@ class MedicalFileViewModel extends ChangeNotifier { final result = await medicalFileRepo.getPatientVaccinesList(); result.fold( - (failure) async => await errorHandlerService.handleError( - failure: failure, - onOkPressed: () { - onError!(failure.message); - }, - ), + // (failure) async => await errorHandlerService.handleError( + // failure: failure, + // onOkPressed: () { + // onError!(failure.message); + // }, + // ), + (failure) async { + // onError!(failure.message); + isPatientVaccineListLoading = false; + notifyListeners(); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); @@ -134,12 +139,16 @@ class MedicalFileViewModel extends ChangeNotifier { final result = await medicalFileRepo.getPatientSickLeavesList(); result.fold( - (failure) async => await errorHandlerService.handleError( - failure: failure, - onOkPressed: () { - onError!(failure.message); - }, - ), + // (failure) async => await errorHandlerService.handleError( + // failure: failure, + // onOkPressed: () { + // onError!(failure.message); + // }, + // ), + (failure) async { + isPatientSickLeaveListLoading = false; + notifyListeners(); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart b/lib/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart new file mode 100644 index 00000000..59ad7079 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart @@ -0,0 +1,83 @@ +class GetTamaraInstallmentsDetailsResponseModel { + String? name; + String? description; + MinLimit? minLimit; + MinLimit? maxLimit; + List? supportedInstalments; + + GetTamaraInstallmentsDetailsResponseModel({this.name, this.description, this.minLimit, this.maxLimit, this.supportedInstalments}); + + GetTamaraInstallmentsDetailsResponseModel.fromJson(Map json) { + name = json['name']; + description = json['description']; + minLimit = json['minLimit'] != null ? new MinLimit.fromJson(json['minLimit']) : null; + maxLimit = json['maxLimit'] != null ? new MinLimit.fromJson(json['maxLimit']) : null; + if (json['supportedInstalments'] != null) { + supportedInstalments = []; + json['supportedInstalments'].forEach((v) { + supportedInstalments!.add(new SupportedInstalments.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['name'] = this.name; + data['description'] = this.description; + if (this.minLimit != null) { + data['minLimit'] = this.minLimit!.toJson(); + } + if (this.maxLimit != null) { + data['maxLimit'] = this.maxLimit!.toJson(); + } + if (this.supportedInstalments != null) { + data['supportedInstalments'] = this.supportedInstalments!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class MinLimit { + String? currency; + num? amount; + + MinLimit({this.currency, this.amount}); + + MinLimit.fromJson(Map json) { + currency = json['currency']; + amount = json['amount']; + } + + Map toJson() { + final Map data = new Map(); + data['currency'] = this.currency; + data['amount'] = this.amount; + return data; + } +} + +class SupportedInstalments { + int? instalments; + MinLimit? minLimit; + MinLimit? maxLimit; + + SupportedInstalments({this.instalments, this.minLimit, this.maxLimit}); + + SupportedInstalments.fromJson(Map json) { + instalments = json['instalments']; + minLimit = json['minLimit'] != null ? new MinLimit.fromJson(json['minLimit']) : null; + maxLimit = json['maxLimit'] != null ? new MinLimit.fromJson(json['maxLimit']) : null; + } + + Map toJson() { + final Map data = new Map(); + data['instalments'] = this.instalments; + if (this.minLimit != null) { + data['minLimit'] = this.minLimit!.toJson(); + } + if (this.maxLimit != null) { + data['maxLimit'] = this.maxLimit!.toJson(); + } + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index eb8611dd..b94f004d 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; 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/models/resp_models/patient_appointment_share_response_model.dart'; @@ -44,6 +45,8 @@ abstract class MyAppointmentsRepo { Future>>> getPatientDoctorsList(); Future>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> getTamaraInstallmentsDetails(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -543,4 +546,41 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getTamaraInstallmentsDetails() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.get( + ApiConsts.GET_TAMARA_INSTALLMENTS_URL, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response; + + final tamaraInstallmentsList = GetTamaraInstallmentsDetailsResponseModel.fromJson(list.first); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: tamaraInstallmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index c553f0ee..acf0a392 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; -import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper; - class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -32,6 +31,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; + GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; + bool isTamaraDetailsLoading = false; + MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { @@ -50,6 +52,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientMyDoctorsList.clear(); isPatientMyDoctorsLoading = true; } + isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; notifyListeners(); } @@ -79,6 +82,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsTamaraDetailsLoading(bool val) { + isTamaraDetailsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -142,7 +150,13 @@ class MyAppointmentsViewModel extends ChangeNotifier { final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo, isLiveCareAppointment: isLiveCareAppointment); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); @@ -337,4 +351,31 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getTamaraInstallmentsDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getTamaraInstallmentsDetails(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + getTamaraInstallmentsDetailsResponseModel = apiResponse.data!; + isTamaraDetailsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + + // if (apiResponse.messageStatus == 2) { + // onError!(apiResponse.errorMessage!); + // // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + // } else if (apiResponse.messageStatus == 1) { + // getTamaraInstallmentsDetailsResponseModel = apiResponse.data!; + // notifyListeners(); + // if (onSuccess != null) { + // onSuccess(apiResponse); + // } + // } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 7334369e..ee50ab44 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -51,17 +51,28 @@ class _AppointmentPaymentPageState extends State { String transID = ""; + bool isShowTamara = false; + @override void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); + myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { + if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } + }); payfortViewModel.setIsApplePayConfigurationLoading(false); myAppointmentsViewModel.getPatientShareAppointment( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, - widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, - ); + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); }); super.initState(); } @@ -162,11 +173,12 @@ class _AppointmentPaymentPageState extends State { openPaymentURL("visa"); }), SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, + isShowTamara + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, ), child: Row( mainAxisSize: MainAxisSize.max, @@ -196,7 +208,8 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; openPaymentURL("tamara"); - }), + }) + : SizedBox.shrink(), ], ), ), 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 d89ef33b..5437945e 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -56,10 +56,20 @@ class _ImmediateLiveCarePaymentPageState extends State= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } + }); }); super.initState(); } @@ -67,9 +77,9 @@ class _ImmediateLiveCarePaymentPageState extends State(); - myAppointmentsViewModel = Provider.of(context); + myAppointmentsViewModel = Provider.of(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); - payfortViewModel = Provider.of(context); + payfortViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, myAppointmentsVM, child) { @@ -161,9 +171,10 @@ class _ImmediateLiveCarePaymentPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Quick Links".toText16(isBold: true), + "Quick Links".needTranslation.toText16(isBold: true), Row( children: [ - "View medical file".toText12(color: AppColors.primaryRedColor), + "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], diff --git a/lib/presentation/lab/lab_result_item_view.dart b/lib/presentation/lab/lab_result_item_view.dart index 03f721ed..57a10aef 100644 --- a/lib/presentation/lab/lab_result_item_view.dart +++ b/lib/presentation/lab/lab_result_item_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.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/utils/date_util.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/lab/models/resp_models/patient_lab_orders_response_model.dart'; @@ -49,7 +50,7 @@ class LabResultItemView extends StatelessWidget { backgroundColor: getLabOrderStatusColor(labOrder?.status ?? 0).withOpacity(0.15), textColor: getLabOrderStatusColor(labOrder?.status ?? 0), ).toShimmer2(isShow: isLoading, width: 100), - if (!isLoading) Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + // if (!isLoading) Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), @@ -59,7 +60,7 @@ class LabResultItemView extends StatelessWidget { isLoading ? "" : labOrder!.doctorImageURL!, width: 24.h, height: 24.h, - fit: BoxFit.fill, + fit: BoxFit.cover, errorBuilder: (cxt, child, tr) { return SizedBox(height: 24, width: 24); }, @@ -69,71 +70,83 @@ class LabResultItemView extends StatelessWidget { ], ), SizedBox(height: 8.h), - Wrap( - spacing: 8.h, - runSpacing: 0.h, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AppCustomChipWidget(labelText: isLoading ? "null" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(labOrder!.createdOn), false)).toShimmer2(isShow: isLoading, width: 70), - AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100), + Wrap( + spacing: 8.h, + runSpacing: 0.h, + children: [ + AppCustomChipWidget(labelText: isLoading ? "null" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(labOrder!.createdOn), false)).toShimmer2(isShow: isLoading, width: 70), + AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.textColor, + width: 20.h, + height: 14.h, + fit: BoxFit.contain, + ), ], ), ], ), ), - AnimatedSwitcher( - duration: Duration(milliseconds: 300), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, - ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...labOrder!.testDetails!.map((detail) { - return Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${detail.description}'.toText14(weight: FontWeight.w500), - ); - }).toList(), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ], - ), - ) - : SizedBox.shrink(key: ValueKey(-index)), - ), + // AnimatedSwitcher( + // duration: Duration(milliseconds: 300), + // switchInCurve: Curves.easeIn, + // switchOutCurve: Curves.easeOut, + // transitionBuilder: (Widget child, Animation animation) { + // return FadeTransition( + // opacity: animation, + // child: SizeTransition( + // sizeFactor: animation, + // axisAlignment: 0.0, + // child: child, + // ), + // ); + // }, + // child: isExpanded + // ? Container( + // key: ValueKey(index), + // padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // ...labOrder!.testDetails!.map((detail) { + // return Padding( + // padding: EdgeInsets.only(bottom: 8.h), + // child: '● ${detail.description}'.toText14(weight: FontWeight.w500), + // ); + // }).toList(), + // SizedBox(height: 16.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // SizedBox(), + // CustomButton( + // icon: AppAssets.view_report_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // text: LocaleKeys.viewReport.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.bold, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ], + // ), + // ], + // ), + // ) + // : SizedBox.shrink(key: ValueKey(-index)), + // ), ], ), ), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 9358d002..dd805131 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -79,7 +79,7 @@ class _MedicalFilePageState extends State { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); - medicalFileViewModel.getFamilyFiles(); + // medicalFileViewModel.getFamilyFiles(); medicalFileViewModel.onTabChanged(0); } }); @@ -582,7 +582,7 @@ class _MedicalFilePageState extends State { myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, width: 64.h, height: 64.h, - fit: BoxFit.fill, + fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: false, radius: 50.h), SizedBox(height: 8.h), Expanded( diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index 215836f8..a28f518d 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -30,10 +30,7 @@ class _PatientSickleavesListPageState extends State { void initState() { scheduleMicrotask(() { medicalFileViewModel.setIsPatientSickLeaveListLoading(true); - medicalFileViewModel.getPatientSickLeaveList(onError: (error) { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); + medicalFileViewModel.getPatientSickLeaveList(); }); super.initState(); } @@ -75,6 +72,7 @@ class _PatientSickleavesListPageState extends State { child: PatientSickLeaveCard( patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, isLoading: false, + isSickLeaveListPage: true, ).paddingSymmetrical(24.h, 0.0), ), ), diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 969e3f58..82c94413 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -5,6 +5,7 @@ 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/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -28,10 +29,7 @@ class _VaccineListPageState extends State { void initState() { scheduleMicrotask(() { medicalFileViewModel.setIsPatientVaccineListLoading(true); - medicalFileViewModel.getPatientVaccinesList(onError: (error) { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }); + medicalFileViewModel.getPatientVaccinesList(); }); super.initState(); } @@ -51,7 +49,11 @@ class _VaccineListPageState extends State { SizedBox(height: 16.h), ListView.separated( scrollDirection: Axis.vertical, - itemCount: medicalFileVM.isPatientVaccineListLoading ? 5 : medicalFileVM.patientVaccineList.length, + itemCount: medicalFileVM.isPatientVaccineListLoading + ? 5 + : medicalFileVM.patientVaccineList.isNotEmpty + ? medicalFileVM.patientVaccineList.length + : 1, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.only(left: 24.h, right: 24.h), @@ -102,8 +104,9 @@ class _VaccineListPageState extends State { ), ), ) - : AnimationConfiguration.staggeredList( - position: index, + : medicalFileVM.patientVaccineList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, duration: const Duration(milliseconds: 1000), child: SlideAnimation( verticalOffset: 100.0, @@ -166,7 +169,8 @@ class _VaccineListPageState extends State { ), ), ), - ); + ) + : Utils.getNoDataWidget(context, noDataText: "No vaccines data found...".needTranslation); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index 766ef6de..e396d62e 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -54,7 +54,7 @@ class LabRadCard extends StatelessWidget { SizedBox.shrink(), 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) + child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor) .toShimmer2(isShow: false, radius: 12.h), ), ], 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 1f6c0bfc..8fc38050 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -23,11 +23,12 @@ import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; class PatientSickLeaveCard extends StatelessWidget { - PatientSickLeaveCard({super.key, required this.patientSickLeavesResponseModel, this.isLoading = false}); + PatientSickLeaveCard({super.key, required this.patientSickLeavesResponseModel, this.isLoading = false, this.isSickLeaveListPage = false}); late MedicalFileViewModel medicalFileViewModel; PatientSickLeavesResponseModel patientSickLeavesResponseModel; bool isLoading; + bool isSickLeaveListPage = false; @override Widget build(BuildContext context) { @@ -128,7 +129,7 @@ class PatientSickLeaveCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), ), SizedBox(width: 8.h), - Expanded( + isSickLeaveListPage ? SizedBox.shrink() : Expanded( flex: 1, child: Container( height: 40.h, diff --git a/lib/widgets/in_app_browser/InAppBrowser.dart b/lib/widgets/in_app_browser/InAppBrowser.dart index 3fe0de9d..e8370e5b 100644 --- a/lib/widgets/in_app_browser/InAppBrowser.dart +++ b/lib/widgets/in_app_browser/InAppBrowser.dart @@ -219,12 +219,12 @@ class MyInAppBrowser extends InAppBrowser { // service.tamaraInsertRequest(tamaraRequestModel, context).then((res) { // // if (context != null) GifLoaderDialogUtils.hideDialog(context); - // generateTamaraURL(amount, orderDesc, transactionID, projId, emailId, paymentMethod, patientType, patientName, patientID, authenticatedUser, isLiveCareAppo, servID, LiveServID, appoDate, - // appoNo, clinicID, doctorID, "", installments) - // .then((value) { - // paymentType = _PAYMENT_TYPE.PATIENT; - // this.browser.openUrlRequest(urlRequest: URLRequest(url: WebUri.uri(Uri.parse(value))), options: _InAppBrowserOptions); - // }); + generateTamaraURL(amount, orderDesc, transactionID, projId, emailId, paymentMethod, patientType, patientName, patientID, authenticatedUser, isLiveCareAppo, servID, LiveServID, appoDate, appoNo, + clinicID, doctorID, "", installments) + .then((value) { + paymentType = _PAYMENT_TYPE.PATIENT; + this.browser.openUrlRequest(urlRequest: URLRequest(url: WebUri.uri(Uri.parse(value))), options: _InAppBrowserOptions); + }); // }).catchError((err) { // print(err); // // if (context != null) GifLoaderDialogUtils.hideDialog(context); @@ -333,12 +333,12 @@ class MyInAppBrowser extends InAppBrowser { form = form.replaceFirst('PROJECT_ID_VALUE', projId); form = form.replaceFirst('PAYMENT_OPTION_VALUE', paymentMethod); form = form.replaceFirst('LANG_VALUE', currentLanguageID); - form = form.replaceFirst('SERVICE_URL_VALUE', "https://mdlaboratories.com/tamaralive/Home/Checkout"); + form = form.replaceFirst('SERVICE_URL_VALUE', ApiConsts.TAMARA_URL); - form = form.replaceFirst('INSTALLMENTS_VALUE', installments); + form = form.replaceFirst('INSTALLMENTS_VALUE', "3"); form = form.replaceFirst('CUSTNATIONALID_VALUE', authUser.patientIdentificationNo!); form = form.replaceFirst('CUSTMOBILE_VALUE', authUser.mobileNumber!); - form = form.replaceFirst('CUSTDOB_VALUE', DateUtil.getDayMonthYearDateFormatted(authUser.strDateofBirth!)); + form = form.replaceFirst('CUSTDOB_VALUE', DateUtil.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(authUser.dateofBirth!))); form = form.replaceFirst('CURRENCY_VALUE', authUser.outSa == 0 ? "SAR" : "AED"); form = form.replaceFirst('COUNTRY_CODE_VALUE', authUser.outSa == 0 ? "966" : "971"); From bbf83d4dc9046061cc5e9a52e4d380e8495a8ec9 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 5 Oct 2025 13:27:53 +0300 Subject: [PATCH 18/29] bottom handled when dental and laser clinic is selected from clinic page --- .../appointment_via_region_viewmodel.dart | 11 ++++++++++- .../region_bottomsheet/region_list_widget.dart | 2 +- .../book_appointment/select_clinic_page.dart | 15 +++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 852e678c..ffa6ec16 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -17,7 +17,8 @@ enum AppointmentViaRegionState { enum RegionBottomSheetType{ FOR_REGION, - FOR_CLINIIC + REGION_FOR_DENTAL_AND_LASER, + FOR_CLINIIC, } class AppointmentViaRegionViewmodel extends ChangeNotifier { @@ -94,4 +95,12 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { page: SelectDoctorPage(), ),); } + + void handleLastStepForDentalAndLaser() { + //todo handle the routing here + navigationService.pop(); + navigationService.push(CustomPageRoute( + page: SelectDoctorPage(), + ),); + } } diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart index 09c7c40a..174fe101 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart @@ -24,7 +24,7 @@ class _RegionBottomSheetBodyState extends State { @override void initState() { scheduleMicrotask(() { - if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { + if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION || regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER ) { myAppointmentsViewModel.getRegionMappedProjectList(); } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { myAppointmentsViewModel.getMappedDoctors(); diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 36a490db..2c005d41 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -206,16 +206,17 @@ class _SelectClinicPageState extends State { Navigator.of(context).push( CustomPageRoute( page: SelectLivecareClinicPage(onNegativeClicked: (){ - handleDoctorScreen(); + handleDoctorScreen(clinic); },), ), ); } else { - handleDoctorScreen(); + handleDoctorScreen(clinic); } } - void handleDoctorScreen() { + //17 and 235 + void handleDoctorScreen(GetClinicsListResponseModel clinic) { if (widget.isFromRegionFlow) { Navigator.of(context).push( CustomPageRoute( @@ -223,7 +224,13 @@ class _SelectClinicPageState extends State { ), ); } else { - openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC); + + var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; + print("the cliininc id is ${clinic.clinicID}"); + if(clinic.clinicID == 17 || clinic.clinicID == 235) { + bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER; + } + openRegionListBottomSheet(context, bottomSheetType); } } From 903248133eef8f2af1f86b81e205dbddfa3ae8ec Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 5 Oct 2025 13:49:58 +0300 Subject: [PATCH 19/29] Lab order & results enhancements --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- .../book_appointment_page.dart | 107 +++++++++++++++++- .../book_appointment/select_clinic_page.dart | 1 + .../select_livecare_clinic_page.dart | 3 - lib/presentation/lab/lab_order_by_test.dart | 56 ++++----- lib/presentation/lab/lab_orders_page.dart | 2 +- .../lab/lab_result_item_view.dart | 6 +- 8 files changed, 138 insertions(+), 41 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 2efeeed9..35d63b40 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -174,7 +174,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 1018977; + // body['PatientID'] = 4767477; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9e1a9ba6..8225796c 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/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 516aff9e..b124438e 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -9,6 +9,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/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/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; @@ -22,6 +23,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_ 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'; 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' show showCommonBottomSheetWithoutHeight; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; @@ -43,9 +45,11 @@ class _BookAppointmentPageState extends State { late AppointmentViaRegionViewmodel regionalViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late ImmediateLiveCareViewModel immediateLiveCareViewModel; + late final AuthenticationViewModel authVM; @override void initState() { + authVM = context.read(); scheduleMicrotask(() { bookAppointmentsViewModel.selectedTabIndex = 0; bookAppointmentsViewModel.initBookAppointmentViewModel(); @@ -204,11 +208,12 @@ class _BookAppointmentPageState extends State { ).paddingSymmetrical(24.h, 0.h); case 1: //TODO: Get LiveCare type Select UI from Hussain - return Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, + return appState.isAuthenticated + ? Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: false, ), @@ -318,7 +323,8 @@ class _BookAppointmentPageState extends State { ), ), ], - ).paddingSymmetrical(24.h, 0.h); + ).paddingSymmetrical(24.h, 0.h) + : getLiveCareNotLoggedInUI(); default: SizedBox.shrink(); } @@ -375,4 +381,93 @@ class _BookAppointmentPageState extends State { }); } } + + Widget getLiveCareNotLoggedInUI() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.no_visit_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "No visit required".needTranslation.toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.doctor_contact_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.free_med_delivery_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 36.h), + CustomButton( + text: "Login to use this service".needTranslation, + onPressed: () async { + await authVM.onLoginPressed(); + }, + 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.login1, + iconColor: AppColors.whiteColor, + iconSize: 24.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h); + } } diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 36a490db..869fea39 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -215,6 +215,7 @@ class _SelectClinicPageState extends State { } } + // ClinicID 17 & Clinic ID 253 void handleDoctorScreen() { if (widget.isFromRegionFlow) { Navigator.of(context).push( diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 1e40357c..12a99bcb 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -6,12 +6,9 @@ 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/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; class SelectLivecareClinicPage extends StatelessWidget { diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index bb0f3915..4c0d2c3b 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -35,41 +35,43 @@ class LabOrderByTest extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // ...labOrder!.testDetails!.map((detail) { - Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '${tests!.description}'.toText14(weight: FontWeight.w500), - ), - - SizedBox(height: 12.h), - + '${tests!.description}'.toText16(isBold: true), + SizedBox(height: 4.h), + //TODO: Add test long description from the Lab Order API + "Measurement of ALT (alanine aminotransferase) level in blood used to assess liver functions".toText12(fontWeight: FontWeight.w500), + SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget( - richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true), - // chipType: ChipTypeEnum.lightBg, + richText: '${"Last Tested:".needTranslation} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(fontWeight: FontWeight.w500), backgroundColor: AppColors.greyLightColor, textColor: AppColors.textColor, - // borderRadius: 5, - ), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () { + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded(child: Container()), + Expanded( + child: CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () { onTap(); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, + }, + 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, + ), ), ], ), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 1c78bbdf..6b65d5de 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late LabRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; scheduleMicrotask(() { labProvider .getPatientLabResultByHospital( model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); }); Navigator.push(context,FadePage( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().length, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( onTap: () { if(model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult( model.uniqueTests .toList()[index] .model!, model.uniqueTests .toList()[index].description!); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late LabRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; scheduleMicrotask(() { labProvider .getPatientLabResultByHospital( model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); }); Navigator.push( context, CustomPageRoute( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().length, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( onTap: () { if(model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult( model.uniqueTests .toList()[index] .model!, model.uniqueTests .toList()[index].description!); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_item_view.dart b/lib/presentation/lab/lab_result_item_view.dart index 57a10aef..9269c6d2 100644 --- a/lib/presentation/lab/lab_result_item_view.dart +++ b/lib/presentation/lab/lab_result_item_view.dart @@ -81,8 +81,10 @@ class LabResultItemView extends StatelessWidget { AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100), ], ), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, + isLoading + ? SizedBox.shrink() + : Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.textColor, width: 20.h, height: 14.h, From 35e145ddf94408e8e3c01c6af6b6e298b3050f62 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 5 Oct 2025 14:20:39 +0300 Subject: [PATCH 20/29] id selection from the appropiate list --- .../hospital_bottom_sheet_body.dart | 5 +++-- .../book_appointment/select_clinic_page.dart | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index 44cc43eb..34714f45 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -100,10 +100,11 @@ class HospitalBottomSheetBody extends StatelessWidget { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); regionalViewModel.handleLastStepForRegion(); }else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { - regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); regionalViewModel.handleLastStepForClinic(); - + }else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForDentalAndLaser(); } });}, separatorBuilder: (_, __) => SizedBox( diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 2c005d41..285dcfc3 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -260,7 +260,21 @@ class _SelectClinicPageState extends State { return HospitalBottomSheetBody(); } if(data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION){ - bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID.toString()); + //if the region screen is opened for the dental clinic thenthe project id will be in the hospital list as the list is formed form the get project api + var id = ""; + if (data.regionBottomSheetType == + RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + id = regionalViewModel.selectedHospital?.hospitalList?.first?.iD + ?.toString() ?? + ""; + } else { + id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList + ?.first?.projectID + ?.toString() ?? + ""; + } + bookAppointmentsViewModel.setProjectID(id); + return SizedBox.shrink(); } else { return SizedBox.shrink(); From 415c0eb3c72897bd40f11a796524ef6791da2013 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 5 Oct 2025 17:16:25 +0300 Subject: [PATCH 21/29] Dental appointment booking implementation contd. --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 25 ++-- lib/core/utils/utils.dart | 6 +- .../book_appointments_repo.dart | 44 ++++++ .../book_appointments_view_model.dart | 42 +++++- ...et_patient_dental_plan_response_model.dart | 40 ++++++ .../book_appointment/select_clinic_page.dart | 125 +++++++++++++++++- .../book_appointment/select_doctor_page.dart | 103 ++++++++------- lib/presentation/home/landing_page.dart | 4 +- 9 files changed, 317 insertions(+), 74 deletions(-) create mode 100644 lib/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 35d63b40..9140a13c 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -174,7 +174,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4767477; + // body['PatientID'] = 4767884; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 8225796c..511cb371 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -746,6 +746,8 @@ class ApiConsts { static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + // var payFortEnvironment = FortEnvironment.test; // var applePayMerchantId = "merchant.com.hmgwebservices.uat"; @@ -764,42 +766,41 @@ class ApiConsts { payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; - // GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; break; case AppEnvironmentTypeEnum.preProd: - + baseUrl = "https://webservices.hmg.com/"; payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; - TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://mdlaboratories.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamara/Home/GetInstallments"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; break; } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index f3a53b91..2bff2ac4 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -323,7 +323,7 @@ class Utils { children: [ Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true), SizedBox(height: 8.h), ], ).center; @@ -355,7 +355,7 @@ class Utils { ).center; } - static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Function? onConfirmTap, Function? onCancelTap}) { + static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -364,6 +364,8 @@ class Utils { SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), + bodyWidget ?? SizedBox.shrink(), + SizedBox(height: 16.h), isShowActionButtons ? Row( children: [ diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 43a7eb3b..f15c5ea1 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -10,6 +10,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_patient_dental_plan_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'; @@ -68,6 +69,9 @@ abstract class BookAppointmentsRepo { required int serviceID, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> getPatientDentalEstimation( + {required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -656,4 +660,44 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientDentalEstimation( + {required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "ProjectID": projectID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + HAS_DENTAL_PLAN, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_IsPatientHasOnGoingEstimation']; + final estimationList = list.map((item) => PatientDentalPlanEstimationResponseModel.fromJson(item as Map)).toList().cast(); + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: estimationList, + ); + } 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 ad59a277..f04394bf 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_patient_dental_plan_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'; @@ -55,6 +56,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { List liveCareDoctorsList = []; + List patientDentalPlanEstimationList = []; + int totalTimeNeededForDentalProcedure = 0; + bool isContinueDentalPlan = false; + GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(); DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); GetLiveCareClinicsResponseModel selectedLiveCareClinic = GetLiveCareClinicsResponseModel(); @@ -128,6 +133,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { clinicsList.clear(); doctorsList.clear(); liveCareClinicsList.clear(); + patientDentalPlanEstimationList.clear(); + isContinueDentalPlan = false; // getLocation(); notifyListeners(); } @@ -275,11 +282,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { } //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName - Future getDoctorsList( - {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; - final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); + final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName, isContinueDentalPlan: isContinueDentalPlan); result.fold( (failure) async { @@ -901,4 +907,34 @@ class BookAppointmentsViewModel extends ChangeNotifier { filteredDoctorList = getDoctorListAsPerSelection(); notifyListeners(); } + + Future getPatientDentalEstimation({required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientDentalPlanEstimationList.clear(); + totalTimeNeededForDentalProcedure = 0; + isContinueDentalPlan = false; + notifyListeners(); + + final result = await bookAppointmentsRepo.getPatientDentalEstimation(projectID: projectID); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientDentalPlanEstimationList = apiResponse.data!; + + patientDentalPlanEstimationList.forEach((v) { + totalTimeNeededForDentalProcedure += (v.neededTime ?? 0); + }); + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart b/lib/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart new file mode 100644 index 00000000..6b90199f --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart @@ -0,0 +1,40 @@ +class PatientDentalPlanEstimationResponseModel { + dynamic setupID; + dynamic estimationNo; + int? projectID; + String? procedureId; + int? patientID; + int? sequenceNo; + int? neededTime; + String? procedureName; + String? procedureNameN; + + PatientDentalPlanEstimationResponseModel( + {this.setupID, this.estimationNo, this.projectID, this.procedureId, this.patientID, this.sequenceNo, this.neededTime, this.procedureName, this.procedureNameN}); + + PatientDentalPlanEstimationResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + estimationNo = json['EstimationNo']; + projectID = json['ProjectID']; + procedureId = json['ProcedureId']; + patientID = json['PatientID']; + sequenceNo = json['sequenceNo']; + neededTime = json['NeededTime']; + procedureName = json['ProcedureName']; + procedureNameN = json['ProcedureNameN']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['EstimationNo'] = this.estimationNo; + data['ProjectID'] = this.projectID; + data['ProcedureId'] = this.procedureId; + data['PatientID'] = this.patientID; + data['sequenceNo'] = this.sequenceNo; + data['NeededTime'] = this.neededTime; + data['ProcedureName'] = this.procedureName; + data['ProcedureNameN'] = this.procedureNameN; + return data; + } +} diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index e1bbf1fe..18e6b4b6 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -23,8 +23,11 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/select_livecar import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/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/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/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -216,16 +219,124 @@ class _SelectClinicPageState extends State { } //17 and 235 - void handleDoctorScreen(GetClinicsListResponseModel clinic) { + void handleDoctorScreen(GetClinicsListResponseModel clinic) async { if (widget.isFromRegionFlow) { - Navigator.of(context).push( - CustomPageRoute( - page: SelectDoctorPage(), - ), - ); + //Dental Clinic Flow + if (clinic.clinicID == 17) { + LoaderBottomSheet.showLoader(loadingText: "Checking for an existing dental plan, Please wait...".needTranslation); + await bookAppointmentsViewModel.getPatientDentalEstimation(projectID: int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")).then((value) { + LoaderBottomSheet.hideLoader(); + if (bookAppointmentsViewModel.patientDentalPlanEstimationList.isNotEmpty) { + showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: context), + title: "Dental treatment plan".needTranslation, + context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "You have an existing treatment plan: ".needTranslation.toText14(weight: FontWeight.w500), + SizedBox(height: 8.h), + Container( + width: double.infinity, + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsViewModel.patientDentalPlanEstimationList.length, + separatorBuilder: (_, __) { + return Column( + children: [ + SizedBox(height: 8.h), + Divider(height: 1, color: AppColors.greyColor), + SizedBox(height: 8.h), + ], + ); + }, + itemBuilder: (context, index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} Mins".needTranslation), + ], + ); + }, + ), + SizedBox( + height: 16.h, + ), + Divider(height: 1, color: AppColors.greyColor), + SizedBox( + height: 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Total time required".needTranslation.toText14(isBold: true), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 Mins".needTranslation), + ], + ) + ], + ), + ), + SizedBox(height: 16.h), + "Would you like to continue it?".needTranslation.toText14(weight: FontWeight.w500), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async {}, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ) + ], + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else { + // Navigate to Chief Complaint Screen + } + }); + } else { + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), + ), + ); + } } else { var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; - print("the cliininc id is ${clinic.clinicID}"); if (clinic.clinicID == 17 || clinic.clinicID == 235) { bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER; } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 59756317..8d24b43b 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -89,11 +89,7 @@ class _SelectDoctorPageState extends State { bookAppointmentsViewModel.filterClinics(""); textFocusNode.unfocus(); }, - child: Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - width: 20.h, - height: 20.h, - fit: BoxFit.scaleDown), + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), ) : null, onChange: (value) { @@ -111,8 +107,11 @@ class _SelectDoctorPageState extends State { padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: - bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList.length : bookAppointmentsVM.doctorsList.length), + itemCount: bookAppointmentsVM.isDoctorsListLoading + ? 5 + : (bookAppointmentsVM.isLiveCareSchedule + ? (bookAppointmentsVM.liveCareDoctorsList.isNotEmpty ? bookAppointmentsVM.liveCareDoctorsList.length : 1) + : (bookAppointmentsVM.doctorsList.isNotEmpty ? bookAppointmentsVM.doctorsList.length : 1)), itemBuilder: (context, index) { return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( @@ -120,47 +119,49 @@ class _SelectDoctorPageState extends State { isLoading: true, bookAppointmentsViewModel: bookAppointmentsViewModel, ) - : 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: DoctorCard( - doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index], - isLoading: false, - bookAppointmentsViewModel: bookAppointmentsViewModel, - ).onPress(() async { - bookAppointmentsVM - .setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]); - // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); - LoaderBottomSheet.showLoader(); - await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorProfilePage(), - ), - ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), + : checkIsDoctorsListEmpty() + ? Utils.getNoDataWidget(context, noDataText: "No Doctor found for selected criteria...".needTranslation) + : 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: DoctorCard( + doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index], + isLoading: false, + bookAppointmentsViewModel: bookAppointmentsViewModel, + ).onPress(() async { + bookAppointmentsVM + .setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]); + // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); + LoaderBottomSheet.showLoader(); + await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + ), + ), ), - ), - ), - ); + ); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), @@ -173,4 +174,12 @@ class _SelectDoctorPageState extends State { ), ); } + + bool checkIsDoctorsListEmpty() { + if (bookAppointmentsViewModel.isLiveCareSchedule) { + return bookAppointmentsViewModel.liveCareDoctorsList.isEmpty; + } else { + return bookAppointmentsViewModel.doctorsList.isEmpty; + } + } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f28beed7..db8a0684 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -135,8 +135,8 @@ class _LandingPageState extends State { fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, ), Row( mainAxisSize: MainAxisSize.min, From 6cf08c04d29106410510435a1cfaf827b2f33040 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 6 Oct 2025 09:57:12 +0300 Subject: [PATCH 22/29] appointment filters added. --- assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 4 +- .../models/appointemnet_filters.dart | 17 + .../my_appointments_view_model.dart | 157 +++++++- lib/generated/locale_keys.g.dart | 1 + lib/main.dart | 6 +- .../appointments/my_appointments_page.dart | 350 +++++++----------- .../widgets/AppointmentFilter.dart | 48 +++ lib/presentation/home/landing_page.dart | 1 - lib/presentation/lab/lab_orders_page.dart | 6 +- .../lab/lab_results/lab_result_details.dart | 12 +- lib/theme/colors.dart | 1 + lib/widgets/chip/app_custom_chip_widget.dart | 4 +- .../date_range_calender.dart} | 40 +- .../viewmodel/date_range_view_model.dart} | 12 +- 15 files changed, 420 insertions(+), 242 deletions(-) create mode 100644 lib/features/my_appointments/models/appointemnet_filters.dart create mode 100644 lib/presentation/appointments/widgets/AppointmentFilter.dart rename lib/{presentation/lab/lab_results/lab_result_calender.dart => widgets/date_range_selector/date_range_calender.dart} (90%) rename lib/{features/lab/lab_range_view_model.dart => widgets/date_range_selector/viewmodel/date_range_view_model.dart} (94%) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index b08e7830..cc114c8c 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -864,5 +864,6 @@ "regionAndLocation": "المنطقة والمواقع", "clearAllFilters": "مسح جميع الفلاتر", "filters": "فلاتر", - "searchClinic": "بحث عن عيادة" + "searchClinic": "بحث عن عيادة", + "walkin": "زيارة بدون موعد" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index adab1d5c..d0069a3a 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -860,5 +860,7 @@ "regionAndLocation": "Region And Locations", "clearAllFilters": "Clear all filters", "filters": "Filters", - "searchClinic": "Search Clinic" + "searchClinic": "Search Clinic", + "walkin": "Walk In" + } \ No newline at end of file diff --git a/lib/features/my_appointments/models/appointemnet_filters.dart b/lib/features/my_appointments/models/appointemnet_filters.dart new file mode 100644 index 00000000..e157af4a --- /dev/null +++ b/lib/features/my_appointments/models/appointemnet_filters.dart @@ -0,0 +1,17 @@ +import 'package:hmg_patient_app_new/core/app_assets.dart'; + +enum AppointmentListingFilters{ + WALKIN("walkin", AppAssets.walkin_appointment_icon), + BOOKED("booked", AppAssets.calendar), + CONFIRMED("confirmed", AppAssets.calendar), + ARRIVED("arrived", AppAssets.calendar), + LIVECARE("livecare", AppAssets.small_livecare_icon), + DATESELECTION("",AppAssets.calendar, trailingIcon: AppAssets.arrow_down); + + final String labelText; + final String leadingIcon; + final String trailingIcon; + + const AppointmentListingFilters(this.labelText, this.leadingIcon, + {this.trailingIcon = ""}); +} \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index c553f0ee..99fbf608 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.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/features/my_appointments/models/appointemnet_filters.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/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper; class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; + int previouslySelectedTab = -1; MyAppointmentsRepo myAppointmentsRepo; ErrorHandlerService errorHandlerService; @@ -21,7 +25,14 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentDataToBeLoaded = true; + List availableFilters = []; + List? selectedFilter = []; + bool isDateFilterSelected = false; + DateTime? start =null; + DateTime? end =null; + List patientAppointmentsHistoryList = []; + List filteredAppointmentList = []; List patientUpcomingAppointmentsHistoryList = []; List patientArrivedAppointmentsHistoryList = []; @@ -35,6 +46,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { + previouslySelectedTab = selectedTabIndex; selectedTabIndex = index; notifyListeners(); } @@ -132,10 +144,43 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); - + filteredAppointmentList.addAll(patientAppointmentsHistoryList); print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); print('All Appointments: ${patientAppointmentsHistoryList.length}'); + getFiltersForSelectedAppointmentList(filteredAppointmentList); + } + + void getFiltersForSelectedAppointmentList( + List filteredAppointmentList) { + availableFilters.clear(); + if (filteredAppointmentList.isEmpty == true) return; + availableFilters.add(AppointmentListingFilters.DATESELECTION); + if (filteredAppointmentList + .any((element) => element.isLiveCareAppointment == true)) { + availableFilters.add(AppointmentListingFilters.LIVECARE); + } + + if (filteredAppointmentList + .any((element) => element.isLiveCareAppointment == false)) { + availableFilters.add(AppointmentListingFilters.WALKIN); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isArrived(element) == true)) { + availableFilters.add(AppointmentListingFilters.ARRIVED); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isBooked(element) == true)) { + availableFilters.add(AppointmentListingFilters.BOOKED); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isConfirmed(element) == true)) { + availableFilters.add(AppointmentListingFilters.CONFIRMED); + } + notifyListeners(); } Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, bool isLiveCareAppointment, {Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -337,4 +382,114 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + void updateListWRTTab(int index) { + isDateFilterSelected = false; + selectedFilter = []; + // if(previouslySelectedTab == selectedTabIndex ) return; + switch (index) { + case 0: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientAppointmentsHistoryList); + break; + case 1: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList); + break; + case 2: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList); + break; + } + getFiltersForSelectedAppointmentList(filteredAppointmentList); + notifyListeners(); + } + + void setSelectedFilter(AppointmentListingFilters availableFilter) { + if (selectedFilter?.contains(availableFilter) == true) { + selectedFilter?.remove(availableFilter); + notifyListeners(); + + return; + } + selectedFilter?.add(availableFilter) ; + notifyListeners(); + } + + void getSelectedDateRange(DateTime? start, DateTime? end) { + this.start = start; + this.end = end; + isDateFilterSelected = true; + List sourceList = []; + if (selectedTabIndex == 0) { + sourceList = patientAppointmentsHistoryList; + } else if (selectedTabIndex == 1) { + sourceList = patientUpcomingAppointmentsHistoryList; + } else if (selectedTabIndex == 2) { + sourceList = patientArrivedAppointmentsHistoryList; + } + // if (isDateFilterSelected) sourceList = filteredAppointmentList; + if (start == null && end == null) { + isDateFilterSelected = false; + filteredAppointmentList.clear(); + sourceList.forEach((element) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + }); + filteredAppointmentList.addAll(sourceList); + } else { + filteredAppointmentList.clear(); + sourceList.forEach((element) { + try { + var dateTime = DateUtil.convertStringToDate(element.appointmentDate); + if (start != null && end == null) { + if (dateTime.isAtSameMomentAs(start)) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + } + } else if (start != null && end != null) { + if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + } + } + } catch (e) {} + }); + } + notifyListeners(); + } + + void filterTheListAsPerSelection() { + getSelectedDateRange(start, end); + } + + bool isUnderFilter(PatientAppointmentHistoryResponseModel element) { + bool isUnderTheFilter = false; + if (selectedFilter == null || selectedFilter!.isEmpty) return true; + int count = 0; + for (var filter in selectedFilter ?? []) { + switch (filter) { + case AppointmentListingFilters.WALKIN: + if (element.isLiveCareAppointment == false) return true; + case AppointmentListingFilters.BOOKED: + if (AppointmentType.isBooked(element))return true; + + case AppointmentListingFilters.CONFIRMED: + if (AppointmentType.isConfirmed(element))return true; + + case AppointmentListingFilters.ARRIVED: + if (AppointmentType.isArrived(element))return true; + + case AppointmentListingFilters.LIVECARE: + if (element.isLiveCareAppointment == true) return true; + + case AppointmentListingFilters.DATESELECTION: + + } + } + return false; + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9447bd11..724ae2ff 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -863,5 +863,6 @@ abstract class LocaleKeys { static const clearAllFilters = 'clearAllFilters'; static const filters = 'filters'; static const searchClinic = 'searchClinic'; + static const walkin = 'walkin'; } diff --git a/lib/main.dart b/lib/main.dart index cdfcc135..ac11f8fe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,7 +15,6 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode 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'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; @@ -28,6 +27,7 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -163,8 +163,8 @@ void main() async { navigationService: getIt(), appState: getIt())), ChangeNotifierProvider( create: (_) => LabHistoryViewModel()), - ChangeNotifierProvider( - create: (_) => LabRangeViewModel()) , + ChangeNotifierProvider( + create: (_) => DateRangeSelectorRangeViewModel()) , ChangeNotifierProvider( create: (_) => DoctorFilterViewModel()) ], child: MyApp()), diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 2518c880..c9392342 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -8,19 +8,26 @@ 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/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/AppointmentFilter.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.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:provider/provider.dart'; +import '../../widgets/common_bottom_sheet.dart' + show showCommonBottomSheetWithoutHeight; + class MyAppointmentsPage extends StatefulWidget { const MyAppointmentsPage({super.key}); @@ -61,6 +68,8 @@ class _MyAppointmentsPageState extends State { ], onTabChange: (index) { myAppointmentsViewModel.onTabChange(index); + myAppointmentsViewModel.updateListWRTTab(index); + context.read().flush(); }, ).paddingSymmetrical(24.h, 0.h), Consumer(builder: (context, myAppointmentsVM, child) { @@ -74,218 +83,147 @@ class _MyAppointmentsPageState extends State { } Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) { - switch (index) { - case 0: - //All Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Expandable list - ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - isLoading: true, - isFromHomePage: false, - ), - ).paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.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: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - isLoading: false, - isFromHomePage: false, - ), - ).paddingSymmetrical(24.h, 0.h), + return getAppointList( + myAppointmentsVM, myAppointmentsVM.filteredAppointmentList); + } + + Widget getAppointList(MyAppointmentsViewModel myAppointmentsVM, + List filteredAppointmentList) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + Visibility( + visible: myAppointmentsVM.availableFilters.isNotEmpty, + child: getAppointmentFilters(myAppointmentsVM)), + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : filteredAppointmentList.isNotEmpty + ? filteredAppointmentList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: + PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + isLoading: true, + isFromHomePage: false, + ), + ).paddingSymmetrical(24.h, 0.h) + : filteredAppointmentList.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: AppointmentCard( + patientAppointmentHistoryResponseModel: + filteredAppointmentList[index], + myAppointmentsViewModel: + myAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); - case 1: - //Upcoming Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.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, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientUpcomingAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget( + context, + noDataText: "You don't have any appointments yet." + .needTranslation, + callToActionButton: CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: BookAppointmentPage(), ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); - case 2: - //Completed Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ).paddingSymmetrical(48.h, 0.h), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => + SizedBox(height: 16.h), + ), + ], + ); + } + + Widget getAppointmentFilters(MyAppointmentsViewModel myAppointmentsVM) { + return SizedBox( + height: 56.h, + child: Row( children: [ - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientArrivedAppointmentsHistoryList.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, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), + Expanded( + child: ListView.separated( + separatorBuilder: (_, index) => SizedBox( + width: 8.h, + ), + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.availableFilters.length, + itemBuilder: (_, index) => AppointmentFilters( + selectedFilter: myAppointmentsVM.selectedFilter, + item: myAppointmentsVM.availableFilters[index], + onClicked: () { + if (myAppointmentsVM.availableFilters[index] == + AppointmentListingFilters.DATESELECTION) { + showCommonBottomSheetWithoutHeight( + title: "Set The Date Range".needTranslation, + context, + child: DateRangeSelector( + onRangeSelected: (start, end) { + // if (start != null) { + myAppointmentsVM.getSelectedDateRange( + start, end); + // } + }, ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + } else { + myAppointmentsVM.setSelectedFilter( + myAppointmentsVM.availableFilters[index]); + myAppointmentsVM.filterTheListAsPerSelection(); + } + }, + )), ), ], - ); - default: - return Container(); - } + )).paddingOnly(top: 24.h, left: 24.h, right: 24.h); } } diff --git a/lib/presentation/appointments/widgets/AppointmentFilter.dart b/lib/presentation/appointments/widgets/AppointmentFilter.dart new file mode 100644 index 00000000..fd2a9fe6 --- /dev/null +++ b/lib/presentation/appointments/widgets/AppointmentFilter.dart @@ -0,0 +1,48 @@ +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:smooth_corner/smooth_corner.dart'; + +class AppointmentFilters extends StatelessWidget { + final AppointmentListingFilters item; + final List? selectedFilter; + final VoidCallback onClicked; + + const AppointmentFilters( + {super.key, + required this.item, + required this.onClicked, + required this.selectedFilter}); + + @override + Widget build(BuildContext context) { + return AppCustomChipWidget( + backgroundColor: selectedFilter?.contains(item) == true?AppColors.chipSecondaryLightRedColor:AppColors.whiteColor, + icon: item.leadingIcon, + textColor: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor: AppColors.blackColor, + labelText: item.labelText.isNotEmpty?item.labelText.tr():"", + iconHasColor: true, + iconColor: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor:AppColors.blackColor, + iconSize: 16, + deleteIcon: item.trailingIcon, + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: 0.h), + padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.h), + deleteIconSize: Size(18.h, 18.h), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(10 ), + smoothness: 10, + side: BorderSide( + color: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor + : AppColors.borderGrayColor, + width: 1), + )).onPress(onClicked); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 9443ee2f..abdaac57 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -32,7 +32,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card. import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; -import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6fd82347..3f6ab996 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -9,7 +9,6 @@ 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/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; @@ -18,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; @@ -32,7 +32,7 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; - late LabRangeViewModel rangeViewModel; + late DateRangeSelectorRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; @@ -50,7 +50,7 @@ class _LabOrdersPageState extends State { @override Widget build(BuildContext context) { labProvider = Provider.of(context); - rangeViewModel = Provider.of(context); + rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 3e9558ca..5030d37e 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -6,10 +6,10 @@ 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/lab/lab_range_view_model.dart' show LabRangeViewModel; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; 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/lab_results/lab_result_calender.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_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'; @@ -133,7 +133,7 @@ class LabResultDetails extends StatelessWidget { ], )); - Widget LabGraph(BuildContext context) => Consumer( + Widget LabGraph(BuildContext context) => Consumer( builder: (_, model, ___) => Consumer( builder: (_, labmodel, ___) => Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -179,7 +179,7 @@ class LabResultDetails extends StatelessWidget { showCommonBottomSheetWithoutHeight( title: "Set The Date Range".needTranslation, context, - child: LabResultCalender( + child: DateRangeSelector( onRangeSelected: (start, end) { // if (start != null) { @@ -227,7 +227,7 @@ class LabResultDetails extends StatelessWidget { ); } - Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) { + Widget historyBody(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { if(model.isGraphVisible){ var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); return CustomGraph( @@ -280,7 +280,7 @@ class LabResultDetails extends StatelessWidget { } } - Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) { + Widget labHistoryList(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { return SizedBox( height: 180.h, child: ListView.separated( diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 291f81db..55f2b2a5 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -29,6 +29,7 @@ class AppColors { static const Color bgRedLightColor = Color(0xFFFEE9EA); static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); + static const Color borderGrayColor = Color(0x332E3039); static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color chipBorderColorOpacity20 = Color(0x332E3039); diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index d11b8f40..751b01c1 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,6 +24,7 @@ class AppCustomChipWidget extends StatelessWidget { this.deleteIconColor = AppColors.textColor, this.deleteIconHasColor = false, this.padding = EdgeInsets.zero, + this.labelPadding , }); final String? labelText; @@ -40,6 +41,7 @@ class AppCustomChipWidget extends StatelessWidget { final bool deleteIconHasColor; final OutlinedBorder? shape; final EdgeInsets? padding; + final EdgeInsetsDirectional? labelPadding; @override Widget build(BuildContext context) { @@ -62,7 +64,7 @@ class AppCustomChipWidget extends StatelessWidget { // padding: EdgeInsets.all(0.0), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + labelPadding: labelPadding??EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, shape: shape ?? SmoothRectangleBorder( diff --git a/lib/presentation/lab/lab_results/lab_result_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart similarity index 90% rename from lib/presentation/lab/lab_results/lab_result_calender.dart rename to lib/widgets/date_range_selector/date_range_calender.dart index b118293d..1a551f3e 100644 --- a/lib/presentation/lab/lab_results/lab_result_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -7,32 +7,32 @@ import 'package:hmg_patient_app_new/core/app_export.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/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/Range.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; typedef OnRangeSelected = void Function(DateTime? start, DateTime? end); -class LabResultCalender extends StatefulWidget { +class DateRangeSelector extends StatefulWidget { final OnRangeSelected onRangeSelected; - const LabResultCalender({super.key, required this.onRangeSelected}); + const DateRangeSelector({super.key, required this.onRangeSelected}); @override - State createState() => _LabResultCalenderState(); + State createState() => _DateRangeSelectorState(); } -class _LabResultCalenderState extends State { +class _DateRangeSelectorState extends State { late DateRangePickerController _calendarController; DateTime? start; DateTime? end; - late LabRangeViewModel model; + late DateRangeSelectorRangeViewModel model; @override void initState() { _calendarController = DateRangePickerController(); @@ -44,13 +44,17 @@ class _LabResultCalenderState extends State { @override Widget build(BuildContext context) { - model = Provider.of(context); + model = Provider.of(context); + + + _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + return Padding( padding: EdgeInsets.symmetric(horizontal: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( + Consumer( builder: (_, model, __) => selectionChip(model), ).paddingOnly(bottom: 16.h), Container( @@ -135,13 +139,19 @@ class _LabResultCalenderState extends State { ), onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + print("the value is ${args.value}"); if (args.value is PickerDateRange) { final PickerDateRange range = args.value; start = range.startDate; end = range.endDate; model.fromDate = start; model.toDate = end; - model.resetCurrentlySelectedRange(); + if(end == null) { + scheduleMicrotask((){ + model.resetCurrentlySelectedRange(); + }); + + } } }, ), @@ -151,7 +161,7 @@ class _LabResultCalenderState extends State { ), Row( children: [ - Consumer( + Consumer( builder: (_, model, __) => Visibility( visible: (model.fromDate != null || model.toDate != null), child: Expanded( @@ -164,6 +174,8 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; _calendarController.selectedDate = null; model.flush(); + Navigator.of(context).pop(); + widget.onRangeSelected(null, null); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -203,7 +215,7 @@ class _LabResultCalenderState extends State { } fromDateComponent() { - return Consumer( + return Consumer( builder: (_, model, __) { return displayDate("Start Date".needTranslation, model.getDateString(model.fromDate), model.fromDate == null); @@ -212,7 +224,7 @@ class _LabResultCalenderState extends State { } toDateComponent() { - return Consumer( + return Consumer( builder: (_, model, __) { return displayDate("End Date".needTranslation, model.getDateString(model.toDate), model.toDate == null); @@ -254,7 +266,7 @@ class _LabResultCalenderState extends State { ), ); - selectionChip(LabRangeViewModel model) { + selectionChip(DateRangeSelectorRangeViewModel model) { return Row( spacing: 8.h, children: [ @@ -311,6 +323,7 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_6MONTH; model.calculateDatesFromRange(); + }), AppCustomChipWidget( labelText: "Year ${model.getCurrentYear}", @@ -329,6 +342,7 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.THIS_YEAR; model.calculateDatesFromRange(); + }), ], ); diff --git a/lib/features/lab/lab_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart similarity index 94% rename from lib/features/lab/lab_range_view_model.dart rename to lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index aa919643..42f222bd 100644 --- a/lib/features/lab/lab_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -2,7 +2,7 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/lab/models/Range.dart'; -class LabRangeViewModel extends ChangeNotifier { +class DateRangeSelectorRangeViewModel extends ChangeNotifier { List months = [ 'Jan', 'Feb', @@ -24,8 +24,6 @@ class LabRangeViewModel extends ChangeNotifier { set currentlySelectedRange(Range? value) { _currentlySelectedRange = value; - notifyListeners(); - } DateTime? _toDate; @@ -34,7 +32,7 @@ class LabRangeViewModel extends ChangeNotifier { set toDate(DateTime? value) { _toDate = value; - notifyListeners(); + } DateTime? _fromDate; @@ -43,11 +41,11 @@ class LabRangeViewModel extends ChangeNotifier { set fromDate(DateTime? value) { _fromDate = value; - notifyListeners(); + } - LabRangeViewModel(); + DateRangeSelectorRangeViewModel(); get getCurrentYear => DateTime.now().year; @@ -65,6 +63,7 @@ class LabRangeViewModel extends ChangeNotifier { _fromDate = DateTime(_toDate!.year, DateTime.january, 01); default: } + notifyListeners(); } getDateString(DateTime? date){ @@ -84,6 +83,7 @@ class LabRangeViewModel extends ChangeNotifier { resetCurrentlySelectedRange(){ currentlySelectedRange = null; + notifyListeners(); } alterGraphVisibility(){ From 2b21086cc17d210d91d4b6b22237d859ab0f8906 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 10:19:53 +0300 Subject: [PATCH 23/29] Dental appointment booking flow implementation contd. --- lib/core/api/api_client.dart | 4 ++-- .../book_appointments_view_model.dart | 5 +++++ lib/features/lab/lab_view_model.dart | 20 ++++++++++++------- .../patient_lab_orders_response_model.dart | 9 +++++++-- .../book_appointment/select_clinic_page.dart | 10 +++++++++- lib/presentation/lab/lab_order_by_test.dart | 7 ++++--- lib/presentation/lab/lab_orders_page.dart | 2 +- .../LabResultList.dart | 4 ++-- .../lab_order_result_item.dart | 11 ++-------- .../lab/lab_results/lab_result_details.dart | 3 ++- 10 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 9140a13c..cd7b6733 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -173,8 +173,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4767884; + body['TokenID'] = "@dm!n"; + body['PatientID'] = 4767884; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index f04394bf..5e0d424e 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -196,6 +196,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsContinueDentalPlan(bool value) { + isContinueDentalPlan = value; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 1bbc27c7..e7507b07 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -58,7 +58,7 @@ class LabViewModel extends ChangeNotifier { List get labSuggestions => _labSuggestionsList; - Set uniqueTests = {}; + Set uniqueTests = {}; double maxY = 0.0; double maxX = double.infinity; @@ -78,6 +78,11 @@ class LabViewModel extends ChangeNotifier { } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientLabOrders.clear(); + uniqueTests.clear(); + uniqueTests = {}; + notifyListeners(); + final result = await labRepo.getPatientLabOrders(); result.fold( @@ -132,11 +137,12 @@ class LabViewModel extends ChangeNotifier { } getUniqueTestDescription() { - - uniqueTests = { + uniqueTests = { for (var item in patientLabOrders) if (item.testDetails != null) ...?item.testDetails?.map((test) => TestDetails( + testDescriptionEn: test.testDescriptionEn.toString(), + testDescriptionAr: test.testDescriptionAr.toString(), description: test.description.toString(), testCode: test.testCode.toString(), testID: test.testID, @@ -170,8 +176,7 @@ class LabViewModel extends ChangeNotifier { ); } - Future getPatientLabResult( - PatientLabOrdersResponseModel laborder, String procedureName) async { + Future getPatientLabResult(PatientLabOrdersResponseModel laborder, String procedureName, String testDescription) async { LoaderBottomSheet.showLoader(); mainLabResults.clear(); filteredGraphValues.clear(); @@ -223,8 +228,9 @@ class LabViewModel extends ChangeNotifier { recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); // filteredGraphValues = [filteredGraphValues.first]; navigationService.push(MaterialPageRoute( - builder: (_) => - LabResultDetails(recentLabResult: recentResult))); + builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription), + ), + ); notifyListeners(); } }, diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart index aadfc769..4143498b 100644 --- a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart +++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart @@ -224,21 +224,26 @@ class PatientLabOrdersResponseModel { class TestDetails { String? description; + String? testDescriptionEn; + String? testDescriptionAr; String? testCode; String? testID; String? createdOn; PatientLabOrdersResponseModel? model; - TestDetails({this.description, this.testCode, this.testID, this.createdOn, this.model}); + + TestDetails({this.description, this.testDescriptionEn, this.testDescriptionAr, this.testCode, this.testID, this.createdOn, this.model}); TestDetails.fromJson(Map json) { description = json['Description']; + testDescriptionEn = json['TestDescriptionEn'] ?? ""; + testDescriptionAr = json['TestDescriptionAr'] ?? ""; testCode = json['TestCode']; testID = json['TestID']; createdOn = json['CreatedOn']; } Map toJson() { - final Map data = new Map(); + final Map data = {}; data['Description'] = this.description; data['TestCode'] = this.testCode; data['TestID'] = this.testID; diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 18e6b4b6..3326ef51 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -296,6 +296,7 @@ class _SelectClinicPageState extends State { child: CustomButton( text: LocaleKeys.cancel.tr(), onPressed: () { + bookAppointmentsViewModel.setIsContinueDentalPlan(false); Navigator.of(context).pop(); }, backgroundColor: AppColors.primaryRedColor, @@ -309,7 +310,14 @@ class _SelectClinicPageState extends State { Expanded( child: CustomButton( text: LocaleKeys.confirm.tr(), - onPressed: () async {}, + onPressed: () async { + bookAppointmentsViewModel.setIsContinueDentalPlan(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), + ), + ); + }, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, textColor: Colors.white, diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 4c0d2c3b..2b791ae1 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -2,6 +2,7 @@ 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_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -19,8 +20,9 @@ class LabOrderByTest extends StatelessWidget { final TestDetails? tests; final bool isLoading; final bool isExpanded; + final AppState appState; - const LabOrderByTest({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false}); + const LabOrderByTest({super.key, required this.onTap, required this.appState, this.tests, required this.index, this.isLoading = false, this.isExpanded = false}); @override build(BuildContext context) { @@ -37,8 +39,7 @@ class LabOrderByTest extends StatelessWidget { children: [ '${tests!.description}'.toText16(isBold: true), SizedBox(height: 4.h), - //TODO: Add test long description from the Lab Order API - "Measurement of ALT (alanine aminotransferase) level in blood used to assess liver functions".toText12(fontWeight: FontWeight.w500), + (appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(fontWeight: FontWeight.w500), SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6b65d5de..6311451e 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late LabRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; scheduleMicrotask(() { labProvider .getPatientLabResultByHospital( model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); }); Navigator.push( context, CustomPageRoute( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().length, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( onTap: () { if(model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult( model.uniqueTests .toList()[index] .model!, model.uniqueTests .toList()[index].description!); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late LabRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart index 9e1f08cc..d7642c2d 100644 --- a/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart +++ b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart @@ -26,8 +26,8 @@ class LabResultList extends StatelessWidget { shrinkWrap: true,itemCount: list.length,itemBuilder: (____, index) { var labItem = list[index]; return LabOrderResultItem(onTap: () { - model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description??""); - }, + model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description ?? "", labItem.packageShortDescription!); + }, tests: labItem, index: index, iconColor: model.getColor(labItem.calculatedResultFlag ?? "N"), diff --git a/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart index 59638de8..7dc4075e 100644 --- a/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart @@ -54,14 +54,7 @@ class LabOrderResultItem extends StatelessWidget { spacing: 6.h, crossAxisAlignment: CrossAxisAlignment.start, children: [ - - Text(severityText.tr(), - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 10.fSize, - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor - )), + severityText.tr().toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), Utils.buildSvgWithAssets( icon: AppAssets.lab_result_indicator, width: 21, @@ -82,7 +75,7 @@ class LabOrderResultItem extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 790199ae..4b31d0a9 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -25,8 +25,9 @@ class LabResultDetails extends StatelessWidget { // final List graphPoint; late LabViewModel model; + String? testDescription; - LabResultDetails({super.key, required this.recentLabResult}); + LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); @override Widget build(BuildContext context) { From b05e4e2a159f1b91d3d24a5e19bc02034c78608d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 10:30:04 +0300 Subject: [PATCH 24/29] Dental appointment booking flow implementation contd. --- .../book_appointments/book_appointments_repo.dart | 5 +++-- .../book_appointments/book_appointments_view_model.dart | 9 ++++++++- .../book_appointment/review_appointment_page.dart | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index f15c5ea1..82ca9212 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -24,7 +24,7 @@ abstract class BookAppointmentsRepo { Future>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); - Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}); Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); @@ -229,7 +229,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { //TODO: Implement the logic for Dental & laser clinics @override Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, - {Function(dynamic)? onSuccess, Function(String)? onError}) async { + {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { Map mapDevice = { "DoctorID": doctorId, "IsBookingForLiveCare": isBookingForLiveCare, @@ -238,6 +238,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { "OriginalClinicID": clinicID, "days": 0, "isReschadual": false, + "ContinueDentalPlan": continueDentalPlan }; try { diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 5e0d424e..a6c741f7 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -378,7 +378,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; - final result = await bookAppointmentsRepo.getDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError); + final result = await bookAppointmentsRepo.getDoctorFreeSlots( + selectedDoctor.clinicID ?? 0, + selectedDoctor.projectID ?? 0, + selectedDoctor.doctorID ?? 0, + isBookingForLiveCare, + continueDentalPlan: isContinueDentalPlan, + onError: onError, + ); result.fold( (failure) async { diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 280c4ad1..77cd249e 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -229,6 +229,7 @@ class _ReviewAppointmentPageState extends State { }); }); } else { + //TODO: Add patient Derma package check API Here await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { print(err.data["ErrorEndUserMessage"]); LoadingUtils.hideFullScreenLoader(); From 30eb8bf5dea3f8a9a51195c9ce0b3e41c12c362e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 12:34:18 +0300 Subject: [PATCH 25/29] dental booking flow implemented --- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 2 +- .../book_appointments_repo.dart | 99 +++++++ .../book_appointments_view_model.dart | 68 +++++ ...ental_chief_complaints_response_model.dart | 24 ++ .../facility_selection_item.dart | 2 +- .../region_bottomsheet/region_list_item.dart | 2 +- .../dental_chief_complaints_page.dart | 95 +++++++ .../book_appointment/select_clinic_page.dart | 263 ++++++++++-------- .../book_appointment/select_doctor_page.dart | 8 +- .../widgets/chief_complaint_card.dart | 38 +++ .../book_appointment/widgets/clinic_card.dart | 5 +- 12 files changed, 481 insertions(+), 129 deletions(-) create mode 100644 lib/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart create mode 100644 lib/presentation/book_appointment/dental_chief_complaints_page.dart create mode 100644 lib/presentation/book_appointment/widgets/chief_complaint_card.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index cd7b6733..9140a13c 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -173,8 +173,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 4767884; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4767884; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 511cb371..177a5418 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/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 82ca9212..437d2c8b 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; 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'; @@ -72,6 +73,12 @@ abstract class BookAppointmentsRepo { Future>>> getPatientDentalEstimation( {required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> getDentalChiefComplaintsList( + {required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, + {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -701,4 +708,96 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getDentalChiefComplaintsList( + {required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "PatientID": patientID, + "ClinicID": clinicID, + "ProjectID": projectID, + "isDentalAllowedBackend": true, + "ContinueDentalPlan": false, + "IsSearchAppointmnetByClinicID": false, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTORS_LIST_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DentalChiefComplain']; + + final chiefComplaintsList = list.map((item) => DentalChiefComplaintsListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: chiefComplaintsList, + ); + } 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>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "ProjectID": projectID, + "ChiefComplaintID": chiefComplaintID, + "isDentalAllowedBackend": true, + "IsPublicRequest": true, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_DENTAL_DOCTORS_LIST_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DentalDoctorChiefComplaintMapping']; + + final doctorsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: doctorsList, + ); + } 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 a6c741f7..26bfe4f0 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -12,6 +12,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/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; 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'; @@ -57,8 +58,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { List liveCareDoctorsList = []; List patientDentalPlanEstimationList = []; + List dentalChiefComplaintsList = []; int totalTimeNeededForDentalProcedure = 0; bool isContinueDentalPlan = false; + bool isChiefComplaintsListLoading = false; + int selectedChiefComplaintID = 0; GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(); DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); @@ -134,7 +138,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { doctorsList.clear(); liveCareClinicsList.clear(); patientDentalPlanEstimationList.clear(); + dentalChiefComplaintsList.clear(); isContinueDentalPlan = false; + isChiefComplaintsListLoading = true; // getLocation(); notifyListeners(); } @@ -201,6 +207,16 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsChiefComplaintsListLoading(bool value) { + isChiefComplaintsListLoading = value; + notifyListeners(); + } + + setSelectedChiefComplaintID(int id) { + selectedChiefComplaintID = id; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); @@ -949,4 +965,56 @@ class BookAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getDentalChiefComplaintsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + dentalChiefComplaintsList.clear(); + notifyListeners(); + int patientID = _appState.isAuthenticated ? _appState.getAuthenticatedUser()!.patientId ?? -1 : -1; + final result = await bookAppointmentsRepo.getDentalChiefComplaintsList(patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + dentalChiefComplaintsList = apiResponse.data!; + isChiefComplaintsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async { + doctorsList.clear(); + projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; + final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID); + + result.fold( + (failure) async { + onError!("No doctors found for the search criteria...".needTranslation); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + doctorsList = apiResponse.data!; + filteredDoctorList = doctorsList; + isDoctorsListLoading = false; + // initializeFilteredList(); + // clearSearchFilters(); + // getFiltersFromDoctorList(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart b/lib/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart new file mode 100644 index 00000000..99d5cd77 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart @@ -0,0 +1,24 @@ +class DentalChiefComplaintsListResponseModel { + int? projectID; + int? iD; + String? name; + dynamic nameN; + + DentalChiefComplaintsListResponseModel({this.projectID, this.iD, this.name, this.nameN}); + + DentalChiefComplaintsListResponseModel.fromJson(Map json) { + projectID = json['ProjectID']; + iD = json['ID']; + name = json['Name']; + nameN = json['NameN']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['ID'] = this.iD; + data['Name'] = this.name; + data['NameN'] = this.nameN; + return data; + } +} diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart index b267ee4a..16dfb1e3 100644 --- a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart @@ -48,7 +48,7 @@ class FacilitySelectionItem extends StatelessWidget { Transform.flip( flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18, height: 13, diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart index ab9cd9b9..688580c6 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart @@ -45,7 +45,7 @@ class RegionListItem extends StatelessWidget { Transform.flip( flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18, height: 13, diff --git a/lib/presentation/book_appointment/dental_chief_complaints_page.dart b/lib/presentation/book_appointment/dental_chief_complaints_page.dart new file mode 100644 index 00000000..4dc38812 --- /dev/null +++ b/lib/presentation/book_appointment/dental_chief_complaints_page.dart @@ -0,0 +1,95 @@ +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/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/dental_chief_complaints_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/chief_complaint_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/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class DentalChiefComplaintsPage extends StatefulWidget { + const DentalChiefComplaintsPage({super.key}); + + @override + State createState() => _DentalChiefComplaintsPageState(); +} + +class _DentalChiefComplaintsPageState extends State { + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + bookAppointmentsViewModel.getDentalChiefComplaintsList(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return CollapsingListView( + title: "Dental Chief Complaints".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isChiefComplaintsListLoading ? 5 : bookAppointmentsVM.dentalChiefComplaintsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isChiefComplaintsListLoading + ? ChiefComplaintCard( + bookAppointmentsVM: bookAppointmentsVM, + dentalChiefComplaintsListResponseModel: DentalChiefComplaintsListResponseModel(), + isLoading: bookAppointmentsVM.isChiefComplaintsListLoading, + ) + : 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: ChiefComplaintCard( + bookAppointmentsVM: bookAppointmentsVM, + dentalChiefComplaintsListResponseModel: bookAppointmentsVM.dentalChiefComplaintsList[index], + isLoading: bookAppointmentsVM.isChiefComplaintsListLoading, + ).onPress(() { + bookAppointmentsVM.setSelectedChiefComplaintID(bookAppointmentsVM.dentalChiefComplaintsList[index].iD!); + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), + ), + ); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 1c33dd67..ada48090 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -18,6 +18,7 @@ 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/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_livecare_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.dart'; @@ -222,124 +223,13 @@ class _SelectClinicPageState extends State { void handleDoctorScreen(GetClinicsListResponseModel clinic) async { if (widget.isFromRegionFlow) { //Dental Clinic Flow - if (clinic.clinicID == 17) { - LoaderBottomSheet.showLoader(loadingText: "Checking for an existing dental plan, Please wait...".needTranslation); - await bookAppointmentsViewModel.getPatientDentalEstimation(projectID: int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")).then((value) { - LoaderBottomSheet.hideLoader(); - if (bookAppointmentsViewModel.patientDentalPlanEstimationList.isNotEmpty) { - showCommonBottomSheetWithoutHeight( - // title: LocaleKeys.notice.tr(context: context), - title: "Dental treatment plan".needTranslation, - context, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "You have an existing treatment plan: ".needTranslation.toText14(weight: FontWeight.w500), - SizedBox(height: 8.h), - Container( - width: double.infinity, - padding: EdgeInsets.all(16.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsViewModel.patientDentalPlanEstimationList.length, - separatorBuilder: (_, __) { - return Column( - children: [ - SizedBox(height: 8.h), - Divider(height: 1, color: AppColors.greyColor), - SizedBox(height: 8.h), - ], - ); - }, - itemBuilder: (context, index) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} Mins".needTranslation), - ], - ); - }, - ), - SizedBox( - height: 16.h, - ), - Divider(height: 1, color: AppColors.greyColor), - SizedBox( - height: 8.h, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Total time required".needTranslation.toText14(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 Mins".needTranslation), - ], - ) - ], - ), - ), - SizedBox(height: 16.h), - "Would you like to continue it?".needTranslation.toText14(weight: FontWeight.w500), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.cancel.tr(), - onPressed: () { - bookAppointmentsViewModel.setIsContinueDentalPlan(false); - Navigator.of(context).pop(); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - icon: AppAssets.cancel, - iconColor: AppColors.whiteColor, - ), - ), - SizedBox(width: 8.h), - Expanded( - child: CustomButton( - text: LocaleKeys.confirm.tr(), - onPressed: () async { - bookAppointmentsViewModel.setIsContinueDentalPlan(true); - Navigator.of(context).push( - CustomPageRoute( - page: SelectDoctorPage(), - ), - ); - }, - backgroundColor: AppColors.bgGreenColor, - borderColor: AppColors.bgGreenColor, - textColor: Colors.white, - icon: AppAssets.confirm, - ), - ), - ], - ) - ], - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } else { - // Navigate to Chief Complaint Screen - } - }); + if (clinic.clinicID == 17 && appState.isAuthenticated) { + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); } else { + bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); Navigator.of(context).push( CustomPageRoute( - page: SelectDoctorPage(), + page: DentalChiefComplaintsPage(), ), ); } @@ -361,7 +251,18 @@ class _SelectClinicPageState extends State { showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); - }), callBackFunc: () {}); + }), callBackFunc: () { + if (type == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER && appState.isAuthenticated) { + initDentalAppointmentBookingFlow(regionalViewModel.selectedHospital?.hospitalList.first.iD); + } else { + bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); + Navigator.of(context).push( + CustomPageRoute( + page: DentalChiefComplaintsPage(), + ), + ); + } + }); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { @@ -378,12 +279,12 @@ class _SelectClinicPageState extends State { return HospitalBottomSheetBody(); } if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) { - //if the region screen is opened for the dental clinic thenthe project id will be in the hospital list as the list is formed form the get project api + //if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api var id = ""; if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { - id = regionalViewModel.selectedHospital?.hospitalList?.first?.iD?.toString() ?? ""; + id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""; } else { - id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first?.projectID?.toString() ?? ""; + id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; } bookAppointmentsViewModel.setProjectID(id); return SizedBox.shrink(); @@ -410,4 +311,128 @@ class _SelectClinicPageState extends State { }); } } + + void initDentalAppointmentBookingFlow(int projectID) async { + bookAppointmentsViewModel.setProjectID(projectID.toString()); + LoaderBottomSheet.showLoader(loadingText: "Checking for an existing dental plan, Please wait...".needTranslation); + await bookAppointmentsViewModel.getPatientDentalEstimation(projectID: projectID).then((value) { + LoaderBottomSheet.hideLoader(); + if (bookAppointmentsViewModel.patientDentalPlanEstimationList.isNotEmpty) { + showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: context), + title: "Dental treatment plan".needTranslation, + context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "You have an existing treatment plan: ".needTranslation.toText14(weight: FontWeight.w500), + SizedBox(height: 8.h), + Container( + width: double.infinity, + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsViewModel.patientDentalPlanEstimationList.length, + separatorBuilder: (_, __) { + return Column( + children: [ + SizedBox(height: 8.h), + Divider(height: 1, color: AppColors.greyColor), + SizedBox(height: 8.h), + ], + ); + }, + itemBuilder: (context, index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} Mins".needTranslation), + ], + ); + }, + ), + SizedBox( + height: 16.h, + ), + Divider(height: 1, color: AppColors.greyColor), + SizedBox( + height: 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Total time required".needTranslation.toText14(isBold: true), + AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "30 Mins".needTranslation), + ], + ) + ], + ), + ), + SizedBox(height: 16.h), + "Would you like to continue it?".needTranslation.toText14(weight: FontWeight.w500), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + bookAppointmentsViewModel.setIsContinueDentalPlan(false); + bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); + Navigator.of(context).pop(); + Navigator.of(context).push( + CustomPageRoute( + page: DentalChiefComplaintsPage(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + bookAppointmentsViewModel.setIsContinueDentalPlan(true); + Navigator.of(context).pop(); + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), + ), + ); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ) + ], + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else { + // Navigate to Chief Complaint Screen + } + }); + } } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 8d24b43b..b0b2a45e 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -21,7 +21,6 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; 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/transitions/fade_page.dart'; import 'package:provider/provider.dart'; class SelectDoctorPage extends StatefulWidget { @@ -45,7 +44,11 @@ class _SelectDoctorPageState extends State { if (bookAppointmentsViewModel.isLiveCareSchedule) { bookAppointmentsViewModel.getLiveCareDoctorsList(); } else { - bookAppointmentsViewModel.getDoctorsList(); + if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) { + bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList(); + } else { + bookAppointmentsViewModel.getDoctorsList(); + } } }); super.initState(); @@ -66,7 +69,6 @@ class _SelectDoctorPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // TODO: Implement doctor filter functionality SizedBox(height: 16.h), Row( spacing: 8.h, diff --git a/lib/presentation/book_appointment/widgets/chief_complaint_card.dart b/lib/presentation/book_appointment/widgets/chief_complaint_card.dart new file mode 100644 index 00000000..e80596c0 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/chief_complaint_card.dart @@ -0,0 +1,38 @@ +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/dental_chief_complaints_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class ChiefComplaintCard extends StatelessWidget { + ChiefComplaintCard({super.key, required this.isLoading, required this.bookAppointmentsVM, required this.dentalChiefComplaintsListResponseModel}); + + bool isLoading; + BookAppointmentsViewModel bookAppointmentsVM; + DentalChiefComplaintsListResponseModel dentalChiefComplaintsListResponseModel; + + @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: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded(child: (isLoading ? "Cardiology" : dentalChiefComplaintsListResponseModel.name)!.toText16(isBold: true).toShimmer2(isShow: isLoading)), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)), + ]), + ); + } +} diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart index c157d059..cab3b1a4 100644 --- a/lib/presentation/book_appointment/widgets/clinic_card.dart +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -36,7 +36,8 @@ class ClinicCard extends StatelessWidget { (clinicsListResponseModel.isLiveCareClinicAndOnline ?? true) ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading) : SizedBox.shrink(), - ]), + ], + ), SizedBox(height: 16.h), Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( @@ -44,7 +45,7 @@ class ClinicCard extends StatelessWidget { .toText16(isBold: true) .toShimmer2(isShow: isLoading)), Transform.flip( - flipX: appState.isArabic() ? true : false, + flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)), ]), ], From d220be72831685cb30c41c464a08550c2c31ddc3 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 14:24:32 +0300 Subject: [PATCH 26/29] Tamara payment flow done --- lib/core/api_consts.dart | 9 +- lib/features/payfort/payfort_repo.dart | 103 +++++++ lib/features/payfort/payfort_view_model.dart | 50 ++++ .../appointment_payment_page.dart | 258 +++++++++++------- .../appointment_checkin_bottom_sheet.dart | 2 +- .../immediate_livecare_payment_page.dart | 128 ++++++--- 6 files changed, 410 insertions(+), 140 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 177a5418..98c7ec02 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 @@ -745,6 +745,7 @@ class ApiConsts { static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; @@ -760,6 +761,7 @@ class ApiConsts { SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -768,6 +770,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -776,6 +779,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; break; case AppEnvironmentTypeEnum.preProd: @@ -785,6 +789,7 @@ class ApiConsts { SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -793,6 +798,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -801,6 +807,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; break; } } diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index 9f836d16..74a323a5 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -18,6 +18,13 @@ abstract class PayfortRepo { Future>> generateSdkSignatureFromAPI({required SdkTokenRequest tokenRequest}); Future>> checkPaymentStatus({required String transactionID}); + + Future>> checkTamaraPaymentStatus({required String transactionID}); + + Future>> markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo}); + + Future>> updateTamaraRequestStatus( + {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}); } class PayfortRepoImp implements PayfortRepo { @@ -147,4 +154,100 @@ class PayfortRepoImp implements PayfortRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> checkTamaraPaymentStatus({required String transactionID}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.get( + '${ApiConsts.GET_TAMARA_PAYMENT_STATUS}$transactionID', + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> updateTamaraRequestStatus({required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}) async { + Map body = { + "Response_Message": responseMessage, + "ClientRequestID": clientRequestID, + "Status": status, + "FortID": tamaraOrderID, // Tamara order ID + "LanguageID": 1, + "Installments_Number": 3, + }; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(UPDATE_TAMARA_STATUS, body: body, 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()); + } + }, isAllowAny: true, isPaymentServices: true); + 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> markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo}) async { + Map body = {"ProjectID": projectID, "AppointmentNo": appointmentNo, "LanguageID": 1}; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(MARK_APPOINTMENT_TAMARA_STATUS, body: body, 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()); + } + }, isAllowAny: true, isPaymentServices: true); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 6b67ce9f..89effcdc 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -94,6 +94,40 @@ class PayfortViewModel extends ChangeNotifier { ); } + Future checkTamaraPaymentStatus({required String transactionID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.checkTamaraPaymentStatus(transactionID: transactionID); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + print(apiResponse.data); + if (onSuccess != null) { + onSuccess(apiResponse); + } + // } + }, + ); + } + + Future updateTamaraRequestStatus( + {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.updateTamaraRequestStatus(responseMessage: responseMessage, status: status, clientRequestID: clientRequestID, tamaraOrderID: tamaraOrderID); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + print(apiResponse.data); + if (onSuccess != null) { + onSuccess(apiResponse); + } + }, + ); + } + Future _generateSdkResponse({ String? applePayAccessCode, String? merchantIdentifier, @@ -199,4 +233,20 @@ class PayfortViewModel extends ChangeNotifier { onFailed!(e.toString() as PayFortFailureResult); } } + + Future markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.markAppointmentAsTamaraPaid(projectID: projectID, appointmentNo: appointmentNo); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + print(apiResponse.data); + if (onSuccess != null) { + onSuccess(apiResponse); + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index ee50ab44..3c475c10 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -52,6 +52,8 @@ class _AppointmentPaymentPageState extends State { String transID = ""; bool isShowTamara = false; + String tamaraPaymentStatus = ""; + String tamaraOrderID = ""; @override void initState() { @@ -117,7 +119,7 @@ class _AppointmentPaymentPageState extends State { Transform.flip( flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, @@ -159,7 +161,7 @@ class _AppointmentPaymentPageState extends State { Transform.flip( flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, @@ -196,18 +198,18 @@ class _AppointmentPaymentPageState extends State { 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, - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - selectedPaymentMethod = "TAMARA"; - openPaymentURL("tamara"); + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + openPaymentURL("tamara"); }) : SizedBox.shrink(), ], @@ -328,12 +330,12 @@ class _AppointmentPaymentPageState extends State { if (selectedPaymentMethod == "tamara") { if (Platform.isAndroid) { Uri uri = new Uri.dataFromString(url); - // tamaraPaymentStatus = uri.queryParameters['status']!; - // tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!; + tamaraPaymentStatus = uri.queryParameters['status']!; + tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!; } else { Uri uri = new Uri.dataFromString(url); - // tamaraPaymentStatus = uri.queryParameters['paymentStatus']!; - // tamaraOrderID = uri.queryParameters['orderId']!; + tamaraPaymentStatus = uri.queryParameters['paymentStatus']!; + tamaraOrderID = uri.queryParameters['orderId']!; } } @@ -359,95 +361,155 @@ class _AppointmentPaymentPageState extends State { } 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); - } + checkPaymentStatus(); } void checkPaymentStatus() async { - LoaderBottomSheet.showLoader(); - await payfortViewModel.checkPaymentStatus( - transactionID: transID, - onSuccess: (apiResponse) async { - print(apiResponse.data); - if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { - await myAppointmentsViewModel.createAdvancePayment( - paymentMethodName: selectedPaymentMethod, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!, - paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, - patientID: appState.getAuthenticatedUser()!.patientId.toString(), - patientType: appState.getAuthenticatedUser()!.patientType!, - onSuccess: (value) async { - print(value); - await myAppointmentsViewModel.addAdvanceNumberRequest( - advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) - ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() - : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), - paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - onSuccess: (value) async { - if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { - //TODO: Implement LiveCare Check-In API Call - await myAppointmentsViewModel.insertLiveCareVIDARequest( - clientRequestID: transID, - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onSuccess: (apiResponse) { - Future.delayed(Duration(milliseconds: 500), () { - LoaderBottomSheet.hideLoader(); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }); - }, - onError: (error) {}); - } else { - await myAppointmentsViewModel.generateAppointmentQR( - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, - onSuccess: (apiResponse) { - Future.delayed(Duration(milliseconds: 500), () { - LoaderBottomSheet.hideLoader(); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - // Navigator.of(context).push( - // CustomPageRoute(page: MyAppointmentsPage()), - // ); - }); - }); - } - }); - }); - } else { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + if (selectedPaymentMethod == "TAMARA") { + await payfortViewModel.checkTamaraPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + if (apiResponse.data["status"].toString().toLowerCase() == "success") { + tamaraOrderID = apiResponse.data["tamara_order_id"].toString(); + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + await payfortViewModel.markAppointmentAsTamaraPaid( + projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: "Tamara-Advance-0000", + paymentReference: tamaraOrderID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: tamaraOrderID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + } else { + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: err), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - } - }); + }); + } else { + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + print(apiResponse.data); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + patientID: appState.getAuthenticatedUser()!.patientId.toString(), + patientType: appState.getAuthenticatedUser()!.patientType!, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + }); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } } openPaymentURL(String paymentMethod) { diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index e79f26f4..93f2bda7 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -124,7 +124,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { Transform.flip( flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, 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 5437945e..95d63022 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -57,6 +57,8 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( CustomPageRoute( - page: LandingNavigation(), + page: ImmediateLiveCarePendingRequestPage(), ), - (r) => 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 { + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Unknown error occurred...".needTranslation), + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } - } else { + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + child: Utils.getErrorWidget(loadingText: err), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); - } - }); + }); + } else { + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + debugPrint(apiResponse.data.toString()); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transID); + await immediateLiveCareViewModel.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); + if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => 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, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } } openPaymentURL(String paymentMethod) { From e5dcabaccee8e88eb8abbfbe5283389ef0e3c71e Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 6 Oct 2025 14:46:43 +0300 Subject: [PATCH 27/29] lab prescription details are added andgraph visibility added to the graph page for result that are IRR --- lib/features/lab/lab_view_model.dart | 14 ++ .../lab/models/resp_models/lab_result.dart | 17 ++ .../LabResultList.dart | 4 +- .../lab_order_result_item.dart | 47 +++- .../lab/lab_results/lab_result_details.dart | 235 ++++++++++-------- .../viewmodel/date_range_view_model.dart | 7 +- 6 files changed, 216 insertions(+), 108 deletions(-) diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index e7507b07..8a397e3d 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -22,6 +22,8 @@ class LabViewModel extends ChangeNotifier { bool isLabResultsLoading = false; bool isLabResultByHospitalLoading = false; bool isSpecialResultsLoading = false; + bool isGraphVisible = true; + bool shouldShowGraph = true; LabRepo labRepo; ErrorHandlerService errorHandlerService; @@ -225,6 +227,7 @@ class LabViewModel extends ChangeNotifier { } catch (e) {} }); LabResult recentResult = recentThree.first; + checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); // filteredGraphValues = [filteredGraphValues.first]; navigationService.push(MaterialPageRoute( @@ -237,6 +240,12 @@ class LabViewModel extends ChangeNotifier { ); } + void checkIfGraphShouldBeDisplayed(LabResult recentResult){ + shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed(); + isGraphVisible = shouldShowGraph; + notifyListeners(); + } + Future getPatientSpecialResult( PatientLabOrdersResponseModel laborder) async { isSpecialResultsLoading = true; @@ -551,4 +560,9 @@ class LabViewModel extends ChangeNotifier { return "normal"; } } + + alterGraphVisibility(){ + isGraphVisible = !isGraphVisible; + notifyListeners(); + } } diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart index 3f14d542..a075be3d 100644 --- a/lib/features/lab/models/resp_models/lab_result.dart +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -24,6 +24,7 @@ class LabResult { String? referenceHigh; String? criticalLow; String? referenceLow; + num? resultTypeID; String? packageShortDescription; LabResult( @@ -80,6 +81,7 @@ class LabResult { criticalLow = json['CriticalLow']; referenceLow = json['ReferenceLow']; packageShortDescription = json['PackageShortDescription']; + resultTypeID = json['ResultTypeID']; } Map toJson() { @@ -111,6 +113,21 @@ class LabResult { return data; } + bool checkIfGraphShouldBeDisplayed(){ + if (resultTypeID == null) return false; + if (resultTypeID == 6) return false; + if (referanceRange == null || referanceRange == "" || referanceRange == "\n") return false; + bool isDigit = RegExp(r"\\d+").hasMatch("$resultValue"); + if(isDigit) return true; + try { + num.parse(resultValue ?? ""); + } catch (e) { + return false; + } + + return true; + } + @override String toString() { return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)'; diff --git a/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart index d7642c2d..e94bb5cd 100644 --- a/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart +++ b/lib/presentation/lab/lab_result_via_hospital/LabResultList.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; 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/lab_result_via_hospital/lab_order_result_item.dart'; -import 'package:provider/provider.dart' show Selector, Provider; +import 'package:provider/provider.dart' show Selector, Provider, ReadContext; class LabResultList extends StatelessWidget { late LabViewModel model; @@ -15,7 +15,7 @@ class LabResultList extends StatelessWidget { return Selector>( selector: (_, model) => model.mainLabResultsByHospitals, builder: (__, list, ___) { - if (list.isEmpty) { + if (list.isEmpty && context.read().labSpecialResult.isEmpty) { return Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet." .needTranslation); diff --git a/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart index 7dc4075e..87fd790f 100644 --- a/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_hospital/lab_order_result_item.dart @@ -7,6 +7,7 @@ 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/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart' show LabViewModel; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -14,6 +15,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; +import 'package:provider/provider.dart'; class LabOrderResultItem extends StatelessWidget { final VoidCallback onTap; @@ -45,8 +47,49 @@ class LabOrderResultItem extends StatelessWidget { child: '${tests!.description}'.toText14(weight: FontWeight.w500), ), '${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - // - SizedBox(height: 24.h), + SizedBox(height: 12.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Text( + tests?.resultValue ?? "", + style: TextStyle( + fontSize: 24.fSize, + fontWeight: FontWeight.w600, + fontFamily: 'Poppins', + color: context.read().getColor( + tests?.calculatedResultFlag ?? "", + ), + letterSpacing: -2, + ), + overflow: TextOverflow.ellipsis, // prevent overflow + maxLines: 1, + softWrap: false, + ), + ), + SizedBox(width: 4.h,), + Expanded( + flex: 2, + child: Visibility( + visible: tests?.referanceRange != null, + child: Text( + "(Reference range ${tests?.referanceRange})".needTranslation, + style: TextStyle( + fontSize: 12.fSize, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.greyTextColor, + ), + // overflow: TextOverflow.ellipsis, + // maxLines: 2, + softWrap: true, + ), + ), + ), + ], + ), + SizedBox(height: 12.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 89e86bb8..30c19101 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -15,35 +15,38 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list 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; +import 'package:provider/provider.dart' show Consumer, Provider, ReadContext; import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; class LabResultDetails extends StatelessWidget { final LabResult recentLabResult; + final String? testDescription; - // final List graphPoint; - late LabViewModel model; - String? testDescription; - - LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); + const LabResultDetails( + {super.key, + required this.recentLabResult, + required this.testDescription}); @override Widget build(BuildContext context) { - model = Provider.of(context, listen: false); return CollapsingListView( title: 'Lab Result Details'.needTranslation, child: SingleChildScrollView( child: Column( spacing: 16.h, - children: [LabNameAndStatus, LabGraph(context)], + children: [ + LabNameAndStatus(context), + getLabDescription(context), + LabGraph(context) + ], ).paddingAll(24.h), ), ); } - Widget get LabNameAndStatus => Container( + Widget LabNameAndStatus(BuildContext context) => Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -80,11 +83,12 @@ class LabResultDetails extends StatelessWidget { //todo change the text color according to the provided test values Row( crossAxisAlignment: CrossAxisAlignment.center, + children: [ Expanded( child: Row( - spacing: 4.h, + mainAxisSize: MainAxisSize.max, children: [ Flexible( child: Text( @@ -93,8 +97,8 @@ class LabResultDetails extends StatelessWidget { fontSize: 24.fSize, fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: model.getColor( - recentLabResult.calculatedResultFlag ?? "", + color: context.read().getColor( + recentLabResult.calculatedResultFlag ?? "", ), letterSpacing: -2, ), @@ -103,30 +107,37 @@ class LabResultDetails extends StatelessWidget { softWrap: false, ), ), - Visibility( - visible: recentLabResult.referanceRange != null, - child: Text( - "(Reference range ${recentLabResult.referanceRange})".needTranslation, - style: TextStyle( - fontSize: 12.fSize, - fontWeight: FontWeight.w500, - fontFamily: 'Poppins', - color: AppColors.greyTextColor, + SizedBox(width: 4.h,), + Expanded( + flex: 2, + child: Visibility( + visible: recentLabResult.referanceRange != null, + child: Text( + "(Reference range ${recentLabResult.referanceRange})".needTranslation, + style: TextStyle( + fontSize: 12.fSize, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.greyTextColor, + ), + // overflow: TextOverflow.ellipsis, + // maxLines: 2, + softWrap: true, ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - softWrap: false, ), ), ], ), ), - Utils.buildSvgWithAssets( - icon: AppAssets.lab_result_indicator, + SizedBox( width: 21, - height: 23, - iconColor: model.getColor( - recentLabResult.calculatedResultFlag ?? "", + child: Utils.buildSvgWithAssets( + icon: AppAssets.lab_result_indicator, + width: 21, + height: 23, + iconColor: context.read().getColor( + recentLabResult.calculatedResultFlag ?? "", + ), ), ), ], @@ -135,73 +146,80 @@ class LabResultDetails extends StatelessWidget { ], )); - Widget LabGraph(BuildContext context) => Consumer( - builder: (_, model, ___) => Consumer( - builder: (_, labmodel, ___) => Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), - height: model.isGraphVisible?260.h:(labmodel.filteredGraphValues.length<3)?(labmodel.filteredGraphValues.length*64)+80.h:260.h, - padding: EdgeInsets.all(16.h), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, + Widget LabGraph(BuildContext context) => Consumer( + builder: (_, labmodel, ___) => Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + height: labmodel.isGraphVisible + ? 260.h + : (labmodel.filteredGraphValues.length < 3) + ? (labmodel.filteredGraphValues.length * 64) + 80.h + : 260.h, + padding: EdgeInsets.all(16.h), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + //title and filter icon + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - //title and filter icon + Text( + labmodel.isGraphVisible + ? LocaleKeys.historyFlowchart.tr() + : LocaleKeys.history.tr(), + style: TextStyle( + fontSize: 16, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + color: AppColors.textColor, + ), + ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 16.h, children: [ - Text( - model.isGraphVisible?LocaleKeys.historyFlowchart.tr(): LocaleKeys.history.tr(), - style: TextStyle( - fontSize: 16, - fontFamily: 'Poppins', - - fontWeight: FontWeight.w600, - color: AppColors.textColor, - ), - ), - Row( - spacing: 16.h, - children: [ - //todo handle when the graph icon is being displayed - Utils.buildSvgWithAssets( - icon: model.isGraphVisible?AppAssets.ic_list:AppAssets.ic_graph, - width: 24.h, - height: 24.h) - .onPress(() { - model.alterGraphVisibility(); - }), - Utils.buildSvgWithAssets( - icon: AppAssets.ic_date_filter, - width: 24, - height: 24) - .onPress(() { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.setTheDateRange.tr(), - context, - child: DateRangeSelector( - onRangeSelected: (start, end) { - - // if (start != null) { - labmodel.getSelectedDateRange(start, end); - // } - }, - ), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); - }), - ], - ) + //todo handle when the graph icon is being displayed + Utils.buildSvgWithAssets( + icon: labmodel.isGraphVisible + ? AppAssets.ic_list + : AppAssets.ic_graph, + width: 24.h, + height: 24.h) + .onPress(() { + if (labmodel.shouldShowGraph) { + labmodel.alterGraphVisibility(); + } + }), + Utils.buildSvgWithAssets( + icon: AppAssets.ic_date_filter, + width: 24, + height: 24) + .onPress(() { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.setTheDateRange.tr(), + context, + child: DateRangeSelector( + onRangeSelected: (start, end) { + // if (start != null) { + labmodel.getSelectedDateRange(start, end); + // } + }, + ), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }), ], - ).paddingOnly(bottom: model.isGraphVisible? 16.h :24.h), - historyBody(model, labmodel) + ) ], - )), - )); + ).paddingOnly(bottom: labmodel.isGraphVisible ? 16.h : 24.h), + historyBody(labmodel) + ], + )), + ); Widget leftLabels(String value) { return Text( @@ -229,17 +247,15 @@ class LabResultDetails extends StatelessWidget { ); } - Widget historyBody(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { - if(model.isGraphVisible){ + Widget historyBody(LabViewModel labmodel) { + if (labmodel.isGraphVisible && labmodel.shouldShowGraph) { var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, - // maxY: 100, - makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, leftLabelInterval: getInterval(labmodel), - maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/5, + maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, maxX: labmodel.filteredGraphValues.length.toDouble()-.75, leftLabelFormatter: (value) { return leftLabels(value.toStringAsFixed(2).tr()); @@ -279,11 +295,11 @@ class LabResultDetails extends StatelessWidget { scrollDirection: Axis.horizontal, height: 180.h); }else { - return labHistoryList(model, labmodel); + return labHistoryList(labmodel); } } - Widget labHistoryList(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { + Widget labHistoryList(LabViewModel labmodel) { return SizedBox( height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, child: ListView.separated( @@ -315,4 +331,27 @@ class LabResultDetails extends StatelessWidget { if(maxX >100 && maxX < 200) return 30; return 50; } + + Widget getLabDescription(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + height: 98.h, + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + "What is this result?" + .needTranslation + .toText16(weight: FontWeight.w600, color: AppColors.textColor), + testDescription?.toText12( + fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? + SizedBox.shrink() + ], + )); + } } diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index 42f222bd..398a809d 100644 --- a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -17,7 +17,7 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { 'Nov', 'Dec' ]; - bool isGraphVisible = true; + Range? _currentlySelectedRange; Range? get currentlySelectedRange => _currentlySelectedRange; @@ -77,7 +77,6 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { toDate = null; fromDate = null; currentlySelectedRange = null; - isGraphVisible = true; notifyListeners(); } @@ -86,8 +85,4 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { notifyListeners(); } - alterGraphVisibility(){ - isGraphVisible = !isGraphVisible; - notifyListeners(); - } } From 61007302c9dd4fc0fd180bf51844f3250a0026e9 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 6 Oct 2025 16:21:05 +0300 Subject: [PATCH 28/29] appointment date filter handled --- lib/core/utils/date_util.dart | 8 ++++++++ .../my_appointments/my_appointments_view_model.dart | 8 +++++--- .../viewmodel/date_range_view_model.dart | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 1eb3d9f4..2fbbd092 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -485,3 +485,11 @@ class DateUtil { return ""; } } + + +extension OnlyDate on DateTime{ + + DateTime provideDateOnly(){ + return DateTime(this.year, month, day); + } +} \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 916327c6..f278558b 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -50,6 +50,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { void onTabChange(int index) { previouslySelectedTab = selectedTabIndex; selectedTabIndex = index; + start = null; + end = null; notifyListeners(); } @@ -451,12 +453,12 @@ class MyAppointmentsViewModel extends ChangeNotifier { filteredAppointmentList.add(element); } }); - filteredAppointmentList.addAll(sourceList); } else { filteredAppointmentList.clear(); sourceList.forEach((element) { try { - var dateTime = DateUtil.convertStringToDate(element.appointmentDate); + var dateTime = DateUtil.convertStringToDate(element.appointmentDate).provideDateOnly(); + if (start != null && end == null) { if (dateTime.isAtSameMomentAs(start)) { if (isUnderFilter(element)) { @@ -464,7 +466,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } } else if (start != null && end != null) { - if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { + if ((dateTime.isAfter(start)) && ((dateTime.isBefore(end))||((dateTime.isAtSameMomentAs(end))))) { if (isUnderFilter(element)) { filteredAppointmentList.add(element); } diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index 42f222bd..7bdb6582 100644 --- a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -1,5 +1,6 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/lab/models/Range.dart'; class DateRangeSelectorRangeViewModel extends ChangeNotifier { @@ -50,7 +51,7 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { get getCurrentYear => DateTime.now().year; calculateDatesFromRange() { - _toDate = DateTime.now(); + _toDate = DateTime.now().provideDateOnly(); switch (_currentlySelectedRange) { case Range.WEEKLY: _fromDate = _toDate!.subtract(Duration(days: 7)); From f1a60a9dfb4ccff7840be4d771e698cfe558ff3d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 16:35:10 +0300 Subject: [PATCH 29/29] updates --- lib/core/utils/size_utils.dart | 8 +- lib/core/utils/utils.dart | 1 + .../my_appointments_view_model.dart | 1 + lib/main.dart | 81 +++++-------------- .../appointments/my_appointments_page.dart | 1 + lib/presentation/lab/lab_orders_page.dart | 2 +- 6 files changed, 28 insertions(+), 66 deletions(-) diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 6ad18359..e5690212 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design. // These are used in the code as a reference to create your UI Responsively. -const num FIGMA_DESIGN_WIDTH = 375; -const num FIGMA_DESIGN_HEIGHT = 667; +final num FIGMA_DESIGN_WIDTH = SizeUtils.width; +final num FIGMA_DESIGN_HEIGHT = SizeUtils.height; const num FIGMA_DESIGN_STATUS_BAR = 0; extension ResponsiveExtension on num { @@ -70,10 +70,10 @@ class SizeUtils { static late DeviceType deviceType; /// Device's Height - static late double height; + static double height = 667; /// Device's Width - static late double width; + static double width = 375; static void setScreenSize( BoxConstraints constraints, diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 2bff2ac4..c9eef9dd 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -757,4 +757,5 @@ class Utils { } return isHavePrivilege; } + } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 916327c6..0c9509ad 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -153,6 +153,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); filteredAppointmentList.addAll(patientAppointmentsHistoryList); + print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); print('All Appointments: ${patientAppointmentsHistoryList.length}'); diff --git a/lib/main.dart b/lib/main.dart index ac11f8fe..20507d06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,94 +79,53 @@ void main() async { fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ ChangeNotifierProvider( - create: (_) => LabViewModel( - labRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt()), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => RadiologyViewModel( - radiologyRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => PrescriptionsViewModel( - prescriptionsRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => InsuranceViewModel( - insuranceRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => MedicalFileViewModel( - medicalFileRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => ProfileSettingsViewModel(), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => MyAppointmentsViewModel( - myAppointmentsRepo: getIt(), - errorHandlerService: getIt(), - appState: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => PayfortViewModel( - payfortRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => HabibWalletViewModel( - habibWalletRepo: getIt(), - errorHandlerService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => BookAppointmentsViewModel( - bookAppointmentsRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt(), - myAppointmentsViewModel: getIt(), - locationUtils: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => ImmediateLiveCareViewModel( - immediateLiveCareRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt(), - myAppointmentsViewModel: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => AuthenticationViewModel( - authenticationRepo: getIt(), - appState: getIt(), - dialogService: getIt(), - errorHandlerService: getIt(), - navigationService: getIt(), - cacheService: getIt(), - localAuthService: getIt(), - ), + create: (_) => getIt.get(), ), ChangeNotifierProvider( - create: (_) => AppointmentViaRegionViewmodel( - navigationService: getIt(), appState: getIt())), + create: (_) => getIt.get(), + ), ChangeNotifierProvider( - create: (_) => LabHistoryViewModel()), + create: (_) => getIt.get(), + ), ChangeNotifierProvider( - create: (_) => DateRangeSelectorRangeViewModel()) , + create: (_) => getIt.get(), + ), ChangeNotifierProvider( - create: (_) => DoctorFilterViewModel()) + create: (_) => getIt.get(), + ) ], child: MyApp()), ), ); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index c9392342..150f0bf5 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -177,6 +177,7 @@ class _MyAppointmentsPageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), + SizedBox(height: 24.h), ], ); } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 4d7066f0..882c737f 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_hospital/LabResultByHospital.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.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'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByHospitals(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file