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