diff --git a/assets/images/png/smartwatches/Apple-Watch-6.png b/assets/images/png/smartwatches/Apple-Watch-6.png new file mode 100644 index 0000000..1e67050 Binary files /dev/null and b/assets/images/png/smartwatches/Apple-Watch-6.png differ diff --git a/assets/images/png/smartwatches/apple-watch-1.jpeg b/assets/images/png/smartwatches/apple-watch-1.jpeg new file mode 100644 index 0000000..7262e7e Binary files /dev/null and b/assets/images/png/smartwatches/apple-watch-1.jpeg differ diff --git a/assets/images/png/smartwatches/apple-watch-2.jpg b/assets/images/png/smartwatches/apple-watch-2.jpg new file mode 100644 index 0000000..f688f74 Binary files /dev/null and b/assets/images/png/smartwatches/apple-watch-2.jpg differ diff --git a/assets/images/png/smartwatches/apple-watch-3.jpg b/assets/images/png/smartwatches/apple-watch-3.jpg new file mode 100644 index 0000000..b68c1ce Binary files /dev/null and b/assets/images/png/smartwatches/apple-watch-3.jpg differ diff --git a/assets/images/png/smartwatches/apple-watch-4.jpg b/assets/images/png/smartwatches/apple-watch-4.jpg new file mode 100644 index 0000000..2fc19b6 Binary files /dev/null and b/assets/images/png/smartwatches/apple-watch-4.jpg differ diff --git a/assets/images/png/smartwatches/apple-watch-5.jpg b/assets/images/png/smartwatches/apple-watch-5.jpg new file mode 100644 index 0000000..4c497ea Binary files /dev/null and b/assets/images/png/smartwatches/apple-watch-5.jpg differ diff --git a/assets/images/png/smartwatches/bloodoxygen_icon.svg b/assets/images/png/smartwatches/bloodoxygen_icon.svg new file mode 100644 index 0000000..0971a30 --- /dev/null +++ b/assets/images/png/smartwatches/bloodoxygen_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/png/smartwatches/calories_icon.svg b/assets/images/png/smartwatches/calories_icon.svg new file mode 100644 index 0000000..660ce0d --- /dev/null +++ b/assets/images/png/smartwatches/calories_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/png/smartwatches/distance_icon.svg b/assets/images/png/smartwatches/distance_icon.svg new file mode 100644 index 0000000..29dcf3d --- /dev/null +++ b/assets/images/png/smartwatches/distance_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/png/smartwatches/galaxy_fit_3.jpg b/assets/images/png/smartwatches/galaxy_fit_3.jpg new file mode 100644 index 0000000..ff05834 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_fit_3.jpg differ diff --git a/assets/images/png/smartwatches/galaxy_watch_7.webp b/assets/images/png/smartwatches/galaxy_watch_7.webp new file mode 100644 index 0000000..09748b4 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_watch_7.webp differ diff --git a/assets/images/png/smartwatches/galaxy_watch_7_classic.jpg b/assets/images/png/smartwatches/galaxy_watch_7_classic.jpg new file mode 100644 index 0000000..f177dd4 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_watch_7_classic.jpg differ diff --git a/assets/images/png/smartwatches/galaxy_watch_8.jpg b/assets/images/png/smartwatches/galaxy_watch_8.jpg new file mode 100644 index 0000000..7fd4746 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_watch_8.jpg differ diff --git a/assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg b/assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg new file mode 100644 index 0000000..6e84096 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg differ diff --git a/assets/images/png/smartwatches/galaxy_watch_ultra.jpg b/assets/images/png/smartwatches/galaxy_watch_ultra.jpg new file mode 100644 index 0000000..e401d73 Binary files /dev/null and b/assets/images/png/smartwatches/galaxy_watch_ultra.jpg differ diff --git a/assets/images/png/smartwatches/heartrate_icon.svg b/assets/images/png/smartwatches/heartrate_icon.svg new file mode 100644 index 0000000..dac05ef --- /dev/null +++ b/assets/images/png/smartwatches/heartrate_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/png/smartwatches/steps_icon.svg b/assets/images/png/smartwatches/steps_icon.svg new file mode 100644 index 0000000..4af073a --- /dev/null +++ b/assets/images/png/smartwatches/steps_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/ancillary_orders_list_icon.svg b/assets/images/svg/ancillary_orders_list_icon.svg new file mode 100644 index 0000000..f0497d8 --- /dev/null +++ b/assets/images/svg/ancillary_orders_list_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/ask_doctor_medical_file_icon.svg b/assets/images/svg/ask_doctor_medical_file_icon.svg new file mode 100644 index 0000000..11facfb --- /dev/null +++ b/assets/images/svg/ask_doctor_medical_file_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/blood_sugar_icon.svg b/assets/images/svg/blood_sugar_icon.svg new file mode 100644 index 0000000..3c77019 --- /dev/null +++ b/assets/images/svg/blood_sugar_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/daily_water_monitor.svg b/assets/images/svg/daily_water_monitor.svg new file mode 100644 index 0000000..b5f057d --- /dev/null +++ b/assets/images/svg/daily_water_monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/health_calculators_services_icon.svg b/assets/images/svg/health_calculators_services_icon.svg new file mode 100644 index 0000000..9f30d08 --- /dev/null +++ b/assets/images/svg/health_calculators_services_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/health_converters_icon.svg b/assets/images/svg/health_converters_icon.svg new file mode 100644 index 0000000..225ad01 --- /dev/null +++ b/assets/images/svg/health_converters_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/insurance_approval_icon.svg b/assets/images/svg/insurance_approval_icon.svg new file mode 100644 index 0000000..b46a54a --- /dev/null +++ b/assets/images/svg/insurance_approval_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/internet_pairing_icon.svg b/assets/images/svg/internet_pairing_icon.svg new file mode 100644 index 0000000..3e1ac63 --- /dev/null +++ b/assets/images/svg/internet_pairing_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/svg/invoices_list_icon.svg b/assets/images/svg/invoices_list_icon.svg new file mode 100644 index 0000000..f123096 --- /dev/null +++ b/assets/images/svg/invoices_list_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/medical_reports_icon.svg b/assets/images/svg/medical_reports_icon.svg new file mode 100644 index 0000000..862b813 --- /dev/null +++ b/assets/images/svg/medical_reports_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/monthly_reports_icon.svg b/assets/images/svg/monthly_reports_icon.svg new file mode 100644 index 0000000..5e786e2 --- /dev/null +++ b/assets/images/svg/monthly_reports_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/my_doctors_icon.svg b/assets/images/svg/my_doctors_icon.svg new file mode 100644 index 0000000..c5fc541 --- /dev/null +++ b/assets/images/svg/my_doctors_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/my_radiology_icon.svg b/assets/images/svg/my_radiology_icon.svg new file mode 100644 index 0000000..7b5ebe4 --- /dev/null +++ b/assets/images/svg/my_radiology_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/my_sick_leave_icon.svg b/assets/images/svg/my_sick_leave_icon.svg new file mode 100644 index 0000000..f488cff --- /dev/null +++ b/assets/images/svg/my_sick_leave_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/sick_leave_report_icon.svg b/assets/images/svg/sick_leave_report_icon.svg new file mode 100644 index 0000000..521c063 --- /dev/null +++ b/assets/images/svg/sick_leave_report_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/smartwatch_icon.svg b/assets/images/svg/smartwatch_icon.svg new file mode 100644 index 0000000..162ab36 --- /dev/null +++ b/assets/images/svg/smartwatch_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/update_insurance_icon.svg b/assets/images/svg/update_insurance_icon.svg new file mode 100644 index 0000000..684d672 --- /dev/null +++ b/assets/images/svg/update_insurance_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/weight_tracker_icon.svg b/assets/images/svg/weight_tracker_icon.svg new file mode 100644 index 0000000..5110575 --- /dev/null +++ b/assets/images/svg/weight_tracker_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 8f2ef94..ab9828e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -71,6 +71,8 @@ This app requires contacts access to show incoming virtual consultation request. NSFaceIDUsageDescription This app requires Face ID to allow biometric authentication for app login. + NSHealthClinicalHealthRecordsShareUsageDescription + This App need access to HealthKit to read heart rate & other data from your smart watch. NSHealthShareUsageDescription This App need access to HealthKit to read heart rate & other data from your smart watch. NSHealthUpdateUsageDescription diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 319178a..2c37e77 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,6 +4,14 @@ aps-environment development + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + health-records + + com.apple.developer.healthkit.background-delivery + com.apple.developer.in-app-payments merchant.com.hmgwebservices diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5499787..d284b5c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -723,6 +723,8 @@ class ApiConsts { static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; + static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; // var payFortEnvironment = FortEnvironment.test; @@ -739,6 +741,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -749,6 +752,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -759,6 +763,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -769,6 +774,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -779,6 +785,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -789,6 +796,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; break; } } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 3ee41f2..aa8d26e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -188,6 +188,24 @@ class AppAssets { static const String latest_news_icon = '$svgBasePath/latest_news_icon.svg'; static const String hmg_contact_icon = '$svgBasePath/hmg_contact_icon.svg'; static const String services_medical_file_icon = '$svgBasePath/services_medical_file_icon.svg'; + static const String blood_sugar_icon = '$svgBasePath/blood_sugar_icon.svg'; + static const String weight_tracker_icon = '$svgBasePath/weight_tracker_icon.svg'; + static const String ask_doctor_medical_file_icon = '$svgBasePath/ask_doctor_medical_file_icon.svg'; + static const String internet_pairing_icon = '$svgBasePath/internet_pairing_icon.svg'; + static const String my_doctors_icon = '$svgBasePath/my_doctors_icon.svg'; + static const String my_sick_leave_icon = '$svgBasePath/my_sick_leave_icon.svg'; + static const String my_radiology_icon = '$svgBasePath/my_radiology_icon.svg'; + static const String monthly_reports_icon = '$svgBasePath/monthly_reports_icon.svg'; + static const String medical_reports_icon = '$svgBasePath/medical_reports_icon.svg'; + static const String sick_leave_report_icon = '$svgBasePath/sick_leave_report_icon.svg'; + static const String update_insurance_icon = '$svgBasePath/update_insurance_icon.svg'; + static const String insurance_approval_icon = '$svgBasePath/insurance_approval_icon.svg'; + static const String invoices_list_icon = '$svgBasePath/invoices_list_icon.svg'; + static const String ancillary_orders_list_icon = '$svgBasePath/ancillary_orders_list_icon.svg'; + static const String daily_water_monitor_icon = '$svgBasePath/daily_water_monitor.svg'; + static const String health_calculators_services_icon = '$svgBasePath/health_calculators_services_icon.svg'; + static const String health_converters_icon = '$svgBasePath/health_converters_icon.svg'; + static const String smartwatch_icon = '$svgBasePath/smartwatch_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 4c17de6..054819e 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -39,6 +39,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; @@ -232,6 +233,10 @@ class AppDependencies { () => BloodDonationViewModel(bloodDonationRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt()), ); + getIt.registerLazySingleton( + () => HealthProvider(), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( // () => BookAppointmentsViewModel( diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 857c0c2..397adad 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -913,14 +913,14 @@ class Utils { return AppColors.primaryRedColor; } - static String getCardButtonText(int currentQueueStatus) { + static String getCardButtonText(int currentQueueStatus, String roomNumber) { switch (currentQueueStatus) { case 0: return "Please wait! you will be called for vital signs".needTranslation; case 1: - return "Please visit Room S5 for vital signs".needTranslation; + return "Please visit Room $roomNumber for vital signs".needTranslation; case 2: - return "Please visit Room S5 to the Doctor".needTranslation; + return "Please visit Room $roomNumber to the Doctor".needTranslation; } return ""; } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index fa16423..faedcd8 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -586,6 +586,7 @@ class AuthenticationViewModel extends ChangeNotifier { activation.list!.first.bloodGroup = activation.patientBlodType; _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); + _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; diff --git a/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart new file mode 100644 index 0000000..c36b9ec --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart @@ -0,0 +1,40 @@ +class PatientQueueDetails { + int? patientID; + String? patientName; + String? queueNo; + int? callType; + String? roomNo; + String? calledOn; + bool? servingNow; + + PatientQueueDetails( + {this.patientID, + this.patientName, + this.queueNo, + this.callType, + this.roomNo, + this.calledOn, + this.servingNow}); + + PatientQueueDetails.fromJson(Map json) { + patientID = json['patientID']; + patientName = json['patientName']; + queueNo = json['queueNo']; + callType = json['callType']; + roomNo = json['roomNo']; + calledOn = json['calledOn']; + servingNow = json['servingNow']; + } + + Map toJson() { + final Map data = new Map(); + data['patientID'] = this.patientID; + data['patientName'] = this.patientName; + data['queueNo'] = this.queueNo; + data['callType'] = this.callType; + data['roomNo'] = this.roomNo; + data['calledOn'] = this.calledOn; + data['servingNow'] = this.servingNow; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 87ec10d..3308f93 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ 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/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { @@ -58,6 +59,8 @@ abstract class MyAppointmentsRepo { Future>> sendAskDocCallRequest( {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}); + + Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -746,4 +749,35 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}) async { + Map mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"}; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post(ApiConsts.QLINE_URL, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['data']; + final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: queueList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: mapDevice, isExternal: true, isAllowAny: true); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 71e0312..7dfef90 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -37,8 +38,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { DateTime? start = null; DateTime? end = null; + bool isAppointmentQueueDetailsLoading = false; bool isPatientHasQueueAppointment = false; int currentQueueStatus = 0; + List patientQueueDetailsList = []; + late PatientQueueDetails currentPatientQueueDetails; List patientAppointmentsHistoryList = []; List filteredAppointmentList = []; @@ -88,7 +92,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; isEyeMeasurementsAppointmentsLoading = true; - isPatientHasQueueAppointment = false; + notifyListeners(); } @@ -218,6 +222,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); filteredAppointmentList.addAll(patientAppointmentsHistoryList); + if (patientArrivedAppointmentsHistoryList.isNotEmpty) { + getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); + } + print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); print('All Appointments: ${patientAppointmentsHistoryList.length}'); @@ -659,6 +667,50 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } + Future getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isAppointmentQueueDetailsLoading = true; + notifyListeners(); + final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails(appointmentNo: appointmentNo, patientID: patientID); + + isAppointmentQueueDetailsLoading = false; + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + isPatientHasQueueAppointment = true; + patientQueueDetailsList = apiResponse.data!; + for (var element in patientQueueDetailsList) { + if (element.patientID == patientID) { + currentPatientQueueDetails = element; + currentQueueStatus = element.callType!; + // currentQueueStatus = 2; + break; + } + } + // patientQueueDetailsList.first.callType = 1; + + patientQueueDetailsList.removeWhere((element) => element.patientID == patientID); + } else { + isPatientHasQueueAppointment = false; + } + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + Future sendAskDocCallRequest({ required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart new file mode 100644 index 0000000..fc9dacc --- /dev/null +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -0,0 +1,85 @@ +import 'package:flutter/foundation.dart'; +import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart'; + +class HealthProvider with ChangeNotifier { + final HealthService _healthService = HealthService(); + Map> healthData = {}; + bool isLoading = false; + String? error; + String selectedTimeRange = '7D'; + int selectedTabIndex = 0; + + void onTabChanged(int index) { + selectedTabIndex = index; + notifyListeners(); + } + + Future fetchHealthData() async { + isLoading = true; + error = null; + notifyListeners(); + healthData.clear(); + try { + final authorized = await _healthService.requestAuthorization(); + if (!authorized) { + error = 'Health data access not authorized'; + isLoading = false; + notifyListeners(); + return; + } + + final startTime = _getStartDate(); + final endTime = DateTime.now(); + + healthData = await _healthService.getAllHealthData(startTime, endTime); + + isLoading = false; + notifyListeners(); + } catch (e) { + error = 'Error fetching health data: $e'; + isLoading = false; + notifyListeners(); + } + } + + Future refreshMetric(HealthDataType type) async { + try { + final startTime = _getStartDate(); + final endTime = DateTime.now(); + + final data = await _healthService.getSpecificHealthData( + type, + startTime, + endTime, + ); + + healthData[type] = data; + notifyListeners(); + } catch (e) { + print('Error refreshing metric $type: $e'); + } + } + + void updateTimeRange(String range) { + selectedTimeRange = range; + fetchHealthData(); + } + + DateTime _getStartDate() { + switch (selectedTimeRange) { + case '1D': + return DateTime.now().subtract(const Duration(days: 1)); + case '7D': + return DateTime.now().subtract(const Duration(days: 7)); + case '1M': + return DateTime.now().subtract(const Duration(days: 30)); + case '3M': + return DateTime.now().subtract(const Duration(days: 90)); + case '1Y': + return DateTime.now().subtract(const Duration(days: 365)); + default: + return DateTime.now().subtract(const Duration(days: 7)); + } + } +} diff --git a/lib/features/smartwatch_health_data/health_service.dart b/lib/features/smartwatch_health_data/health_service.dart new file mode 100644 index 0000000..c5dca48 --- /dev/null +++ b/lib/features/smartwatch_health_data/health_service.dart @@ -0,0 +1,166 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:health/health.dart'; +import 'package:permission_handler/permission_handler.dart'; + +import 'health_utils.dart'; + +class HealthService { + static final HealthService _instance = HealthService._internal(); + + factory HealthService() => _instance; + + HealthService._internal(); + + final Health health = Health(); + + final List _healthMetrics = [ + HealthDataType.HEART_RATE, + // HealthDataType.STEPS, + // HealthDataType.BLOOD_OXYGEN, + // HealthDataType.BLOOD_PRESSURE_SYSTOLIC, + // HealthDataType.BLOOD_PRESSURE_DIASTOLIC, + // HealthDataType.BODY_TEMPERATURE, + // HealthDataType.DISTANCE_WALKING_RUNNING, + // HealthDataType.ACTIVE_ENERGY_BURNED, + ]; + + final List _healthMetricsAndroid = [ + HealthDataType.HEART_RATE, + HealthDataType.STEPS, + HealthDataType.BLOOD_OXYGEN, + // HealthDataType.BLOOD_PRESSURE_SYSTOLIC, + // HealthDataType.BLOOD_PRESSURE_DIASTOLIC, + // HealthDataType.BODY_TEMPERATURE, + HealthDataType.DISTANCE_DELTA, + HealthDataType.ACTIVE_ENERGY_BURNED, + HealthDataType.BASAL_ENERGY_BURNED, + HealthDataType.TOTAL_CALORIES_BURNED + ]; + + final List _healthPermissions = [ + HealthDataType.HEART_RATE, + HealthDataType.STEPS, + HealthDataType.BLOOD_OXYGEN, + // HealthDataType.BLOOD_PRESSURE_SYSTOLIC, + // HealthDataType.BLOOD_PRESSURE_DIASTOLIC, + // HealthDataType.BODY_TEMPERATURE, + Platform.isAndroid ? HealthDataType.DISTANCE_DELTA : HealthDataType.DISTANCE_WALKING_RUNNING, + HealthDataType.ACTIVE_ENERGY_BURNED, + HealthDataType.BASAL_ENERGY_BURNED, + HealthDataType.TOTAL_CALORIES_BURNED + ]; + + final List _healthMetricsCumulative = [ + HealthDataType.BLOOD_OXYGEN, + HealthDataType.STEPS, + Platform.isAndroid ? HealthDataType.DISTANCE_DELTA : HealthDataType.DISTANCE_WALKING_RUNNING, + HealthDataType.ACTIVE_ENERGY_BURNED, + HealthDataType.BASAL_ENERGY_BURNED, + HealthDataType.TOTAL_CALORIES_BURNED + ]; + + Future requestAuthorization() async { + try { + final types = (Platform.isAndroid) ? dataTypesAndroid : _healthPermissions; + final granted = await health.requestAuthorization(types); + await Permission.activityRecognition.request(); + await Permission.location.request(); + // request access to read historic data + await Health().requestHealthDataHistoryAuthorization(); + // await authorize(); + return granted; + } catch (e) { + print('Authorization error: $e'); + return false; + } + } + + Future>> getAllHealthData( + DateTime startTime, + DateTime endTime, + ) async { + final Map> allData = {}; + + try { + for (var type in _healthMetricsCumulative) { + try { + final data = await health.getHealthIntervalDataFromTypes( + startDate: startTime, + endDate: endTime, + types: [type], + interval: 86400, + ); + + if (type == HealthDataType.BLOOD_OXYGEN) { + for (var point in data) { + if (point.value is NumericHealthValue) { + final numericValue = (point.value as NumericHealthValue).numericValue; + point.value = NumericHealthValue( + numericValue: numericValue * 100, + ); + } + } + } + + if (type == HealthDataType.DISTANCE_WALKING_RUNNING) { + for (var point in data) { + if (point.value is NumericHealthValue) { + final numericValue = (point.value as NumericHealthValue).numericValue; + point.value = NumericHealthValue( + numericValue: numericValue / 1000, + ); + } + } + } + + allData[type] = data; + } catch (e) { + print('Error fetching $type: $e'); + allData[type] = []; + } + } + + for (var type in Platform.isIOS ? _healthMetrics : _healthMetricsAndroid) { + try { + final data = await health.getHealthDataFromTypes( + startTime: startTime, + endTime: endTime, + types: [type], + // includeManualEntry: false + // interval: 86400, + ); + + allData[type] = data; + } catch (e) { + print('Error fetching $type: $e'); + allData[type] = []; + } + } + } catch (e) { + print('Error fetching health data: $e'); + } + + return allData; + } + + Future> getSpecificHealthData( + HealthDataType type, + DateTime startTime, + DateTime endTime, + ) async { + try { + final data = await health.getHealthDataFromTypes( + startTime: startTime, + endTime: endTime, + types: [type], + ); + return data; + } catch (e) { + print('Error fetching $type: $e'); + return []; + } + } +} diff --git a/lib/features/smartwatch_health_data/health_utils.dart b/lib/features/smartwatch_health_data/health_utils.dart new file mode 100644 index 0000000..3d3588e --- /dev/null +++ b/lib/features/smartwatch_health_data/health_utils.dart @@ -0,0 +1,109 @@ +import 'package:health/health.dart'; + +/// Data types available on iOS via Apple Health. +const List dataTypesIOS = [ + HealthDataType.ACTIVE_ENERGY_BURNED, + HealthDataType.APPLE_STAND_TIME, + HealthDataType.APPLE_STAND_HOUR, + HealthDataType.APPLE_MOVE_TIME, + HealthDataType.AUDIOGRAM, + HealthDataType.BASAL_ENERGY_BURNED, + HealthDataType.BLOOD_GLUCOSE, + HealthDataType.BLOOD_OXYGEN, + HealthDataType.BLOOD_PRESSURE_DIASTOLIC, + HealthDataType.BLOOD_PRESSURE_SYSTOLIC, + HealthDataType.BODY_FAT_PERCENTAGE, + HealthDataType.BODY_MASS_INDEX, + HealthDataType.BODY_TEMPERATURE, + HealthDataType.DIETARY_CARBS_CONSUMED, + HealthDataType.DIETARY_CAFFEINE, + HealthDataType.DIETARY_ENERGY_CONSUMED, + HealthDataType.DIETARY_FATS_CONSUMED, + HealthDataType.DIETARY_PROTEIN_CONSUMED, + HealthDataType.ELECTRODERMAL_ACTIVITY, + HealthDataType.FORCED_EXPIRATORY_VOLUME, + HealthDataType.HEART_RATE, + HealthDataType.HEART_RATE_VARIABILITY_SDNN, + HealthDataType.HEIGHT, + HealthDataType.INSULIN_DELIVERY, + HealthDataType.RESPIRATORY_RATE, + HealthDataType.PERIPHERAL_PERFUSION_INDEX, + HealthDataType.STEPS, + HealthDataType.WAIST_CIRCUMFERENCE, + HealthDataType.WEIGHT, + HealthDataType.FLIGHTS_CLIMBED, + HealthDataType.DISTANCE_WALKING_RUNNING, + HealthDataType.WALKING_SPEED, + HealthDataType.MINDFULNESS, + HealthDataType.SLEEP_AWAKE, + HealthDataType.SLEEP_ASLEEP, + HealthDataType.SLEEP_IN_BED, + HealthDataType.SLEEP_LIGHT, + HealthDataType.SLEEP_DEEP, + HealthDataType.SLEEP_REM, + HealthDataType.WATER, + HealthDataType.EXERCISE_TIME, + HealthDataType.WORKOUT, + HealthDataType.HEADACHE_NOT_PRESENT, + HealthDataType.HEADACHE_MILD, + HealthDataType.HEADACHE_MODERATE, + HealthDataType.HEADACHE_SEVERE, + HealthDataType.HEADACHE_UNSPECIFIED, + HealthDataType.LEAN_BODY_MASS, + + // note that a phone cannot write these ECG-based types - only read them + // HealthDataType.ELECTROCARDIOGRAM, + // HealthDataType.HIGH_HEART_RATE_EVENT, + // HealthDataType.IRREGULAR_HEART_RATE_EVENT, + // HealthDataType.LOW_HEART_RATE_EVENT, + // HealthDataType.RESTING_HEART_RATE, + // HealthDataType.WALKING_HEART_RATE, + // HealthDataType.ATRIAL_FIBRILLATION_BURDEN, + + HealthDataType.NUTRITION, + HealthDataType.GENDER, + HealthDataType.BLOOD_TYPE, + HealthDataType.BIRTH_DATE, + HealthDataType.MENSTRUATION_FLOW, + HealthDataType.WATER_TEMPERATURE, + HealthDataType.UNDERWATER_DEPTH, + HealthDataType.UV_INDEX, +]; + +/// Data types available on Android via the Google Health Connect API. +const List dataTypesAndroid = [ + HealthDataType.ACTIVE_ENERGY_BURNED, + // HealthDataType.BASAL_ENERGY_BURNED, + // HealthDataType.BLOOD_GLUCOSE, + HealthDataType.BLOOD_OXYGEN, + // HealthDataType.BLOOD_PRESSURE_DIASTOLIC, + // HealthDataType.BLOOD_PRESSURE_SYSTOLIC, + // HealthDataType.BODY_FAT_PERCENTAGE, + // HealthDataType.HEIGHT, + // HealthDataType.WEIGHT, + // HealthDataType.LEAN_BODY_MASS, + // HealthDataType.BODY_MASS_INDEX, + // HealthDataType.BODY_TEMPERATURE, + HealthDataType.HEART_RATE, + // HealthDataType.HEART_RATE_VARIABILITY_RMSSD, + HealthDataType.STEPS, + HealthDataType.DISTANCE_DELTA, + // HealthDataType.SPEED, + HealthDataType.RESPIRATORY_RATE, + // HealthDataType.SLEEP_ASLEEP, + // HealthDataType.SLEEP_AWAKE_IN_BED, + // HealthDataType.SLEEP_AWAKE, + // HealthDataType.SLEEP_DEEP, + // HealthDataType.SLEEP_LIGHT, + // HealthDataType.SLEEP_OUT_OF_BED, + // HealthDataType.SLEEP_REM, + // HealthDataType.SLEEP_UNKNOWN, + // HealthDataType.SLEEP_SESSION, + // HealthDataType.WATER, + // HealthDataType.WORKOUT, + HealthDataType.RESTING_HEART_RATE, + // HealthDataType.FLIGHTS_CLIMBED, + // HealthDataType.NUTRITION, + HealthDataType.TOTAL_CALORIES_BURNED, + // HealthDataType.MENSTRUATION_FLOW, +]; \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 30714c6..409503e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; @@ -153,6 +154,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index a2b7475..2086c2a 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -66,7 +66,6 @@ class _AppointmentDetailsPageState extends State { scheduleMicrotask(() async { CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance; var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}"); - print("the appointment reminder exist $doesExist"); myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel); setState((){ @@ -78,7 +77,6 @@ class _AppointmentDetailsPageState extends State { @override Widget build(BuildContext context) { - AppState appState = getIt.get(); myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); @@ -386,7 +384,7 @@ class _AppointmentDetailsPageState extends State { label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.allergy_info_icon, + svgIcon: AppAssets.radiology_icon, isLargeText: true, iconSize: 36.w, ).onPress(() async { diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart index f48f1eb..d9e5832 100644 --- a/lib/presentation/appointments/appointment_queue_page.dart +++ b/lib/presentation/appointments/appointment_queue_page.dart @@ -42,7 +42,8 @@ class AppointmentQueuePage extends StatelessWidget { color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, - side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), + side: + BorderSide(color: myAppointmentsVM.isAppointmentQueueDetailsLoading ? AppColors.whiteColor : Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), ), child: Padding( padding: EdgeInsets.all(16.h), @@ -59,16 +60,19 @@ class AppointmentQueuePage extends StatelessWidget { ), Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), ], - ), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 10.h), - "Hala ${appState!.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + "Hala ${appState!.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 8.h), - "Thank you for your patience, here is your queue number.".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + "Thank you for your patience, here is your queue number." + .needTranslation + .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 8.h), - "IMD W-A-5".needTranslation.toText32(isBold: true), + myAppointmentsVM.currentPatientQueueDetails.queueNo!.toText32(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), SizedBox(height: 8.h), CustomButton( - text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus), + text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo!), onPressed: () {}, backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), @@ -80,15 +84,16 @@ class AppointmentQueuePage extends StatelessWidget { height: 40.h, iconColor: AppColors.whiteColor, iconSize: 18.h, - ), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), ], ), ), ), SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, + myAppointmentsVM.patientQueueDetailsList.isNotEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true, ), @@ -97,14 +102,14 @@ class AppointmentQueuePage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Serving Now".needTranslation.toText16(isBold: true), - SizedBox(height: 18.h), - ListView.separated( + "Serving Now".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + SizedBox(height: 18.h), + ListView.separated( padding: EdgeInsets.zero, shrinkWrap: true, - itemCount: 3, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (BuildContext context, int index) { + itemCount: myAppointmentsVM.patientQueueDetailsList.length, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (BuildContext context, int index) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -113,28 +118,32 @@ class AppointmentQueuePage extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - "Room: S2".toText12(fontWeight: FontWeight.w500), - SizedBox(width: 8.w), - AppCustomChipWidget( - deleteIcon: AppAssets.call_for_vitals, - labelText: "Call for vital signs".needTranslation, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - iconSize: 14.w, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), - ), + "Room: ${myAppointmentsVM.patientQueueDetailsList[index].roomNo}".toText12(fontWeight: FontWeight.w500), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: + myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, + iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + textColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 + ? AppColors.primaryRedColor.withValues(alpha: 0.1) + : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + ), ], ), ], ); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h), - ), - ], - ), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + ], + ), ), - ), + ) + : SizedBox.shrink(), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -174,7 +183,7 @@ class AppointmentQueuePage extends StatelessWidget { SizedBox(height: 16.h), ], - ), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), ), ), ], diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index f5ec31b..b4d327b 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart'; @@ -180,12 +181,14 @@ class AppointmentCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!).toShimmer2(isShow: isLoading), AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), icon: AppAssets.appointment_calendar_icon, labelText: isLoading ? 'Cardiology' : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", ).toShimmer2(isShow: isLoading), AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), isIconPNG: true, icon: getIt.get().getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, iconSize: 18.h, diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index f430447..cd43b17 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -149,6 +149,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { onSuccess: (apiResponse) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + myAppointmentsViewModel.getPatientAppointmentQueueDetails(appointmentNo: patientAppointmentHistoryResponseModel.appointmentNo, patientID: patientAppointmentHistoryResponseModel.patientID); Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 96d538b..405aa14 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -99,6 +99,7 @@ class AppointmentDoctorCard extends StatelessWidget { labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), icon: AppAssets.doctor_calendar_icon, labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), @@ -106,6 +107,7 @@ class AppointmentDoctorCard extends StatelessWidget { )}", ), AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 0e60a28..a27e76d 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -118,6 +118,7 @@ class _BookAppointmentPageState extends State { : myAppointmentsVM.patientMyDoctorsList.isEmpty ? SizedBox() : Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ if (appState.isAuthenticated) ...[], "Recent Visits".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), diff --git a/lib/presentation/book_appointment/laser/widgets/body_type_listing.dart b/lib/presentation/book_appointment/laser/widgets/body_type_listing.dart index d8fa736..46047ff 100644 --- a/lib/presentation/book_appointment/laser/widgets/body_type_listing.dart +++ b/lib/presentation/book_appointment/laser/widgets/body_type_listing.dart @@ -45,20 +45,22 @@ class BodyTypeListing extends StatelessWidget { ? AppColors.chipPrimaryRedBorderColor : AppColors.blackColor, iconSize: 16, - labelPadding: - EdgeInsetsDirectional.only(start: 8.h, end: 0.h), - padding: - EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.h), - deleteIconSize: Size(18.h, 18.h), - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(10), - smoothness: 10, + labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), + deleteIconSize: Size(18.w, 18.h), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + smoothness: 10, side: BorderSide( color: index == selectedBodyAreaIndex ? AppColors.chipPrimaryRedBorderColor : AppColors.borderGrayColor, width: 1), - )).onPress(() => onCategoryChanged(index)))) + )).onPress( + () => onCategoryChanged(index), + ), + ), + ) ], ), ); diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 85ccdac..1d4131a 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -120,6 +120,45 @@ class ServicesPage extends StatelessWidget { // ) ]; + late final List hmgHealthToolServices = [ + HmgServicesComponentModel( + 11, + "Daily Water Monitor".needTranslation, + "".needTranslation, + AppAssets.daily_water_monitor_icon, + bgColor: AppColors.whiteColor, + true, + route: AppRoutes.eReferralPage, + ), + HmgServicesComponentModel( + 11, + "Health\nCalculators".needTranslation, + "".needTranslation, + AppAssets.health_calculators_services_icon, + bgColor: AppColors.whiteColor, + true, + route: AppRoutes.eReferralPage, + ), + HmgServicesComponentModel( + 5, + "Health\nConverters".needTranslation, + "".needTranslation, + AppAssets.health_converters_icon, + bgColor: AppColors.whiteColor, + true, + route: AppRoutes.comprehensiveCheckupPage, + ), + HmgServicesComponentModel( + 11, + "Smart\nWatches".needTranslation, + "".needTranslation, + AppAssets.smartwatch_icon, + bgColor: AppColors.whiteColor, + true, + route: AppRoutes.smartWatches, + ), + ]; + @override Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); @@ -149,7 +188,7 @@ class ServicesPage extends StatelessWidget { itemCount: hmgServices.length, padding: EdgeInsets.zero, itemBuilder: (BuildContext context, int index) { - return ServiceGridViewItem(hmgServices[index], index, false); + return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false); }, ), SizedBox(height: 24.h), @@ -298,6 +337,29 @@ class ServicesPage extends StatelessWidget { ], ), SizedBox(height: 24.h), + "Health Tools".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 4 icons per row + crossAxisSpacing: 12.w, + mainAxisSpacing: 18.h, + childAspectRatio: 0.8, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgHealthToolServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem( + hmgHealthToolServices[index], + index, + false, + isHealthToolIcon: true, + ); + }, + ), + SizedBox(height: 24.h), "Support Services".needTranslation.toText18(isBold: true), SizedBox(height: 16.h), Column( diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index b24b0b4..3f0c44e 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -13,8 +13,10 @@ class ServiceGridViewItem extends StatelessWidget { final int index; final bool isHomePage; final bool isLocked; + final bool isHealthToolIcon; final Function? onTap; - const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false, this.onTap}); + + const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false, this.onTap, this.isHealthToolIcon = false}); @override Widget build(BuildContext context) { @@ -42,7 +44,7 @@ class ServiceGridViewItem extends StatelessWidget { padding: EdgeInsets.all(12.h), child: Utils.buildSvgWithAssets( icon: hmgServiceComponentModel.icon, - iconColor: AppColors.whiteColor, + iconColor: isHealthToolIcon ? null : AppColors.whiteColor, fit: BoxFit.contain, ), ), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index e6716cc..3e34a92 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -90,7 +90,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "radiology_results", - icon: AppAssets.home_lab_result_icon, + icon: AppAssets.my_radiology_icon, title: "Radiology", subtitle: "Results", backgroundColor: AppColors.whiteColor, @@ -120,7 +120,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "my_doctors", - icon: AppAssets.insurance_update_icon, + icon: AppAssets.my_doctors_icon, title: "My", subtitle: "Doctors", backgroundColor: AppColors.whiteColor, @@ -130,7 +130,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "sick_leaves", - icon: AppAssets.insurance_update_icon, + icon: AppAssets.my_sick_leave_icon, title: "Sick", subtitle: "Leaves", backgroundColor: AppColors.whiteColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 96ec558..1c1a77d 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; @@ -24,6 +25,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ 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'; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; @@ -39,11 +41,14 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; +import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; +import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/countdown_timer.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:provider/provider.dart'; @@ -91,359 +96,19 @@ class _LandingPageState extends State { if (appState.isAuthenticated) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); insuranceViewModel.initInsuranceProvider(); - immediateLiveCareViewModel.initImmediateLiveCare(); - immediateLiveCareViewModel.getPatientLiveCareHistory(); emergencyServicesViewModel.checkPatientERAdvanceBalance(); } }); super.initState(); } - Widget buildOptionsForAuthenticatedUser() { - return Column( - children: [ - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Appointments & Visits".toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); - }), - SizedBox(height: 16.h), - Consumer( - builder: (context, myAppointmentsVM, child) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: true, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : Swiper( - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, - layout: SwiperLayout.STACK, - loop: true, - itemWidth: MediaQuery.of(context).size.width - 48.h, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, - controller: _controller, - itemHeight: 200.h, - pagination: const SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 210 + 8 + 24), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), - ), - itemBuilder: (BuildContext context, int index) { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ); - }, - ) - : Container( - width: double.infinity, - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true), - SizedBox(height: 12.h), - 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.f, - fontWeight: FontWeight.w500, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - height: 46.h, - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h); - }, - ), - - // Consumer for LiveCare pending request - Consumer( - builder: (context, immediateLiveCareVM, child) { - return immediateLiveCareVM.patientHasPendingLiveCareRequest - ? Column( - children: [ - SizedBox(height: 12.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), - ), - width: double.infinity, - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus, - backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), - textColor: AppColors.alertColor, - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), - // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain), - ], - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "You have a pending LiveCare request".needTranslation.toText12(isBold: true), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 20.h, - height: 15.h, - fit: BoxFit.contain, - ), - ], - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); - }), - SizedBox(height: 12.h), - ], - ) - : SizedBox(height: 12.h); - }, - ), - - // Consumer for ER Online Check-In pending request - Consumer( - builder: (context, emergencyServicesVM, child) { - return emergencyServicesVM.patientHasAdvanceERBalance - ? Column( - children: [ - SizedBox(height: 4.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), - ), - width: double.infinity, - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: "ER Online Check-In Request", - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), - textColor: AppColors.primaryRedColor, - ), - Utils.buildSvgWithAssets( - icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), - ], - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 20.h, - height: 15.h, - fit: BoxFit.contain, - ), - ], - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); - // context.read().navigateToEROnlineCheckIn(); - }), - SizedBox(height: 12.h), - ], - ) - : SizedBox(height: 12.h); - }, - ), - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Quick Links".needTranslation.toText16(isBold: true), - Row( - children: [ - "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); - }), - SizedBox(height: 16.h), - Container( - height: 121.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - 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) => 10.width, - ), - ), - ], - ), - ).paddingSymmetrical(24.h, 0.h), - ], - ); - } - - Widget buildOptionsForNotAuthenticatedUser() { - return Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - 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( - serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, - 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, - ), - ), - ], - ), - ).paddingSymmetrical(24.h, 0.h); - } - @override Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); @@ -478,10 +143,19 @@ class _LandingPageState extends State { text: LocaleKeys.loginOrRegister.tr(context: context), onPressed: () async { await authVM.onLoginPressed(); + + // Navigator.pushReplacementNamed( + // // context, + // context, + // AppRoutes.zoomCallPage, + // // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), + // arguments: CallArguments("test123", "123", "Patient", "40", "0", true, 1), + // // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), + // ); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, fontSize: 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, @@ -492,8 +166,22 @@ class _LandingPageState extends State { mainAxisSize: MainAxisSize.min, spacing: 12.h, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {}), + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: MedicalFilePage(), + // // page: LoginScreen(), + // ), + // ); + }), Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { showCommonBottomSheetWithoutHeight( context, @@ -507,7 +195,451 @@ class _LandingPageState extends State { ), ], ).paddingSymmetrical(24.h, 0.h), - if (appState.isAuthenticated) buildOptionsForAuthenticatedUser() else buildOptionsForNotAuthenticatedUser(), + appState.isAuthenticated + ? Column( + children: [ + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Appointments & Visits".toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + }), + SizedBox(height: 16.h), + Consumer2( + builder: (context, myAppointmentsVM, immediateLiveCareVM, child) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : Swiper( + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 3, + layout: SwiperLayout.STACK, + loop: true, + itemWidth: MediaQuery.of(context).size.width - 48.h, + indicatorLayout: PageIndicatorLayout.COLOR, + axisDirection: AxisDirection.right, + controller: _controller, + itemHeight: 255.h, + pagination: SwiperPagination( + alignment: Alignment.bottomCenter, + margin: EdgeInsets.only(top: 240.h + 8 + 24), + builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + ), + itemBuilder: (BuildContext context, int index) { + return (myAppointmentsVM.isPatientHasQueueAppointment && index == 0) + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide(color: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: myAppointmentsVM.currentQueueStatus == 0 ? "In Queue".needTranslation : "Your Turn".needTranslation, + backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), + textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + ], + ), + SizedBox(height: 10.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + "Thank you for your patience, here is your queue number." + .needTranslation + .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + SizedBox(height: 8.h), + myAppointmentsVM.currentPatientQueueDetails.queueNo!.toText28(isBold: true), + SizedBox(height: 12.h), + myAppointmentsVM.patientQueueDetailsList.isNotEmpty ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + "Serving Now: ".needTranslation.toText14(isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + myAppointmentsVM.patientQueueDetailsList.first.queueNo!.toText12(isBold: true), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? "Call for vital signs".needTranslation : "Call for Doctor".needTranslation, + iconColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + textColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: myAppointmentsVM.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor.withValues(alpha: 0.1) : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + ), + ], + ), + ], + ) : SizedBox(height: 12.h), + SizedBox(height: 8.h), + CustomButton( + text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo!), + onPressed: () {}, + backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), + borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), + textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus), + fontSize: 12.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + ], + ), + ), + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: AppointmentQueuePage(), + ), + ); + }) + : (immediateLiveCareVM.patientHasPendingLiveCareRequest && index == 0) + ? Column( + children: [ + SizedBox(height: 12.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate LiveCare Request".needTranslation.toText16(isBold: true), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppCustomChipWidget( + labelText: immediateLiveCareVM.patientLiveCareHistoryList[0].stringCallStatus, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(immediateLiveCareVM.patientLiveCareHistoryList[0].arrivalTime), false)), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), + ], + ), + SizedBox(height: 10.h), + "Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + "Your turn is after ${immediateLiveCareVM.patientLiveCareHistoryList[0].patCount} patients.".toText14(isBold: true), + SizedBox(height: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Expected waiting time: ".toText12(isBold: true), + SizedBox(height: 8.h), + ValueListenableBuilder( + valueListenable: immediateLiveCareVM.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), + SizedBox(height: 12.h), + ], + ), + // CustomButton( + // text: "View Details".needTranslation, + // onPressed: () async { + // Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + // }, + // backgroundColor: Color(0xffFEE9EA), + // borderColor: Color(0xffFEE9EA), + // textColor: Color(0xffED1C2B), + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + // height: 40.h, + // ), + ], + ), + ), + ).paddingSymmetrical(0.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage())); + }), + SizedBox(height: 12.h), + ], + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: + myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index], + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ); + }, + ) + : Container( + width: double.infinity, + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "You do not have any upcoming appointment. Please book an appointment" + .needTranslation + .toText12(isCenter: true), + SizedBox(height: 12.h), + 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.f, + fontWeight: FontWeight.w500, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + height: 46.h, + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + }, + ), + + // Consumer for ER Online Check-In pending request + Consumer( + builder: (context, emergencyServicesVM, child) { + return emergencyServicesVM.patientHasAdvanceERBalance + ? Column( + children: [ + SizedBox(height: 4.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + ), + width: double.infinity, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: "ER Online Check-In Request", + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + textColor: AppColors.primaryRedColor, + ), + Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // context.read().navigateToEROnlineCheckIn(); + }), + SizedBox(height: 12.h), + ], + ) + : SizedBox(height: 12.h); + }, + ), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Quick Links".needTranslation.toText16(isBold: true), + Row( + children: [ + "View medical file".needTranslation.toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); + }), + SizedBox(height: 16.h), + Container( + height: 121.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + 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) => 10.width, + ), + ), + ], + ), + ).paddingSymmetrical(24.h, 0.h), + ], + ) + : Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + 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( + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, + 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, + ), + ), + ], + ), + ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -522,7 +654,7 @@ class _LandingPageState extends State { Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); }), ], - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.w, 0.h), SizedBox( height: 340.h, child: ListView.separated( @@ -569,15 +701,19 @@ class _LandingPageState extends State { isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - authVM.loginWithFingerPrintFace(() { + authVM.loginWithFingerPrintFace(() async { isDone = true; cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true); setState(() {}); + await Future.delayed(Duration(milliseconds: 2000)).then((value) { + if (mounted) Navigator.pop(context); + }); }); }, ); }, ), + // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, callBackFunc: () { isDone = true; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index f8d978e..7d149ed 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -191,20 +191,21 @@ class _MedicalFilePageState extends State { AppCustomChipWidget( icon: AppAssets.file_icon, labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", - labelPadding: EdgeInsetsDirectional.only(end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), onChipTap: () { navigationService.pushPage( page: FamilyMedicalScreen( profiles: medicalFileViewModel.patientFamilyFiles, onSelect: (FamilyFileResponseModelLists p1) {}, - )); + ), + ); }, ), AppCustomChipWidget( icon: AppAssets.checkmark_icon, labelText: LocaleKeys.verified.tr(context: context), iconColor: AppColors.successColor, - labelPadding: EdgeInsetsDirectional.only(end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), ], ), @@ -226,9 +227,9 @@ class _MedicalFilePageState extends State { ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}", + labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup}", iconColor: AppColors.primaryRedColor, - labelPadding: EdgeInsetsDirectional.only(end: 4.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), padding: EdgeInsets.zero, ), Consumer(builder: (context, insuranceVM, child) { @@ -239,7 +240,7 @@ class _MedicalFilePageState extends State { textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12.w, backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), - labelPadding: EdgeInsetsDirectional.only(end: 8.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ); }), ], @@ -298,9 +299,8 @@ class _MedicalFilePageState extends State { title: "Tracker & Others".toText18(weight: FontWeight.w600), expandedBackgroundColor: Colors.transparent, children: [ - Text("Blood Report"), - SizedBox(height: 8), - Text("X-Ray Report"), + SizedBox(height: 10.h), + getSelectedTabData(3), ], ), ], @@ -442,7 +442,7 @@ class _MedicalFilePageState extends State { ? Container( padding: EdgeInsets.all(12.w), width: MediaQuery.of(context).size.width, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), @@ -810,7 +810,7 @@ class _MedicalFilePageState extends State { GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 16.h, + crossAxisSpacing: 10.h, mainAxisSpacing: 16.w, mainAxisExtent: 115.h, ), @@ -927,7 +927,7 @@ class _MedicalFilePageState extends State { GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 16.h, + crossAxisSpacing: 10.h, mainAxisSpacing: 16.w, mainAxisExtent: 120.h, ), @@ -939,7 +939,7 @@ class _MedicalFilePageState extends State { label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, + svgIcon: AppAssets.update_insurance_icon, isLargeText: true, iconSize: 36.w, ).onPress(() { @@ -949,7 +949,7 @@ class _MedicalFilePageState extends State { label: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.approvals.tr(context: context)}", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, + svgIcon: AppAssets.insurance_approval_icon, isLargeText: true, iconSize: 36.w, ).onPress(() { @@ -963,7 +963,7 @@ class _MedicalFilePageState extends State { label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, + svgIcon: AppAssets.invoices_list_icon, isLargeText: true, iconSize: 36.w, ), @@ -971,7 +971,7 @@ class _MedicalFilePageState extends State { label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, + svgIcon: AppAssets.ancillary_orders_list_icon, isLargeText: true, iconSize: 36.w, ), @@ -1014,7 +1014,7 @@ class _MedicalFilePageState extends State { GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - crossAxisSpacing: 16.h, + crossAxisSpacing: 10.h, mainAxisSpacing: 16.w, mainAxisExtent: 110.h, ), @@ -1026,7 +1026,7 @@ class _MedicalFilePageState extends State { label: LocaleKeys.monthlyReports.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, + svgIcon: AppAssets.monthly_reports_icon, isLargeText: true, iconSize: 36.h, ), @@ -1034,7 +1034,7 @@ class _MedicalFilePageState extends State { label: "Medical Reports".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.allergy_info_icon, + svgIcon: AppAssets.medical_reports_icon, isLargeText: true, iconSize: 36.h, ).onPress(() { @@ -1050,7 +1050,7 @@ class _MedicalFilePageState extends State { label: "Sick Leave Report".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.vaccine_info_icon, + svgIcon: AppAssets.sick_leave_report_icon, isLargeText: true, iconSize: 36.h, ).onPress(() { @@ -1066,7 +1066,92 @@ class _MedicalFilePageState extends State { ], ); case 3: - return Container(); + // Trackers Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Health Trackers".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + ], + ), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Blood Sugar".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.blood_sugar_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() {}), + MedicalFileCard( + label: "Blood Pressure".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() {}), + MedicalFileCard( + label: "Weight Tracker".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.weight_tracker_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() {}), + ], + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 16.h), + Row( + children: [ + "Others".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.textColor), + ], + ), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Ask Your Doctor".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ask_doctor_medical_file_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() {}), + MedicalFileCard( + label: "Internet Pairing".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.internet_pairing_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() {}), + ], + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 24.h), + ], + ); default: return Container(); } diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index a28f518..709e4e3 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -47,7 +47,9 @@ class _PatientSickleavesListPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox(height: 24.h), ListView.separated( + padding: EdgeInsets.zero, scrollDirection: Axis.vertical, itemCount: medicalFileVM.isPatientSickLeaveListLoading ? 3 diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 6343e2a..77968c5 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -28,7 +28,7 @@ class MedicalFileCard extends StatelessWidget { return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: backgroundColor, - borderRadius: 12.r, + borderRadius: 20.r, hasShadow: false ), padding: EdgeInsets.all(12.w), diff --git a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart new file mode 100644 index 0000000..7ede201 --- /dev/null +++ b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart @@ -0,0 +1,422 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/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/smartwatch_health_data/health_provider.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/health_metric.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/health_metric_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; +import 'package:health/health.dart'; +import 'package:intl/intl.dart'; + +class HealthDashboard extends StatefulWidget { + const HealthDashboard({Key? key}) : super(key: key); + + @override + _HealthDashboardState createState() => _HealthDashboardState(); +} + +class _HealthDashboardState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + final dateFormat = DateFormat('MMM dd, yyyy'); + final timeFormat = DateFormat('hh:mm a'); + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + Future.microtask( + () async { + await Health().configure(); + context.read().fetchHealthData(); + }, + ); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer( + builder: (context, healthProvider, child) { + return SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 96.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Overview".needTranslation), + CustomTabBarModel(null, "Details".needTranslation), + ], + onTabChange: (index) { + healthProvider.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + getSelectedTabData(healthProvider.selectedTabIndex, healthProvider).paddingSymmetrical(24.w, 0), + ], + ), + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () => context.read().fetchHealthData(), + tooltip: 'Refresh health data', + child: Icon(Icons.refresh), + ), + ); + } + + Widget getSelectedTabData(int index, HealthProvider healthProvider) { + switch (index) { + case 0: + return _buildOverviewTab(healthProvider); + case 1: + return _buildDetailsTab(healthProvider); + default: + SizedBox.shrink(); + } + return Container(); + } + + Widget _buildOverviewTab(HealthProvider healthProvider) { + if (healthProvider.isLoading) { + return _buildLoadingState(); + } + + if (healthProvider.error != null) { + return _buildErrorState(healthProvider.error!); + } + return CustomScrollView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: _buildTimeRangeSelector(healthProvider), + ), + SliverPadding( + padding: EdgeInsets.symmetric(vertical: 12.w), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 0.62, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + final metric = HealthMetrics.metrics[index]; + return HealthMetricCard( + metric: metric, + data: healthProvider.healthData[metric.type] ?? [], + onTap: () => _showMetricDetails(context, metric, healthProvider), + ); + }, + childCount: HealthMetrics.metrics.length, + ), + ), + ), + ], + ); + } + + Widget _buildDetailsTab(HealthProvider healthProvider) { + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: HealthMetrics.metrics.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final metric = HealthMetrics.metrics[index]; + final data = healthProvider.healthData[metric.type] ?? []; + + return Card( + color: AppColors.whiteColor, + margin: EdgeInsets.only(bottom: 16), + child: ExpansionTile( + leading: Icon( + metric.icon, + color: metric.color, + size: 32.h, + ), + // title: Text(metric.name), + title: (getIt.get().isArabic() ? metric.nameAr : metric.nameEn).toText14(isBold: true), + subtitle: Text( + data.isEmpty ? LocaleKeys.noDataAvailable.tr(context: context) : '${_getValueAsDouble(data.last.value, metric.type).toStringAsFixed(1)} ${metric.unit}', + ), + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + metric.description.toText13(), + SizedBox(height: 16.h), + if (data.isNotEmpty) ...[ + Text( + 'History', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + SizedBox(height: 8), + ...data.reversed + .take(5) + .map((point) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + dateFormat.format(point.dateFrom), + style: TextStyle(color: Colors.grey[600]), + ), + Text( + '${_getValueAsDouble(point.value, metric.type).toStringAsFixed(1)} ${metric.unit}', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + )) + .toList(), + ], + ], + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildTimeRangeSelector(HealthProvider provider) { + return Container( + height: 40, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (final range in ['1D', '7D', '1M', '3M', '1Y']) + Padding( + padding: const EdgeInsets.only(right: 8), + child: ChoiceChip( + label: Text(range), + labelStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + checkmarkColor: AppColors.primaryRedColor, + selected: provider.selectedTimeRange == range, + selectedColor: AppColors.secondaryLightRedColor, + onSelected: (selected) { + if (selected) { + provider.updateTimeRange(range); + } + }, + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Loading health data...'), + ], + ), + ); + } + + Widget _buildErrorState(String error) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 48, + color: Colors.red, + ), + SizedBox(height: 16), + Text( + 'Error loading health data', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Text( + error, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[600]), + ), + SizedBox(height: 24), + ElevatedButton( + onPressed: () => context.read().fetchHealthData(), + child: Text('Try Again'), + ), + ], + ), + ); + } + + void _showMetricDetails( + BuildContext context, + HealthMetricInfo metric, + HealthProvider provider, + ) { + final data = provider.healthData[metric.type] ?? []; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) => DraggableScrollableSheet( + initialChildSize: 0.7, + minChildSize: 0.5, + maxChildSize: 0.95, + expand: false, + builder: (context, scrollController) => Container( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + ), + Row( + children: [ + Icon(metric.icon, color: metric.color, size: 30), + SizedBox(width: 16), + (getIt.get().isArabic() ? metric.nameAr : metric.nameEn).toText24(isBold: true), + // Text( + // metric.name, + // style: TextStyle( + // fontSize: 24, + // fontWeight: FontWeight.bold, + // ), + // ), + ], + ), + SizedBox(height: 16), + Text( + metric.description, + style: TextStyle( + color: Colors.grey[600], + fontSize: 16, + ), + ), + SizedBox(height: 24), + Text( + 'Healthy Range', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + '${metric.minHealthyValue} - ${metric.maxHealthyValue} ${metric.unit}'.toText16(isBold: true, color: AppColors.primaryRedColor), + // Text( + // '${metric.minHealthyValue} - ${metric.maxHealthyValue} ${metric.unit}', + // style: TextStyle( + // color: mainPurple, + // fontSize: 16, + // ), + // ), + SizedBox(height: 24), + if (data.isNotEmpty) ...[ + Text( + 'History', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: data.length, + itemBuilder: (context, index) { + final point = data[data.length - 1 - index]; + final value = _getValueAsDouble(point.value, metric.type); + return Card( + child: ListTile( + title: Text( + '${value.toStringAsFixed(1)} ${metric.unit}', + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + // dateFormat.format(point.dateFrom), + Utils.getMonthDayYearDateFormatted(point.dateFrom) + // dateFormat.format(point.dateFrom), + ), + trailing: Icon( + value >= metric.minHealthyValue && value <= metric.maxHealthyValue ? Icons.check_circle : Icons.warning, + color: value >= metric.minHealthyValue && value <= metric.maxHealthyValue ? AppColors.textColor : Colors.orange, + ), + ), + ); + }, + ), + ), + ] else + Center( + child: Text( + 'No data available', + style: TextStyle(color: Colors.grey[600]), + ), + ), + ], + ), + ), + ), + ); + } + + // Add this helper method to the _HealthDashboardState class + double _getValueAsDouble(HealthValue value, HealthDataType type) { + if (value is NumericHealthValue) { + // if(type == HealthDataType.BLOOD_OXYGEN) { + // return (value.numericValue.toDouble() * 100); + // } else { + return value.numericValue.toDouble(); + // } + } + return 0.0; + } +} diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart new file mode 100644 index 0000000..357f5f5 --- /dev/null +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -0,0 +1,464 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/health_dashboard/health_dashboard.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; + +class SmartwatchInstructionsPage extends StatelessWidget { + const SmartwatchInstructionsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.smartWatches.tr(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: SingleChildScrollView( + child: Platform.isIOS ? getIOSInstructionsUI(context) : getAndroidInstructionsUI(context), + ), + ), + ), + ); + } + + Widget getAndroidInstructionsUI(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Supported Smart Watches".needTranslation.toText20(isBold: true), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_ultra.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Watch Ultra", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Watch 8 Classic", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_8.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Watch 8", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_7_classic.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Watch 7 Classic", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_watch_7.webp", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Watch 7", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/galaxy_fit_3.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Galaxy Fit3", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + ), + SizedBox(height: 12), + "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.".needTranslation.toText14(isBold: true), + SizedBox(height: 12), + "Before syncing data, please make sure that you have followed the instructions properly.".needTranslation.toText14(isBold: true), + SizedBox(height: 12), + InkWell( + onTap: () { + showInstructionsDialog(context); + }, + child: "View watch instructions".needTranslation.toText12(isBold: true, color: AppColors.textColor, isUnderLine: true)), + SizedBox( + height: 130.h, + ), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async { + await Health().getHealthConnectSdkStatus().then((val) { + print('Health Connect SDK Status: $val'); + if (val == HealthConnectSdkStatus.sdkAvailable) { + // Navigator.of(context).push( + // FadePage( + // page: HealthDashboard(), // Replace with the actual vital signs page + // // page: HealthApp(), // Replace with the actual vital signs page + // ), + // ); + } else { + // Utils.showAppDialog(context, LocaleKeys.error.tr(context: context), LocaleKeys.healthConnectNotInstalled.tr(), () { + // Navigator.pop(context); + // Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); + // launchUrl(uri, mode: LaunchMode.externalApplication); + // }); + // return; + } + }); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + // DefaultButton( + // LocaleKeys.confirm.tr(context: context), + // () async { + // await Health().getHealthConnectSdkStatus().then((val) { + // print('Health Connect SDK Status: $val'); + // if (val == HealthConnectSdkStatus.sdkAvailable) { + // Navigator.of(context).push( + // FadePage( + // page: HealthDashboard(), // Replace with the actual vital signs page + // // page: HealthApp(), // Replace with the actual vital signs page + // ), + // ); + // } else { + // Utils.showAppDialog(context, LocaleKeys.error.tr(context: context), LocaleKeys.healthConnectNotInstalled.tr(), () { + // Navigator.pop(context); + // Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata"); + // launchUrl(uri, mode: LaunchMode.externalApplication); + // }); + // return; + // } + // }); + // }, + // ) + ], + ); + } + + Widget getIOSInstructionsUI(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Supported Smart Watches".needTranslation.toText20(isBold: true), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-1.jpeg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 5", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-2.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 6", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-3.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 7", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-4.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 8", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 9", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 15.0), + child: Row( + children: [ + Image.asset("assets/images/png/smartwatches/Apple-Watch-6.png", width: 70.0, height: 70.0), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Container( + width: MediaQuery.of(context).size.width * 0.21, + child: Text("Apple Watch Series 10", overflow: TextOverflow.clip, style: TextStyle(fontSize: 12.0)), + ), + ) + ], + ), + ), + ), + ], + ), + ), + SizedBox(height: 12), + "Please make sure that your Apple Watch is connected to your iPhone, is actively synced & updated.".toText14(isBold: true), + SizedBox(height: 12), + "Before syncing data, please make sure that you have followed the instructions properly.".toText14(isBold: true), + SizedBox(height: 12), + InkWell( + onTap: () { + showInstructionsDialog(context); + }, + child: "View watch instructions".toText12(isBold: true, color: AppColors.textColor, isUnderLine: true), + ), + SizedBox( + height: 130.h, + ), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async { + Navigator.of(context).push( + CustomPageRoute( + page: HealthDashboard(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ), + ], + ); + } + + showInstructionsDialog(BuildContext context) { + showGeneralDialog( + barrierColor: Colors.black.withOpacity(0.5), + transitionBuilder: (context, a1, a2, widget) { + final curvedValue = Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: Matrix4.translationValues(0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: Dialog( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 350.0, + padding: EdgeInsets.all(21), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "SmartWatch Tracker".toText18(), + IconButton( + icon: Icon( + Icons.close, + color: Color(0xff2E303A), + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (Platform.isIOS + ? "Make sure that you have installed 'Health' App & 'Watch' App from Apple Store." + : "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.") + .needTranslation + .toText14(), + SizedBox(height: 12), + ], + ) + ], + ), + ), + ], + ), + ), + ), + ); + }, + transitionDuration: Duration(milliseconds: 500), + barrierDismissible: true, + barrierLabel: '', + context: context, + pageBuilder: (context, animation1, animation2) { + return SizedBox(); + //Chanbged By Aamir + }); + } +} diff --git a/lib/presentation/smartwatches/widgets/health_chart.dart b/lib/presentation/smartwatches/widgets/health_chart.dart new file mode 100644 index 0000000..fe6ae78 --- /dev/null +++ b/lib/presentation/smartwatches/widgets/health_chart.dart @@ -0,0 +1,87 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class HealthChart extends StatelessWidget { + final List spots; + final String title; + final Color gradientColor; + + const HealthChart({ + Key? key, + required this.spots, + required this.title, + required this.gradientColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 200, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Expanded( + child: LineChart( + LineChartData( + gridData: FlGridData(show: false), + titlesData: FlTitlesData(show: false), + borderData: FlBorderData(show: false), + minX: 0, + maxX: spots.length.toDouble() - 1, + minY: spots.map((e) => e.y).reduce((a, b) => a < b ? a : b), + maxY: spots.map((e) => e.y).reduce((a, b) => a > b ? a : b), + lineBarsData: [ + LineChartBarData( + spots: spots, + isCurved: true, + gradient: LinearGradient( + colors: [ + gradientColor.withOpacity(0.5), + gradientColor, + ], + ), + barWidth: 3, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + gradientColor.withOpacity(0.1), + gradientColor.withOpacity(0.2), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/smartwatches/widgets/health_metric.dart b/lib/presentation/smartwatches/widgets/health_metric.dart new file mode 100644 index 0000000..13e65ad --- /dev/null +++ b/lib/presentation/smartwatches/widgets/health_metric.dart @@ -0,0 +1,97 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:health/health.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/theme/colors.dart'; + +class HealthMetricInfo { + final HealthDataType type; + final String nameEn; + final String nameAr; + final String unit; + final Color color; + final IconData icon; + final String description; + final double minHealthyValue; + final double maxHealthyValue; + final String svgIcon; + + const HealthMetricInfo( + {required this.type, + required this.nameEn, + required this.nameAr, + required this.unit, + required this.color, + required this.icon, + required this.description, + required this.minHealthyValue, + required this.maxHealthyValue, + required this.svgIcon}); +} + +class HealthMetrics { + static final metrics = [ + HealthMetricInfo( + type: HealthDataType.HEART_RATE, + nameEn: "Heart Rate", + nameAr: "معدل النبض", + unit: 'BPM', + color: AppColors.primaryRedColor, + icon: Icons.favorite, + description: "Your heart rate indicates how many times your heart beats per minute".needTranslation, + minHealthyValue: 60, + maxHealthyValue: 100, + svgIcon: "assets/images/smartwatches/heartrate_icon.svg"), + HealthMetricInfo( + type: HealthDataType.BLOOD_OXYGEN, + nameEn: "Blood Oxygen", + nameAr: "أكسجين الدم", + unit: '%', + // color: Colors.blue, + color: Color(0xff3A3558), + icon: Icons.air, + description: "Blood oxygen level indicates how much oxygen your red blood cells are carrying".needTranslation, + minHealthyValue: 95, + maxHealthyValue: 100, + svgIcon: "assets/images/smartwatches/bloodoxygen_icon.svg"), + HealthMetricInfo( + type: HealthDataType.STEPS, + nameEn: "Steps", + nameAr: "خطوات", + unit: 'steps', + // color: Colors.green, + color: Color(0xff3263B8), + icon: Icons.directions_walk, + description: "Number of steps taken throughout the day".needTranslation, + minHealthyValue: 7000, + maxHealthyValue: 15000, + svgIcon: "assets/images/smartwatches/steps_icon.svg"), + HealthMetricInfo( + type: Platform.isIOS ? HealthDataType.ACTIVE_ENERGY_BURNED : HealthDataType.TOTAL_CALORIES_BURNED, + nameEn: "Active Calories", + nameAr: "السعرات الحرارية النشطة", + unit: 'kcal', + color: Color(0xffD59E95), + icon: Icons.local_fire_department, + description: "Calories burned during physical activity".needTranslation, + minHealthyValue: 300, + maxHealthyValue: 1000, + svgIcon: "assets/images/smartwatches/calories_icon.svg"), + HealthMetricInfo( + type: Platform.isIOS ? HealthDataType.DISTANCE_WALKING_RUNNING : HealthDataType.DISTANCE_DELTA, + nameEn: "Distance Covered", + nameAr: "المسافة المغطاة", + unit: 'KMs', + // color: mainPurple, + color: Color(0xff6A46F5), + icon: Icons.directions_run, + description: "Distance covered throughout the day".needTranslation, + minHealthyValue: 3, + maxHealthyValue: 10, + svgIcon: "assets/images/smartwatches/distance_icon.svg"), + // Add more metrics as needed + ]; +} diff --git a/lib/presentation/smartwatches/widgets/health_metric_card.dart b/lib/presentation/smartwatches/widgets/health_metric_card.dart new file mode 100644 index 0000000..023f41b --- /dev/null +++ b/lib/presentation/smartwatches/widgets/health_metric_card.dart @@ -0,0 +1,151 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:health/health.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/health_metric.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class HealthMetricCard extends StatelessWidget { + final HealthMetricInfo metric; + final List data; + final VoidCallback onTap; + + const HealthMetricCard({ + Key? key, + required this.metric, + required this.data, + required this.onTap, + }) : super(key: key); + + // Helper method to convert HealthValue to double + double _getValueAsDouble(HealthValue value, HealthDataType type) { + if (value is NumericHealthValue) { + return value.numericValue.toDouble(); + } + return 0.0; + } + + @override + Widget build(BuildContext context) { + final latestValue = data.isNotEmpty ? _getValueAsDouble(data.last.value, data.last.type) : 0.0; + final isHealthy = latestValue >= metric.minHealthyValue && latestValue <= metric.maxHealthyValue; + + return Card( + color: AppColors.whiteColor, + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + metric.svgIcon, + height: 30.h, + width: 30.h, + fit: BoxFit.contain, + // color: color, + ), + // Icon( + // metric.icon, + // color: metric.color, + // size: 30, + // ), + if (data.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: isHealthy ? Colors.green[100] : Color(0xffD7D2F3), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + isHealthy ? "Healthy".needTranslation : 'Warning'.needTranslation, + style: TextStyle( + color: isHealthy ? Colors.green[700] : AppColors.blackColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + (getIt.get().isArabic() ? metric.nameAr : metric.nameEn).toText18(isBold: true, maxlines: 1), + // Text( + // metric.name, + // style: const TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // ), + // ), + const SizedBox(height: 8), + Text( + data.isEmpty ? LocaleKeys.noDataAvailable.tr(context: context) : '${latestValue.toStringAsFixed(metric.type == HealthDataType.STEPS ? 0 : 1)} ${metric.unit}', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: metric.color, + ), + ), + if (data.isNotEmpty) ...[ + const SizedBox(height: 16), + SizedBox( + height: 100, + child: LineChart( + LineChartData( + gridData: FlGridData(show: false), + titlesData: FlTitlesData(show: false), + borderData: FlBorderData(show: false), + lineBarsData: [ + LineChartBarData( + spots: _convertToSpots(data), + isCurved: true, + color: metric.color, + barWidth: 3, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: metric.color.withOpacity(0.1), + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + ), + ); + } + + List _convertToSpots(List data) { + if (data.isEmpty) return []; + + return List.generate( + data.length, + (index) => FlSpot( + index.toDouble(), + _getValueAsDouble(data[index].value, data[index].type), + ), + ); + } +} diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index ad204fa..fdfcc04 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -180,7 +180,6 @@ class _AncillaryOrderPaymentPageState extends State { ), ), ), - // Payment Summary Footer todoVM.isProcessingPayment ? SizedBox.shrink() : _buildPaymentSummary() ], @@ -220,7 +219,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ "Amount before tax".needTranslation.toText14(isBold: true), Utils.getPaymentAmountWithSymbol( - amountBeforeTax.toString().toText16(isBold: true), + amountBeforeTax.toStringAsFixed(2).toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true, @@ -234,7 +233,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( - taxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), + taxAmount.toStringAsFixed(2).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true, @@ -250,7 +249,7 @@ class _AncillaryOrderPaymentPageState extends State { children: [ "".needTranslation.toText14(isBold: true), Utils.getPaymentAmountWithSymbol( - widget.totalAmount.toString().toText24(isBold: true), + widget.totalAmount.toStringAsFixed(2).toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true, diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index 940c8eb..2755ac0 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; +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'; @@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_order_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -130,7 +132,6 @@ class _AncillaryOrderDetailsListState extends State { if (viewModel.patientAncillaryOrderProceduresList.isNotEmpty) { orderData = viewModel.patientAncillaryOrderProceduresList[0]; } - return Column( children: [ Expanded( @@ -618,39 +619,102 @@ class _AncillaryOrderDetailsListState extends State { Widget _buildStickyPaymentButton(orderData) { final isButtonEnabled = selectedProcedures.isNotEmpty; - return Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(height: 16.h), - _buildSummarySection(orderData), - SizedBox(height: 16.h), - CustomButton( - borderWidth: 0, - backgroundColor: AppColors.infoLightColor, - text: "Proceed to Payment".needTranslation, - onPressed: () { - // Navigate to payment page with selected procedures - Navigator.of(context).push( - CustomPageRoute( - page: AncillaryOrderPaymentPage( - appointmentNoVida: widget.appointmentNoVida, - orderNo: widget.orderNo, - projectID: widget.projectID, - selectedProcedures: selectedProcedures, - totalAmount: _getTotalAmount(), - appointmentDate: orderData.appointmentDate, - ), + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + ], + ), + SizedBox(height: 18.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 150.h, + child: Utils.getPaymentMethods(), ), - ); - }, - isDisabled: !isButtonEnabled, - textColor: AppColors.whiteColor, - borderRadius: 12.r, - borderColor: Colors.transparent, - padding: EdgeInsets.symmetric(vertical: 16.h), - ), - SizedBox(height: 22.h), - ], - ).paddingSymmetrical(24.w, 0); + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Utils.getPaymentAmountWithSymbol(_getTotalAmount().toStringAsFixed(2).toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), + ], + ), + ], + ), + SizedBox(height: 16.h), + CustomButton( + borderWidth: 0, + backgroundColor: AppColors.infoLightColor, + text: "Proceed to Payment".needTranslation, + onPressed: () { + // Navigate to payment page with selected procedures + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderPaymentPage( + appointmentNoVida: widget.appointmentNoVida, + orderNo: widget.orderNo, + projectID: widget.projectID, + selectedProcedures: selectedProcedures, + totalAmount: _getTotalAmount(), + appointmentDate: orderData.appointmentDate, + ), + ), + ); + }, + isDisabled: !isButtonEnabled, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 16.h), + ), + SizedBox(height: 22.h), + ], + ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), + ); + + // Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // SizedBox(height: 16.h), + // _buildSummarySection(orderData), + // SizedBox(height: 16.h), + // CustomButton( + // borderWidth: 0, + // backgroundColor: AppColors.infoLightColor, + // text: "Proceed to Payment".needTranslation, + // onPressed: () { + // // Navigate to payment page with selected procedures + // Navigator.of(context).push( + // CustomPageRoute( + // page: AncillaryOrderPaymentPage( + // appointmentNoVida: widget.appointmentNoVida, + // orderNo: widget.orderNo, + // projectID: widget.projectID, + // selectedProcedures: selectedProcedures, + // totalAmount: _getTotalAmount(), + // appointmentDate: orderData.appointmentDate, + // ), + // ), + // ); + // }, + // isDisabled: !isButtonEnabled, + // textColor: AppColors.whiteColor, + // borderRadius: 12.r, + // borderColor: Colors.transparent, + // padding: EdgeInsets.symmetric(vertical: 16.h), + // ), + // SizedBox(height: 22.h), + // ], + // ).paddingSymmetrical(24.w, 0); } } diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index 11e258f..8907fc8 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -68,6 +68,7 @@ class _ToDoPageState extends State { children: [ SizedBox(height: 16.h), "Ancillary Orders".needTranslation.toText18(isBold: true), + SizedBox(height: 24.h), Consumer( builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) { return todoSectionViewModel.isAncillaryOrdersLoading @@ -82,8 +83,9 @@ class _ToDoPageState extends State { orderNo: order.orderNo ?? 0, projectID: order.projectID ?? 0, projectName: order.projectName ?? "", - ))); - log("View details for order: ${order.orderNo}"); + ), + ), + ); }, ); }, diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index 31a778f..8a3e3fa 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -35,10 +35,10 @@ class AncillaryOrdersList extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: orders.length, + padding: EdgeInsets.zero, separatorBuilder: (BuildContext context, int index) => SizedBox(height: 12.h), itemBuilder: (context, index) { final order = orders[index]; - return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 183c2a0..f356310 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart' import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_instructions_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/risk_factors_screen.dart'; @@ -32,6 +33,7 @@ class AppRoutes { static const String homeHealthCarePage = '/homeHealthCarePage'; static const String zoomCallPage = '/zoomCallPage'; static const String bloodDonationPage = '/bloodDonationPage'; + static const String smartWatches = '/smartWatches'; //appointments static const String bookAppointmentPage = '/bookAppointmentPage'; @@ -69,6 +71,7 @@ class AppRoutes { bookAppointmentPage: (context) => BookAppointmentPage(), userInfoSelection: (context) => UserInfoSelectionScreen(), userInfoFlowManager: (context) => UserInfoFlowManager(), + smartWatches: (context) => SmartwatchInstructionsPage(), // }; diff --git a/pubspec.yaml b/pubspec.yaml index c4fa97b..ecfc5ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -114,6 +114,7 @@ flutter: - assets/images/svg/ - assets/images/png/ - assets/images/png/zoom/ + - assets/images/png/smartwatches/ - assets/animations/ - assets/animations/lottie/