From 30eb8bf5dea3f8a9a51195c9ce0b3e41c12c362e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 12:34:18 +0300 Subject: [PATCH 1/3] 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 cd7b673..9140a13 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 511cb37..177a541 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 82ca921..437d2c8 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 a6c741f..26bfe4f 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 0000000..99d5cd7 --- /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 b267ee4..16dfb1e 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 ab9cd9b..688580c 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 0000000..4dc3881 --- /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 1c33dd6..ada4809 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 8d24b43..b0b2a45 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 0000000..e80596c --- /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 c157d05..cab3b1a 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 2/3] 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 177a541..98c7ec0 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 9f836d1..74a323a 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 6b67ce9..89effcd 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 ee50ab4..3c475c1 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 e79f26f..93f2bda 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 5437945..95d6302 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 f1a60a9dfb4ccff7840be4d771e698cfe558ff3d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 6 Oct 2025 16:35:10 +0300 Subject: [PATCH 3/3] 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 6ad1835..e569021 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 2bff2ac..c9eef9d 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 916327c..0c9509a 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 ac11f8f..20507d0 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 c939234..150f0bf 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 4d7066f..882c737 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