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 94313b8870fb2376e4694de314adc3d1ad179862 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Thu, 25 Sep 2025 14:49:49 +0300 Subject: [PATCH 5/9] Improved Error Handling --- ios/Runner.xcodeproj/project.pbxproj | 68 ++++++------- lib/core/api/api_client.dart | 21 ++-- lib/core/dependencies.dart | 16 +-- lib/core/exceptions/api_failure.dart | 24 ++++- .../authentication_view_model.dart | 6 +- .../medical_file/medical_file_page.dart | 2 +- .../{my_Family.dart => my_family.dart} | 11 +-- .../my_family/widget/family_cards.dart | 20 ++-- .../my_family/widget/my_family_sheet.dart | 6 +- lib/services/dialog_service.dart | 99 ++++++------------- lib/services/error_handler_service.dart | 33 ++++--- lib/services/navigation_service.dart | 12 +++ .../bottomsheet/exception_bottom_sheet.dart | 17 ++-- lib/widgets/common_bottom_sheet.dart | 4 +- 14 files changed, 161 insertions(+), 178 deletions(-) rename lib/presentation/my_family/{my_Family.dart => my_family.dart} (96%) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a982556..3a65395 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,12 +10,12 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 40721B3F0DC8DB0598443DBF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E824BDA50EF77318022F59D /* Pods_Runner.framework */; }; 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACE60DF9393168FD748550B3 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,16 +44,16 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1E824BDA50EF77318022F59D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 62D069322AC3B532E0B4F137 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -61,8 +61,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - ACE60DF9393168FD748550B3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B35471C63C8DD1B1DECDB3A5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D0FB40CE52522242F351743B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -70,7 +70,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */, + 40721B3F0DC8DB0598443DBF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -88,9 +88,9 @@ 79DD2093A1D9674C94359FC8 /* Pods */ = { isa = PBXGroup; children = ( - 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */, - 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */, - D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */, + 62D069322AC3B532E0B4F137 /* Pods-Runner.debug.xcconfig */, + D0FB40CE52522242F351743B /* Pods-Runner.release.xcconfig */, + B35471C63C8DD1B1DECDB3A5 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -114,7 +114,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 79DD2093A1D9674C94359FC8 /* Pods */, - A07D637C76A0ABB38659D189 /* Frameworks */, + C1312CE4ABEB9ED47FF21174 /* Frameworks */, ); sourceTree = ""; }; @@ -144,10 +144,10 @@ path = Runner; sourceTree = ""; }; - A07D637C76A0ABB38659D189 /* Frameworks */ = { + C1312CE4ABEB9ED47FF21174 /* Frameworks */ = { isa = PBXGroup; children = ( - ACE60DF9393168FD748550B3 /* Pods_Runner.framework */, + 1E824BDA50EF77318022F59D /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -176,15 +176,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */, + E3E41B3FF6C30233949963A9 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, - 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, + 968C3FBB62CE1E0E307ECBCE /* [CP] Embed Pods Frameworks */, + CC3E3923FDE40035D0CA6762 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -273,24 +273,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */ = { + 968C3FBB62CE1E0E307ECBCE /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -322,7 +305,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BFED6CCFE59BB148875A533B /* [CP] Check Pods Manifest.lock */ = { + CC3E3923FDE40035D0CA6762 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E3E41B3FF6C30233949963A9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 62339bf..903fc4d 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -9,7 +9,6 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; -import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:http/http.dart' as http; @@ -80,14 +79,11 @@ abstract class ApiClient { class ApiClientImp implements ApiClient { final _analytics = getIt(); - final LoggerService _loggerService; final AppState _appState; ApiClientImp({ - required LoggerService loggerService, required AppState appState, - }) : _appState = appState, - _loggerService = loggerService; + }) : _appState = appState; @override post( @@ -176,7 +172,7 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; + // body['TokenID'] = "dlfjhagldfgahjsdf"; // body['PatientID'] = 3628599; } @@ -200,7 +196,6 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { - var parsed = json.decode(utf8.decode(response.bodyBytes)); onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { @@ -218,6 +213,7 @@ class ApiClientImp implements ApiClient { } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } if (parsed['ErrorType'] == 2) { @@ -225,9 +221,9 @@ class ApiClientImp implements ApiClient { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), ); - // logApiEndpointError(endPoint, "session logged out", statusCode); + // logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); @@ -248,6 +244,12 @@ class ApiClientImp implements ApiClient { } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['IsAuthenticated'] == false) { + onFailure( + "User is not Authenticated", + statusCode, + failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), + ); } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); @@ -277,7 +279,6 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } } - } else if (!parsed['IsAuthenticated']) { } else { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 4de7c5f..c9cca96 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -77,7 +77,7 @@ class AppDependencies { final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); - getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt(), appState: getIt())); + getIt.registerLazySingleton(() => ApiClientImp(appState: getIt())); // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); @@ -125,11 +125,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => MyAppointmentsViewModel( - myAppointmentsRepo: getIt(), - errorHandlerService: getIt(), - appState: getIt() - ), + () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()), ); getIt.registerLazySingleton( @@ -154,13 +150,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => BookAppointmentsViewModel( - bookAppointmentsRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt(), - myAppointmentsViewModel: getIt(), - locationUtils: getIt() - ), + () => BookAppointmentsViewModel(bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt()), ); getIt.registerLazySingleton( diff --git a/lib/core/exceptions/api_failure.dart b/lib/core/exceptions/api_failure.dart index c950195..0c5bc66 100644 --- a/lib/core/exceptions/api_failure.dart +++ b/lib/core/exceptions/api_failure.dart @@ -7,7 +7,9 @@ abstract class Failure extends Equatable implements Exception { } class ServerFailure extends Failure { - const ServerFailure(super.message); + final String url; + + const ServerFailure(super.message, {this.url = ""}); @override List get props => [message]; @@ -27,6 +29,13 @@ class MessageStatusFailure extends Failure { List get props => [message]; } +class AppUpdateFailure extends Failure { + const AppUpdateFailure(super.message); + + @override + List get props => [message]; +} + class StatusCodeFailure extends Failure { const StatusCodeFailure(super.message); @@ -34,6 +43,15 @@ class StatusCodeFailure extends Failure { List get props => [message]; } +class UnAuthenticatedUserFailure extends Failure { + final String url; + + const UnAuthenticatedUserFailure(super.message, {this.url = ""}); + + @override + List get props => [message]; +} + class ConnectivityFailure extends Failure { const ConnectivityFailure(super.message); @@ -56,7 +74,9 @@ class DataParsingFailure extends Failure { } class UnknownFailure extends Failure { - const UnknownFailure(super.message); + final String url; + + const UnknownFailure(super.message, {this.url = ""}); @override List get props => [message]; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c8adf78..b43858c 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,16 +1,16 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; -import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; @@ -18,7 +18,6 @@ import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; -import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; @@ -37,7 +36,6 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; -import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; import 'models/request_models/get_user_mobile_device_data.dart'; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 18b6085..29da51c 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -36,7 +36,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page. import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; -import 'package:hmg_patient_app_new/presentation/my_family/my_Family.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/my_family_sheet.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_family.dart similarity index 96% rename from lib/presentation/my_family/my_Family.dart rename to lib/presentation/my_family/my_family.dart index 6e4ef91..25f5f3c 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -1,11 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.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/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -13,7 +11,6 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -27,10 +24,10 @@ class FamilyMedicalScreen extends StatefulWidget { final Function(FamilyFileResponseModelLists) onSelect; const FamilyMedicalScreen({ - Key? key, + super.key, required this.profiles, required this.onSelect, - }) : super(key: key); + }); @override State createState() => _FamilyMedicalScreenState(); @@ -85,9 +82,9 @@ class _FamilyMedicalScreenState extends State { ); } - void showModelSheet() { + Future showModelSheet() async { AuthenticationViewModel authVm = getIt.get(); - return showCommonBottomSheetWithoutHeight(context, + return await showCommonBottomSheetWithoutHeight(context, title: "Add Family Member", child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 480f91f..306692b 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -43,9 +43,7 @@ class _FamilyCardsState extends State { ), itemBuilder: (context, index) { final profile = widget.profiles[index]; - final isActive = (profile.responseId == appState - .getAuthenticatedUser() - ?.patientId); + final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); return Container( padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), @@ -73,13 +71,13 @@ class _FamilyCardsState extends State { widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), widget.isShowDetails ? CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Age: ${profile.age ?? "N/A"} Years", - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor, - ) + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Age: ${profile.age ?? "N/A"} Years", + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor, + ) : SizedBox(), widget.isShowDetails ? SizedBox(height: 8.h) : SizedBox(), Spacer(), @@ -102,7 +100,7 @@ class _FamilyCardsState extends State { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 13.h, - icon: widget.isBottomSheet ? null : AppAssets.heart, + icon: widget.isBottomSheet ? null : AppAssets.heart, iconColor: AppColors.primaryRedColor, padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), ).paddingOnly(top: 0, bottom: 0), diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart index a119906..da822b1 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; -import 'package:hmg_patient_app_new/presentation/my_family/my_Family.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; class MyFamilySheet { - static void show(BuildContext context, List familyLists, Function(FamilyFileResponseModelLists) onSelect) { - return showCommonBottomSheetWithoutHeight( + static Future show(BuildContext context, List familyLists, Function(FamilyFileResponseModelLists) onSelect) async { + return await showCommonBottomSheetWithoutHeight( context, titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 29aee3d..6ee0ddc 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -4,18 +4,15 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.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/bottomsheet/exception_bottom_sheet.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:provider/provider.dart'; abstract class DialogService { - Future showErrorBottomSheet({required String message, Function()? onOkPressed}); + Future showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed}); Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}); @@ -28,21 +25,40 @@ abstract class DialogService { class DialogServiceImp implements DialogService { final NavigationService navigationService; + bool _isErrorSheetShowing = false; + DialogServiceImp({required this.navigationService}); @override - Future showErrorBottomSheet({required String message, Function()? onOkPressed}) async { + Future showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed}) async { + if (_isErrorSheetShowing) return; final context = navigationService.navigatorKey.currentContext; if (context == null) return; + _isErrorSheetShowing = true; await showModalBottomSheet( context: context, isScrollControlled: false, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (_) => _ErrorBottomSheet(message: message, onOkPressed: onOkPressed ?? () {}), + builder: (_) => ExceptionBottomSheet( + // title: title, + message: message, + showCancel: onCancelPressed != null ? true : false, + onOkPressed: () { + if (onOkPressed != null) { + onOkPressed(); + } + }, + onCancelPressed: () { + if (onCancelPressed != null) { + context.pop(); + } + }, + ), ); + _isErrorSheetShowing = false; } @override @@ -73,9 +89,8 @@ class DialogServiceImp implements DialogService { Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; - showCommonBottomSheetWithoutHeight(context, title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), - callBackFunc: () { - }); + showCommonBottomSheetWithoutHeight(context, + title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), callBackFunc: () {}); } @override @@ -90,10 +105,10 @@ class DialogServiceImp implements DialogService { Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) { return Column( children: [ - (message ?? "").toText16(isBold: false, color: AppColors.textColor), + (message).toText16(isBold: false, color: AppColors.textColor), SizedBox(height: 10.h), SizedBox(height: 24.h), - if (onOkPressed != null && onCancelPressed != null) + if (onCancelPressed != null) Row( children: [ Expanded( @@ -112,7 +127,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin SizedBox(width: 5.h), Expanded( child: CustomButton( - text: onCancelPressed != null ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), + text: LocaleKeys.confirm.tr(), onPressed: onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, @@ -122,14 +137,14 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin ), ], ), - if (onOkPressed != null && onCancelPressed == null) + if (onCancelPressed == null) Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( text: LocaleKeys.ok.tr(), - onPressed: (onOkPressed != null && onCancelPressed == null) + onPressed: (onCancelPressed == null) ? () { - Navigator.of(context).pop(); + context.pop(); } : onOkPressed, backgroundColor: AppColors.primaryRedColor, @@ -144,7 +159,6 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) { return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { - AuthenticationViewModel authViewModel = context.read(); return Column( children: [ (message ?? "").toText16(isBold: false, color: AppColors.textColor), @@ -202,56 +216,3 @@ Widget showPhoneNumberPickerWidget({required BuildContext context, String? messa // ); }); } - -class _ErrorBottomSheet extends StatelessWidget { - final String message; - final Function()? onOkPressed; - - const _ErrorBottomSheet({required this.message, this.onOkPressed}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 40), - const SizedBox(height: 12), - Text( - "Error", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - message, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () { - if (onOkPressed != null) { - onOkPressed!(); - } else { - context.pop(); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: const Text("OK", style: TextStyle(color: Colors.white)).onPress(() { - Navigator.of(context).pop(); - }), - ), - ], - ), - ); - } -} diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index c29dc17..aeefefb 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; -import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -28,46 +27,54 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { if (failure is APIException) { loggerService.errorLogs("API Exception: ${failure.message}"); } else if (failure is ServerFailure) { - loggerService.errorLogs("Server Failure: ${failure.message}"); - await _showDialog(failure); + loggerService.errorLogs("URL: ${failure.url} \n Server Failure: ${failure.message}"); + await _showDialog(failure, title: "Server Failure"); } else if (failure is DataParsingFailure) { loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); await _showDialog(failure, title: "Data Error"); } else if (failure is StatusCodeFailure) { loggerService.errorLogs("StatusCode Failure: ${failure.message}"); - await _showDialog(failure, title: "Status Code Failure Error"); + await _showDialog(failure, title: "StatusCodeFailure"); + } else if (failure is ConnectivityFailure) { + loggerService.errorLogs("ConnectivityFailure : ${failure.message}"); + await _showDialog(failure, title: "ConnectivityFailure ", onOkPressed: () {}); + } else if (failure is UnAuthenticatedUserFailure) { + loggerService.errorLogs("URL: ${failure.url} \n UnAuthenticatedUser Failure: ${failure.message}"); + await _showDialog(failure, title: "UnAuthenticatedUser Failure", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); + } else if (failure is AppUpdateFailure) { + loggerService.errorLogs("AppUpdateFailure : ${failure.message}"); + await _showDialog(failure, title: "AppUpdateFailure Error", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); } else if (failure is HttpException) { loggerService.errorLogs("Http Exception: ${failure.message}"); await _showDialog(failure, title: "Network Error"); } else if (failure is UnknownFailure) { - loggerService.errorLogs("Unknown Failure: ${failure.message}"); - await _showDialog(failure, title: "Unknown Error"); + loggerService.errorLogs("URL: ${failure.url} \n Unknown Failure: ${failure.message}"); + await _showDialog(failure, title: "Unknown Failure"); } else if (failure is InvalidCredentials) { loggerService.errorLogs("Invalid Credentials : ${failure.message}"); - await _showDialog(failure, title: "Unknown Error"); + await _showDialog(failure, title: "Invalid Credentials "); } else if (failure is UserIntimationFailure) { if (onUnHandledFailure != null) { onUnHandledFailure(failure); } else { - await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); + await _showDialog(failure, title: "UserIntimationFailure", onOkPressed: onOkPressed); } } else if (failure is MessageStatusFailure) { if (onMessageStatusFailure != null) { onMessageStatusFailure(failure); } else { - await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); + await _showDialog(failure, title: "MessageStatusFailure", onOkPressed: onOkPressed); } } else { loggerService.errorLogs("Unhandled failure type: $failure"); - await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); + await _showDialog(failure, title: "Unhandled Error", onOkPressed: onOkPressed); } } - Future _showDialog(Failure failure, {String title = "Error", Function()? onOkPressed}) async { + Future _showDialog(Failure failure, {required String title, Function()? onOkPressed}) async { if (LoadingUtils.isLoading) { LoadingUtils.hideFullScreenLoader(); } - - await dialogService.showErrorBottomSheet(message: failure.message, onOkPressed: onOkPressed); + await dialogService.showErrorBottomSheet(title: title, message: failure.message, onOkPressed: onOkPressed); } } diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 420c4cf..bfdedef 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/widgets/otp_verification_screen.dart'; +import 'package:hmg_patient_app_new/routes/app_routes.dart'; class NavigationService { final GlobalKey navigatorKey = GlobalKey(); @@ -18,6 +21,15 @@ class NavigationService { navigatorKey.currentState?.popUntil(ModalRoute.withName(routeName)); } + void replaceAllRoutesAndNavigateToLanding() { + AppState appState = getIt.get(); + appState.isAuthenticated = false; + navigatorKey.currentState?.pushNamedAndRemoveUntil( + AppRoutes.landingScreen, + (Route route) => false, + ); + } + void pushAndReplace(String routeName) { navigatorKey.currentState?.pushReplacementNamed(routeName); } diff --git a/lib/widgets/bottomsheet/exception_bottom_sheet.dart b/lib/widgets/bottomsheet/exception_bottom_sheet.dart index f0ea651..c11dec1 100644 --- a/lib/widgets/bottomsheet/exception_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/exception_bottom_sheet.dart @@ -11,13 +11,14 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; class ExceptionBottomSheet extends StatelessWidget { - String message; - bool showOKButton; - bool showCancel; - Function() onOkPressed; - Function()? onCancelPressed; + final String? title; + final String message; + final bool showOKButton; + final bool showCancel; + final Function() onOkPressed; + final Function()? onCancelPressed; - ExceptionBottomSheet({Key? key, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}) : super(key: key); + const ExceptionBottomSheet({super.key, this.title, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}); @override Widget build(BuildContext context) { @@ -44,7 +45,7 @@ class ExceptionBottomSheet extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.notice.tr().toText28(), + (title ?? LocaleKeys.notice.tr()).toText28(), InkWell( onTap: () { Navigator.of(context).pop(); @@ -54,7 +55,7 @@ class ExceptionBottomSheet extends StatelessWidget { ], ), SizedBox(height: 10.h), - (message ?? "").toText16(isBold: false, color: AppColors.textColor), + (message).toText16(isBold: false, color: AppColors.textColor), SizedBox(height: 10.h), SizedBox(height: 24.h), if (showOKButton && showCancel) diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index c50ffad..29df19b 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -104,7 +104,7 @@ class ButtonSheetContent extends StatelessWidget { } } -void showCommonBottomSheetWithoutHeight( +Future showCommonBottomSheetWithoutHeight( BuildContext context, { required Widget child, required VoidCallback callBackFunc, @@ -113,7 +113,7 @@ void showCommonBottomSheetWithoutHeight( bool isFullScreen = true, bool isDismissible = true, Widget? titleWidget, -}) { +}) async { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( duration: Duration(milliseconds: 500), // Custom animation duration From 62e82a7a316e282158b6f50268653f28b3f4bb01 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Fri, 26 Sep 2025 17:17:31 +0300 Subject: [PATCH 6/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 6b28b573077c67a022eb30fc7da5944141dbe0db Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 28 Sep 2025 16:41:52 +0300 Subject: [PATCH 7/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 8/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 = [ From 4cc4a7b0d9beaa3bbcfc7b25f541ab33eff6dfeb Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 29 Sep 2025 15:51:41 +0300 Subject: [PATCH 9/9] merged main --- lib/presentation/lab/lab_results/lab_result_details.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index e3ffaea..3e9558c 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,29 +1,23 @@ -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'; 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/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; import 'package:provider/provider.dart' show Consumer, Provider; import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; -import '../../book_appointment/widgets/appointment_calendar.dart' - show AppointmentCalendar; class LabResultDetails extends StatelessWidget { final LabResult recentLabResult;