From a711d3a6dd43b35e1b8c537df7cfe04fdb74f314 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 24 Sep 2025 14:33:00 +0300 Subject: [PATCH 1/9] LiveCare shcedule implementation contd. --- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 2 +- lib/extensions/string_extensions.dart | 2 +- .../book_appointments_repo.dart | 137 ++++++++++++++ .../book_appointments_view_model.dart | 172 ++++++++++++++---- .../get_clinic_list_response_model.dart | 4 +- .../get_livecare_clinics_response_model.dart | 33 ++++ .../book_appointment_page.dart | 137 ++++++++++++-- .../book_appointment/select_clinic_page.dart | 124 +++++++++---- .../book_appointment/select_doctor_page.dart | 14 +- .../book_appointment/widgets/clinic_card.dart | 11 +- .../book_appointment/widgets/doctor_card.dart | 70 ++++--- lib/presentation/home/navigation_screen.dart | 1 + .../bottom_navigation/bottom_navigation.dart | 42 ++--- lib/widgets/buttons/custom_button.dart | 2 +- 15 files changed, 613 insertions(+), 142 deletions(-) create mode 100644 lib/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 62339bf..deb107d 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -176,8 +176,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 3628599; + body['TokenID'] = "@dm!n"; + body['PatientID'] = 4767477; } body.removeWhere((key, value) => value == null); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5886dfa..fa02b2e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 5465ce3..059e553 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -365,7 +365,7 @@ extension DynamicTextStyleExtension on BuildContext { TextBaseline? textBaseline, FontStyle? fontStyle, bool isLanguageSwitcher = false}) { - final family = FontUtils.getFontFamilyForLanguage(true); + final family = FontUtils.getFontFamilyForLanguage(false); // TODO: @Aamir make it dynamic based on app language return TextStyle( fontFamily: family, fontSize: fontSize, diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 81faf91..c3cc9cf 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.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'; +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/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'; @@ -45,6 +46,13 @@ abstract class BookAppointmentsRepo { Future>>> getProjectList(); Future>>> getClinicsWithRespectToClinicId(String projectID); + + Future>>> getLiveCareScheduleClinics(int age, int genderID); + + Future>>> getLiveCareDoctorsList(int serviceID, int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> getLiveCareDoctorFreeSlots(int clinicID, int serviceID, int projectID, int doctorId, bool isBookingForLiveCare, + {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -443,4 +451,133 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getLiveCareScheduleClinics(int age, int genderID) async { + Map mapDevice = {"Age": age, "Gender": genderID}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_SCHEDULE_CLINICS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ClinicsHaveScheduleList']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final clinicsList = list.map((item) => GetLiveCareClinicsResponseModel.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>>> getLiveCareDoctorsList(int serviceID, int age, int genderID, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "ServiceID": serviceID, + "Age": age, + "Gender": genderID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_SCHEDULE_CLINIC_DOCTOR_LIST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['DoctorByClinicIDList']; + + 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())); + } + } + + @override + Future> getLiveCareDoctorFreeSlots(int clinicID, int serviceID, int projectID, int doctorId, bool isBookingForLiveCare, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "DoctorID": doctorId, + "IsBookingForLiveCare": true, + "ClinicID": clinicID, + "ServiceID": serviceID, + "ProjectID": projectID, + "OriginalClinicID": clinicID, + "days": 50, + "isReschadual": false, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_SCHEDULE_DOCTOR_TIME_SLOTS, + 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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 6488dd8..e8d02b9 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -29,6 +29,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:location/location.dart' show Location; +import 'models/resp_models/get_livecare_clinics_response_model.dart'; + class BookAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -37,6 +39,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isDoctorProfileLoading = false; bool isDoctorSearchByNameStarted = false; + bool isLiveCareSchedule = false; + int initialSlotDuration = 0; LocationUtils locationUtils; @@ -44,12 +48,17 @@ class BookAppointmentsViewModel extends ChangeNotifier { List clinicsList = []; List _filteredClinicsList = []; + List liveCareClinicsList = []; + List get filteredClinicsList => _filteredClinicsList; List doctorsList = []; + List liveCareDoctorsList = []; + GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(); DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + GetLiveCareClinicsResponseModel selectedLiveCareClinic = GetLiveCareClinicsResponseModel(); late DoctorsProfileResponseModel doctorsProfileResponseModel; @@ -78,7 +87,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool shouldLoadSpecificClinic = false; String? currentlySelectedHospitalFromRegionFlow; - BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils}) {; + BookAppointmentsViewModel( + {required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils}) { + ; initBookAppointmentViewModel(); } @@ -101,8 +112,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { isClinicsListLoading = true; isDoctorsListLoading = true; isDoctorProfileLoading = true; + isLiveCareSchedule = false; clinicsList.clear(); doctorsList.clear(); + liveCareClinicsList.clear(); // getLocation(); notifyListeners(); } @@ -154,6 +167,16 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsLiveCareSchedule(bool value) { + isLiveCareSchedule = value; + notifyListeners(); + } + + setLiveCareSelectedClinic(GetLiveCareClinicsResponseModel clinic) { + selectedLiveCareClinic = clinic; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); @@ -161,10 +184,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { /// this function will decide which clinic api to be called /// either api for region flow or the select clinic api - Future getClinics() async - { - if(shouldLoadSpecificClinic) { + Future getClinics() async { + if (shouldLoadSpecificClinic) { getRegionSelectedClinics(); + } else if (isLiveCareSchedule) { + getLiveCareScheduleClinics(); } else { getAllClinics(); } @@ -191,11 +215,58 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future getLiveCareScheduleClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { + liveCareClinicsList.clear(); + final result = await bookAppointmentsRepo.getLiveCareScheduleClinics(_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) { + liveCareClinicsList = apiResponse.data!; + isClinicsListLoading = false; + initializeFilteredList(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getLiveCareDoctorsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + doctorsList.clear(); + final result = + await bookAppointmentsRepo.getLiveCareDoctorsList(selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, onError: onError); + + 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) { + liveCareDoctorsList = apiResponse.data!; + isDoctorsListLoading = false; + // initializeFilteredList(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + //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 { doctorsList.clear(); - projectID = currentlySelectedHospitalFromRegionFlow != null?int.parse(currentlySelectedHospitalFromRegionFlow!):projectID; + projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); result.fold( @@ -238,7 +309,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - //TODO: Handle the cases for LiveCare Schedule Future getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; @@ -281,6 +351,50 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + docFreeSlots.clear(); + DateTime date; + final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); + Map _eventsParsed; + + final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots( + selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, + onError: onError); + + result.fold( + (failure) async { + print(failure); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data['PatientER_DoctorFreeSlots'] == null || apiResponse.data['PatientER_DoctorFreeSlots'].isEmpty) { + onError!("No free slots available".tr()); + return; + } + initialSlotDuration = apiResponse.data["InitialSlotDuration"]; + freeSlotsResponse = apiResponse.data['PatientER_DoctorFreeSlots']; + freeSlotsResponse.forEach((element) { + // date = (isLiveCareSchedule != null && isLiveCareSchedule) + // ? DateUtil.convertStringToDate(element) + // : + date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString())); + slotsList.add(FreeSlot(date, ['slot'])); + docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); + }); + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); @@ -419,22 +533,21 @@ class BookAppointmentsViewModel extends ChangeNotifier { final result = await bookAppointmentsRepo.getProjectList(); result.fold( - (failure) async => - await errorHandlerService.handleError(failure: failure), - (apiResponse) async { + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) async { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { var projectList = apiResponse.data!; - hospitalList = await DoctorMapper.getMappedHospitals(projectList, + hospitalList = await DoctorMapper.getMappedHospitals( + projectList, isArabic: _appState.isArabic(), lat: _appState.userLat, lng: _appState.userLong, ); var isLocationEnabled = (_appState.userLat != 0) && (_appState.userLong != 0); - hospitalList = - await DoctorMapper.sortList(isLocationEnabled, hospitalList!); + hospitalList = await DoctorMapper.sortList(isLocationEnabled, hospitalList!); isRegionListLoading = false; filteredHospitalList = hospitalList; @@ -450,22 +563,21 @@ class BookAppointmentsViewModel extends ChangeNotifier { } void filterHospitalListByString(String? value, String? selectedRegionId, bool isHMG) { - if(value ==null || value.isEmpty){ + if (value == null || value.isEmpty) { filteredHospitalList = hospitalList; } else { filteredHospitalList = RegionList(); - var list = isHMG - ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList - : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; + var list = isHMG ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; - if(list != null && list.isEmpty){ notifyListeners(); return;} + if (list != null && list.isEmpty) { + notifyListeners(); + return; + } - var filteredList = list!.where((element) => - element.filterName!.toLowerCase().contains(value.toLowerCase()) - ).toList(); + var filteredList = list!.where((element) => element.filterName!.toLowerCase().contains(value.toLowerCase())).toList(); var regionData = PatientDoctorAppointmentListByRegion(); - if(isHMG){ + if (isHMG) { regionData.hmgDoctorList = filteredList; regionData.hmgSize = filteredList.length; } else { @@ -473,9 +585,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { regionData.hmcSize = filteredList.length; } - filteredHospitalList?.registeredDoctorMap = { - selectedRegionId! : regionData - }; + filteredHospitalList?.registeredDoctorMap = {selectedRegionId!: regionData}; } notifyListeners(); } @@ -498,12 +608,12 @@ class BookAppointmentsViewModel extends ChangeNotifier { currentlySelectedHospitalFromRegionFlow = mainProjectID; } - Future getRegionSelectedClinics() async{ - final result = await bookAppointmentsRepo.getClinicsWithRespectToClinicId(currentlySelectedHospitalFromRegionFlow??""); + Future getRegionSelectedClinics() async { + final result = await bookAppointmentsRepo.getClinicsWithRespectToClinicId(currentlySelectedHospitalFromRegionFlow ?? ""); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), - (apiResponse) { + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -515,12 +625,12 @@ class BookAppointmentsViewModel extends ChangeNotifier { }, ); } - void resetFilterList(){ + + void resetFilterList() { filteredHospitalList = hospitalList; } - - void getLocation(){ + void getLocation() { locationUtils.getLocation(); } } diff --git a/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart b/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart index 0b0fa13..24d1c70 100644 --- a/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart +++ b/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart @@ -12,8 +12,8 @@ class GetClinicsListResponseModel { GetClinicsListResponseModel.fromJson(Map json) { clinicID = json['ClinicID']; - clinicDescription = json['ClinicDescription']; - clinicDescriptionN = json['ClinicDescriptionN']; + clinicDescription = json['ClinicDescription'] ?? "LiveCare Clinic"; + clinicDescriptionN = json['ClinicDescriptionN'] ?? "LiveCare Clinic"; age = json['Age']; gender = json['Gender']; isLiveCareClinicAndOnline = json['IsLiveCareClinicAndOnline']; diff --git a/lib/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart b/lib/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart new file mode 100644 index 0000000..2b66cfc --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/get_livecare_clinics_response_model.dart @@ -0,0 +1,33 @@ +class GetLiveCareClinicsResponseModel { + int? clinicID; + int? serviceID; + int? projectID; + String? clinicDesc; + String? clinicDescN; + String? projectDesc; + String? projectDescN; + + GetLiveCareClinicsResponseModel({this.clinicID, this.serviceID, this.projectID, this.clinicDesc, this.clinicDescN, this.projectDesc, this.projectDescN}); + + GetLiveCareClinicsResponseModel.fromJson(Map json) { + clinicID = json['ClinicID']; + serviceID = json['ServiceID']; + projectID = json['ProjectID']; + clinicDesc = json['ClinicDesc'] ?? "LiveCare Clinic"; + clinicDescN = json['ClinicDescN']; + projectDesc = json['ProjectDesc']; + projectDescN = json['ProjectDescN']; + } + + Map toJson() { + final Map data = new Map(); + data['ClinicID'] = this.clinicID; + data['ServiceID'] = this.serviceID; + data['ProjectID'] = this.projectID; + data['ClinicDesc'] = this.clinicDesc; + data['ClinicDescN'] = this.clinicDescN; + data['ProjectDesc'] = this.projectDesc; + data['ProjectDescN'] = this.projectDescN; + return data; + } +} diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 140b593..78f1540 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -42,6 +42,7 @@ class _BookAppointmentPageState extends State { @override void initState() { scheduleMicrotask(() { + bookAppointmentsViewModel.selectedTabIndex = 0; bookAppointmentsViewModel.initBookAppointmentViewModel(); bookAppointmentsViewModel.getLocation(); }); @@ -123,6 +124,7 @@ class _BookAppointmentPageState extends State { ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); bookAppointmentsViewModel.setLoadSpecificClinic(false); + bookAppointmentsViewModel.setIsLiveCareSchedule(false); bookAppointmentsViewModel.setProjectID(null); Navigator.of(context).push( CustomPageRoute( @@ -191,6 +193,112 @@ 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, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + 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(), + // ), + // ); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + 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.setIsLiveCareSchedule(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectClinicPage(), + ), + ); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + flipX: appState.isArabic() ? true : false, child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), + ], + ).onPress(() { + openRegionListBottomSheet(context); + }), + ], + ), + ), + ), + ], + ).paddingSymmetrical(24.h, 0.h); default: SizedBox.shrink(); } @@ -200,11 +308,7 @@ class _BookAppointmentPageState extends State { void openRegionListBottomSheet(BuildContext context) { regionalViewModel.flush(); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, - title: "", - titleWidget: Consumer( - builder: (_, data, __) => getTitle(data)), - isDismissible: false, + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); }), callBackFunc: () {}); @@ -238,19 +342,16 @@ class _BookAppointmentPageState extends State { if (data.selectedRegionId == null) { return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); } else { - return - Transform.flip( - flipX: data.isArabic ? true : false, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - iconColor: Color(0xff2B353E), - - fit: BoxFit.contain, - ), - ).onPress(() { - data.handleBackPress(); - }); - + return Transform.flip( + flipX: data.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + data.handleBackPress(); + }); } } } diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 0580480..ba5c2ee 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -12,6 +12,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/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/generated/locale_keys.g.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'; @@ -58,7 +59,7 @@ class _SelectClinicPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.selectClinic.tr(context: context), + title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -94,40 +95,83 @@ class _SelectClinicPageState extends State { horizontal: ResponsiveExtension(15).h, ), ), - ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : 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: ClinicCard( - clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); - }), - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), + bookAppointmentsVM.isLiveCareSchedule + ? ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : 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: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ) + : ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : 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: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), ], ); }), @@ -137,6 +181,16 @@ class _SelectClinicPageState extends State { ); } + void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) { + bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic); + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), + ), + ); + } + void onClinicSelected(GetClinicsListResponseModel clinic) { bookAppointmentsViewModel.setSelectedClinic(clinic); bookAppointmentsViewModel.setIsDoctorsListLoading(true); diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 27a60f6..25a6618 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -42,7 +42,11 @@ class _SelectDoctorPageState extends State { @override void initState() { scheduleMicrotask(() { - bookAppointmentsViewModel.getDoctorsList(); + if (bookAppointmentsViewModel.isLiveCareSchedule) { + bookAppointmentsViewModel.getLiveCareDoctorsList(); + } else { + bookAppointmentsViewModel.getDoctorsList(); + } }); super.initState(); } @@ -96,7 +100,8 @@ class _SelectDoctorPageState extends State { padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.doctorsList.length, + itemCount: + bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList.length : bookAppointmentsVM.doctorsList.length), itemBuilder: (context, index) { return bookAppointmentsVM.isDoctorsListLoading ? DoctorCard( @@ -115,11 +120,12 @@ class _SelectDoctorPageState extends State { curve: Curves.easeInOut, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: DoctorCard( - doctorsListResponseModel: bookAppointmentsVM.doctorsList[index], + doctorsListResponseModel: bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index], isLoading: false, bookAppointmentsViewModel: bookAppointmentsViewModel, ).onPress(() async { - bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]); + bookAppointmentsVM + .setSelectedDoctor(bookAppointmentsVM.isLiveCareSchedule ? bookAppointmentsVM.liveCareDoctorsList[index] : bookAppointmentsVM.doctorsList[index]); // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); LoaderBottomSheet.showLoader(); await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart index 57315e6..c9c0555 100644 --- a/lib/presentation/book_appointment/widgets/clinic_card.dart +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -6,14 +6,18 @@ 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/theme/colors.dart'; class ClinicCard extends StatelessWidget { - ClinicCard({super.key, required this.clinicsListResponseModel, required this.isLoading}); + ClinicCard({super.key, required this.clinicsListResponseModel, required this.liveCareClinicsResponseModel, required this.isLoading, required this.bookAppointmentsVM}); GetClinicsListResponseModel clinicsListResponseModel; + GetLiveCareClinicsResponseModel liveCareClinicsResponseModel; bool isLoading; + BookAppointmentsViewModel bookAppointmentsVM; @override Widget build(BuildContext context) { @@ -35,7 +39,10 @@ class ClinicCard extends StatelessWidget { ]), SizedBox(height: 16.h), Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: (isLoading ? "Cardiology" : clinicsListResponseModel.clinicDescription!).toText16(isBold: true).toShimmer2(isShow: isLoading)), + Expanded( + child: (isLoading ? "Cardiology" : (bookAppointmentsVM.isLiveCareSchedule ? liveCareClinicsResponseModel.clinicDesc! : clinicsListResponseModel.clinicDescription!)) + .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)), diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 1e1e0ae..3f3d51c 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -108,29 +108,53 @@ class DoctorCard extends StatelessWidget { onPressed: () async { bookAppointmentsViewModel.setSelectedDoctor(doctorsListResponseModel); LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorFreeSlots( - isBookingForLiveCare: false, - onSuccess: (dynamic respData) async { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, - context, - child: AppointmentCalendar(), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); + bookAppointmentsViewModel.isLiveCareSchedule + ? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots( + isBookingForLiveCare: true, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: "Pick a Date".needTranslation, + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + : await bookAppointmentsViewModel.getDoctorFreeSlots( + isBookingForLiveCare: false, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: "Pick a Date".needTranslation, + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 8feeb5f..6b33b2f 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -34,6 +34,7 @@ class _LandingNavigationState extends State { ], ), bottomNavigationBar: BottomNavigation( + context: context, currentIndex: _currentIndex, onTap: (index) { setState(() => _currentIndex = index); diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index da289c1..1163c66 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -12,27 +12,26 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class BottomNavigation extends StatelessWidget { final int currentIndex; final ValueChanged onTap; + BuildContext? context; - const BottomNavigation({ - super.key, - required this.currentIndex, - required this.onTap, - }); + BottomNavigation({super.key, required this.currentIndex, required this.onTap, this.context}); @override Widget build(BuildContext context) { - AppState appState = getIt.get(); + AppState appState = getIt.get(); final items = [ - BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr()), - appState.isAuthenticated ? BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.myFiles.tr()) : BottomNavItem(icon: AppAssets.feedback, label: LocaleKeys.feedback.tr()), + BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr(context: context)), + appState.isAuthenticated + ? BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.myFiles.tr(context: context)) + : BottomNavItem(icon: AppAssets.feedback, label: LocaleKeys.feedback.tr()), BottomNavItem( icon: AppAssets.bookAppoBottom, - label: LocaleKeys.appointment.tr(), + label: LocaleKeys.appointment.tr(context: context), iconSize: 27, isSpecial: true, ), - appState.isAuthenticated ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr()) : BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()) , - BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr()), + appState.isAuthenticated ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) : BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()), + BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr(context: context)), ]; return Container( @@ -42,7 +41,7 @@ class BottomNavigation extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: List.generate( items.length, - (index) => _buildNavItem(items[index], index), + (index) => _buildNavItem(items[index], index), ), ), ); @@ -57,23 +56,22 @@ class BottomNavigation extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Center( - child: Utils.buildSvgWithAssets( - icon: item.icon, - height: item.iconSize.h, - width: item.iconSize.h, - // iconColor: isSelected ? Colors.black87 : Colors.black87, - ), + Center( + child: Utils.buildSvgWithAssets( + icon: item.icon, + height: item.iconSize.h, + width: item.iconSize.h, + // iconColor: isSelected ? Colors.black87 : Colors.black87, ), + ), const SizedBox(height: 10), item.label.toText12( - fontWeight:FontWeight.w500, + fontWeight: FontWeight.w500, // color: Colors.black87, // textAlign: TextAlign.center, ), - SizedBox(height: item.isSpecial ? 5:0 ) + SizedBox(height: item.isSpecial ? 5 : 0) ], - ), ); } diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index d0b4a6c..954877f 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -66,7 +66,7 @@ class CustomButton extends StatelessWidget { child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), ), Padding( - padding: EdgeInsets.only(top: 2.5), + padding: EdgeInsets.only(top: 0), child: Text( text, style: context.dynamicTextStyle( From 344ecaba18e61a27b2b540dbcf003ee17229351a Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 24 Sep 2025 17:18:36 +0300 Subject: [PATCH 2/9] LiveCare Schedule implementation done --- assets/images/svg/small_livecare_icon.svg | 3 + assets/images/svg/walkin_appointment_icon.svg | 4 + lib/core/app_assets.dart | 2 + .../book_appointments_repo.dart | 77 ++++++++++++ .../book_appointments_view_model.dart | 109 +++++++++++++++-- .../my_appointments/my_appointments_repo.dart | 65 ++++++++-- .../my_appointments_view_model.dart | 25 +++- .../appointment_details_page.dart | 111 +++++++++--------- .../appointment_payment_page.dart | 30 +++-- .../widgets/appointment_card.dart | 71 ++++++----- .../widgets/appointment_doctor_card.dart | 78 +++++++----- .../book_appointment/doctor_profile_page.dart | 70 +++++++---- .../review_appointment_page.dart | 66 ++++++----- lib/widgets/chip/app_custom_chip_widget.dart | 57 ++++----- 14 files changed, 529 insertions(+), 239 deletions(-) create mode 100644 assets/images/svg/small_livecare_icon.svg create mode 100644 assets/images/svg/walkin_appointment_icon.svg diff --git a/assets/images/svg/small_livecare_icon.svg b/assets/images/svg/small_livecare_icon.svg new file mode 100644 index 0000000..a496914 --- /dev/null +++ b/assets/images/svg/small_livecare_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/walkin_appointment_icon.svg b/assets/images/svg/walkin_appointment_icon.svg new file mode 100644 index 0000000..ceafb38 --- /dev/null +++ b/assets/images/svg/walkin_appointment_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 83fb842..f7ca57d 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -133,6 +133,8 @@ class AppAssets { static const String minus = '$svgBasePath/minus.svg'; static const String home_lab_result_icon = '$svgBasePath/home_lab_result_icon.svg'; static const String visa_mastercard_icon = '$svgBasePath/visa_mastercard.svg'; + static const String small_livecare_icon = '$svgBasePath/small_livecare_icon.svg'; + static const String walkin_appointment_icon = '$svgBasePath/walkin_appointment_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index c3cc9cf..5e99585 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -53,6 +53,19 @@ abstract class BookAppointmentsRepo { Future>> getLiveCareDoctorFreeSlots(int clinicID, int serviceID, int projectID, int doctorId, bool isBookingForLiveCare, {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> insertSpecificAppointmentForLiveCare( + {required int docID, + required int clinicID, + required int projectID, + required String selectedTime, + required String selectedDate, + required int initialSlotDuration, + required int genderID, + required int userAge, + required int serviceID, + Function(dynamic)? onSuccess, + Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -580,4 +593,68 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> insertSpecificAppointmentForLiveCare( + {required int docID, + required int clinicID, + required int projectID, + required String selectedTime, + required String selectedDate, + required int initialSlotDuration, + required int genderID, + required int userAge, + required int serviceID, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + Map mapDevice = { + "IsForLiveCare": true, + "ProjectID": projectID, + "ClinicID": clinicID, + "DoctorID": docID, + "ServiceID": serviceID, + "StartTime": selectedTime, + "SelectedTime": selectedTime, + "EndTime": selectedTime, + "InitialSlotDuration": initialSlotDuration, + "StrAppointmentDate": selectedDate, + "IsVirtual": false, + "BookedBy": 102, + "VisitType": 1, + "VisitFor": 1, + "GenderID": genderID, + "Age": userAge + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + INSERT_LIVECARE_SCHEDULE_APPOINTMENT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final appointmentNo = response['AppointmentNo']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: response["ErrorEndUserMessage"], + 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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index e8d02b9..c42a20d 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -414,18 +414,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - //TODO: Handle the cases for LiveCare Schedule, Dental & Laser Clinics + //TODO: Handle the cases for Dental & Laser Clinics Future insertSpecificAppointment( - { - // required int docID, - // required int clinicID, - // required int projectID, - // required String selectedTime, - // required String selectedDate, - // required int initialSlotDuration, - // required int genderID, - // required int userAge, - String? procedureID, + {String? procedureID, num? testTypeEnum, num? testProcedureEnum, int? invoiceNumber, @@ -527,6 +518,102 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future insertSpecificAppointmentForLiveCare({Function(dynamic p1)? onSuccess, Function(dynamic p1)? onError}) async { + _appState = getIt(); + final result = await bookAppointmentsRepo.insertSpecificAppointmentForLiveCare( + docID: selectedDoctor.doctorID!, + clinicID: selectedDoctor.clinicID!, + projectID: selectedDoctor.projectID!, + serviceID: selectedLiveCareClinic.serviceID!, + selectedDate: selectedAppointmentDate, + selectedTime: selectedAppointmentTime, + initialSlotDuration: initialSlotDuration, + genderID: _appState.getAuthenticatedUser()!.gender!, + userAge: _appState.getAuthenticatedUser()!.age!, + onError: onError); + + result.fold( + (failure) async { + print(failure); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // onError!(apiResponse); + LoadingUtils.hideFullScreenLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), + navigationService.navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: apiResponse.data["ErrorEndUserMessage"], + isShowActionButtons: true, + onCancelTap: () { + navigationService.pop(); + }, + onConfirmTap: () async { + navigationService.pop(); + PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel( + appointmentNo: apiResponse.data["SameClinicApptList"][0]['AppointmentNo'], + clinicID: apiResponse.data["SameClinicApptList"][0]['ClinicID'], + projectID: apiResponse.data["SameClinicApptList"][0]['ProjectID'], + endDate: apiResponse.data["SameClinicApptList"][0]['EndTime'], + startTime: apiResponse.data["SameClinicApptList"][0]['StartTime'], + doctorID: apiResponse.data["SameClinicApptList"][0]['DoctorID'], + isLiveCareAppointment: apiResponse.data["SameClinicApptList"][0]['IsLiveCareAppointment'], + originalClinicID: 0, + originalProjectID: 0, + appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'], + ); + + showCommonBottomSheet(navigationService.navigatorKey.currentContext!, + child: Utils.getLoadingWidget(loadingText: "Cancelling your previous appointment....".needTranslation), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false); + await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { + navigationService.pop(); + Future.delayed(Duration(milliseconds: 50)).then((value) async {}); + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + await insertSpecificAppointment( + onError: (err) {}, + onSuccess: (apiResp) async { + LoadingUtils.hideFullScreenLoader(); + await Future.delayed(Duration(milliseconds: 50)).then((value) async { + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + await Future.delayed(Duration(milliseconds: 4000)).then((value) { + LoadingUtils.hideFullScreenLoader(); + Navigator.pushAndRemoveUntil( + navigationService.navigatorKey.currentContext!, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + }); + }); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data == null || apiResponse.data!.isEmpty) { + onError!("No free slots available".tr()); + return; + } + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future getRegionMappedProjectList() async { isRegionListLoading = true; notifyListeners(); diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index e4745cb..99f7c7d 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -1,10 +1,13 @@ +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/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/features/my_appointments/models/resp_models/hospital_model.dart' - show HospitalsModel; +import 'package:hmg_patient_app_new/core/utils/utils.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'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -12,7 +15,8 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); - Future>> getPatientShareAppointment({required int projectID, required int clinicID, required String appointmentNo}); + Future>> getPatientShareAppointment( + {required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment}); Future>> createAdvancePayment( {required String paymentMethodName, @@ -39,7 +43,7 @@ abstract class MyAppointmentsRepo { Future>>> getPatientDoctorsList(); - + Future>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -99,14 +103,15 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { } @override - Future>> getPatientShareAppointment({required int projectID, required int clinicID, required String appointmentNo}) async { - Map mapRequest = {"ProjectID": projectID, "ClinicID": clinicID, "AppointmentNo": appointmentNo, "IsActiveAppointment": true}; + Future>> getPatientShareAppointment( + {required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment}) async { + Map mapRequest = {"ProjectID": projectID, "ClinicID": clinicID, "AppointmentNo": appointmentNo, "IsActiveAppointment": true, "IsForLiveCare": isLiveCareAppointment}; try { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - GET_PATIENT_SHARE, + isLiveCareAppointment ? GET_PATIENT_SHARE_LIVECARE : GET_PATIENT_SHARE, body: mapRequest, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; @@ -495,4 +500,50 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Map requestBody = { + "AppointmentNo": patientAppointmentHistoryResponseModel.appointmentNo, + "AppointmentDate": patientAppointmentHistoryResponseModel.appointmentDate, + "ClientRequestID": clientRequestID, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ServiceID": patientAppointmentHistoryResponseModel.serviceID, + "AcceptedBy": patientAppointmentHistoryResponseModel.doctorID, + "IsFlutter": true, + "DeviceToken": await Utils.getStringFromPrefs(CacheConst.pushToken), + "VoipToken": "", // TODO: Add VoIP Token functionality + "IsVoip": Platform.isIOS ? true : false + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + INSERT_VIDA_REQUEST, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 55fafc4..1b884f7 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -79,7 +79,6 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { - patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); @@ -127,8 +126,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { print('All Appointments: ${patientAppointmentsHistoryList.length}'); } - Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo); + Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, bool isLiveCareAppointment, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo, isLiveCareAppointment: isLiveCareAppointment); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -305,4 +304,24 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future insertLiveCareVIDARequest( + {required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.insertLiveCareVIDARequest(clientRequestID: clientRequestID, patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + + 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) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7a98b9d..f474104 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -81,29 +81,6 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // "Appointment Details".needTranslation.toText20(isBold: true), - // if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - // CustomButton( - // text: "Report".needTranslation, - // onPressed: () {}, - // 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, - // iconSize: 16.h, - // icon: AppAssets.report_icon, - // iconColor: AppColors.primaryRedColor, - // ) - // ], - // ), - // SizedBox(height: 24.h), AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () {}, @@ -162,42 +139,60 @@ class _AppointmentDetailsPageState extends State { ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), SizedBox(height: 16.h), - Stack( - children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24), - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", - fit: BoxFit.contain, - ), - ), - Positioned( - bottom: 0, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.785, - child: CustomButton( - text: "Get Directions".needTranslation, - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); - }, - backgroundColor: AppColors.textColor.withOpacity(0.8), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - iconSize: 13.h, - ).paddingAll(12.h), + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false + ? Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "LiveCare Appointment".toText18(color: AppColors.textColor, isBold: true), + "The doctor will call you once the appointment time approaches." + .needTranslation + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ) + : Stack( + children: [ + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24), + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", + fit: BoxFit.contain, + ), + ), + Positioned( + bottom: 0, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.785, + child: CustomButton( + text: "Get Directions".needTranslation, + onPressed: () { + MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + }, + backgroundColor: AppColors.textColor.withOpacity(0.8), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + iconSize: 13.h, + ).paddingAll(12.h), + ), + ), + ], ), - ), - ], - ), ], ), ), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 77d5d03..28355dc 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -27,9 +27,8 @@ 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: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 'package:smooth_corner/smooth_corner.dart'; @@ -61,6 +60,7 @@ class _AppointmentPaymentPageState extends State { widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, ); }); super.initState(); @@ -361,8 +361,7 @@ class _AppointmentPaymentPageState extends State { } void checkPaymentStatus() async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(); await payfortViewModel.checkPaymentStatus( transactionID: transID, onSuccess: (apiResponse) async { @@ -388,6 +387,21 @@ class _AppointmentPaymentPageState extends State { 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, @@ -396,16 +410,16 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, CustomPageRoute( page: LandingNavigation(), ), (r) => false); - Navigator.of(context).push( - CustomPageRoute(page: MyAppointmentsPage()), - ); + // Navigator.of(context).push( + // CustomPageRoute(page: MyAppointmentsPage()), + // ); }); }); } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index c345099..152436a 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package: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}); @@ -63,44 +64,38 @@ class _AppointmentCardState extends State { spacing: 6.h, runSpacing: 6.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.isLoading - ? "OutPatient" - : appState.isArabic() - ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! - : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!, - 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, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.isLoading ? "Booked" : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), - onPressed: () {}, - backgroundColor: AppColors.successColor.withOpacity(0.1), - borderColor: AppColors.successColor.withOpacity(0.0), - textColor: AppColors.successColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), + AppCustomChipWidget( + icon: widget.isLoading + ? AppAssets.walkin_appointment_icon + : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), + iconColor: widget.isLoading + ? AppColors.textColor + : !widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? AppColors.textColor + : AppColors.whiteColor, + labelText: widget.isLoading + ? "Walk In" + : widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? LocaleKeys.livecare.tr(context: context) + : "Walk In".needTranslation, + backgroundColor: + widget.isLoading ? AppColors.greyColor : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), + textColor: widget.isLoading ? AppColors.textColor : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), + ).toShimmer2(isShow: widget.isLoading), + AppCustomChipWidget( + labelText: widget.isLoading + ? "OutPatient" + : appState.isArabic() + ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! + : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + textColor: AppColors.primaryRedColor, + ).toShimmer2(isShow: widget.isLoading), + AppCustomChipWidget( + labelText: widget.isLoading ? "Booked" : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), + backgroundColor: AppColors.successColor.withOpacity(0.1), + textColor: AppColors.successColor, + ).toShimmer2(isShow: widget.isLoading), ], ).toShimmer2(isShow: widget.isLoading), ), diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index da00d88..b35305f 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -59,6 +59,13 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.doctor_calendar_icon, labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"), + AppCustomChipWidget( + icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, + iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, + labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : "Walk In".needTranslation, + backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, + textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, + ), AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), @@ -113,29 +120,8 @@ class AppointmentDoctorCard extends StatelessWidget { iconSize: 16.h, ); } else { - return Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.reschedule.tr(), - onPressed: () { - onRescheduleTap(); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.appointment_calendar_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - SizedBox(width: 16.h), - Expanded( - child: CustomButton( + return patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false + ? CustomButton( text: LocaleKeys.cancel.tr(), onPressed: () { onCancelTap(); @@ -150,10 +136,48 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.cancel, iconColor: AppColors.whiteColor, iconSize: 16.h, - ), - ), - ], - ); + ) + : Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.reschedule.tr(), + onPressed: () { + onRescheduleTap(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.appointment_calendar_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + onCancelTap(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + ], + ); } } } diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 8640c79..943bf59 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -115,29 +115,53 @@ class DoctorProfilePage extends StatelessWidget { text: "View available appointments".needTranslation, onPressed: () async { LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorFreeSlots( - isBookingForLiveCare: false, - onSuccess: (dynamic respData) async { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, - context, - child: AppointmentCalendar(), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); + bookAppointmentsViewModel.isLiveCareSchedule + ? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots( + isBookingForLiveCare: true, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: "Pick a Date".needTranslation, + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + : await bookAppointmentsViewModel.getDoctorFreeSlots( + isBookingForLiveCare: false, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: "Pick a Date".needTranslation, + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index d3804d8..1439270 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -204,34 +204,46 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); - await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { - print(err.data["ErrorEndUserMessage"]); - LoadingUtils.hideFullScreenLoader(); - }, onSuccess: (apiResp) async { - LoadingUtils.hideFullScreenLoader(); - await Future.delayed(Duration(milliseconds: 50)).then((value) async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); - await Future.delayed(Duration(milliseconds: 4000)).then((value) { - LoadingUtils.hideFullScreenLoader(); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); + if (bookAppointmentsViewModel.isLiveCareSchedule) { + await bookAppointmentsViewModel.insertSpecificAppointmentForLiveCare(onError: (err) { + print(err.data["ErrorEndUserMessage"]); + LoadingUtils.hideFullScreenLoader(); + }, onSuccess: (apiResp) async { + LoadingUtils.hideFullScreenLoader(); + await Future.delayed(Duration(milliseconds: 50)).then((value) async { + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + await Future.delayed(Duration(milliseconds: 4000)).then((value) { + LoadingUtils.hideFullScreenLoader(); + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); }); }); - }); - - // await Future.delayed(Duration(milliseconds: 4000)).then((value) async { - // LoadingUtils.hideFullScreenLoader(); - - // await Future.delayed(Duration(milliseconds: 50)).then((value) async { - // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); - // await Future.delayed(Duration(milliseconds: 4000)).then((value) { - // LoadingUtils.hideFullScreenLoader(); - // }); - // }); - // }); + } else { + await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { + print(err.data["ErrorEndUserMessage"]); + LoadingUtils.hideFullScreenLoader(); + }, onSuccess: (apiResp) async { + LoadingUtils.hideFullScreenLoader(); + await Future.delayed(Duration(milliseconds: 50)).then((value) async { + LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + await Future.delayed(Duration(milliseconds: 4000)).then((value) { + LoadingUtils.hideFullScreenLoader(); + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + }); + } } } diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index a6e1817..a4db172 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -48,63 +48,46 @@ class AppCustomChipWidget extends StatelessWidget { padding: EdgeInsets.all(0.0), shape: SmoothRectangleBorder( side: BorderSide( - width: 0.0, + width: 10.0, color: Colors.transparent, // Crucially, set color to transparent style: BorderStyle.none, ), - borderRadius: BorderRadius.circular(10.0), // Apply a border radius of 16.0 + borderRadius: BorderRadius.circular(8.0), // Apply a border radius of 16.0 ), ), 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), + 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), // 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: -4.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, - shape: shape, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8 ?? 0), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: deleteIconSize!.width!.h, - height: deleteIconSize!.height.h, - iconColor: deleteIconHasColor ? deleteIconColor : null) + ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width!.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) : null, onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ) : 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.64, color: textColor), padding: EdgeInsets.all(0.0), backgroundColor: backgroundColor, - shape: shape, - labelPadding: EdgeInsetsDirectional.only( - start: 8.h, - end: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), + shape: shape ?? SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8 ?? 0), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: deleteIconSize!.width.h, - height: deleteIconSize!.height.h, - iconColor: deleteIconHasColor ? deleteIconColor : null) + ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) : null, onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ), From a07369165d9618fd5287d092e3a2ea2c091c658d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 25 Sep 2025 13:04:56 +0300 Subject: [PATCH 3/9] Updates --- assets/images/svg/cardiology_clinic_icon.svg | 3 + assets/images/svg/generic_clinic_icon.svg | 3 + assets/images/svg/hmc.svg | 11 ++- assets/images/svg/hmg.svg | 11 ++- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 2 +- lib/core/app_assets.dart | 2 + lib/core/utils/doctor_response_mapper.dart | 23 +++--- .../book_appointments_view_model.dart | 37 ++++++++++ .../doctors_list_response_model.dart | 36 +++++++++- .../appointment_via_region_viewmodel.dart | 23 +++++- .../resp_models/doctor_list_api_response.dart | 5 +- .../appointment_details_page.dart | 6 +- .../hospital_bottom_sheet_body.dart | 13 +++- .../region_list_widget.dart | 28 +++----- .../book_appointment_page.dart | 11 +-- .../book_appointment/select_clinic_page.dart | 72 +++++++++++++++++-- .../select_livecare_clinic_page.dart | 15 ++-- .../widgets/appointment_calendar.dart | 2 +- .../book_appointment/widgets/clinic_card.dart | 2 +- lib/presentation/home/landing_page.dart | 2 +- .../medical_file/medical_file_page.dart | 4 +- .../medical_file/widgets/lab_rad_card.dart | 2 +- .../widgets/patient_medical_report_card.dart | 8 +-- lib/splashPage.dart | 8 +-- 25 files changed, 248 insertions(+), 85 deletions(-) create mode 100644 assets/images/svg/cardiology_clinic_icon.svg create mode 100644 assets/images/svg/generic_clinic_icon.svg diff --git a/assets/images/svg/cardiology_clinic_icon.svg b/assets/images/svg/cardiology_clinic_icon.svg new file mode 100644 index 0000000..a4e73f1 --- /dev/null +++ b/assets/images/svg/cardiology_clinic_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/generic_clinic_icon.svg b/assets/images/svg/generic_clinic_icon.svg new file mode 100644 index 0000000..adb1303 --- /dev/null +++ b/assets/images/svg/generic_clinic_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/hmc.svg b/assets/images/svg/hmc.svg index a127cd9..824ca06 100644 --- a/assets/images/svg/hmc.svg +++ b/assets/images/svg/hmc.svg @@ -1,8 +1,5 @@ - - - + + + + diff --git a/assets/images/svg/hmg.svg b/assets/images/svg/hmg.svg index 7b199bf..ccaed0c 100644 --- a/assets/images/svg/hmg.svg +++ b/assets/images/svg/hmg.svg @@ -1,8 +1,5 @@ - - - + + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index deb107d..a4e76fc 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -176,8 +176,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 4767477; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4767477; } body.removeWhere((key, value) => value == null); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index fa02b2e..5886dfa 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index f7ca57d..1022b5d 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -135,6 +135,8 @@ class AppAssets { static const String visa_mastercard_icon = '$svgBasePath/visa_mastercard.svg'; static const String small_livecare_icon = '$svgBasePath/small_livecare_icon.svg'; static const String walkin_appointment_icon = '$svgBasePath/walkin_appointment_icon.svg'; + static const String cardiology_clinic_icon = '$svgBasePath/cardiology_clinic_icon.svg'; + static const String generic_clinic_icon = '$svgBasePath/generic_clinic_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/utils/doctor_response_mapper.dart b/lib/core/utils/doctor_response_mapper.dart index bff4598..994e9a1 100644 --- a/lib/core/utils/doctor_response_mapper.dart +++ b/lib/core/utils/doctor_response_mapper.dart @@ -2,11 +2,12 @@ import 'dart:math'; import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst; import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; +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 RegionList, PatientDoctorAppointmentList, DoctorList, PatientDoctorAppointmentListByRegion; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; class DoctorMapper{ - static Future getMappedDoctor(List doctorList, + static Future getMappedDoctor(List doctorList, {bool isArabic = false,double lat = 0.0,double long = 0.0}) async { RegionList regionList = RegionList(); @@ -41,16 +42,16 @@ class DoctorMapper{ isHMC: element.isHMC ); if(element.projectDistanceInKiloMeters!= null ){ - if(regionDoctorList!.distance>element.projectDistanceInKiloMeters){ - regionDoctorList.distance = element.projectDistanceInKiloMeters; + if(regionDoctorList!.distance>element.projectDistanceInKiloMeters!){ + regionDoctorList.distance = element.projectDistanceInKiloMeters!; } if (element.isHMC == true && - element.projectDistanceInKiloMeters < + element.projectDistanceInKiloMeters! < regionDoctorList.hmcDistance) { - regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters; - } else if (element.projectDistanceInKiloMeters < + regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters!; + } else if (element.projectDistanceInKiloMeters! < regionDoctorList.hmgDistance) { - regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters; + regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters!; } }else if (lat != 0&& @@ -65,12 +66,12 @@ class DoctorMapper{ regionDoctorList.distance = distance; } if (element.isHMC == true && - element.projectDistanceInKiloMeters < + (element.projectDistanceInKiloMeters??0) < regionDoctorList.hmcDistance) { - regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters; - } else if (element.projectDistanceInKiloMeters < + regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters??0; + } else if ((element.projectDistanceInKiloMeters??0) < regionDoctorList.hmgDistance) { - regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters; + regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters??0; } } targetList?.add(newAppointment); diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index c42a20d..5ef653a 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -113,6 +113,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { isDoctorsListLoading = true; isDoctorProfileLoading = true; isLiveCareSchedule = false; + currentlySelectedHospitalFromRegionFlow = null; clinicsList.clear(); doctorsList.clear(); liveCareClinicsList.clear(); @@ -289,6 +290,42 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future getMappedDoctors( + {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + filteredHospitalList = null; + hospitalList = null; + isRegionListLoading = true; + notifyListeners(); + projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; + final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); + + result.fold( + (failure) async { + onError!("No doctors found for the search criteria".needTranslation); + }, + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + var doctorList = apiResponse.data!; + hospitalList = await DoctorMapper.getMappedDoctor( + doctorList, + isArabic: _appState.isArabic(), + lat: _appState.userLat, + long: _appState.userLong, + ); + + var isLocationEnabled = (_appState.userLat != 0) && (_appState.userLong != 0); + hospitalList = await DoctorMapper.sortList(isLocationEnabled, hospitalList!); + + isRegionListLoading = false; + filteredHospitalList = hospitalList; + notifyListeners(); + } + }, + ); + } + Future getDoctorProfile({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await bookAppointmentsRepo.getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); diff --git a/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart index 51a9403..5df6ed8 100644 --- a/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart +++ b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart @@ -59,6 +59,13 @@ class DoctorsListResponseModel { int? virtualEmploymentType; dynamic workingHours; dynamic vida3Id; + String? region; + String? regionArabic; + String? regionEnglish; + String? regionID; + String? projectBottomName; + String? projectTopName; + DoctorsListResponseModel( {this.clinicID, @@ -120,7 +127,13 @@ class DoctorsListResponseModel { this.transactionType, this.virtualEmploymentType, this.workingHours, - this.vida3Id}); + this.vida3Id, + this.region, + this.regionArabic, + this.regionEnglish, + this.regionID, + this.projectBottomName, + this.projectTopName,}); DoctorsListResponseModel.fromJson(Map json) { clinicID = json['ClinicID']; @@ -183,6 +196,10 @@ class DoctorsListResponseModel { virtualEmploymentType = json['VirtualEmploymentType']; workingHours = json['WorkingHours']; vida3Id = json['vida3Id']; + regionArabic = json['RegionNameN']; + regionEnglish = json['RegionName']; + projectBottomName = json['ProjectNameBottom']; + projectTopName = json['ProjectNameTop']; } Map toJson() { @@ -249,4 +266,21 @@ class DoctorsListResponseModel { data['vida3Id'] = this.vida3Id; return data; } + + String? getRegionName(bool isArabic) { + if (isArabic) { + return regionArabic; + } + return regionEnglish; + } + String getProjectCompleteName(){ + return "${this.projectTopName} ${this.projectBottomName}"; + } + + String getProjectCompleteNameWithLocale({bool isArabic = false}) { + if (isArabic) { + return "${this.projectBottomName} ${this.projectTopName}"; + } + return "${this.projectTopName} ${this.projectBottomName}"; + } } diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 4829f17..51ec097 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart' show ChangeNotifier; import 'package:hmg_patient_app_new/core/app_state.dart' show AppState; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -14,6 +15,11 @@ enum AppointmentViaRegionState { DOCTOR_SELECTION } +enum RegionBottomSheetType{ + FOR_REGION, + FOR_CLINIIC +} + class AppointmentViaRegionViewmodel extends ChangeNotifier { String? selectedRegionId; String? selectedFacilityType; @@ -23,6 +29,8 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { AppointmentViaRegionState.REGION_SELECTION; final AppState appState; + RegionBottomSheetType regionBottomSheetType = RegionBottomSheetType.FOR_REGION; + AppointmentViaRegionViewmodel({required this.navigationService,required this.appState}); void setSelectedRegionId(String? regionId) { @@ -35,12 +43,17 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { notifyListeners(); } + void setBottomSheetType(RegionBottomSheetType type) { + regionBottomSheetType = type; + notifyListeners(); + } + void setBottomSheetState(AppointmentViaRegionState state) { bottomSheetState = state; notifyListeners(); } - void handleLastStep(){ + void handleLastStepForRegion(){ navigationService.pop(); navigationService.push(CustomPageRoute( page: SelectClinicPage(), @@ -65,6 +78,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { void flush() { setSelectedRegionId(null); setFacility(null); + setBottomSheetType(RegionBottomSheetType.FOR_REGION); setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION); } @@ -73,4 +87,11 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { } bool get isArabic => appState.isArabic(); + + void handleLastStepForClinic() { + navigationService.pop(); + navigationService.push(CustomPageRoute( + page: SelectDoctorPage(), + ),); + } } diff --git a/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart index c3155e1..c2b9add 100644 --- a/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart +++ b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart @@ -1,4 +1,5 @@ +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/hospital_model.dart' show HospitalsModel; class DoctorList { @@ -236,7 +237,7 @@ class DoctorList { class PatientDoctorAppointmentList { String? filterName = ""; String? distanceInKMs = ""; - List? patientDoctorAppointmentList = []; + List? patientDoctorAppointmentList = []; String? projectTopName = ""; String? projectBottomName = ""; bool? isHMC; @@ -247,7 +248,7 @@ class PatientDoctorAppointmentList { this.distanceInKMs, this.projectTopName, this.projectBottomName, - DoctorList? patientDoctorAppointment, + DoctorsListResponseModel? patientDoctorAppointment, HospitalsModel? model, this.isHMC = false}) { if (model != null) { diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index f474104..7ebaa37 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -139,16 +139,16 @@ class _AppointmentDetailsPageState extends State { ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), SizedBox(height: 16.h), + //TODO Add countdown timer in case of LiveCare Appointment widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 58.h, height: 58.h), - SizedBox(width: 18.h), + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "LiveCare Appointment".toText18(color: AppColors.textColor, isBold: true), "The doctor will call you once the appointment time approaches." .needTranslation .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), 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 43ce9c6..e7726ae 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 @@ -96,9 +96,16 @@ class HospitalBottomSheetBody extends StatelessWidget { isLocationEnabled: appointmentsViewModel.isLocationEnabled(), ).onPress(() { regionalViewModel.setHospitalModel(hospital); - regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); - regionalViewModel.handleLastStep(); - });}, + if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); + regionalViewModel.handleLastStepForRegion(); + }else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { + + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForClinic(); + + } + });}, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), 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 180185f..09c7c40 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart @@ -6,14 +6,11 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; 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/my_appointments/appointment_via_region_viewmodel.dart'; -import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart' - show MyAppointmentsViewModel; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart' - show RegionListItem; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart' show MyAppointmentsViewModel; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart' show RegionListItem; import 'package:provider/provider.dart'; class RegionBottomSheetBody extends StatefulWidget { - const RegionBottomSheetBody({super.key}); @override @@ -27,7 +24,11 @@ class _RegionBottomSheetBodyState extends State { @override void initState() { scheduleMicrotask(() { - myAppointmentsViewModel.getRegionMappedProjectList(); + if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { + myAppointmentsViewModel.getRegionMappedProjectList(); + } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { + myAppointmentsViewModel.getMappedDoctors(); + } }); super.initState(); } @@ -53,26 +54,19 @@ class _RegionBottomSheetBodyState extends State { return SizedBox( height: MediaQuery.of(context).size.height * 0.5, child: ListView.separated( - itemCount: - myAppointmentsVM.hospitalList?.registeredDoctorMap?.length ?? - 0, + itemCount: myAppointmentsVM.hospitalList?.registeredDoctorMap?.length ?? 0, separatorBuilder: (_, __) { return SizedBox( height: 16.h, ); }, itemBuilder: (_, index) { - String key = myAppointmentsVM - .hospitalList?.registeredDoctorMap?.keys - .toList()[index] ?? - ''; + String key = myAppointmentsVM.hospitalList?.registeredDoctorMap?.keys.toList()[index] ?? ''; return RegionListItem( title: key, subTitle: "", - hmcCount: - "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmcSize ?? 0}", - hmgCount: - "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}", + hmcCount: "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmcSize ?? 0}", + hmgCount: "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}", ).onPress(() { regionalViewModel.setSelectedRegionId(key); regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION); diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 78f1540..0168f24 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -58,7 +58,7 @@ class _BookAppointmentPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.bookAppo.tr(context: context), - isLeading: Navigator.canPop(context), + isLeading: false, child: SingleChildScrollView( child: Consumer(builder: (context, bookAppointmentsVM, child) { return Column( @@ -156,6 +156,7 @@ class _BookAppointmentPageState extends State { ], ).onPress(() { bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false); + bookAppointmentsViewModel.setProjectID(null); Navigator.of(context).push( CustomPageRoute( page: SearchDoctorByName(), @@ -185,7 +186,8 @@ 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(() { - openRegionListBottomSheet(context); + bookAppointmentsViewModel.setProjectID(null); + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); }), ], ), @@ -291,7 +293,7 @@ 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(() { - openRegionListBottomSheet(context); + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); }), ], ), @@ -305,8 +307,9 @@ class _BookAppointmentPageState extends State { return Container(); } - void openRegionListBottomSheet(BuildContext context) { + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { regionalViewModel.flush(); + regionalViewModel.setBottomSheetType(type); // AppointmentViaRegionViewmodel? viewmodel = null; showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (_, data, __) { diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index ba5c2ee..d1c7fbd 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -13,12 +13,17 @@ 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/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/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/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'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -34,7 +39,7 @@ class SelectClinicPage extends StatefulWidget { class _SelectClinicPageState extends State { TextEditingController searchEditingController = TextEditingController(); FocusNode textFocusNode = FocusNode(); - + late AppointmentViaRegionViewmodel regionalViewModel; late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; @@ -55,6 +60,7 @@ class _SelectClinicPageState extends State { @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); + regionalViewModel = Provider.of(context, listen: true); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -197,15 +203,69 @@ class _SelectClinicPageState extends State { if (clinic.isLiveCareClinicAndOnline ?? false) { Navigator.of(context).push( CustomPageRoute( - page: SelectLivecareClinicPage(), + page: SelectLivecareClinicPage(onNegativeClicked: (){ + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC); + },), ), ); } else { - Navigator.of(context).push( - CustomPageRoute( - page: SelectDoctorPage(), - ), + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC); + // Navigator.of(context).push( + // CustomPageRoute( + // page: SelectDoctorPage(), + // ), + // ); + } + } + + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { + bookAppointmentsViewModel.setProjectID(null); + + regionalViewModel.flush(); + regionalViewModel.setBottomSheetType(type); + // 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.REGION_SELECTION) { + return RegionBottomSheetBody(); + } + if (data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION) { + bookAppointmentsViewModel.resetFilterList(); + return FacilityTypeSelectionWidget( + selectedRegion: data.selectedRegionId ?? "", ); } + if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { + return HospitalBottomSheetBody(); + } + if(data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION){ + bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID.toString()); + } + else { + return SizedBox.shrink(); + } + return SizedBox.shrink(); + } + + getTitle(AppointmentViaRegionViewmodel data) { + if (data.selectedRegionId == null) { + return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); + } else { + return Transform.flip( + flipX: data.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + data.handleBackPress(); + }); + } } } diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 76d85af..ac6b617 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -14,7 +14,9 @@ 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 { - const SelectLivecareClinicPage({super.key}); + + final VoidCallback? onNegativeClicked; + const SelectLivecareClinicPage({super.key, this.onNegativeClicked}); @override Widget build(BuildContext context) { @@ -122,11 +124,12 @@ class SelectLivecareClinicPage extends StatelessWidget { text: "No, Thanks. I would like a physical visit".needTranslation, onPressed: () { Navigator.of(context).pop(); - Navigator.of(context).push( - CustomPageRoute( - page: SelectDoctorPage(), - ), - ); + onNegativeClicked?.call(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: SelectDoctorPage(), + // ), + // ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 046003a..e7a47aa 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -59,8 +59,8 @@ class _AppointmentCalendarState extends State { @override void initState() { + _calendarController = CalendarController(); scheduleMicrotask(() { - _calendarController = CalendarController(); _events = { _selectedDay: ['Event A0'] }; diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart index c9c0555..6850f9f 100644 --- a/lib/presentation/book_appointment/widgets/clinic_card.dart +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -32,7 +32,7 @@ class ClinicCard extends StatelessWidget { child: Column( children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "".toText16(isBold: true).toShimmer2(isShow: isLoading), + Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 24.h, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), (clinicsListResponseModel.isLiveCareClinicAndOnline ?? true) ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading) : SizedBox.shrink(), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 12bc8f5..402fb6b 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -210,7 +210,7 @@ class _LandingPageState extends State { : 3, layout: SwiperLayout.STACK, loop: true, - itemWidth: MediaQuery.of(context).size.width - 72, + itemWidth: MediaQuery.of(context).size.width - 48.h, indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 18b6085..01504b3 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -468,7 +468,7 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14, + fontSize: 13, fontWeight: FontWeight.w500, borderRadius: 12.h, height: 40.h, @@ -485,7 +485,7 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14, + fontSize: 13, fontWeight: FontWeight.w500, borderRadius: 12.h, height: 40.h, diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index 66021b2..766ef6d 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -32,7 +32,7 @@ class LabRadCard extends StatelessWidget { fit: BoxFit.contain, ).toShimmer2(isShow: false, radius: 12.h), SizedBox(width: 8.h), - labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.h, height: 32.h), + labelText.toText13(isBold: true).toShimmer2(isShow: false, radius: 6.h, height: 32.h), ], ), SizedBox(height: 16.h), diff --git a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart index eb1730c..1762886 100644 --- a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart +++ b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart @@ -110,15 +110,15 @@ class PatientMedicalReportCard extends StatelessWidget { onPressed: () async { getMedicalReportPDF(false, context, _appState); }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12.h, height: 40.h, icon: AppAssets.download_1, - iconColor: AppColors.primaryRedColor, + iconColor: AppColors.whiteColor, iconSize: 16.h, ).toShimmer2(isShow: isLoading), ), diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 3e70742..fc0d40d 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -48,11 +48,11 @@ class _SplashScreenState extends State { Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); - if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { + // 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()))); - } + // } else { + // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + // } }); var zoom = ZoomVideoSdk(); InitConfig initConfig = InitConfig( From 918eb18b5f4ce94528fb963141008462a7f90a5e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 25 Sep 2025 13:09:41 +0300 Subject: [PATCH 4/9] Region flow in select clinic implemented --- .../appointment_via_region_viewmodel.dart | 2 +- .../book_appointment/select_clinic_page.dart | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 51ec097..852e678 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -56,7 +56,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { void handleLastStepForRegion(){ navigationService.pop(); navigationService.push(CustomPageRoute( - page: SelectClinicPage(), + page: SelectClinicPage(isFromRegionFlow: true,), ),); } diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index d1c7fbd..101f29e 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -30,7 +30,9 @@ import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; class SelectClinicPage extends StatefulWidget { - const SelectClinicPage({super.key}); + bool isFromRegionFlow; + + SelectClinicPage({super.key, this.isFromRegionFlow = false}); @override State createState() => _SelectClinicPageState(); @@ -204,17 +206,24 @@ class _SelectClinicPageState extends State { Navigator.of(context).push( CustomPageRoute( page: SelectLivecareClinicPage(onNegativeClicked: (){ - openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC); - },), + handleDoctorScreen(); + },), + ), + ); + } else { + handleDoctorScreen(); + } + } + + void handleDoctorScreen() { + if (widget.isFromRegionFlow) { + Navigator.of(context).push( + CustomPageRoute( + page: SelectDoctorPage(), ), ); } else { openRegionListBottomSheet(context, RegionBottomSheetType.FOR_CLINIIC); - // Navigator.of(context).push( - // CustomPageRoute( - // page: SelectDoctorPage(), - // ), - // ); } } From 62e82a7a316e282158b6f50268653f28b3f4bb01 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Fri, 26 Sep 2025 17:17:31 +0300 Subject: [PATCH 5/9] Empty data state implementation contd. --- assets/animations/lottie/Nodata.json | 1 + lib/core/api_consts.dart | 2 +- lib/core/app_assets.dart | 1 + lib/core/utils/utils.dart | 9 +- lib/features/lab/lab_view_model.dart | 7 +- .../my_appointments_view_model.dart | 29 ++- .../appointment_details_page.dart | 4 +- .../appointment_payment_page.dart | 2 +- .../appointments/my_appointments_page.dart | 8 +- .../appointments/my_doctors_page.dart | 2 +- .../hospital_bottom_sheet_body.dart | 2 +- .../book_appointment_page.dart | 2 +- .../book_appointment/doctor_profile_page.dart | 2 +- .../review_appointment_page.dart | 7 +- .../search_doctor_by_name.dart | 2 +- .../book_appointment/select_clinic_page.dart | 2 +- .../book_appointment/select_doctor_page.dart | 2 +- .../select_livecare_clinic_page.dart | 2 +- .../habib_wallet/habib_wallet_page.dart | 2 +- .../habib_wallet/recharge_wallet_page.dart | 2 +- .../wallet_payment_confirm_page.dart | 2 +- .../widgets/select_hospital_bottom_sheet.dart | 2 +- .../insurance/insurance_home_page.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 66 ++--- lib/presentation/lab/search_lab_report.dart | 2 +- .../medical_file/medical_file_page.dart | 2 +- .../medical_file/medical_reports_page.dart | 2 +- .../patient_sickleaves_list_page.dart | 2 +- .../medical_file/vaccine_list_page.dart | 2 +- .../prescription_detail_page.dart | 2 +- .../prescriptions_list_page.dart | 2 +- .../profile_settings/profile_settings.dart | 2 +- .../radiology/radiology_orders_page.dart | 232 +++++++++--------- .../radiology/radiology_result_page.dart | 2 +- lib/splashPage.dart | 8 +- .../appbar}/collapsing_list_view.dart | 0 pubspec.yaml | 2 +- 37 files changed, 231 insertions(+), 191 deletions(-) create mode 100644 assets/animations/lottie/Nodata.json rename lib/{presentation/lab => widgets/appbar}/collapsing_list_view.dart (100%) diff --git a/assets/animations/lottie/Nodata.json b/assets/animations/lottie/Nodata.json new file mode 100644 index 0000000..647c39d --- /dev/null +++ b/assets/animations/lottie/Nodata.json @@ -0,0 +1 @@ +{"nm":"Comp 1","ddd":0,"h":120,"w":120,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"ruoi","sr":1,"st":0,"op":50,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[60.531,10.945,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[57.361,61.016,0],"t":0,"ti":[-13.9099960327148,5.27300262451172,0],"to":[-4.67500305175781,-4.12800598144531,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[57.699,41.796,0],"t":10.219,"ti":[-4.54498291015625,3.73400115966797,0],"to":[12.8159942626953,-4.85800170898438,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[79.084,33.982,0],"t":19.445,"ti":[20.0290069580078,1.20700073242188,0],"to":[6.61601257324219,-5.43799591064453,0]},{"s":[59.691,9.121,0],"t":35}]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.033},"i":{"x":0.833,"y":0.967},"s":[100],"t":35},{"s":[0],"t":49}]}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"ruoi","ix":1,"cix":2,"np":3,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","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.994,0],[0,-0.994],[0.995,0],[0,0.994]],"o":[[0.995,0],[0,0.994],[-0.994,0],[0,-0.994]],"v":[[-0.001,-1.801],[1.801,-0.001],[-0.001,1.801],[-1.801,-0.001]]}}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.1804,0.1882,0.2235]},"r":1,"o":{"a":0,"k":100}},{"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":[62.4,13.144],"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 2","ix":2,"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":[[-1.422,0],[0,-1.422],[1.421,0],[0,1.422]],"o":[[1.421,0],[0,1.422],[-1.422,0],[0,-1.422]],"v":[[0.001,-2.574],[2.574,0],[0.001,2.574],[-2.574,0]]}}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"c":{"a":0,"k":[0.1804,0.1882,0.2235]}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"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":[64.145,9.606],"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 3","ix":3,"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":[[-1.996,0],[0,-1.996],[1.996,0],[0,1.996]],"o":[[1.996,0],[0,1.996],[-1.996,0],[0,-1.996]],"v":[[0,-3.614],[3.614,0],[0,3.614],[-3.614,0]]}}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"c":{"a":0,"k":[0.1804,0.1882,0.2235]}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"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":[57.957,10.552],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[60.531,10.941],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[60.531,10.941],"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},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":50,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-0.75,-0.75,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.033},"i":{"x":0.833,"y":0.967},"s":[100],"t":35},{"s":[0],"t":49}]}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 6","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":false,"i":[[0,0],[-13.91,5.273],[-4.545,3.734],[20.029,1.207]],"o":[[-4.675,-4.128],[12.816,-4.858],[6.616,-5.438],[0,0]],"v":[[-7.383,24.76],[-7.046,5.54],[14.34,-2.273],[-3.178,-24.76]]}}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100},"w":{"a":0,"k":1},"d":[{"nm":"dash","n":"d","v":{"a":0,"k":2.028}},{"nm":"gap","n":"g","v":{"a":0,"k":2.028}},{"nm":"offset","n":"o","v":{"a":0,"k":0}}],"c":{"a":0,"k":[0.1804,0.1882,0.2235]}},{"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":[67.87,37.631],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.167,"y":0.033},"i":{"x":0.833,"y":0.953},"s":[0],"t":0},{"s":[100],"t":35}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":2},{"ty":4,"nm":"im_emptyBox Outlines","sr":1,"st":0,"op":50,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[60,60,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"box","ix":1,"cix":2,"np":4,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 7","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,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-0.001,-16.607],[-32.143,-0.002],[-0.001,16.607],[32.144,-0.002]]}}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8,0.8196,0.851]},"r":1,"o":{"a":0,"k":100}},{"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":[60,55.75],"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 8","ix":2,"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,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[12.856,-23.249],[0,-16.605],[-12.857,-23.249],[-45,-6.641],[-32.144,0.001],[-45,6.645],[-12.857,23.249],[0,16.609],[12.856,23.249],[45,6.645],[32.143,0.001],[45,-6.641]]}}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9373,0.9373,0.9373]},"r":1,"o":{"a":0,"k":100}},{"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":[60,55.748],"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 9","ix":3,"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,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.072,24.171],[16.072,11.312],[16.072,-24.171],[-16.072,-24.171]]}}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9529,0.9529,0.9529]},"r":1,"o":{"a":0,"k":100}},{"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":[76.072,83.33],"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 10","ix":4,"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,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-32.143,-24.171],[-32.143,11.311],[-0.001,24.171],[32.144,11.311],[32.144,-24.171]]}}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8,0.8196,0.851]},"r":1,"o":{"a":0,"k":100}},{"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":[60,83.33],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[60,60.186],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[60,60.186],"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}],"v":"4.7.0","fr":25,"op":50,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5886dfa..fa02b2e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 1022b5d..447cef2 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -175,4 +175,5 @@ class AppAnimations { static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json'; static const String warningAnimation = '$lottieBasePath/warningAnimation.json'; static const String splashLaunching = '$lottieBasePath/splash_launching.json'; + static const String noData = '$lottieBasePath/Nodata.json'; } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 35b99bf..0a84100 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -301,13 +301,16 @@ class Utils { return false; } - static Widget getNoDataWidget(BuildContext context, {String? errorText}) { + static Widget getNoDataWidget(BuildContext context, {String? noDataText}) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - SvgPicture.asset('assets/images/svg/not_found.svg', width: 150.0, height: 150.0), - (errorText ?? LocaleKeys.noDataAvailable.tr()).toText16(isCenter: true).paddingOnly(top: 15), + SizedBox(height: 48.h), + Lottie.asset(AppAnimations.noData, repeat: false, reverse: false, frameRate: FrameRate(60), width: 150.h, height: 150.h, fit: BoxFit.fill), + SizedBox(height: 12.h), + (noDataText ?? LocaleKeys.noDataAvailable.tr()).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 12.h), ], ).center; } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 29b90f8..06657b5 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -34,7 +34,12 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getPatientLabOrders(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isLabOrdersLoading = false; + isLabResultsLoading = false; + notifyListeners(); + }, + // => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 1b884f7..c553f0e 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -19,6 +19,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isTimeLineAppointmentsLoading = false; bool isPatientMyDoctorsLoading = false; + bool isAppointmentDataToBeLoaded = true; + List patientAppointmentsHistoryList = []; List patientUpcomingAppointmentsHistoryList = []; @@ -38,15 +40,17 @@ class MyAppointmentsViewModel extends ChangeNotifier { } initAppointmentsViewModel() { - patientAppointmentsHistoryList.clear(); - patientUpcomingAppointmentsHistoryList.clear(); - patientArrivedAppointmentsHistoryList.clear(); - patientTimelineAppointmentsList.clear(); - patientMyDoctorsList.clear(); - isMyAppointmentsLoading = true; + if (isAppointmentDataToBeLoaded) { + patientAppointmentsHistoryList.clear(); + patientUpcomingAppointmentsHistoryList.clear(); + patientArrivedAppointmentsHistoryList.clear(); + patientTimelineAppointmentsList.clear(); + isMyAppointmentsLoading = true; + isTimeLineAppointmentsLoading = true; + patientMyDoctorsList.clear(); + isPatientMyDoctorsLoading = true; + } isAppointmentPatientShareLoading = true; - isTimeLineAppointmentsLoading = true; - isPatientMyDoctorsLoading = true; notifyListeners(); } @@ -70,6 +74,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsAppointmentDataToBeLoaded(bool val) { + isAppointmentDataToBeLoaded = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -79,6 +88,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isAppointmentDataToBeLoaded) return; + patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); @@ -94,6 +105,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientUpcomingAppointmentsHistoryList = apiResponse.data!; isMyAppointmentsLoading = false; + isAppointmentDataToBeLoaded = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -283,6 +295,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isAppointmentDataToBeLoaded) return; final result = await myAppointmentsRepo.getPatientDoctorsList(); result.fold( diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7ebaa37..6976188 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -22,7 +22,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/appointment_paymen import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -85,6 +85,7 @@ class _AppointmentDetailsPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () {}, onCancelTap: () async { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); showCommonBottomSheet(context, child: Utils.getLoadingWidget(), callBackFunc: (str) {}, @@ -471,6 +472,7 @@ class _AppointmentDetailsPageState extends State { : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 28355dc..9d6b557 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -21,7 +21,7 @@ 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/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 5972435..97ca236 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -9,7 +9,7 @@ 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/presentation/appointments/widgets/appointment_card.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; @@ -116,7 +116,7 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context); + : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), @@ -159,7 +159,7 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context); + : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), @@ -202,7 +202,7 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context); + : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 0b002c9..2fedfd8 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -10,7 +10,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/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/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; 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 e7726ae..44cc43e 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 @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie 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/lab/collapsing_list_view.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'; diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 0168f24..66a4ab7 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -17,7 +17,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_s 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/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 943bf59..6084c3d 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -14,7 +14,7 @@ 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/widgets/appointment_calendar.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 1439270..af3d354 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -10,14 +10,14 @@ 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/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/home/navigation_screen.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; class ReviewAppointmentPage extends StatefulWidget { @@ -31,10 +31,12 @@ class _ReviewAppointmentPageState extends State { late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; late AuthenticationViewModel authVM; + late MyAppointmentsViewModel myAppointmentsViewModel; @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); + myAppointmentsViewModel = Provider.of(context, listen: false); authVM = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( @@ -203,6 +205,7 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { await bookAppointmentsViewModel.insertSpecificAppointmentForLiveCare(onError: (err) { diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index e6b0ba1..8f611dc 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/generated/locale_keys.g.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/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 101f29e..36a490d 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -21,7 +21,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bot 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'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 25a6618..fd6e691 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -15,7 +15,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/generated/locale_keys.g.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/presentation/lab/collapsing_list_view.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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index ac6b617..1e40357 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -7,7 +7,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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 2e909d2..a7ec23d 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -10,7 +10,7 @@ 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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 0cd2d69..74eebec 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -13,7 +13,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/wallet_payment_confirm_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 115557f..63aa313 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -17,7 +17,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; diff --git a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart index 54c6fb3..b7a16c5 100644 --- a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart +++ b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart @@ -13,7 +13,7 @@ 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/habib_wallet/widgets/hospital_list_item.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index dd63e55..41640b7 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -9,7 +9,7 @@ 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/patient_insurance_card.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_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'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6e910b8..7a581f2 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -6,6 +6,7 @@ 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/models/resp_models/patient_lab_orders_response_model.dart'; @@ -19,7 +20,7 @@ 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 'collapsing_list_view.dart'; +import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @@ -34,6 +35,7 @@ class _LabOrdersPageState extends State { int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; + @override void initState() { scheduleMicrotask(() { @@ -85,10 +87,8 @@ class _LabOrdersPageState extends State { // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { - activeIndex = index; - setState(() { - - }); + activeIndex = index; + setState(() {}); }, ), SizedBox(height: 16.h), @@ -104,7 +104,11 @@ class _LabOrdersPageState extends State { shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, - itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, + itemCount: model.isLabOrdersLoading + ? 5 + : model.patientLabOrders.isNotEmpty + ? model.patientLabOrders.length + : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading @@ -114,39 +118,42 @@ class _LabOrdersPageState extends State { index: index, isLoading: true, ) - : 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)), - ), - ); + : 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, + 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( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( @@ -163,7 +170,6 @@ class _LabOrdersPageState extends State { isExpanded: isExpanded)), ), ); - }, ) ], diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart index 1b47a55..f1fd389 100644 --- a/lib/presentation/lab/search_lab_report.dart +++ b/lib/presentation/lab/search_lab_report.dart @@ -4,7 +4,7 @@ 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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/input_widget.dart'; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 01504b3..dbfbb58 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -29,7 +29,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme 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/patient_insurance_card.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_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_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; diff --git a/lib/presentation/medical_file/medical_reports_page.dart b/lib/presentation/medical_file/medical_reports_page.dart index ea3c34d..9baf538 100644 --- a/lib/presentation/medical_file/medical_reports_page.dart +++ b/lib/presentation/medical_file/medical_reports_page.dart @@ -5,7 +5,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/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/presentation/lab/collapsing_list_view.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'; diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index 9950779..cb78d4c 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -8,7 +8,7 @@ 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_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 0cf48d6..969e3f5 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -8,7 +8,7 @@ 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/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index b3ace3d..9f6d55b 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -13,7 +13,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_item_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 3bca56d..20e4430 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -13,7 +13,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/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 4328a58..58273fc 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.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/presentation/lab/collapsing_list_view.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/app_language_change.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index c7732a4..b367b04 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -11,7 +11,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/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/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -61,130 +61,136 @@ class _RadiologyOrdersPageState extends State { ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: model.isRadiologyOrdersLoading ? 5 : model.patientRadiologyOrders.length, + itemCount: model.isRadiologyOrdersLoading + ? 5 + : model.patientRadiologyOrders.isNotEmpty + ? model.patientRadiologyOrders.length + : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == 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, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), + return model.patientRadiologyOrders.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: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - AppCustomChipWidget( - labelText: LocaleKeys.resultsAvailable.tr(context: context), - backgroundColor: AppColors.successColor.withOpacity(0.15), - textColor: AppColors.successColor, - ).toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 100), - SizedBox(height: 8.h), - Row( - children: [ - Image.network( - model.isRadiologyOrdersLoading - ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" - : model.patientRadiologyOrders[index].doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: model.isRadiologyOrdersLoading), - SizedBox(width: 4.h), - (model.isRadiologyOrdersLoading ? "Dr John Smith" : model.patientRadiologyOrders[index].doctorName!) - .toText16(isBold: true) - .toShimmer2(isShow: model.isRadiologyOrdersLoading) - ], - ), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), - ).toShimmer2(isShow: model.isRadiologyOrdersLoading), - AppCustomChipWidget( - labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : model.patientRadiologyOrders[index].clinicDescription!, - ).toShimmer2(isShow: model.isRadiologyOrdersLoading), + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppCustomChipWidget( + labelText: LocaleKeys.resultsAvailable.tr(context: context), + backgroundColor: AppColors.successColor.withOpacity(0.15), + textColor: AppColors.successColor, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 100), + SizedBox(height: 8.h), + Row( + children: [ + Image.network( + model.isRadiologyOrdersLoading + ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" + : model.patientRadiologyOrders[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: model.isRadiologyOrdersLoading), + SizedBox(width: 4.h), + (model.isRadiologyOrdersLoading ? "Dr John Smith" : model.patientRadiologyOrders[index].doctorName!) + .toText16(isBold: true) + .toShimmer2(isShow: model.isRadiologyOrdersLoading) + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), + AppCustomChipWidget( + labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : model.patientRadiologyOrders[index].clinicDescription!, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), - ], + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + ], + ), + ], + ), ), + model.isRadiologyOrdersLoading + ? SizedBox.shrink() + : AnimatedCrossFade( + firstChild: SizedBox.shrink(), + secondChild: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${model.patientRadiologyOrders[index].description}'.toText14(weight: FontWeight.w500), + ), + 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: () { + Navigator.of(context).push( + CustomPageRoute( + page: RadiologyResultPage(patientRadiologyResponseModel: model.patientRadiologyOrders[index]), + ), + ); + }, + 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, + ), + ], + ), + ], + ), + ), + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: Duration(milliseconds: 300), + ), ], ), ), - model.isRadiologyOrdersLoading - ? SizedBox.shrink() - : AnimatedCrossFade( - firstChild: SizedBox.shrink(), - secondChild: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${model.patientRadiologyOrders[index].description}'.toText14(weight: FontWeight.w500), - ), - 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: () { - Navigator.of(context).push( - CustomPageRoute( - page: RadiologyResultPage(patientRadiologyResponseModel: model.patientRadiologyOrders[index]), - ), - ); - }, - 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, - ), - ], - ), - ], - ), - ), - crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - ), - ], + ), ), ), - ), - ), - ), - ); + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any radiology results yet.".needTranslation); }, ), ], diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index f0f9af4..e15c8d6 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; diff --git a/lib/splashPage.dart b/lib/splashPage.dart index fc0d40d..3e70742 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -48,11 +48,11 @@ class _SplashScreenState extends State { Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); - // if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { + 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()))); - // } + } else { + Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + } }); var zoom = ZoomVideoSdk(); InitConfig initConfig = InitConfig( diff --git a/lib/presentation/lab/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart similarity index 100% rename from lib/presentation/lab/collapsing_list_view.dart rename to lib/widgets/appbar/collapsing_list_view.dart diff --git a/pubspec.yaml b/pubspec.yaml index 0466933..ea41d43 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.1.1 + fl_chart: ^1.0.0 geolocator: ^14.0.2 dropdown_search: ^6.0.2 google_maps_flutter: ^2.12.3 From 512628fb8b2c1f1179c14bb5d7eeab377669a278 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 28 Sep 2025 16:11:59 +0300 Subject: [PATCH 6/9] Lab result view along with the listing of items is added --- assets/images/svg/critical_low_result.svg | 3 + assets/images/svg/graph.svg | 5 + assets/images/svg/ic_date_filter.svg | 7 + assets/images/svg/ic_list.svg | 8 + assets/images/svg/low_result.svg | 3 + assets/images/svg/normal_result.svg | 3 + assets/images/svg/range_calender.svg | 8 + .../images/svg/refernce_range_indicator.svg | 7 + lib/core/app_assets.dart | 8 + lib/core/common_models/data_points.dart | 13 + lib/core/dependencies.dart | 1 + .../lab/history/lab_history_viewmodel.dart | 7 + lib/features/lab/lab_range_view_model.dart | 93 +++++ lib/features/lab/lab_repo.dart | 56 ++- lib/features/lab/lab_view_model.dart | 358 +++++++++++++++++- lib/features/lab/models/Range.dart | 6 + .../lab/models/resp_models/lab_result.dart | 116 ++++++ .../patient_lab_orders_response_model.dart | 3 +- lib/main.dart | 10 +- lib/presentation/home/landing_page.dart | 1 + lib/presentation/lab/lab_order_by_test.dart | 86 ++--- lib/presentation/lab/lab_orders_page.dart | 16 +- .../lab/lab_results/lab_result_calender.dart | 336 ++++++++++++++++ .../lab/lab_results/lab_result_details.dart | 291 ++++++++++++++ .../lab/lab_results/lab_result_list_item.dart | 51 +++ lib/theme/colors.dart | 6 + lib/widgets/graph/custom_graph.dart | 241 ++++++------ pubspec.yaml | 5 +- 28 files changed, 1563 insertions(+), 185 deletions(-) create mode 100644 assets/images/svg/critical_low_result.svg create mode 100644 assets/images/svg/graph.svg create mode 100644 assets/images/svg/ic_date_filter.svg create mode 100644 assets/images/svg/ic_list.svg create mode 100644 assets/images/svg/low_result.svg create mode 100644 assets/images/svg/normal_result.svg create mode 100644 assets/images/svg/range_calender.svg create mode 100644 assets/images/svg/refernce_range_indicator.svg create mode 100644 lib/features/lab/history/lab_history_viewmodel.dart create mode 100644 lib/features/lab/lab_range_view_model.dart create mode 100644 lib/features/lab/models/Range.dart create mode 100644 lib/features/lab/models/resp_models/lab_result.dart create mode 100644 lib/presentation/lab/lab_results/lab_result_calender.dart create mode 100644 lib/presentation/lab/lab_results/lab_result_details.dart create mode 100644 lib/presentation/lab/lab_results/lab_result_list_item.dart diff --git a/assets/images/svg/critical_low_result.svg b/assets/images/svg/critical_low_result.svg new file mode 100644 index 0000000..2706852 --- /dev/null +++ b/assets/images/svg/critical_low_result.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/graph.svg b/assets/images/svg/graph.svg new file mode 100644 index 0000000..8c79844 --- /dev/null +++ b/assets/images/svg/graph.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/ic_date_filter.svg b/assets/images/svg/ic_date_filter.svg new file mode 100644 index 0000000..43d28f2 --- /dev/null +++ b/assets/images/svg/ic_date_filter.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/svg/ic_list.svg b/assets/images/svg/ic_list.svg new file mode 100644 index 0000000..e68f20b --- /dev/null +++ b/assets/images/svg/ic_list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/low_result.svg b/assets/images/svg/low_result.svg new file mode 100644 index 0000000..52a2ef1 --- /dev/null +++ b/assets/images/svg/low_result.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/normal_result.svg b/assets/images/svg/normal_result.svg new file mode 100644 index 0000000..abe036f --- /dev/null +++ b/assets/images/svg/normal_result.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/range_calender.svg b/assets/images/svg/range_calender.svg new file mode 100644 index 0000000..940c002 --- /dev/null +++ b/assets/images/svg/range_calender.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/refernce_range_indicator.svg b/assets/images/svg/refernce_range_indicator.svg new file mode 100644 index 0000000..a3508b9 --- /dev/null +++ b/assets/images/svg/refernce_range_indicator.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 83fb842..82546d3 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -7,6 +7,7 @@ class AppAssets { static const String arrow_forward = '$svgBasePath/arrow_forward.svg'; static const String externalLink = '$svgBasePath/external_link.svg'; static const String calendar = '$svgBasePath/calendar.svg'; + static const String rangeCalendar = '$svgBasePath/range_calender.svg'; static const String hmc = '$svgBasePath/hmc.svg'; static const String ksa = '$svgBasePath/ksa.svg'; static const String sms = '$svgBasePath/sms.svg'; @@ -133,6 +134,13 @@ class AppAssets { static const String minus = '$svgBasePath/minus.svg'; static const String home_lab_result_icon = '$svgBasePath/home_lab_result_icon.svg'; static const String visa_mastercard_icon = '$svgBasePath/visa_mastercard.svg'; + static const String lab_result_indicator = '$svgBasePath/refernce_range_indicator.svg'; + static const String ic_date_filter = '$svgBasePath/ic_date_filter.svg'; + static const String ic_list = '$svgBasePath/ic_list.svg'; + static const String ic_graph = '$svgBasePath/graph.svg'; + 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'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/common_models/data_points.dart b/lib/core/common_models/data_points.dart index af7c473..89fa6e1 100644 --- a/lib/core/common_models/data_points.dart +++ b/lib/core/common_models/data_points.dart @@ -6,9 +6,22 @@ class DataPoint { final double value; ///label shown on the bottom of the graph String label; + String refernceValue; + String actualValue; + DateTime time; + String displayTime; DataPoint( {required this.value, required this.label, + required this.refernceValue, + required this.actualValue, + required this.time, + required this.displayTime, }); + + @override + String toString() { + return "the time is $time"; + } } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 4de7c5f..b42706f 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -100,6 +100,7 @@ class AppDependencies { () => LabViewModel( labRepo: getIt(), errorHandlerService: getIt(), + navigationService: getIt() ), ); diff --git a/lib/features/lab/history/lab_history_viewmodel.dart b/lib/features/lab/history/lab_history_viewmodel.dart new file mode 100644 index 0000000..a377b52 --- /dev/null +++ b/lib/features/lab/history/lab_history_viewmodel.dart @@ -0,0 +1,7 @@ + + +import 'package:flutter/material.dart'; + +class LabHistoryViewModel extends ChangeNotifier{ + bool isGraphShowing = false; +} \ No newline at end of file diff --git a/lib/features/lab/lab_range_view_model.dart b/lib/features/lab/lab_range_view_model.dart new file mode 100644 index 0000000..aa91964 --- /dev/null +++ b/lib/features/lab/lab_range_view_model.dart @@ -0,0 +1,93 @@ +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 { + List months = [ + 'Jan', + 'Feb', + 'Mar', + 'April', + 'May', + 'Jun', + 'July', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ]; + bool isGraphVisible = true; + Range? _currentlySelectedRange; + + Range? get currentlySelectedRange => _currentlySelectedRange; + + set currentlySelectedRange(Range? value) { + _currentlySelectedRange = value; + notifyListeners(); + + } + + DateTime? _toDate; + + DateTime? get toDate => _toDate; + + set toDate(DateTime? value) { + _toDate = value; + notifyListeners(); + } + + DateTime? _fromDate; + + DateTime? get fromDate => _fromDate; + + set fromDate(DateTime? value) { + _fromDate = value; + notifyListeners(); + + } + + LabRangeViewModel(); + + get getCurrentYear => DateTime.now().year; + + calculateDatesFromRange() { + _toDate = DateTime.now(); + switch (_currentlySelectedRange) { + case Range.WEEKLY: + _fromDate = _toDate!.subtract(Duration(days: 7)); + case Range.LAST_MONTH: + _fromDate = _toDate!.subtract(Duration(days: 30)); + case Range.LAST_6MONTH: + _fromDate = _toDate!.subtract(Duration(days: (30 * 6))); + case Range.THIS_YEAR: + _toDate = DateTime(_toDate!.year, DateTime.december, 31); + _fromDate = DateTime(_toDate!.year, DateTime.january, 01); + default: + } + } + + getDateString(DateTime? date){ + if(date == null) return "-"; + + String year = date.year.toString().substring(2); + return '${date.day} ${months[date.month-1]},$year'; + } + + flush(){ + toDate = null; + fromDate = null; + currentlySelectedRange = null; + isGraphVisible = true; + notifyListeners(); + } + + resetCurrentlySelectedRange(){ + currentlySelectedRange = null; + } + + alterGraphVisibility(){ + isGraphVisible = !isGraphVisible; + notifyListeners(); + } +} diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index f205494..36f9767 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -6,8 +6,11 @@ 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/services/logger_service.dart'; +import 'models/resp_models/lab_result.dart' show LabResult; + abstract class LabRepo { Future>>> getPatientLabOrders(); + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); } class LabRepoImp implements LabRepo { @@ -19,7 +22,6 @@ class LabRepoImp implements LabRepo { @override Future>>> getPatientLabOrders() async { Map mapDevice = {}; - try { GenericApiModel>? apiResponse; Failure? failure; @@ -56,4 +58,56 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientLabResults( + PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName + ) 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; + request['Procedure'] = procedureName; + request['LanguageID'] = 1; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_Patient_LAB_ORDERS_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>( + 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 29b90f8..811e5f8 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,7 +1,20 @@ +import 'dart:core'; + import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.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/presentation/lab/lab_results/lab_result_details.dart'; 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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:logger/logger.dart'; class LabViewModel extends ChangeNotifier { bool isLabOrdersLoading = false; @@ -9,17 +22,42 @@ class LabViewModel extends ChangeNotifier { LabRepo labRepo; ErrorHandlerService errorHandlerService; + NavigationService navigationService; List patientLabOrders = []; List filteredLabOrders = []; List tempLabOrdersList = []; + + List mainLabResults = []; + List mainGraphPoints = []; + List filteredGraphValues = []; + List months = [ + 'Jan', + 'Feb', + 'Mar', + 'April', + 'May', + 'Jun', + 'July', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ]; + late List _labSuggestionsList = []; List get labSuggestions => _labSuggestionsList; Set uniqueTests = {}; - LabViewModel({required this.labRepo, required this.errorHandlerService}); + double maxYForThreeDots = 0.0; + + LabViewModel( + {required this.labRepo, + required this.errorHandlerService, + required this.navigationService}); initLabProvider() { patientLabOrders.clear(); @@ -84,9 +122,321 @@ class LabViewModel extends ChangeNotifier { uniqueTests = { for (var item in patientLabOrders) if (item.testDetails != null) - ...?item.testDetails?.map((test) => - TestDetails(description: test.description.toString(), testCode: test.testCode.toString(), testID: test.testID, createdOn: item.createdOn)) + ...?item.testDetails?.map((test) => TestDetails( + description: test.description.toString(), + testCode: test.testCode.toString(), + testID: test.testID, + createdOn: item.createdOn, + model: item)) }; - uniqueTests.forEach(print); + } + + Future getPatientLabResult( + PatientLabOrdersResponseModel laborder, String procedureName) async { + LoaderBottomSheet.showLoader(); + mainLabResults.clear(); + filteredGraphValues.clear(); + + final result = await labRepo.getPatientLabResults( + laborder, + Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), + procedureName); + + result.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoaderBottomSheet.hideLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + var sortedResponse = sortByFlagAndValue(apiResponse.data!); + var recentThree = sort(sortedResponse); + mainLabResults = recentThree; + + double counter = 1; + recentThree.reversed.forEach((element) { + try { + var dateTime = + DateUtil.convertStringToDate(element.verifiedOnDateTime!); + if (double.parse(element.resultValue!) > maxYForThreeDots) { + maxYForThreeDots = double.parse(element.resultValue!); + } + filteredGraphValues.add(DataPoint( + value: transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), + actualValue:element.resultValue!, + label: formatDateAsMMYY(dateTime), + displayTime: resultDate(dateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), + refernceValue: element.calculatedResultFlag ?? "", + + )); + counter++; + } catch (e) {} + }); + LabResult recentResult = recentThree.first; + recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); + navigationService.push(MaterialPageRoute( + builder: (_) => + LabResultDetails(recentLabResult: recentResult))); + notifyListeners(); + } + }, + ); + } + + String resultDate(DateTime date){ + + + return '${date.day} ${months[date.month-1]},${date.year}'; + } + + double transformValueInRange(double inputValue, String flag) { + // Define range boundaries + double rangeStart, rangeEnd; + + switch (flag) { + case'LCL': + case 'CL': + rangeStart = 0.0; + rangeEnd = 19.0; + break; + case 'L': + rangeStart = 20.0; + rangeEnd = 39.0; + break; + case 'N': + rangeStart = 40.0; + rangeEnd = 59.0; + break; + case 'H': + rangeStart = 60.0; + rangeEnd = 79.0; + break; + case 'HCH': + case 'CH': + rangeStart = 80.0; + rangeEnd = 100.0; + break; + default: + throw ArgumentError('Invalid flag: $flag'); + } + + // Clamp input value to 0-100 and map it to the range bounds + final clampedValue = inputValue.clamp(0.0, 100.0); + final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1 + + // Map the normalized value to the target range bounds + final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart)); + + return transformedValue; + } + void getSelectedDateRange(DateTime? start, DateTime? end) { + if(start == null && end == null) { + print("the dates are null"); + mainLabResults.forEach((element) { + final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!); + try{ + filteredGraphValues.add(DataPoint( + value: transformValueInRange(double.parse(element.resultValue!), + element.calculatedResultFlag ?? ""), + actualValue: element.resultValue!, + label: formatDateAsMMYY(time), + displayTime: resultDate(time), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), + refernceValue: element.calculatedResultFlag ?? "", + )); + }catch(e){ + + } + }); + + }else { + filteredGraphValues.clear(); + + mainLabResults.forEach((element) { + try { + var dateTime = + DateUtil.convertStringToDate(element.verifiedOnDateTime!); + if (start != null && end == null) { + if (dateTime.isAtSameMomentAs(start)) { + + filteredGraphValues.add(DataPoint( + value: transformValueInRange( + double.parse(element.resultValue!), + element.calculatedResultFlag ?? ""), + actualValue: element.resultValue!, + label: formatDateAsMMYY(dateTime), + displayTime: resultDate(dateTime), + time: + DateUtil.convertStringToDate(element.verifiedOnDateTime), + refernceValue: element.calculatedResultFlag ?? "")); + } + } else if (start != null && end != null) { + if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { + + filteredGraphValues.add(DataPoint( + value: transformValueInRange( + double.parse(element.resultValue!), + element.calculatedResultFlag ?? ""), + actualValue: element.resultValue!, + label: formatDateAsMMYY(dateTime), + displayTime: resultDate(dateTime), + time: + DateUtil.convertStringToDate(element.verifiedOnDateTime), + refernceValue: element.calculatedResultFlag ?? "")); + } + } + } catch (e) {} + }); + } + filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList(); + + + notifyListeners(); + } + + String formatDateAsMMYY(DateTime date) { + + String year = date.year.toString().substring(2); + return '${months[date.month-1]},$year'; + } + + + List sortByFlagAndValue(List original) { + const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH']; + + int getFlagPriority(String? flag) { + if (flag == null) return priorityOrder.length; + final index = priorityOrder.indexOf(flag); + return index == -1 ? priorityOrder.length : index; + } + + double parseResultValue(String? value) { + if (value == null) return double.nan; + return double.tryParse(value) ?? double.nan; + } + + final copy = List.from(original); + copy.sort((a, b) { + final aFlagPriority = getFlagPriority(a.calculatedResultFlag); + final bFlagPriority = getFlagPriority(b.calculatedResultFlag); + + if (aFlagPriority != bFlagPriority) { + return aFlagPriority.compareTo(bFlagPriority); + } + + final aValue = parseResultValue(a.resultValue); + final bValue = parseResultValue(b.resultValue); + + return aValue.compareTo(bValue); + }); + + return copy; + } + + List sort(List original) { + DateTime? parseVerifiedDate(String? raw) { + if (raw == null) return null; + final regex = RegExp(r'\/Date\((\d+)\)\/'); + final match = regex.firstMatch(raw); + if (match != null) { + final millis = int.tryParse(match.group(1)!); + if (millis != null) { + + return DateTime.fromMillisecondsSinceEpoch(millis); + } + } + return null; + } + + final copy = List.from(original); + copy.sort((a, b) { + final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime); + final bDate = DateUtil.convertStringToDate(b.verifiedOnDateTime); + final now = DateTime.now(); + if (aDate == now && bDate == now) return 0; + if (aDate == now) return 1; + if (bDate == now) return -1; + return bDate.compareTo(aDate); // descending + }); + return copy.toList(); + } + + List sortFilteredList(List original) { + + + final copy = List.from(original); + copy.sort((a, b) { + final aDate =a.time; + final bDate = a.time; + final now = DateTime.now(); + if (aDate == now && bDate == now) return 0; + if (aDate == now) return 1; + if (bDate == now) return -1; + return bDate.compareTo(aDate); // descending + }); + return copy.toList(); + } + + Color getColor(String flag) { + switch (flag) { + case 'LCL': + return AppColors.criticalLowAndHigh; + case 'CL': + return AppColors.criticalLowAndHigh; + case 'L': + return AppColors.highAndLow; + case 'N': + return AppColors.bgGreenColor; + case 'H': + return AppColors.highAndLow; + case 'CH': + return AppColors.criticalLowAndHigh; + case 'HCH': + return AppColors.criticalLowAndHigh; + default: + return Colors.grey; + } + } + + String getFormattedDate(DateTime date){ + return DateFormat('EEEE, dd MMMM. yyyy').format(date); + } + + String getAssetUrlWRTResult(String refernceValue) { + switch (refernceValue) { + case 'CL': + case 'LCL': + return AppAssets.ic_critical_low_result; + case 'L': + return AppAssets.ic_low_result; + case 'N': + return AppAssets.ic_normal_result; + case 'H': + return AppAssets.ic_low_result; + case 'CH': + case 'HCH': + return AppAssets.ic_critical_low_result; + default: + return AppAssets.ic_normal_result; + } + } + + bool getRotationWRTResult(String refernceValue) { + switch (refernceValue) { + case 'CL': + case 'LCL': + case 'L': + case 'N': + return false; + case 'H': + case 'CH': + case 'HCH': + return true; + default: + return true; + } } } diff --git a/lib/features/lab/models/Range.dart b/lib/features/lab/models/Range.dart new file mode 100644 index 0000000..5dd39dc --- /dev/null +++ b/lib/features/lab/models/Range.dart @@ -0,0 +1,6 @@ +enum Range{ + WEEKLY, + LAST_MONTH, + LAST_6MONTH, + THIS_YEAR, +} \ No newline at end of file diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart new file mode 100644 index 0000000..d4e9223 --- /dev/null +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -0,0 +1,116 @@ +class LabResult { + String? description; + dynamic femaleInterpretativeData; + int? gender; + int? lineItemNo; + dynamic maleInterpretativeData; + dynamic notes; + String? packageID; + int? patientID; + String? projectID; + String? referanceRange; + String? resultValue; + String? sampleCollectedOn; + String? sampleReceivedOn; + String? setupID; + dynamic superVerifiedOn; + String? testCode; + String? uOM; + String? verifiedOn; + String? verifiedOnDateTime; + String? changeResult; + String? calculatedResultFlag; + String? criticalHigh; + String? referenceHigh; + String? criticalLow; + String? referenceLow; + + LabResult( + {this.description, + this.femaleInterpretativeData, + this.gender, + this.lineItemNo, + this.maleInterpretativeData, + this.notes, + this.packageID, + this.patientID, + this.projectID, + this.referanceRange, + this.resultValue, + this.sampleCollectedOn, + this.sampleReceivedOn, + this.setupID, + this.superVerifiedOn, + this.testCode, + this.uOM, + this.verifiedOn, + this.calculatedResultFlag, + this.verifiedOnDateTime, + this.criticalHigh, + this.referenceHigh, + this.criticalLow, + this.referenceLow, + }); + + LabResult.fromJson(Map json) { + description = json['Description']; + femaleInterpretativeData = json['FemaleInterpretativeData']; + gender = json['Gender']; + lineItemNo = json['LineItemNo']; + maleInterpretativeData = json['MaleInterpretativeData']; + notes = json['Notes']; + packageID = json['PackageID']; + patientID = json['PatientID']; + projectID = json['ProjectID']; + referanceRange = json['ReferanceRange']; + resultValue = json['ResultValue']; + sampleCollectedOn = json['SampleCollectedOn']; + sampleReceivedOn = json['SampleReceivedOn']; + setupID = json['SetupID']; + superVerifiedOn = json['SuperVerifiedOn']; + testCode = json['TestCode']; + uOM = json['UOM']; + verifiedOn = json['VerifiedOn']; + verifiedOnDateTime = json['VerifiedOnDateTime']; + changeResult = json['ChangeResult']; + calculatedResultFlag = json['CalculatedResultFlag']; + criticalHigh = json['CriticalHigh']; + referenceHigh = json['ReferenceHigh']; + criticalLow = json['CriticalLow']; + referenceLow = json['ReferenceLow']; + } + + Map toJson() { + final Map data = new Map(); + data['Description'] = this.description; + data['FemaleInterpretativeData'] = this.femaleInterpretativeData; + data['Gender'] = this.gender; + data['LineItemNo'] = this.lineItemNo; + data['MaleInterpretativeData'] = this.maleInterpretativeData; + data['Notes'] = this.notes; + data['PackageID'] = this.packageID; + data['PatientID'] = this.patientID; + data['ProjectID'] = this.projectID; + data['ReferanceRange'] = this.referanceRange; + data['ResultValue'] = this.resultValue; + data['SampleCollectedOn'] = this.sampleCollectedOn; + data['SampleReceivedOn'] = this.sampleReceivedOn; + data['SetupID'] = this.setupID; + data['SuperVerifiedOn'] = this.superVerifiedOn; + data['TestCode'] = this.testCode; + data['UOM'] = this.uOM; + data['VerifiedOn'] = this.verifiedOn; + data['VerifiedOnDateTime'] = this.verifiedOnDateTime; + data['ChangeResult'] = this.changeResult; + data['CriticalHigh'] = this.criticalHigh; + data['ReferenceHigh'] = this.referenceHigh; + data['CriticalLow'] = this.criticalLow; + data['ReferenceLow'] = this.referenceLow; + return data; + } + + @override + String toString() { + return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)'; + } +} 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 265a19e..aadfc76 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 @@ -227,7 +227,8 @@ class TestDetails { String? testCode; String? testID; String? createdOn; - TestDetails({this.description, this.testCode, this.testID, this.createdOn}); + PatientLabOrdersResponseModel? model; + TestDetails({this.description, this.testCode, this.testID, this.createdOn, this.model}); TestDetails.fromJson(Map json) { description = json['Description']; diff --git a/lib/main.dart b/lib/main.dart index bdd1f29..3690c72 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ 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/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'; @@ -78,7 +80,7 @@ void main() async { create: (_) => LabViewModel( labRepo: getIt(), errorHandlerService: getIt(), - ), + navigationService: getIt()), ), ChangeNotifierProvider( create: (_) => RadiologyViewModel( @@ -148,7 +150,11 @@ void main() async { ), ChangeNotifierProvider( create: (_) => AppointmentViaRegionViewmodel( - navigationService: getIt(), appState: getIt())) + navigationService: getIt(), appState: getIt())), + ChangeNotifierProvider( + create: (_) => LabHistoryViewModel()), + ChangeNotifierProvider( + create: (_) => LabRangeViewModel()) ], child: MyApp()), ), ); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 12bc8f5..65f5c11 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -29,6 +29,7 @@ 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_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index e5c6929..bb0f391 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -29,56 +29,52 @@ class LabOrderByTest extends StatelessWidget { curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - if (!isLoading) { - onTap(); - } - }, - child: Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.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), - ), + child: Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.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), + ), - SizedBox(height: 12.h), + SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true), - // chipType: ChipTypeEnum.lightBg, - backgroundColor: AppColors.greyLightColor, - textColor: AppColors.textColor, - // borderRadius: 5, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true), + // chipType: ChipTypeEnum.lightBg, + 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: () {}, - 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, - ), - ], + ), + 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_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6e910b8..8657534 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -8,6 +8,7 @@ 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/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'; @@ -30,6 +31,8 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; + late LabRangeViewModel rangeViewModel; + List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; @@ -45,6 +48,8 @@ class _LabOrdersPageState extends State { @override Widget build(BuildContext context) { labProvider = Provider.of(context); + rangeViewModel = Provider.of(context); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -154,9 +159,14 @@ class _LabOrdersPageState extends State { child: FadeInAnimation( child: LabOrderByTest( onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); + 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, diff --git a/lib/presentation/lab/lab_results/lab_result_calender.dart b/lib/presentation/lab/lab_results/lab_result_calender.dart new file mode 100644 index 0000000..b118293 --- /dev/null +++ b/lib/presentation/lab/lab_results/lab_result_calender.dart @@ -0,0 +1,336 @@ +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_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: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 { + final OnRangeSelected onRangeSelected; + + const LabResultCalender({super.key, required this.onRangeSelected}); + + @override + State createState() => _LabResultCalenderState(); +} + +class _LabResultCalenderState extends State { + late DateRangePickerController _calendarController; + DateTime? start; + DateTime? end; + late LabRangeViewModel model; + @override + void initState() { + _calendarController = DateRangePickerController(); + scheduleMicrotask(() { + _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + model = Provider.of(context); + return Padding( + padding: EdgeInsets.symmetric(horizontal: 0.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (_, model, __) => selectionChip(model), + ).paddingOnly(bottom: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + padding: EdgeInsets.all( + 16.h + ), + child: Column( + children: [ + Row( + children: [ + fromDateComponent(), + Text( + "to".needTranslation, + style: TextStyle( + color: AppColors.calenderTextColor, + fontSize: 14.h, + fontWeight: FontWeight.w500, + letterSpacing: -.2 + ), + ).paddingSymmetrical(24.h,0.h), + toDateComponent(), + ], + ), + Divider( + color: AppColors.spacerLineColor, + thickness: 1, + ).paddingOnly(bottom: 16.h, top: 16.h), + Material( + color: Colors.white, + child: SfDateRangePicker( + controller: _calendarController, + selectionMode: DateRangePickerSelectionMode.range, + showNavigationArrow: true, + headerHeight: 40.h, + backgroundColor: Colors.white, + headerStyle: DateRangePickerHeaderStyle( + backgroundColor: Colors.white, + textAlign: TextAlign.start, + textStyle: TextStyle( + fontSize: 18.fSize, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.primaryRedColor, + fontFamily: "Poppins", + ), + ), + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderStyle: DateRangePickerViewHeaderStyle( + backgroundColor: Colors.white, + textStyle: TextStyle( + fontSize: 14.fSize, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.textColor, + ), + ), + showTrailingAndLeadingDates: false, + dayFormat: "EEE", + ), + selectionShape: DateRangePickerSelectionShape.rectangle, + selectionRadius: 12.h, + selectionColor: AppColors.transparent, + startRangeSelectionColor: AppColors.primaryRedColor, + endRangeSelectionColor: AppColors.primaryRedColor, + rangeSelectionColor: + AppColors.primaryRedColor.withOpacity(0.1), + todayHighlightColor: Colors.transparent, + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: TextStyle( + fontSize: 12.fSize, + color: AppColors.textColor, + ), + todayTextStyle: TextStyle( + color: AppColors.textColor, + fontWeight: FontWeight.bold, + ), + ), + onSelectionChanged: + (DateRangePickerSelectionChangedArgs args) { + if (args.value is PickerDateRange) { + final PickerDateRange range = args.value; + start = range.startDate; + end = range.endDate; + model.fromDate = start; + model.toDate = end; + model.resetCurrentlySelectedRange(); + } + }, + ), + ), + ], + ), + ), + Row( + children: [ + Consumer( + builder: (_, model, __) => Visibility( + visible: (model.fromDate != null || model.toDate != null), + child: Expanded( + child: Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + _calendarController.selectedRange = null; + _calendarController.selectedDate = null; + model.flush(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + height: 56.h, + ), + ), + SizedBox(width: 16.h,) + ], + ), + ), + ), + ), + Expanded( + child: CustomButton( + text: LocaleKeys.search.tr(), + onPressed: () { + Navigator.of(context).pop(); + widget.onRangeSelected(model.fromDate, model.toDate); + }, + backgroundColor: AppColors.lightGreenButtonColor, + borderColor: Colors.transparent, + textColor: AppColors.textGreenColor, + icon: AppAssets.reminder_bell, + iconColor: AppColors.textGreenColor, + + height: 56.h, + ), + ), + ], + ).paddingOnly(top: 24.h), + ], + ), + ); + } + + fromDateComponent() { + return Consumer( + builder: (_, model, __) { + return displayDate("Start Date".needTranslation, + model.getDateString(model.fromDate), model.fromDate == null); + }, + ); + } + + toDateComponent() { + return Consumer( + builder: (_, model, __) { + return displayDate("End Date".needTranslation, + model.getDateString(model.toDate), model.toDate == null); + }, + ); + } + + displayDate(String label, String? date, bool isNotSelected) => Expanded( + child: Row( + spacing: 12.h, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.rangeCalendar, + iconColor: isNotSelected ? AppColors.borderOnlyColor: AppColors.blackColor , + height: 24, + width: 24), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + color: AppColors.inputLabelTextColor, + fontSize: 12.h, + fontWeight: FontWeight.w500, + ), + ), + Text( + date!, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.h, + fontWeight: FontWeight.w500, + ), + ) + ], + ) + ], + ), + ); + + selectionChip(LabRangeViewModel model) { + return Row( + spacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: "This Week".needTranslation, + backgroundColor: model.currentlySelectedRange == Range.WEEKLY + ? AppColors.primaryRedColor.withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: model.currentlySelectedRange == Range.WEEKLY + ? AppColors.primaryRedBorderColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ).onPress((){ + _calendarController.selectedRange = null; + model.currentlySelectedRange = Range.WEEKLY; + model.calculateDatesFromRange(); + }), + AppCustomChipWidget( + labelText: "Last Month".needTranslation, + backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH + ? AppColors.primaryRedColor.withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: model.currentlySelectedRange == Range.LAST_MONTH + ? AppColors.primaryRedBorderColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ).onPress((){ + _calendarController.selectedRange = null; + model.currentlySelectedRange = Range.LAST_MONTH; + model.calculateDatesFromRange(); + }), + AppCustomChipWidget( + labelText: "Last 6 Months".needTranslation, + backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH + ? AppColors.primaryRedColor.withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: model.currentlySelectedRange == Range.LAST_6MONTH + ? AppColors.primaryRedBorderColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ).onPress((){ + _calendarController.selectedRange = null; + model.currentlySelectedRange = Range.LAST_6MONTH; + model.calculateDatesFromRange(); + }), + AppCustomChipWidget( + labelText: "Year ${model.getCurrentYear}", + backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR + ? AppColors.primaryRedColor.withOpacity(0.1) + : AppColors.whiteColor, + shape: RoundedRectangleBorder( + side: BorderSide( + color: model.currentlySelectedRange == Range.THIS_YEAR + ? AppColors.primaryRedBorderColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + ).onPress((){ + _calendarController.selectedRange = null; + model.currentlySelectedRange = Range.THIS_YEAR; + model.calculateDatesFromRange(); + }), + ], + ); + } +} diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart new file mode 100644 index 0000000..2cac8fc --- /dev/null +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -0,0 +1,291 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.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/history/lab_history_viewmodel.dart'; +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/graph/custom_graph.dart'; +import 'package:provider/provider.dart' show Consumer, Provider; + +import '../../../widgets/common_bottom_sheet.dart' + show showCommonBottomSheetWithoutHeight; +import '../../book_appointment/widgets/appointment_calendar.dart' + show AppointmentCalendar; + +class LabResultDetails extends StatelessWidget { + final LabResult recentLabResult; + + // final List graphPoint; + late LabViewModel model; + + LabResultDetails({super.key, required this.recentLabResult}); + + @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)], + ).paddingAll(24.h), + ), + ); + } + + Widget get LabNameAndStatus => Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + recentLabResult.testCode ?? "", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w600, + color: AppColors.textColor, + letterSpacing: -2), + ), + Text( + "Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + ], + ), + //todo change the text color according to the provided test values + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Row( + spacing: 4.h, + + children: [ + Flexible( + child: Text( + recentLabResult.resultValue ?? "", + style: TextStyle( + fontSize: 24.fSize, + fontWeight: FontWeight.w600, + color: model.getColor( + recentLabResult.calculatedResultFlag ?? "", + ), + letterSpacing: -2, + ), + overflow: TextOverflow.ellipsis, // prevent overflow + maxLines: 1, + softWrap: false, + ), + ), + Visibility( + visible: recentLabResult.referanceRange != null, + child: Text( + "(Reference range ${recentLabResult.referanceRange})".needTranslation, + style: TextStyle( + fontSize: 12.fSize, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + ), + ), + ], + ), + ), + Utils.buildSvgWithAssets( + icon: AppAssets.lab_result_indicator, + width: 21, + height: 23, + iconColor: model.getColor( + recentLabResult.calculatedResultFlag ?? "", + ), + ), + ], + ) + + ], + )); + + Widget LabGraph(BuildContext context) => Consumer( + builder: (_, model, ___) => Consumer( + builder: (_, labmodel, ___) => Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + height: 260.h, + padding: EdgeInsets.all(16.h), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + //title and filter icon + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation, + style: TextStyle( + fontSize: 16, + 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, + height: 24) + .onPress(() { + model.alterGraphVisibility(); + }), + Utils.buildSvgWithAssets( + icon: AppAssets.ic_date_filter, + width: 24, + height: 24) + .onPress(() { + showCommonBottomSheetWithoutHeight( + title: "Set The Date Range".needTranslation, + context, + child: LabResultCalender( + 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) + ], + )), + )); + + Widget leftLabels(String value) { + return Text( + value, + style: TextStyle( + fontWeight: FontWeight.w300, + fontFamily: 'Poppins', + fontSize: 8.fSize, + color: AppColors.textColor, + ), + ); + } + + Widget buildBottomLabel(String label) { + return Padding( + padding: const EdgeInsets.only(top:8.0), + child: Text( + label, + style: TextStyle( + fontSize: 8.fSize, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + color: AppColors.labelTextColor), + ), + ); + } + + Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) { + if(model.isGraphVisible){ + return CustomGraph( + dataPoints: labmodel.filteredGraphValues, + maxY: 100, + leftLabelFormatter: (value) { + switch (value.toInt()) { + case 20: + return leftLabels("Critical Low".needTranslation); + case 40: + return leftLabels("Low".needTranslation); + case 60: + return leftLabels("Normal".needTranslation); + case 80: + return leftLabels("High".needTranslation); + case 100: + return leftLabels( + "Critical High".needTranslation); + default: + return SizedBox.shrink(); + } + }, + bottomLabelFormatter: (value, data) { + if(data.isEmpty) return SizedBox.shrink(); + if (value == 0) { + return buildBottomLabel(data[value.toInt()].label); + } + if (value == data.length - 1) { + return buildBottomLabel(data[value.toInt()].label); + } + if (value == ((data.length - 1) / 2)) { + return buildBottomLabel(data[value.toInt()].label); + } + return SizedBox.shrink(); + }, + scrollDirection: Axis.horizontal, + height: 180.h); + }else { + return labHistoryList(model, labmodel); + } + } + + Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) { + return SizedBox( + height: 180.h, + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ + var data = labmodel.filteredGraphValues.reversed.toList()[index]; + return Column( + children: [ + LabHistoryItem( + dayNameAndDate: labmodel.getFormattedDate(data.time), + result: data.actualValue, + assetUrl: labmodel.getAssetUrlWRTResult(data.refernceValue), + shouldRotateIcon: labmodel.getRotationWRTResult(data.refernceValue), + ), + if(index != labmodel.filteredGraphValues.length-1) + Divider(color: AppColors.spacerLineColor,thickness: 1.h,) + ], + ); + }), + ); + } +} diff --git a/lib/presentation/lab/lab_results/lab_result_list_item.dart b/lib/presentation/lab/lab_results/lab_result_list_item.dart new file mode 100644 index 0000000..00b5ff2 --- /dev/null +++ b/lib/presentation/lab/lab_results/lab_result_list_item.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart' ; +import 'package:flutter/src/widgets/framework.dart'; +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/theme/colors.dart'; + +class LabHistoryItem extends StatelessWidget{ + + final String dayNameAndDate; + final String result; + final String assetUrl; + final bool shouldRotateIcon; + + const LabHistoryItem({super.key, required this.dayNameAndDate, required this.result, required this.assetUrl, this.shouldRotateIcon = false}); + + @override + Widget build(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + dayNameAndDate, + style: TextStyle( + fontSize: 14.fSize, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.labelTextColor + ), + ), + Text( + result, + style: TextStyle( + fontSize: 18.fSize, + fontWeight: FontWeight.w600, + fontFamily: 'Poppins', + color: AppColors.textColor + ), + ) + ], + ), + Transform.flip( + flipY: shouldRotateIcon, + child: Utils.buildSvgWithAssets(icon: assetUrl,height: 18, width: 18) + ), + ], + ); + +} \ No newline at end of file diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 06079dc..4015249 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -69,4 +69,10 @@ static const Color quickLoginColor = Color(0xFF666666); static const Color tooltipTextColor = Color(0xFF414D55); static const Color graphGridColor = Color(0x4D18C273); +static const Color criticalLowAndHigh = Color(0xFFED1C2B); +static const Color highAndLow = Color(0xFFFFAF15); +static const Color labelTextColor = Color(0xFF838383); +static const Color calenderTextColor = Color(0xFFD0D0D0); +static const Color lightGreenButtonColor = Color(0x2618C273); + } diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index d66d281..ee246ad 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -43,6 +43,7 @@ class CustomGraph extends StatelessWidget { final double height; final double? maxY; final double? maxX; + final double? minX; final Color spotColor; final Color graphColor; final Color graphShadowColor; @@ -52,7 +53,9 @@ class CustomGraph extends StatelessWidget { final FontWeight? bottomLabelFontWeight; ///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart - final Widget Function(double value) leftLabelFormatter; + final Widget Function(double) leftLabelFormatter; + final Widget Function(double , List) bottomLabelFormatter; + final Axis scrollDirection; final bool showBottomTitleDates; @@ -76,6 +79,8 @@ class CustomGraph extends StatelessWidget { this.bottomLabelColor = AppColors.textColor, this.bottomLabelFontWeight = FontWeight.w500, this.bottomLabelSize, + required this.bottomLabelFormatter, + this.minX, }); @override @@ -94,117 +99,108 @@ class CustomGraph extends StatelessWidget { child: SizedBox( width: width, height: height, - child: Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8), - child: LineChart( - LineChartData( - minY: 0, - maxY: - ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), - // minX: dataPoints.first.labelValue - 1, - maxX: maxX, - minX: -0.2, - lineTouchData: LineTouchData( - getTouchLineEnd: (_, __) => 0, - getTouchedSpotIndicator: (barData, indicators) { - // Only show custom marker for touched spot - return indicators.map((int index) { - return TouchedSpotIndicatorData( - FlLine(color: Colors.transparent), - FlDotData( - show: true, - getDotPainter: (spot, percent, barData, idx) { - return FlDotCirclePainter( - radius: 8, - color: spotColor, - strokeWidth: 2, - strokeColor: Colors.white, - ); - }, - ), - ); + child: LineChart( + LineChartData( + minY: 0, + maxY: + ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), + // minX: dataPoints.first.labelValue - 1, + maxX: maxX, + minX: minX ??-0.2, + lineTouchData: LineTouchData( + getTouchLineEnd: (_, __) => 0, + getTouchedSpotIndicator: (barData, indicators) { + // Only show custom marker for touched spot + return indicators.map((int index) { + return TouchedSpotIndicatorData( + FlLine(color: Colors.transparent), + FlDotData( + show: true, + getDotPainter: (spot, percent, barData, idx) { + return FlDotCirclePainter( + radius: 8, + color: spotColor, + strokeWidth: 2, + strokeColor: Colors.white, + ); + }, + ), + ); + }).toList(); + }, + enabled: true, + touchTooltipData: LineTouchTooltipData( + getTooltipColor: (_) => Colors.white, + getTooltipItems: (touchedSpots) { + if (touchedSpots.isEmpty) return []; + // Only show tooltip for the first touched spot, hide others + return touchedSpots.map((spot) { + if (spot == touchedSpots.first) { + final dataPoint = dataPoints[spot.x.toInt()]; + + return LineTooltipItem( + // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}', + '${dataPoint.actualValue} ${dataPoint.displayTime}', + TextStyle( + color: Colors.black, + fontSize: 12.fSize, + fontWeight: FontWeight.w500), + ); + } + return null; // hides the rest }).toList(); }, - enabled: true, - touchTooltipData: LineTouchTooltipData( - getTooltipColor: (_) => Colors.white, - getTooltipItems: (touchedSpots) { - if (touchedSpots.isEmpty) return []; - // Only show tooltip for the first touched spot, hide others - return touchedSpots.map((spot) { - if (spot == touchedSpots.first) { - final dataPoint = dataPoints[spot.x.toInt()]; - - return LineTooltipItem( - // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}', - '${dataPoint.value} ', - TextStyle( - color: Colors.black, - fontSize: 12.fSize, - fontWeight: FontWeight.w500), - ); - } - return null; // hides the rest - }).toList(); - }, - ), ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 77, - interval: .1, // Let fl_chart handle it - getTitlesWidget: (value, _) { - return leftLabelFormatter(value); - }, - ), - ), - bottomTitles: AxisTitles( - axisNameSize: 60, - sideTitles: SideTitles( - showTitles: showBottomTitleDates, - reservedSize: 50, - getTitlesWidget: (value, _) { - if ((value.toDouble() >= 0) && - (value.toDouble() < (maxX ?? dataPoints.length))) { - var label = dataPoints[value.toInt()].label; - - return buildBottomLabel(label); - } - return const SizedBox.shrink(); - }, - interval: 1, // ensures 1:1 mapping with spots - ), + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 77, + interval: .1, // Let fl_chart handle it + getTitlesWidget: (value, _) { + return leftLabelFormatter(value); + }, ), - topTitles: AxisTitles(), - rightTitles: AxisTitles(), ), - borderData: FlBorderData( - show: true, - border: const Border( - bottom: BorderSide.none, - left: BorderSide(color: Colors.grey, width: .5), - right: BorderSide.none, - top: BorderSide.none, + bottomTitles: AxisTitles( + axisNameSize: 20, + sideTitles: SideTitles( + showTitles: showBottomTitleDates, + reservedSize: 20, + getTitlesWidget: (value, _) { + return bottomLabelFormatter(value, dataPoints, ); + }, + interval: 1, // ensures 1:1 mapping with spots ), ), - lineBarsData: _buildColoredLineSegments(dataPoints), - gridData: FlGridData( - show: true, - drawVerticalLine: false, - horizontalInterval: 20, - checkToShowHorizontalLine: (value) => - value >= 0 && value <= 100, - getDrawingHorizontalLine: (value) { - return FlLine( - color: AppColors.graphGridColor, - strokeWidth: 1, - dashArray: [5, 5], - ); - }, + topTitles: AxisTitles(), + rightTitles: AxisTitles(), + ), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide.none, + left: BorderSide(color: Colors.grey, width: .5), + right: BorderSide.none, + top: BorderSide.none, ), ), + lineBarsData: _buildColoredLineSegments(dataPoints), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + horizontalInterval: 20, + checkToShowHorizontalLine: (value) => + value >= 0 && value <= 100, + getDrawingHorizontalLine: (value) { + return FlLine( + color: AppColors.graphGridColor, + strokeWidth: 1, + dashArray: [5, 5], + ); + }, + ), ), ), )); @@ -259,29 +255,20 @@ class CustomGraph extends StatelessWidget { // ); // } - Widget buildBottomLabel(String label) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - label, - style: TextStyle( - fontSize: bottomLabelSize ?? 8.fSize, color: bottomLabelColor), - ), - ); - } + } -final List sampleData = [ - DataPoint( - value: 20, - label: 'Jan 2024', - ), - DataPoint( - value: 36, - label: 'Feb 2024', - ), - DataPoint( - value: 80, - label: 'This result', - ), -]; +// final List sampleData = [ +// DataPoint( +// value: 20, +// label: 'Jan 2024', +// ), +// DataPoint( +// value: 36, +// label: 'Feb 2024', +// ), +// DataPoint( +// value: 80, +// label: 'This result', +// ), +// ]; diff --git a/pubspec.yaml b/pubspec.yaml index 0466933..adf737b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -84,6 +84,7 @@ dependencies: location: ^8.0.1 gms_check: ^1.0.4 huawei_location: ^6.14.2+301 + intl: ^0.20.2 dev_dependencies: flutter_test: @@ -119,8 +120,8 @@ flutter: weight: 500 - asset: assets/fonts/poppins/Poppins-Regular.ttf weight: 400 - # - asset: assets/fonts/poppins/Poppins-Light.ttf - # weight: 300 + - asset: assets/fonts/poppins/Poppins-Light.ttf + weight: 300 # - asset: assets/fonts/poppins/Poppins-ExtraLight.ttf # weight: 200 # - asset: assets/fonts/poppins/Poppins-Thin.ttf From b05b03a6f62c8597eaadfb81c4029b5294a342dc Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 28 Sep 2025 16:26:57 +0300 Subject: [PATCH 7/9] divider changed --- .../lab/lab_results/lab_result_details.dart | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 2cac8fc..402e30e 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -269,23 +269,22 @@ class LabResultDetails extends StatelessWidget { Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) { return SizedBox( height: 180.h, - child: ListView.builder( - padding: EdgeInsets.zero, + child: ListView.separated( + padding: EdgeInsets.zero, itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ var data = labmodel.filteredGraphValues.reversed.toList()[index]; - return Column( - children: [ - LabHistoryItem( - dayNameAndDate: labmodel.getFormattedDate(data.time), - result: data.actualValue, - assetUrl: labmodel.getAssetUrlWRTResult(data.refernceValue), - shouldRotateIcon: labmodel.getRotationWRTResult(data.refernceValue), - ), - if(index != labmodel.filteredGraphValues.length-1) - Divider(color: AppColors.spacerLineColor,thickness: 1.h,) - ], - ); - }), + return LabHistoryItem( + dayNameAndDate: labmodel.getFormattedDate(data.time), + result: data.actualValue, + assetUrl: labmodel.getAssetUrlWRTResult(data.refernceValue), + shouldRotateIcon: labmodel.getRotationWRTResult(data.refernceValue), + ); + }, + separatorBuilder: (_, __) => Divider( + color: AppColors.spacerLineColor, + thickness: 1.h, + ).paddingOnly(top: 4.h, bottom: 4.h), + ), ); } } From 6b28b573077c67a022eb30fc7da5944141dbe0db Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 28 Sep 2025 16:41:52 +0300 Subject: [PATCH 8/9] empty state implementation contd. --- lib/core/utils/utils.dart | 13 +- .../prescriptions_view_model.dart | 1 + .../appointments/my_appointments_page.dart | 82 ++++++++++- .../book_appointment_page.dart | 2 +- lib/presentation/home/landing_page.dart | 130 +++++++++--------- .../home/widgets/small_service_card.dart | 2 +- .../medical_file/medical_file_page.dart | 29 ++-- .../patient_sickleaves_list_page.dart | 10 +- .../widgets/patient_sick_leave_card.dart | 2 +- .../prescriptions_list_page.dart | 30 ++-- .../radiology/radiology_orders_page.dart | 12 +- 11 files changed, 209 insertions(+), 104 deletions(-) diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 0a84100..efa01d0 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -301,16 +301,17 @@ class Utils { return false; } - static Widget getNoDataWidget(BuildContext context, {String? noDataText}) { + static Widget getNoDataWidget(BuildContext context, {double width = 124, double height = 124, String? noDataText, Widget callToActionButton = const SizedBox.shrink(), bool isSmallWidget = false}) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox(height: 48.h), - Lottie.asset(AppAnimations.noData, repeat: false, reverse: false, frameRate: FrameRate(60), width: 150.h, height: 150.h, fit: BoxFit.fill), - SizedBox(height: 12.h), - (noDataText ?? LocaleKeys.noDataAvailable.tr()).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), - SizedBox(height: 12.h), + SizedBox(height: isSmallWidget ? 0.h : 48.h), + Lottie.asset(AppAnimations.noData, repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + (noDataText ?? LocaleKeys.noDataAvailable.tr()).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true).paddingSymmetrical(64.h, 0.h), + SizedBox(height: 16.h), + callToActionButton ], ).center; } diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index f5f32a7..ebc3be8 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -71,6 +71,7 @@ class PrescriptionsViewModel extends ChangeNotifier { // (failure) async => await errorHandlerService.handleError(failure: failure), (failure) async { isPrescriptionsOrdersLoading = false; + notifyListeners(); }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 97ca236..2518c88 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -1,17 +1,23 @@ import 'dart:async'; +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/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/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_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/custom_tab_bar.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'; @@ -116,7 +122,30 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); + : 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), ), @@ -159,7 +188,30 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); + : 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), ), @@ -202,7 +254,30 @@ class _MyAppointmentsPageState extends State { ), ), ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any appointments yet.".needTranslation); + : 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), ), @@ -213,3 +288,4 @@ class _MyAppointmentsPageState extends State { } } } + diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 66a4ab7..6ba14f5 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -58,7 +58,7 @@ class _BookAppointmentPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.bookAppo.tr(context: context), - isLeading: false, + isLeading: Navigator.of(context).canPop(), child: SingleChildScrollView( child: Consumer(builder: (context, bookAppointmentsVM, child) { return Column( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 402fb6b..f6f83fb 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -179,7 +179,7 @@ class _LandingPageState extends State { ), ); }), - SizedBox(height: 12.h), + SizedBox(height: 16.h), Consumer(builder: (context, myAppointmentsVM, child) { return myAppointmentsVM.isMyAppointmentsLoading ? Container( @@ -288,49 +288,46 @@ class _LandingPageState extends State { ), ); }), - SizedBox(height: 12.h), + SizedBox(height: 16.h), Container( - height: 127.h, + height: 120.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.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( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, - ), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, ), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ], - ), + ), + ], ), ).paddingSymmetrical(24.h, 0.h), ], @@ -341,41 +338,38 @@ class _LandingPageState extends State { color: AppColors.whiteColor, borderRadius: 24, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.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( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, - ), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, ), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ], - ), + ), + ], ), ).paddingSymmetrical(24.h, 0.h), Row( diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index fba96eb..416ed9a 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -39,7 +39,7 @@ class SmallServiceCard extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsets.symmetric(horizontal: 3.h), + padding: EdgeInsets.symmetric(horizontal: 0.h, vertical: 12.h), child: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: backgroundColor, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index dbfbb58..28045fb 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -275,7 +275,7 @@ class _MedicalFilePageState extends State { }), Consumer(builder: (context, myAppointmentsVM, child) { return SizedBox( - height: 200.h, + height: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? 200.h : 175.h, child: ListView.separated( scrollDirection: Axis.horizontal, padding: EdgeInsets.only(top: 16.h, left: 24.h, right: 24.h, bottom: 0.h), @@ -500,7 +500,7 @@ class _MedicalFilePageState extends State { ), ), ).paddingSymmetrical(24.h, 0.h) - : SizedBox.shrink(); + : 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 @@ -531,7 +531,11 @@ class _MedicalFilePageState extends State { height: 120.h, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length, + itemCount: myAppointmentsVM.isPatientMyDoctorsLoading + ? 5 + : myAppointmentsVM.patientMyDoctorsList.isNotEmpty + ? myAppointmentsVM.patientMyDoctorsList.length + : 1, shrinkWrap: true, padding: EdgeInsets.only(left: 24.h, right: 24.h), itemBuilder: (context, index) { @@ -554,13 +558,14 @@ class _MedicalFilePageState extends State { ], ), ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SizedBox( + : myAppointmentsVM.patientMyDoctorsList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SizedBox( width: 80.h, child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -583,7 +588,9 @@ 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); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index cb78d4c..215836f 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -4,6 +4,8 @@ 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/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_sickleave_response_model.dart'; @@ -50,7 +52,11 @@ class _PatientSickleavesListPageState extends State { children: [ ListView.separated( scrollDirection: Axis.vertical, - itemCount: medicalFileVM.isPatientSickLeaveListLoading ? 3 : medicalFileVM.patientSickLeaveList.length, + itemCount: medicalFileVM.isPatientSickLeaveListLoading + ? 3 + : medicalFileVM.patientSickLeaveList.isNotEmpty + ? medicalFileVM.patientSickLeaveList.length + : 1, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { @@ -73,7 +79,7 @@ class _PatientSickleavesListPageState extends State { ), ), ) - : SizedBox.shrink(); + : Utils.getNoDataWidget(context, noDataText: "You don't have any sick leaves yet.".needTranslation); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.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 ba1bee5..5cd2547 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -43,7 +43,7 @@ class PatientSickLeaveCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true), + "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: isLoading ? "" : getStatusText(context), backgroundColor: getStatusColor().withOpacity(0.15), diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 20e4430..87f880d 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -13,6 +13,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/prescriptions/prescriptions_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/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -93,20 +94,30 @@ class _PrescriptionsListPageState extends State { SizedBox(height: 20.h), // Expandable list ListView.builder( - itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, + itemCount: model.isPrescriptionsOrdersLoading + ? 4 + : model.patientPrescriptionOrders.isNotEmpty + ? model.patientPrescriptionOrdersViewList.length + : 1, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, padding: const EdgeInsets.only(left: 0, right: 8), itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : model.patientPrescriptionOrders.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, @@ -290,7 +301,8 @@ class _PrescriptionsListPageState extends State { ), ), ), - ); + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any prescriptions yet.".needTranslation); }, ).paddingSymmetrical(24.h, 0.h), ], diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index b367b04..6662a8e 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -11,6 +11,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/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_result_item_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -68,8 +69,15 @@ class _RadiologyOrdersPageState extends State { : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; - return model.patientRadiologyOrders.isNotEmpty - ? AnimationConfiguration.staggeredList( + return model.isRadiologyOrdersLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : model.patientRadiologyOrders.isNotEmpty + ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( From 048f0bb4594634afd08e826646e9f40713244b74 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 29 Sep 2025 14:27:47 +0300 Subject: [PATCH 9/9] graph design change --- lib/core/common_models/data_points.dart | 4 +- lib/features/lab/lab_view_model.dart | 58 +++++++---- .../lab/lab_results/lab_result_details.dart | 69 +++++++++---- lib/widgets/graph/custom_graph.dart | 99 ++++++++++--------- 4 files changed, 141 insertions(+), 89 deletions(-) diff --git a/lib/core/common_models/data_points.dart b/lib/core/common_models/data_points.dart index 89fa6e1..c35d190 100644 --- a/lib/core/common_models/data_points.dart +++ b/lib/core/common_models/data_points.dart @@ -6,7 +6,7 @@ class DataPoint { final double value; ///label shown on the bottom of the graph String label; - String refernceValue; + String referenceValue; String actualValue; DateTime time; String displayTime; @@ -14,7 +14,7 @@ class DataPoint { DataPoint( {required this.value, required this.label, - required this.refernceValue, + required this.referenceValue, required this.actualValue, required this.time, required this.displayTime, diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 811e5f8..51596ea 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,4 +1,5 @@ import 'dart:core'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -52,7 +53,8 @@ class LabViewModel extends ChangeNotifier { Set uniqueTests = {}; - double maxYForThreeDots = 0.0; + double maxY = 0.0; + double maxX = double.infinity; LabViewModel( {required this.labRepo, @@ -136,6 +138,7 @@ class LabViewModel extends ChangeNotifier { LoaderBottomSheet.showLoader(); mainLabResults.clear(); filteredGraphValues.clear(); + maxY = double.negativeInfinity; final result = await labRepo.getPatientLabResults( laborder, @@ -160,16 +163,20 @@ class LabViewModel extends ChangeNotifier { try { var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); - if (double.parse(element.resultValue!) > maxYForThreeDots) { - maxYForThreeDots = double.parse(element.resultValue!); + var resultValue = double.parse(element.resultValue!); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + if (resultValue>maxY) { + maxY = resultValue; + maxX = maxY; } + filteredGraphValues.add(DataPoint( - value: transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), + value: transformedValue, actualValue:element.resultValue!, label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - refernceValue: element.calculatedResultFlag ?? "", + referenceValue: element.calculatedResultFlag ?? "", )); counter++; @@ -177,6 +184,7 @@ class LabViewModel extends ChangeNotifier { }); LabResult recentResult = recentThree.first; recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); + // filteredGraphValues = [filteredGraphValues.first]; navigationService.push(MaterialPageRoute( builder: (_) => LabResultDetails(recentLabResult: recentResult))); @@ -228,24 +236,32 @@ class LabViewModel extends ChangeNotifier { final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1 // Map the normalized value to the target range bounds - final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart)); - + final transformedValue = rangeStart + ((normalizedValue * (rangeEnd - rangeStart))); + debugPrint("the actual value is $inputValue"); + debugPrint("the flag is $flag"); + debugPrint("the transformed value is $transformedValue"); return transformedValue; } void getSelectedDateRange(DateTime? start, DateTime? end) { + maxY = double.negativeInfinity; + if(start == null && end == null) { - print("the dates are null"); mainLabResults.forEach((element) { final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!); try{ + var resultValue = double.parse(element.resultValue!); + + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + if (resultValue > maxY) { + maxY = resultValue; + } filteredGraphValues.add(DataPoint( - value: transformValueInRange(double.parse(element.resultValue!), - element.calculatedResultFlag ?? ""), + value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(time), displayTime: resultDate(time), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - refernceValue: element.calculatedResultFlag ?? "", + referenceValue: element.calculatedResultFlag ?? "", )); }catch(e){ @@ -259,33 +275,34 @@ class LabViewModel extends ChangeNotifier { try { var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var resultValue = double.parse(element.resultValue!); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + if (resultValue > maxY) { + maxY = resultValue; + } if (start != null && end == null) { if (dateTime.isAtSameMomentAs(start)) { filteredGraphValues.add(DataPoint( - value: transformValueInRange( - double.parse(element.resultValue!), - element.calculatedResultFlag ?? ""), + value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - refernceValue: element.calculatedResultFlag ?? "")); + referenceValue: element.calculatedResultFlag ?? "")); } } else if (start != null && end != null) { if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { filteredGraphValues.add(DataPoint( - value: transformValueInRange( - double.parse(element.resultValue!), - element.calculatedResultFlag ?? ""), + value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - refernceValue: element.calculatedResultFlag ?? "")); + referenceValue: element.calculatedResultFlag ?? "")); } } } catch (e) {} @@ -299,8 +316,7 @@ class LabViewModel extends ChangeNotifier { String formatDateAsMMYY(DateTime date) { - String year = date.year.toString().substring(2); - return '${months[date.month-1]},$year'; + return '${months[date.month-1]}, ${date.year}'; } diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 402e30e..11ab5fb 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,4 +1,7 @@ +import 'dart:math'; + import 'package:dartz/dartz.dart'; +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/common_models/data_points.dart'; @@ -62,6 +65,7 @@ class LabResultDetails extends StatelessWidget { recentLabResult.testCode ?? "", style: TextStyle( fontSize: 32, + fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), @@ -70,6 +74,7 @@ class LabResultDetails extends StatelessWidget { "Result of ${recentLabResult.verifiedOn ?? ""}".needTranslation, style: TextStyle( fontSize: 12, + fontFamily: 'Poppins', fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), @@ -91,6 +96,7 @@ class LabResultDetails extends StatelessWidget { style: TextStyle( fontSize: 24.fSize, fontWeight: FontWeight.w600, + fontFamily: 'Poppins', color: model.getColor( recentLabResult.calculatedResultFlag ?? "", ), @@ -108,6 +114,7 @@ class LabResultDetails extends StatelessWidget { style: TextStyle( fontSize: 12.fSize, fontWeight: FontWeight.w500, + fontFamily: 'Poppins', color: AppColors.greyTextColor, ), overflow: TextOverflow.ellipsis, @@ -153,6 +160,8 @@ class LabResultDetails extends StatelessWidget { model.isGraphVisible?"History FlowChart".needTranslation: "History".needTranslation, style: TextStyle( fontSize: 16, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, color: AppColors.textColor, ), @@ -202,7 +211,7 @@ class LabResultDetails extends StatelessWidget { return Text( value, style: TextStyle( - fontWeight: FontWeight.w300, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', fontSize: 8.fSize, color: AppColors.textColor, @@ -226,26 +235,37 @@ class LabResultDetails extends StatelessWidget { Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) { if(model.isGraphVisible){ + print("the round is ${labmodel.maxY.round()}"); + var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, - maxY: 100, + // maxY: 100, + makeGraphBasedOnActualValue: true, + leftLabelReservedSize: 40, + leftLabelInterval: getInterval(labmodel), + maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, + leftLabelFormatter: (value) { - switch (value.toInt()) { - case 20: - return leftLabels("Critical Low".needTranslation); - case 40: - return leftLabels("Low".needTranslation); - case 60: - return leftLabels("Normal".needTranslation); - case 80: - return leftLabels("High".needTranslation); - case 100: - return leftLabels( - "Critical High".needTranslation); - default: - return SizedBox.shrink(); - } + return leftLabels(value.toStringAsFixed(2).tr()); + // switch (value.toInt()) { + // case 10: + // return leftLabels("Critical Low".needTranslation); + // case 30: + // return leftLabels("Low".needTranslation); + // case 50: + // return leftLabels("Normal".needTranslation); + // case 70: + // return leftLabels("High".needTranslation); + // case 90: + // return leftLabels( + // "Critical High".needTranslation); + // default: + // return SizedBox.shrink(); + // } }, + graphColor:graphColor , + graphShadowColor: graphColor.withOpacity(.4), + graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { if(data.isEmpty) return SizedBox.shrink(); if (value == 0) { @@ -259,6 +279,7 @@ class LabResultDetails extends StatelessWidget { } return SizedBox.shrink(); }, + minX:(labmodel.filteredGraphValues.length == 1)?null : -.2, scrollDirection: Axis.horizontal, height: 180.h); }else { @@ -276,8 +297,8 @@ class LabResultDetails extends StatelessWidget { return LabHistoryItem( dayNameAndDate: labmodel.getFormattedDate(data.time), result: data.actualValue, - assetUrl: labmodel.getAssetUrlWRTResult(data.refernceValue), - shouldRotateIcon: labmodel.getRotationWRTResult(data.refernceValue), + assetUrl: labmodel.getAssetUrlWRTResult(data.referenceValue), + shouldRotateIcon: labmodel.getRotationWRTResult(data.referenceValue), ); }, separatorBuilder: (_, __) => Divider( @@ -287,4 +308,14 @@ class LabResultDetails extends StatelessWidget { ), ); } + + double? getInterval(LabViewModel labmodel) { + var maxX = labmodel.maxY; + if(maxX >1 && maxX < 5) return 1; + if(maxX >5 && maxX < 10) return 5; + if(maxX >10 && maxX < 50) return 10; + if(maxX >50 && maxX < 100) return 20; + if(maxX >100 && maxX < 200) return 30; + return 50; + } } diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index ee246ad..fa72b19 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -3,40 +3,43 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +/// A customizable line graph widget using `fl_chart`. /// -/// CustomGraph(dataPoints: sampleData, scrollDirection: Axis.horizontal,height: 200,maxY: 100, maxX:2.5, -/// leftLabelFormatter: (value){ -/// Widget buildLabel(String label) { -/// return Padding( -/// padding: const EdgeInsets.only(right: 8), -/// child: Text( -/// label, -/// style: TextStyle( -/// fontSize: 8.fSize, color: AppColors.textColor, -/// fontFamily: -/// FontUtils.getFontFamilyForLanguage(false) -/// ), -/// textAlign: TextAlign.right, -/// ), -/// ); -/// } -/// switch (value.toInt()) { +/// Displays a line chart with configurable axis labels, colors, and data points. +/// Useful for visualizing time series or other sequential data. /// -/// case 20: -/// return buildLabel("Critical Low"); -/// case 40: -/// return buildLabel("Low"); -/// case 60: -/// return buildLabel("Normal"); -/// case 80: -/// return buildLabel("High"); -/// case 100: -/// return buildLabel("Critical High"); -/// } -/// return const SizedBox.shrink(); -/// }, +/// **Parameters:** +/// - [dataPoints]: List of `DataPoint` objects to plot. +/// - [leftLabelFormatter]: Function to build left axis labels. +/// - [bottomLabelFormatter]: Function to build bottom axis labels. +/// - [width]: Optional width of the chart. +/// - [height]: Required height of the chart. +/// - [maxY], [maxX], [minX]: Axis bounds. +/// - [spotColor]: Color of the touched spot marker. +/// - [graphColor]: Color of the line. +/// - [graphShadowColor]: Color of the area below the line. +/// - [graphGridColor]: Color of the grid lines. +/// - [bottomLabelColor]: Color of bottom axis labels. +/// - [bottomLabelSize]: Font size for bottom axis labels. +/// - [bottomLabelFontWeight]: Font weight for bottom axis labels. +/// - [leftLabelInterval]: Interval between left axis labels. +/// - [leftLabelReservedSize]: Reserved space for left axis labels. +/// - [scrollDirection]: Axis direction for scrolling. +/// - [showBottomTitleDates]: Whether to show bottom axis labels. +/// - [isFullScreeGraph]: Whether the graph is fullscreen. +/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting. /// -/// ), +/// Example usage: +/// ```dart +/// CustomGraph( +/// dataPoints: sampleData, +/// leftLabelFormatter: (value) => ..., +/// bottomLabelFormatter: (value, dataPoints) => ..., +/// height: 200, +/// scrollDirection: Axis.horizontal, +/// maxY: 100, +/// maxX: 2.5, +/// ) class CustomGraph extends StatelessWidget { final List dataPoints; final double? width; @@ -51,6 +54,8 @@ class CustomGraph extends StatelessWidget { final Color bottomLabelColor; final double? bottomLabelSize; final FontWeight? bottomLabelFontWeight; + final double? leftLabelInterval; + final double? leftLabelReservedSize; ///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart final Widget Function(double) leftLabelFormatter; @@ -60,6 +65,7 @@ class CustomGraph extends StatelessWidget { final Axis scrollDirection; final bool showBottomTitleDates; final bool isFullScreeGraph; + final bool makeGraphBasedOnActualValue; const CustomGraph({ super.key, @@ -79,6 +85,9 @@ class CustomGraph extends StatelessWidget { this.bottomLabelColor = AppColors.textColor, this.bottomLabelFontWeight = FontWeight.w500, this.bottomLabelSize, + this.leftLabelInterval, + this.leftLabelReservedSize, + this.makeGraphBasedOnActualValue = false, required this.bottomLabelFormatter, this.minX, }); @@ -87,13 +96,7 @@ class CustomGraph extends StatelessWidget { Widget build(BuildContext context) { // var maxY = 0.0; double interval = 20; - if ((maxY ?? 0) > 10 && (maxY ?? 0) <= 20) { - interval = 2; - } else if ((maxY ?? 0) > 5 && (maxY ?? 0) <= 10) { - interval = 1; - } else if ((maxY ?? 0) >= 0 && (maxY ?? 0) <= 5) { - interval = .4; - } + return Material( color: Colors.white, child: SizedBox( @@ -102,11 +105,11 @@ class CustomGraph extends StatelessWidget { child: LineChart( LineChartData( minY: 0, - maxY: - ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), + // maxY: ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), + maxY: maxY, // minX: dataPoints.first.labelValue - 1, maxX: maxX, - minX: minX ??-0.2, + minX: minX , lineTouchData: LineTouchData( getTouchLineEnd: (_, __) => 0, getTouchedSpotIndicator: (barData, indicators) { @@ -140,7 +143,7 @@ class CustomGraph extends StatelessWidget { return LineTooltipItem( // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}', - '${dataPoint.actualValue} ${dataPoint.displayTime}', + '${dataPoint.value} - ${dataPoint.actualValue} - ${dataPoint.displayTime}', TextStyle( color: Colors.black, fontSize: 12.fSize, @@ -156,8 +159,8 @@ class CustomGraph extends StatelessWidget { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 77, - interval: .1, // Let fl_chart handle it + reservedSize: leftLabelReservedSize??80, + interval: leftLabelInterval ?? .1, // Let fl_chart handle it getTitlesWidget: (value, _) { return leftLabelFormatter(value); }, @@ -190,12 +193,12 @@ class CustomGraph extends StatelessWidget { gridData: FlGridData( show: true, drawVerticalLine: false, - horizontalInterval: 20, + // horizontalInterval: 40, checkToShowHorizontalLine: (value) => value >= 0 && value <= 100, getDrawingHorizontalLine: (value) { return FlLine( - color: AppColors.graphGridColor, + color: graphGridColor, strokeWidth: 1, dashArray: [5, 5], ); @@ -208,7 +211,9 @@ class CustomGraph extends StatelessWidget { List _buildColoredLineSegments(List dataPoints) { final List allSpots = dataPoints.asMap().entries.map((entry) { - return FlSpot(entry.key.toDouble(), entry.value.value); + double value = (makeGraphBasedOnActualValue)?double.tryParse(entry.value.actualValue)??0.0:entry.value.value; + debugPrint("the value is $value"); + return FlSpot(entry.key.toDouble(), value); }).toList(); var data = [