From b240293064970fc2da3a891bf6ada7e9fe9db6fb Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Thu, 11 Sep 2025 23:18:53 +0300 Subject: [PATCH] medical file implementation contd. --- assets/images/svg/allergy_info_icon.svg | 5 + assets/images/svg/vaccine_info_icon.svg | 4 + lib/core/app_assets.dart | 2 + lib/core/dependencies.dart | 21 +- .../{models => }/habib_wallet_repo.dart | 0 .../{models => }/habib_wallet_view_model.dart | 2 +- .../medical_file/medical_file_repo.dart | 59 ++++++ .../medical_file/medical_file_view_model.dart | 46 ++++- .../patient_vaccine_response_model.dart | 160 ++++++++++++++++ .../my_appointments/my_appointments_repo.dart | 47 +++++ .../my_appointments_view_model.dart | 30 +++ lib/main.dart | 7 +- .../appointments/my_doctors_page.dart | 160 ++++++++++++++++ .../habib_wallet/habib_wallet_page.dart | 2 +- lib/presentation/home/landing_page.dart | 3 +- .../home/widgets/habib_wallet_card.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 3 +- .../medical_file/medical_file_page.dart | 134 ++++++++++++- .../medical_file/vaccine_list_page.dart | 181 ++++++++++++++++++ .../widgets/medical_file_card.dart | 2 +- lib/services/error_handler_service.dart | 2 +- 21 files changed, 859 insertions(+), 13 deletions(-) create mode 100644 assets/images/svg/allergy_info_icon.svg create mode 100644 assets/images/svg/vaccine_info_icon.svg rename lib/features/habib_wallet/{models => }/habib_wallet_repo.dart (100%) rename lib/features/habib_wallet/{models => }/habib_wallet_view_model.dart (93%) create mode 100644 lib/features/medical_file/medical_file_repo.dart create mode 100644 lib/features/medical_file/models/patient_vaccine_response_model.dart create mode 100644 lib/presentation/appointments/my_doctors_page.dart create mode 100644 lib/presentation/medical_file/vaccine_list_page.dart diff --git a/assets/images/svg/allergy_info_icon.svg b/assets/images/svg/allergy_info_icon.svg new file mode 100644 index 0000000..fc962cc --- /dev/null +++ b/assets/images/svg/allergy_info_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/vaccine_info_icon.svg b/assets/images/svg/vaccine_info_icon.svg new file mode 100644 index 0000000..d8d22b2 --- /dev/null +++ b/assets/images/svg/vaccine_info_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index f0d0254..c6e6a3c 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -101,6 +101,8 @@ class AppAssets { static const String logout = '$svgBasePath/logout.svg'; static const String alarm_clock_icon = '$svgBasePath/alarm_clock_icon.svg'; static const String all_medications_icon = '$svgBasePath/all_medications_icon.svg'; + static const String allergy_info_icon = '$svgBasePath/allergy_info_icon.svg'; + static const String vaccine_info_icon = '$svgBasePath/vaccine_info_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0edec4e..3518abe 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -7,12 +7,14 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; @@ -88,6 +90,7 @@ class AppDependencies { getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -141,6 +144,20 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => HabibWalletViewModel( + habibWalletRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + + getIt.registerLazySingleton( + () => MedicalFileViewModel( + medicalFileRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), diff --git a/lib/features/habib_wallet/models/habib_wallet_repo.dart b/lib/features/habib_wallet/habib_wallet_repo.dart similarity index 100% rename from lib/features/habib_wallet/models/habib_wallet_repo.dart rename to lib/features/habib_wallet/habib_wallet_repo.dart diff --git a/lib/features/habib_wallet/models/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart similarity index 93% rename from lib/features/habib_wallet/models/habib_wallet_view_model.dart rename to lib/features/habib_wallet/habib_wallet_view_model.dart index 64c6ddf..8e4fc26 100644 --- a/lib/features/habib_wallet/models/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class HabibWalletViewModel extends ChangeNotifier { diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart new file mode 100644 index 0000000..a635380 --- /dev/null +++ b/lib/features/medical_file/medical_file_repo.dart @@ -0,0 +1,59 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class MedicalFileRepo { + Future>> getPatientVaccinesList(); +} + +class MedicalFileRepoImp implements MedicalFileRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + MedicalFileRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientVaccinesList() async { + Map mapDevice = {"To": "0", "From": "0"}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_VACCINES, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DoneVaccines']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final vaccinesList = list.map((item) => PatientVaccineResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vaccinesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 8c00fe4..1d6245a 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,12 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class MedicalFileViewModel extends ChangeNotifier { - int selectedTabIndex = 0; + bool isPatientVaccineListLoading = false; + + MedicalFileRepo medicalFileRepo; + ErrorHandlerService errorHandlerService; + + List patientVaccineList = []; + + MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); + + initMedicalFileProvider() { + isPatientVaccineListLoading = true; + notifyListeners(); + } + + setIsPatientVaccineListLoading(bool isLoading) { + isPatientVaccineListLoading = isLoading; + notifyListeners(); + } void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); } + Future getPatientVaccinesList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientVaccinesList(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientVaccineList = apiResponse.data!; + isPatientVaccineListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/medical_file/models/patient_vaccine_response_model.dart b/lib/features/medical_file/models/patient_vaccine_response_model.dart new file mode 100644 index 0000000..ff27b06 --- /dev/null +++ b/lib/features/medical_file/models/patient_vaccine_response_model.dart @@ -0,0 +1,160 @@ +class PatientVaccineResponseModel { + String? setupID; + int? projectID; + int? patientID; + int? invoiceNo; + String? procedureID; + String? vaccineName; + Null? vaccineNameN; + String? invoiceDate; + int? doctorID; + int? clinicID; + String? firstName; + String? middleName; + String? lastName; + Null? firstNameN; + Null? middleNameN; + Null? lastNameN; + String? dateofBirth; + int? actualDoctorRate; + String? age; + String? clinicName; + Null? decimalDoctorRate; + Null? doctorImageURL; + String? doctorName; + int? doctorRate; + int? doctorStarsRate; + String? doctorTitle; + int? gender; + Null? genderDescription; + Null? invoiceNoVP; + bool? isActiveDoctorProfile; + bool? isDoctorAllowVedioCall; + bool? isExecludeDoctor; + int? noOfPatientsRate; + String? patientName; + String? projectName; + String? qR; + String? vaccinationDate; + + PatientVaccineResponseModel( + {this.setupID, + this.projectID, + this.patientID, + this.invoiceNo, + this.procedureID, + this.vaccineName, + this.vaccineNameN, + this.invoiceDate, + this.doctorID, + this.clinicID, + this.firstName, + this.middleName, + this.lastName, + this.firstNameN, + this.middleNameN, + this.lastNameN, + this.dateofBirth, + this.actualDoctorRate, + this.age, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.gender, + this.genderDescription, + this.invoiceNoVP, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.noOfPatientsRate, + this.patientName, + this.projectName, + this.qR, + this.vaccinationDate}); + + PatientVaccineResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + invoiceNo = json['InvoiceNo']; + procedureID = json['ProcedureID']; + vaccineName = json['VaccineName']; + vaccineNameN = json['VaccineNameN']; + invoiceDate = json['InvoiceDate']; + doctorID = json['DoctorID']; + clinicID = json['ClinicID']; + firstName = json['FirstName']; + middleName = json['MiddleName']; + lastName = json['LastName']; + firstNameN = json['FirstNameN']; + middleNameN = json['MiddleNameN']; + lastNameN = json['LastNameN']; + dateofBirth = json['DateofBirth']; + actualDoctorRate = json['ActualDoctorRate']; + age = json['Age']; + clinicName = json['ClinicName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + invoiceNoVP = json['InvoiceNo_VP']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isExecludeDoctor = json['IsExecludeDoctor']; + noOfPatientsRate = json['NoOfPatientsRate']; + patientName = json['PatientName']; + projectName = json['ProjectName']; + qR = json['QR']; + vaccinationDate = json['VaccinationDate']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['InvoiceNo'] = this.invoiceNo; + data['ProcedureID'] = this.procedureID; + data['VaccineName'] = this.vaccineName; + data['VaccineNameN'] = this.vaccineNameN; + data['InvoiceDate'] = this.invoiceDate; + data['DoctorID'] = this.doctorID; + data['ClinicID'] = this.clinicID; + data['FirstName'] = this.firstName; + data['MiddleName'] = this.middleName; + data['LastName'] = this.lastName; + data['FirstNameN'] = this.firstNameN; + data['MiddleNameN'] = this.middleNameN; + data['LastNameN'] = this.lastNameN; + data['DateofBirth'] = this.dateofBirth; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['Age'] = this.age; + data['ClinicName'] = this.clinicName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['InvoiceNo_VP'] = this.invoiceNoVP; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['PatientName'] = this.patientName; + data['ProjectName'] = this.projectName; + data['QR'] = this.qR; + data['VaccinationDate'] = this.vaccinationDate; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index a5528cf..2035f30 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -34,6 +34,8 @@ abstract class MyAppointmentsRepo { {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); Future>>> getPatientAppointmentsForTimeLine(); + + Future>>> getPatientDoctorsList(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -444,4 +446,49 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientDoctorsList() async { + Map mapDevice = { + "Top": 0, + "beforeDays": 0, + "exludType": 4, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_MY_DOCTOR, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['PatientDoctorAppointmentResultList']; + // if (list == null || list.isEmpty) { + // throw Exception("Appointments list is empty"); + // } + + final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index d3121af..c6d3499 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -13,6 +13,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; bool isTimeLineAppointmentsLoading = false; + bool isPatientMyDoctorsLoading = false; List patientAppointmentsHistoryList = []; @@ -21,6 +22,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { List patientTimelineAppointmentsList = []; + List patientMyDoctorsList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -35,9 +38,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); patientTimelineAppointmentsList.clear(); + patientMyDoctorsList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; isTimeLineAppointmentsLoading = true; + isPatientMyDoctorsLoading = true; notifyListeners(); } @@ -56,6 +61,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientMyDoctorsLoading(bool val) { + isPatientMyDoctorsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -263,4 +273,24 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getPatientDoctorsList(); + + 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) { + patientMyDoctorsList = apiResponse.data!; + isPatientMyDoctorsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/main.dart b/lib/main.dart index 2739a6b..8c6e05b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -96,7 +96,10 @@ void main() async { ), ), ChangeNotifierProvider( - create: (_) => MedicalFileViewModel(), + create: (_) => MedicalFileViewModel( + medicalFileRepo: getIt(), + errorHandlerService: getIt(), + ), ), ChangeNotifierProvider( create: (_) => MyAppointmentsViewModel( diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart new file mode 100644 index 0000000..89b120c --- /dev/null +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -0,0 +1,160 @@ +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/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/theme/colors.dart'; +import 'package:provider/provider.dart'; + +import '../../widgets/chip/app_custom_chip_widget.dart'; + +class MyDoctorsPage extends StatelessWidget { + MyDoctorsPage({super.key}); + + late MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.myDoctor.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (myAppointmentsVM.patientMyDoctorsList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].clinicName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].projectName).toShimmer2(isShow: false, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".toText16(), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ), + ], + ), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 632b11d..1d24d05 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.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'; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b63b404..26e5147 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -64,6 +64,7 @@ class _LandingPageState extends State { habibWalletVM.getPatientBalanceAmount(); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); } }); diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 9bb53f5..3509000 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_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/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 2c6a131..520bfec 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; @@ -57,7 +58,7 @@ class _LabOrdersPageState extends State { } }, child: SingleChildScrollView( - padding: EdgeInsets.all(24), + padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 37fce64..5b40345 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -18,8 +18,10 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie 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/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_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/presentation/medical_file/vaccine_list_page.dart'; 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/prescriptions/prescriptions_list_page.dart'; @@ -46,6 +48,7 @@ class _MedicalFilePageState extends State { late InsuranceViewModel insuranceViewModel; late AppState appState; late MyAppointmentsViewModel myAppointmentsViewModel; + late MedicalFileViewModel medicalFileViewModel; int currentIndex = 0; @@ -61,6 +64,7 @@ class _MedicalFilePageState extends State { Widget build(BuildContext context) { insuranceViewModel = Provider.of(context, listen: false); myAppointmentsViewModel = Provider.of(context, listen: false); + medicalFileViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -321,7 +325,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget() + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -442,6 +446,134 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(24.h, 0.h); }), SizedBox(height: 24.h), + //My Doctor Section + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.myDoctor.tr(context: context).toText18(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ).onPress(() { + myAppointmentsViewModel.setIsPatientMyDoctorsLoading(true); + myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + FadePage( + page: MyDoctorsPage(), + ), + ); + }), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Consumer(builder: (context, myAppointmentsVM, child) { + return SizedBox( + height: 120.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? SizedBox( + width: 80.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), + ), + ], + ), + ) + : 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, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), + ), + ); + }), + SizedBox(height: 24.h), + "Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Eye Test Results".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: true, + iconSize: 40.h, + ), + MedicalFileCard( + label: "Allergy Info".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 40.h, + ), + MedicalFileCard( + label: "Vaccine Info".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: true, + iconSize: 40.h, + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: VaccineListPage(), + ), + ); + }), + ], + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), ], ); case 1: diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart new file mode 100644 index 0000000..1080902 --- /dev/null +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -0,0 +1,181 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/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/theme/colors.dart'; +import 'package:provider/provider.dart'; + +import '../../widgets/chip/app_custom_chip_widget.dart'; + +class VaccineListPage extends StatefulWidget { + const VaccineListPage({super.key}); + + @override + State createState() => _VaccineListPageState(); +} + +class _VaccineListPageState extends State { + late MedicalFileViewModel medicalFileViewModel; + + @override + void initState() { + scheduleMicrotask(() { + medicalFileViewModel.setIsPatientVaccineListLoading(true); + medicalFileViewModel.getPatientVaccinesList(onError: (error) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Vaccine Info", + child: SingleChildScrollView( + child: Consumer(builder: (context, medicalFileVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: medicalFileVM.isPatientVaccineListLoading ? 5 : medicalFileVM.patientVaccineList.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return medicalFileVM.isPatientVaccineListLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image.network( + // medicalFileVM.patientVaccineList[index].doctorImageURL, + // width: 63.h, + // height: 63.h, + // fit: BoxFit.fill, + // ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (medicalFileVM.patientVaccineList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(medicalFileVM.patientVaccineList[index].vaccinationDate), false)), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].clinicName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].projectName).toShimmer2(isShow: false, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // "".toText16(), + // Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + // ], + // ), + ], + ), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 82f38ec..6341225 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -36,7 +36,7 @@ class MedicalFileCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - isLargeText ? label.toText14(color: textColor, isBold: true) : label.toText11(color: textColor, isBold: true), + isLargeText ? label.toText14(color: textColor, isBold: true, maxlines: 1) : label.toText11(color: textColor, isBold: true, maxLine: 1), ], ), ), diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index 59a0205..092cf38 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -47,7 +47,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { } else { loggerService.errorLogs("Unhandled failure type: $failure"); - await _showDialog(failure, title: "Error"); + await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); } }