* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New:
  Added 'send report to email' feature
  Completed the health Tracker Module, Only the email part is left.
  only explain api left.
  Triage Completed. Explain API left
  Till Triage Completed
  refactoring

# Conflicts:
#	lib/presentation/medical_file/medical_file_page.dart
pull/151/head
Sultan khan 2 months ago
commit 0c68ca4438

@ -0,0 +1,4 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 10.5523 18.4477 11 19 11C19.5523 11 20 10.5523 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 10.5523 0.447715 11 1 11C1.55228 11 2 10.5523 2 10Z" fill="white"/>
<path d="M10 6C10.5523 6 11 6.44772 11 7L11 12.1707C12.1652 12.5825 13 13.6938 13 15C13 16.6569 11.6569 18 10 18C8.34315 18 7 16.6569 7 15C7 13.6938 7.83481 12.5825 9 12.1707L9 7C9 6.44772 9.44772 6 10 6Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 568 B

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 6.75C4 3.02208 7.02208 0 10.75 0C14.4779 0 17.5 3.02208 17.5 6.75C17.5 6.81385 17.5 6.84578 17.4946 6.87394C17.4654 7.02516 17.324 7.13066 17.1708 7.11566C17.1422 7.11287 17.1041 7.10157 17.028 7.07899C16.306 6.86492 15.5414 6.75 14.75 6.75C11.4211 6.75 8.56706 8.78325 7.36206 11.6756C7.21403 12.031 7.14001 12.2086 6.99327 12.2449C6.84654 12.2812 6.71484 12.1723 6.45145 11.9546L6.45144 11.9546C4.95411 10.7165 4 8.84466 4 6.75ZM8 14.75C8 11.0221 11.0221 8 14.75 8C18.4779 8 21.5 11.0221 21.5 14.75C21.5 18.4779 18.4779 21.5 14.75 21.5C11.0221 21.5 8 18.4779 8 14.75ZM6.77538 14.11C6.75857 14.3217 6.75 14.5358 6.75 14.7519C6.75 16.87 7.57313 18.7958 8.91695 20.227L8.91696 20.227C9.22203 20.5519 9.37457 20.7143 9.33165 20.8754C9.28873 21.0365 9.09982 21.0941 8.72199 21.2093C8.09819 21.3996 7.43605 21.5019 6.75 21.5019C3.02208 21.5019 0 18.4798 0 14.7519C0 12.6572 0.954182 10.7853 2.45161 9.54723C2.715 9.32946 2.8467 9.22057 2.99343 9.25688C3.14016 9.29319 3.21416 9.4708 3.36215 9.82601C3.9893 11.3312 5.0627 12.6033 6.41512 13.4766L6.4968 13.5294C6.64871 13.6275 6.72467 13.6765 6.76134 13.7507C6.79802 13.8249 6.79047 13.9199 6.77538 14.11Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.0625 16.125C3.6097 16.125 0 12.5153 0 8.0625C0 3.6097 3.6097 0 8.0625 0C12.5153 0 16.125 3.6097 16.125 8.0625C16.125 12.5153 12.5153 16.125 8.0625 16.125ZM8.0625 4.3125C8.47671 4.3125 8.8125 4.64829 8.8125 5.0625V8.43749H8.93401C9.15181 8.43742 9.38226 8.43734 9.56413 8.45912C9.69673 8.47499 10.1846 8.53481 10.4097 8.99153C10.635 9.4486 10.3763 9.85561 10.3061 9.96553C10.2094 10.1169 10.0646 10.2914 9.92764 10.4564L9.90413 10.4848C9.68853 10.7449 9.43178 11.0409 9.1743 11.2764C9.04572 11.394 8.89699 11.5151 8.73553 11.6109C8.58584 11.6996 8.34945 11.8125 8.0625 11.8125C7.77556 11.8125 7.53916 11.6996 7.38947 11.6109C7.22801 11.5151 7.07929 11.394 6.95071 11.2764C6.69322 11.0409 6.43647 10.7449 6.22087 10.4848L6.19736 10.4564C6.06041 10.2914 5.91565 10.1169 5.81894 9.96552C5.74871 9.85561 5.49001 9.4486 5.71529 8.99153C5.94038 8.53481 6.42827 8.47499 6.56087 8.45912C6.74274 8.43734 6.9732 8.43742 7.191 8.43749H7.3125L7.3125 5.0625C7.3125 4.64829 7.64829 4.3125 8.0625 4.3125Z" fill="#FF9E15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="20" height="3" rx="1.5" fill="#18C273"/>
<rect y="4" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.7"/>
<rect y="8" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.5"/>
<rect y="12" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.35"/>
<rect y="16" width="20" height="3" rx="1.5" fill="#18C273" fill-opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 476 B

@ -0,0 +1,3 @@
<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.52965 0.0727261C9.98692 -0.024143 11.5101 -0.024341 12.9704 0.0727261C17.537 0.376286 21.1603 4.06258 21.4582 8.67235C21.5139 9.53477 21.5139 10.4268 21.4582 11.2892C21.1603 15.899 17.537 19.5853 12.9704 19.8889C11.5101 19.9859 9.98692 19.9857 8.52965 19.8889C7.96472 19.8513 7.34978 19.7177 6.80839 19.4948C6.5703 19.3967 6.40868 19.3304 6.29041 19.287C6.20909 19.3429 6.10108 19.4223 5.9438 19.5383C5.15136 20.1226 4.15092 20.5325 2.73117 20.498L2.68544 20.4969C2.41155 20.4903 2.11961 20.4834 1.88152 20.4373C1.59475 20.3818 1.23996 20.2431 1.01791 19.8645C0.776229 19.4525 0.873128 19.0358 0.966883 18.7734C1.05536 18.5257 1.20874 18.2352 1.36542 17.9385L1.3869 17.8978C1.85323 17.0141 1.98314 16.2919 1.73381 15.8104C0.901479 14.554 0.152725 13.0056 0.0418013 11.2892C-0.0139338 10.4268 -0.0139338 9.53477 0.0418013 8.67235C0.339716 4.06258 3.963 0.376286 8.52965 0.0727261ZM6.5 7.75C6.5 8.16421 6.83579 8.5 7.25 8.5H10.75C11.1642 8.5 11.5 8.16421 11.5 7.75C11.5 7.33579 11.1642 7 10.75 7L7.25 7C6.83579 7 6.5 7.33579 6.5 7.75ZM6.5 12.75C6.5 13.1642 6.83579 13.5 7.25 13.5L14.25 13.5C14.6642 13.5 15 13.1642 15 12.75C15 12.3358 14.6642 12 14.25 12L7.25 12C6.83579 12 6.5 12.3358 6.5 12.75Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,3 @@
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.39392 1.28104e-06L11.1061 1.28104e-06C12.009 -2.95155e-05 12.7711 -5.55068e-05 13.3749 0.0704231C14.005 0.143956 14.6207 0.308473 15.1206 0.759151C15.3458 0.962221 15.5373 1.19899 15.6876 1.46113C15.9826 1.97573 16.0312 2.52334 15.9847 3.06609C16.9603 3.1787 17.7696 3.43604 18.4021 4.06966C19.0038 4.67238 19.2625 5.43113 19.3835 6.33241C19.5 7.2007 19.5 8.30477 19.5 9.67486V14.793C19.5 16.1631 19.5 17.2672 19.3835 18.1355C19.2625 19.0367 19.0038 19.7955 18.4021 20.3982C17.8002 21.0011 17.0423 21.2605 16.1419 21.3818C15.2749 21.4986 14.1725 21.4986 12.805 21.4985L6.69504 21.4985C5.32752 21.4986 4.22513 21.4986 3.35808 21.3818C2.45774 21.2605 1.69976 21.0011 1.0979 20.3982C0.496209 19.7955 0.237469 19.0367 0.116503 18.1355C-3.37213e-05 17.2672 -1.84605e-05 16.1631 4.81163e-07 14.7931L4.81163e-07 9.67484C-1.84605e-05 8.30478 -3.37213e-05 7.20069 0.116503 6.33241C0.237469 5.43113 0.496209 4.67238 1.0979 4.06966C1.73043 3.43604 2.53975 3.1787 3.51534 3.06608C3.46879 2.52334 3.51736 1.97573 3.81239 1.46113C3.96268 1.19899 4.15422 0.962221 4.37944 0.759151C4.8793 0.308473 5.49503 0.143956 6.12506 0.0704231C6.72893 -5.54994e-05 7.49097 -2.95155e-05 8.39392 1.28104e-06ZM6.35067 1.9573C5.88641 2.01149 5.74252 2.10241 5.67575 2.1626C5.5993 2.23154 5.53534 2.31095 5.48568 2.39756C5.44637 2.46613 5.39027 2.6108 5.44746 3.05137C5.50673 3.50797 5.66092 4.11081 5.89823 5.02796C6.08725 5.75848 6.21246 6.23783 6.34555 6.59684C6.47171 6.93716 6.57647 7.08584 6.6787 7.18308C6.75978 7.26021 6.85016 7.32847 6.9482 7.38631C7.07461 7.46091 7.2556 7.52465 7.63389 7.56094C7.88784 7.58531 8.19058 7.59448 8.58009 7.59793L9.30138 5.43404C9.47603 4.9101 10.0424 4.62694 10.5663 4.80159C11.0902 4.97624 11.3734 5.54256 11.1988 6.0665L10.6878 7.59935C11.1924 7.59743 11.5638 7.58994 11.8661 7.56094C12.2444 7.52465 12.4254 7.46091 12.5518 7.38631C12.6498 7.32847 12.7402 7.26021 12.8213 7.18308C12.9235 7.08584 13.0283 6.93716 13.1545 6.59684C13.2875 6.23783 13.4128 5.75848 13.6018 5.02796C13.8391 4.11081 13.9933 3.50797 14.0525 3.05137C14.1097 2.6108 14.0536 2.46613 14.0143 2.39756C13.9647 2.31095 13.9007 2.23153 13.8242 2.1626C13.7575 2.10241 13.6136 2.01149 13.1493 1.9573C12.6718 1.90157 12.0247 1.9 11.0479 1.9L8.45213 1.9C7.47532 1.9 6.82816 1.90157 6.35067 1.9573ZM7.75 15.75C7.19772 15.75 6.75 16.1977 6.75 16.75C6.75 17.3023 7.19772 17.75 7.75 17.75H11.75C12.3023 17.75 12.75 17.3023 12.75 16.75C12.75 16.1977 12.3023 15.75 11.75 15.75L7.75 15.75Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient {
final int statusCode = response.statusCode;
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
// log("response.body: ${response.body}");
log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));
logApiEndpointError(endPoint, 'Error While Fetching data', statusCode);

@ -398,19 +398,6 @@ var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_Get
var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord';
var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions';
///My Trackers
var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage';
var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults';
var ADD_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_AddDiabtecResult';
var GET_BLOOD_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage';
var GET_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult';
var ADD_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult';
var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage';
var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult';
var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult';
var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID';
var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult';
@ -420,7 +407,6 @@ var GET_QUESTION_TYPES = 'Services/OUTPs.svc/REST/getQuestionsTypes';
var UPDATE_DIABETIC_RESULT = 'Services/Patients.svc/REST/Patient_UpdateDiabeticResult';
var SEND_AVERAGE_BLOOD_SUGAR_REPORT = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport';
var DEACTIVATE_DIABETIC_STATUS = 'services/Patients.svc/REST/Patient_DeactivateDiabeticStatus';
var DEACTIVATE_BLOOD_PRESSURES_STATUS = 'services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus';
@ -825,11 +811,12 @@ class ApiConsts {
static final String updateHHCOrder = 'api/hhc/update';
static final String addHHCOrder = 'api/HHC/add';
// SYMPTOMS CHECKER
// SYMPTOMS CHECKER API
static final String symptomsUserLogin = '$symptomsCheckerApi/user_login';
static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName';
static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors';
static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion';
static final String diagnosis = '$symptomsCheckerApi/diagnosis';
static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis';
static final String explain = '$symptomsCheckerApi/explain';
//E-REFERRAL SERVICES
@ -848,6 +835,31 @@ class ApiConsts {
static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
// HEALTH TRACKERS
// Blood Sugar (Diabetic)
static String getDiabeticResultAverage = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage';
static String getDiabeticResult = 'Services/Patients.svc/REST/Patient_GetDiabtecResults';
static String addDiabeticResult = 'Services/Patients.svc/REST/Patient_AddDiabtecResult';
static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult';
static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus';
static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport';
// Blood Pressure
static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage';
static String getBloodPressureResult = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult';
static String addBloodPressureResult = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult';
static String updateBloodPressureResult = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult';
static String deactivateBloodPressureStatus = 'Services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus';
static String sendAverageBloodPressureReport = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport';
// Weight Measurement
static String getWeightMeasurementResultAverage = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage';
static String getWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult';
static String addWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult';
static String updateWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult';
static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus';
static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport';
//Blood Donation
static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType";
static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation";

@ -270,16 +270,21 @@ class AppAssets {
static const String yellowArrowDownIcon = '$svgBasePath/yellow_arrow_down_icon.svg';
static const String greenTickIcon = '$svgBasePath/green_tick_icon.svg';
// PNGS
static const String bloodSugar = '$svgBasePath/bloodsugar.svg';
static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg';
static const String triglycerides = '$svgBasePath/triglycerides.svg';
static const String bulb = '$svgBasePath/bulb.svg';
static const String switchBtn = '$svgBasePath/switch.svg';
//Health Trackers
static const String bloodPressureIcon = '$svgBasePath/blood_pressure_icon.svg';
static const String bloodSugarOnlyIcon = '$svgBasePath/blood_sugar_only_icon.svg';
static const String weightIcon = '$svgBasePath/weight_icon.svg';
static const String normalStatusGreenIcon = '$svgBasePath/normal_status_green_icon.svg';
static const String sendEmailIcon = '$svgBasePath/send_email_icon.svg';
static const String lowIndicatorIcon = '$svgBasePath/low_indicator_icon.svg';
// Health Calculators
static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg';
static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg';
static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg';
@ -288,9 +293,7 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
//vital sign
static const String heartRate = '$svgBasePath/heart_rate.svg';
static const String respRate = '$svgBasePath/resp_rate.svg';
static const String weightVital = '$svgBasePath/weight_2.svg';
@ -299,8 +302,6 @@ class AppAssets {
static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
static const String temperature = '$svgBasePath/temperature.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@ -327,8 +328,6 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}
class AppAnimations {
@ -349,6 +348,4 @@ class AppAnimations {
static const String ambulance = '$lottieBasePath/ambulance.json';
static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json';
}

@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart';
@ -52,6 +53,7 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -143,6 +145,7 @@ class AppDependencies {
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
@ -260,10 +263,16 @@ class AppDependencies {
getIt.registerLazySingleton<HealthProvider>(() => HealthProvider());
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<MonthlyReportViewModel>(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(
myInvoicesRepo: getIt(),
errorHandlerService: getIt(),
navServices: getIt(),
));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt()));
}
}

@ -16,7 +16,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CalenderEnum { gregorian, hijri }
enum SelectionTypeEnum { dropdown, calendar, search }
enum SelectionTypeEnum { dropdown, calendar, search, time }
enum GenderTypeEnum { male, female }
@ -38,7 +38,19 @@ enum HealthCalConEnum { calculator, converter }
enum HealthCalculatorEnum { general, women }
enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides }
enum HealthCalculatorsTypeEnum {
bmi,
calories,
bmr,
idealBodyWeight,
bodyFat,
crabsProteinFat,
ovulation,
deliveryDueDate,
bloodSugar,
bloodCholesterol,
triglycerides
}
extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
String get displayName {
@ -297,3 +309,5 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
// SymptomsChecker
enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency }
enum HealthTrackerTypeEnum { bloodSugar, bloodPressure, weightTracker }

@ -7,16 +7,14 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your
const num figmaDesignWidth = 375; // iPhone X / 12 base width
const num figmaDesignHeight = 812; // iPhone X / 12 base height
extension ConstrainedResponsive on num {
/// Width with max cap for tablets
double get wCapped => isTablet ? math.min( w, this * 1.3) : w;
double get wCapped => isTablet ? math.min(w, this * 1.3) : w;
/// Height with max cap for tablets
double get hCapped => isTablet ? math.min(h, this * 1.3) : h;
}
extension ResponsiveExtension on num {
double get _screenWidth => SizeUtils.width;

@ -262,16 +262,6 @@ class Utils {
return await prefs.remove(key);
}
static void showLoading({bool isNeedBinding = true}) {
if (isNeedBinding) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showLoadingDialog();
});
} else {
showLoadingDialog();
}
}
static void showLoadingDialog() {
_isLoadingVisible = true;
showDialog(
@ -288,18 +278,6 @@ class Utils {
);
}
static void hideLoading() {
try {
if (_isLoadingVisible) {
_isLoadingVisible = false;
Navigator.of(navigationService.navigatorKey.currentContext!).pop();
}
_isLoadingVisible = false;
} catch (e) {
log("errr: ${e.toString()}");
}
}
static List<T> uniqueBy<T, K>(List<T> list, K Function(T) keySelector) {
final seenKeys = <K>{};
return list.where((item) => seenKeys.add(keySelector(item))).toList();

@ -0,0 +1,873 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
/// Progress types to request different ranges from the progress API.
enum ProgressType { today, week, month }
abstract class HealthTrackersRepo {
// ==================== BLOOD SUGAR (DIABETIC) ====================
/// Get blood sugar result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResultAverage();
/// Get blood sugar results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResults();
/// Add new blood sugar result.
Future<Either<Failure, GenericApiModel<dynamic>>> addDiabeticResult({
required String bloodSugarDateChart,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
});
/// Update existing blood sugar result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateDiabeticResult({
required DateTime month,
required DateTime hour,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
required int lineItemNo,
});
/// Deactivate blood sugar record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateDiabeticStatus({
required int lineItemNo,
});
/// Send blood sugar report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendBloodSugarReportByEmail({
required String email,
});
// ==================== BLOOD PRESSURE ====================
/// Get blood pressure result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResultAverage();
/// Get blood pressure results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResults();
/// Add new blood pressure result.
Future<Either<Failure, GenericApiModel<dynamic>>> addBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
});
/// Update existing blood pressure result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
required int lineItemNo,
});
/// Deactivate blood pressure record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateBloodPressureStatus({
required int lineItemNo,
});
/// Send blood pressure report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendBloodPressureReportByEmail({
required String email,
});
// ==================== WEIGHT MEASUREMENT ====================
/// Get weight measurement result averages (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResultAverage();
/// Get weight measurement results (week, month, year).
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResults();
/// Add new weight measurement result.
Future<Either<Failure, GenericApiModel<dynamic>>> addWeightMeasurementResult({
required String weightDate,
required String weightMeasured,
required int weightUnit,
});
/// Update existing weight measurement result.
Future<Either<Failure, GenericApiModel<dynamic>>> updateWeightMeasurementResult({
required int lineItemNo,
required int weightUnit,
required String weightMeasured,
required String weightDate,
});
/// Deactivate weight measurement record.
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateWeightMeasurementStatus({
required int lineItemNo,
});
/// Send weight report by email.
Future<Either<Failure, GenericApiModel<dynamic>>> sendWeightReportByEmail({
required String email,
});
}
class HealthTrackersRepoImp implements HealthTrackersRepo {
final ApiClient apiClient;
final LoggerService loggerService;
HealthTrackersRepoImp({required this.loggerService, required this.apiClient});
// ==================== BLOOD SUGAR (DIABETIC) METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getDiabeticResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract average lists
extracted = {
'monthAverageList': response['List_MonthDiabtectResultAverage'] ?? [],
'weekAverageList': response['List_WeekDiabtectResultAverage'] ?? [],
'yearAverageList': response['List_YearDiabtecResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> getDiabeticResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getDiabeticResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract patient result lists
extracted = {
'monthResultList': response['List_MonthDiabtecPatientResult'] ?? [],
'weekResultList': response['List_WeekDiabtecPatientResult'] ?? [],
'yearResultList': response['List_YearDiabtecPatientResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> addDiabeticResult({
required String bloodSugarDateChart,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodSugerDateChart': bloodSugarDateChart,
'BloodSugerResult': bloodSugarResult,
'DiabtecUnit': diabeticUnit,
'MeasuredTime': measuredTime + 1, // Add 1 as per old service
};
await apiClient.post(
ApiConsts.addDiabeticResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> updateDiabeticResult({
required DateTime month,
required DateTime hour,
required String bloodSugarResult,
required String diabeticUnit,
required int measuredTime,
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
// Format: 'YYYY-MM-DD HH:MM:SS' as per old service
String formattedDate = '${month.year}-${month.month}-${month.day} ${hour.hour}:${hour.minute}:00';
Map<String, dynamic> body = {
'BloodSugerDateChart': formattedDate,
'BloodSugerResult': bloodSugarResult,
'DiabtecUnit': diabeticUnit,
'MeasuredTime': measuredTime + 1, // Add 1 as per old service
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.updateDiabeticResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> deactivateDiabeticStatus({
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateDiabeticStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> sendBloodSugarReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBloodSugarReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
// ==================== BLOOD PRESSURE METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getBloodPressureResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract all three list types
extracted = {
'monthList': response['List_MonthBloodPressureResultAverage'] ?? [],
'weekList': response['List_WeekBloodPressureResultAverage'] ?? [],
'yearList': response['List_YearBloodPressureResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> getBloodPressureResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getBloodPressureResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract all three list types
extracted = {
'weekList': response['List_WeekBloodPressureResult'] ?? [],
'monthList': response['List_MonthBloodPressureResult'] ?? [],
'yearList': response['List_YearBloodPressureResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> addBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodPressureDate': bloodPressureDate,
'DiastolicPressure': diastolicPressure,
'SystolicePressure': systolicePressure,
'MeasuredArm': measuredArm,
};
await apiClient.post(
ApiConsts.addBloodPressureResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> updateBloodPressureResult({
required String bloodPressureDate,
required String diastolicPressure,
required String systolicePressure,
required int measuredArm,
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'BloodPressureDate': bloodPressureDate,
'DiastolicPressure': diastolicPressure,
'SystolicePressure': systolicePressure,
'MeasuredArm': measuredArm,
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.updateBloodPressureResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> deactivateBloodPressureStatus({
required int lineItemNo,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateBloodPressureStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> sendBloodPressureReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBloodPressureReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
// ==================== WEIGHT MEASUREMENT METHODS ====================
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResultAverage() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getWeightMeasurementResultAverage,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract average lists
extracted = {
'monthAverageList': response['List_MonthWeightMeasurementResultAverage'] ?? [],
'weekAverageList': response['List_WeekWeightMeasurementResultAverage'] ?? [],
'yearAverageList': response['List_YearWeightMeasurementResultAverage'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> getWeightMeasurementResults() async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getWeightMeasurementResult,
body: {},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
dynamic extracted;
if (response is Map) {
// Extract result lists
extracted = {
'weekResultList': response['List_WeekWeightMeasurementResult'] ?? [],
'monthResultList': response['List_MonthWeightMeasurementResult'] ?? [],
'yearResultList': response['List_YearWeightMeasurementResult'] ?? [],
};
} else {
extracted = response;
}
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
);
} 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<Either<Failure, GenericApiModel<dynamic>>> addWeightMeasurementResult({
required String weightDate,
required String weightMeasured,
required int weightUnit,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'WeightDate': weightDate,
'WeightMeasured': weightMeasured,
'weightUnit': weightUnit,
};
await apiClient.post(
ApiConsts.addWeightMeasurementResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> updateWeightMeasurementResult({
required int lineItemNo,
required int weightUnit,
required String weightMeasured,
required String weightDate,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
'weightUnit': '$weightUnit', // Convert to string as per old service
'WeightMeasured': weightMeasured,
'WeightDate': weightDate,
};
await apiClient.post(
ApiConsts.updateWeightMeasurementResult,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> deactivateWeightMeasurementStatus({required int lineItemNo}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'LineItemNo': lineItemNo,
};
await apiClient.post(
ApiConsts.deactivateWeightMeasurementStatus,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
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<Either<Failure, GenericApiModel<dynamic>>> sendWeightReportByEmail({
required String email,
}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
Map<String, dynamic> body = {
'To': email,
};
await apiClient.post(
ApiConsts.sendAverageBodyWeightReport,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,87 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class BloodPressureResult {
int? patientID;
int? lineItemNo;
DateTime? bloodPressureDate;
int? measuredArm;
int? systolicePressure;
int? diastolicPressure;
dynamic remark;
bool? isActive;
int? chartYear;
String? chartMonth;
dynamic yearSystolicePressureAverageResult;
dynamic monthSystolicePressureResult;
dynamic weekSystolicePressureResult;
int? yearDiastolicPressureAverageResult;
dynamic monthDiastolicPressureResult;
dynamic weekDiastolicPressureResult;
String? measuredArmDesc;
dynamic weekDesc;
BloodPressureResult(
{this.patientID,
this.lineItemNo,
this.bloodPressureDate,
this.measuredArm,
this.systolicePressure,
this.diastolicPressure,
this.remark,
this.isActive,
this.chartYear,
this.chartMonth,
this.yearSystolicePressureAverageResult,
this.monthSystolicePressureResult,
this.weekSystolicePressureResult,
this.yearDiastolicPressureAverageResult,
this.monthDiastolicPressureResult,
this.weekDiastolicPressureResult,
this.measuredArmDesc,
this.weekDesc});
BloodPressureResult.fromJson(Map<String, dynamic> json) {
patientID = json['PatientID'];
lineItemNo = json['LineItemNo'];
bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']);
measuredArm = json['MeasuredArm'];
systolicePressure = json['SystolicePressure'];
diastolicPressure = json['DiastolicPressure'];
remark = json['Remark'];
isActive = json['IsActive'];
chartYear = json['ChartYear'];
chartMonth = json['ChartMonth'];
yearSystolicePressureAverageResult = json['YearSystolicePressureAverageResult'];
monthSystolicePressureResult = json['MonthSystolicePressureResult'];
weekSystolicePressureResult = json['WeekSystolicePressureResult'];
yearDiastolicPressureAverageResult = json['YearDiastolicPressureAverageResult'];
monthDiastolicPressureResult = json['MonthDiastolicPressureResult'];
weekDiastolicPressureResult = json['WeekDiastolicPressureResult'];
measuredArmDesc = json['MeasuredArmDesc'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['PatientID'] = patientID;
data['LineItemNo'] = lineItemNo;
data['BloodPressureDate'] = bloodPressureDate;
data['MeasuredArm'] = measuredArm;
data['SystolicePressure'] = systolicePressure;
data['DiastolicPressure'] = diastolicPressure;
data['Remark'] = remark;
data['IsActive'] = isActive;
data['ChartYear'] = chartYear;
data['ChartMonth'] = chartMonth;
data['YearSystolicePressureAverageResult'] = yearSystolicePressureAverageResult;
data['MonthSystolicePressureResult'] = monthSystolicePressureResult;
data['WeekSystolicePressureResult'] = weekSystolicePressureResult;
data['YearDiastolicPressureAverageResult'] = yearDiastolicPressureAverageResult;
data['MonthDiastolicPressureResult'] = monthDiastolicPressureResult;
data['WeekDiastolicPressureResult'] = weekDiastolicPressureResult;
data['MeasuredArmDesc'] = measuredArmDesc;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,58 @@
class MonthBloodPressureResultAverage {
dynamic weekfourSystolicePressureAverageResult;
dynamic weekfourDiastolicPressureAverageResult;
dynamic weekthreeSystolicePressureAverageResult;
dynamic weekthreeDiastolicPressureAverageResult;
dynamic weektwoSystolicePressureAverageResult;
dynamic weektwoDiastolicPressureAverageResult;
dynamic weekoneSystolicePressureAverageResult;
dynamic weekoneDiastolicPressureAverageResult;
String? weekDesc;
int? weekDiastolicPressureAverageResult;
int? weekSystolicePressureAverageResult;
MonthBloodPressureResultAverage({
this.weekfourSystolicePressureAverageResult,
this.weekfourDiastolicPressureAverageResult,
this.weekthreeSystolicePressureAverageResult,
this.weekthreeDiastolicPressureAverageResult,
this.weektwoSystolicePressureAverageResult,
this.weektwoDiastolicPressureAverageResult,
this.weekoneSystolicePressureAverageResult,
this.weekoneDiastolicPressureAverageResult,
this.weekDesc,
this.weekDiastolicPressureAverageResult,
this.weekSystolicePressureAverageResult,
});
MonthBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
weekfourSystolicePressureAverageResult = json['weekfourSystolicePressureAverageResult'];
weekfourDiastolicPressureAverageResult = json['weekfourDiastolicPressureAverageResult'];
weekthreeSystolicePressureAverageResult = json['weekthreeSystolicePressureAverageResult'];
weekthreeDiastolicPressureAverageResult = json['weekthreeDiastolicPressureAverageResult'];
weektwoSystolicePressureAverageResult = json['weektwoSystolicePressureAverageResult'];
weektwoDiastolicPressureAverageResult = json['weektwoDiastolicPressureAverageResult'];
weekoneSystolicePressureAverageResult = json['weekoneSystolicePressureAverageResult'];
weekoneDiastolicPressureAverageResult = json['weekoneDiastolicPressureAverageResult'];
weekDesc = json['WeekDesc'];
weekDiastolicPressureAverageResult = json['WeekDiastolicPressureAverageResult'];
weekSystolicePressureAverageResult = json['WeekSystolicePressureAverageResult'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourSystolicePressureAverageResult'] = weekfourSystolicePressureAverageResult;
data['weekfourDiastolicPressureAverageResult'] = weekfourDiastolicPressureAverageResult;
data['weekthreeSystolicePressureAverageResult'] = weekthreeSystolicePressureAverageResult;
data['weekthreeDiastolicPressureAverageResult'] = weekthreeDiastolicPressureAverageResult;
data['weektwoSystolicePressureAverageResult'] = weektwoSystolicePressureAverageResult;
data['weektwoDiastolicPressureAverageResult'] = weektwoDiastolicPressureAverageResult;
data['weekoneSystolicePressureAverageResult'] = weekoneSystolicePressureAverageResult;
data['weekoneDiastolicPressureAverageResult'] = weekoneDiastolicPressureAverageResult;
data['WeekDesc'] = weekDesc;
data['WeekDiastolicPressureAverageResult'] = weekDiastolicPressureAverageResult;
data['WeekSystolicePressureAverageResult'] = weekSystolicePressureAverageResult;
return data;
}
}

@ -0,0 +1,24 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekBloodPressureResultAverage {
int? dailySystolicePressureAverageResult;
int? dailyDiastolicPressureAverageResult;
DateTime? bloodPressureDate;
WeekBloodPressureResultAverage({this.dailySystolicePressureAverageResult, this.dailyDiastolicPressureAverageResult, this.bloodPressureDate});
WeekBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
dailySystolicePressureAverageResult = json['DailySystolicePressureAverageResult'];
dailyDiastolicPressureAverageResult = json['DailyDiastolicPressureAverageResult'];
bloodPressureDate = DateUtil.convertStringToDate(json['BloodPressureDate']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailySystolicePressureAverageResult'] = dailySystolicePressureAverageResult;
data['DailyDiastolicPressureAverageResult'] = dailyDiastolicPressureAverageResult;
data['BloodPressureDate'] = bloodPressureDate;
return data;
}
}

@ -0,0 +1,37 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearBloodPressureResultAverage {
int? monthSystolicePressureAverageResult;
int? monthDiastolicPressureAverageResult;
dynamic monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearBloodPressureResultAverage({
this.monthSystolicePressureAverageResult,
this.monthDiastolicPressureAverageResult,
this.monthNumber,
this.monthName,
this.yearName,
});
YearBloodPressureResultAverage.fromJson(Map<String, dynamic> json) {
monthSystolicePressureAverageResult = json['monthSystolicePressureAverageResult'];
monthDiastolicPressureAverageResult = json['monthDiastolicPressureAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthSystolicePressureAverageResult'] = monthSystolicePressureAverageResult;
data['monthDiastolicPressureAverageResult'] = monthDiastolicPressureAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -0,0 +1,99 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class DiabeticPatientResult {
String? chartMonth;
var chartYear;
DateTime? dateChart;
var description;
var descriptionN;
int? diabtecAvarage;
bool? isActive;
int? lineItemNo;
var listMonth;
var listWeek;
int? measured;
String? measuredDesc;
var monthAverageResult;
int? patientID;
var remark;
var resultDesc;
dynamic resultValue;
String? unit;
var weekAverageResult;
String? weekDesc;
var yearAverageResult;
DiabeticPatientResult(
{this.chartMonth,
this.chartYear,
this.dateChart,
this.description,
this.descriptionN,
this.diabtecAvarage,
this.isActive,
this.lineItemNo,
this.listMonth,
this.listWeek,
this.measured,
this.measuredDesc,
this.monthAverageResult,
this.patientID,
this.remark,
this.resultDesc,
this.resultValue,
this.unit,
this.weekAverageResult,
this.weekDesc,
this.yearAverageResult});
DiabeticPatientResult.fromJson(Map<String, dynamic> json) {
chartMonth = json['ChartMonth'];
chartYear = json['ChartYear'];
dateChart = DateUtil.convertStringToDate(json['DateChart']);
description = json['Description'];
descriptionN = json['DescriptionN'];
diabtecAvarage = json['DiabtecAvarage'];
isActive = json['IsActive'];
lineItemNo = json['LineItemNo'];
listMonth = json['List_Month'];
listWeek = json['List_Week'];
measured = json['Measured'];
measuredDesc = json['MeasuredDesc'];
monthAverageResult = json['MonthAverageResult'];
patientID = json['PatientID'];
remark = json['Remark'];
resultDesc = json['ResultDesc'];
resultValue = json['ResultValue'];
unit = json['Unit'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
yearAverageResult = json['YearAverageResult'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['ChartMonth'] = chartMonth;
data['ChartYear'] = chartYear;
data['DateChart'] = DateUtil.convertDateToString(dateChart!);
data['Description'] = description;
data['DescriptionN'] = descriptionN;
data['DiabtecAvarage'] = diabtecAvarage;
data['IsActive'] = isActive;
data['LineItemNo'] = lineItemNo;
data['List_Month'] = listMonth;
data['List_Week'] = listWeek;
data['Measured'] = measured;
data['MeasuredDesc'] = measuredDesc;
data['MonthAverageResult'] = monthAverageResult;
data['PatientID'] = patientID;
data['Remark'] = remark;
data['ResultDesc'] = resultDesc;
data['ResultValue'] = resultValue;
data['Unit'] = unit;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
data['YearAverageResult'] = yearAverageResult;
return data;
}
}

@ -0,0 +1,37 @@
class MonthDiabeticResultAverage {
var weekfourAverageResult;
var weekthreeAverageResult;
var weektwoAverageResult;
var weekoneAverageResult;
dynamic weekAverageResult;
String? weekDesc;
MonthDiabeticResultAverage(
{this.weekfourAverageResult,
this.weekthreeAverageResult,
this.weektwoAverageResult,
this.weekoneAverageResult,
this.weekAverageResult,
this.weekDesc});
MonthDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
weekfourAverageResult = json['weekfourAverageResult'];
weekthreeAverageResult = json['weekthreeAverageResult'];
weektwoAverageResult = json['weektwoAverageResult'];
weekoneAverageResult = json['weekoneAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourAverageResult'] = weekfourAverageResult;
data['weekthreeAverageResult'] = weekthreeAverageResult;
data['weektwoAverageResult'] = weektwoAverageResult;
data['weekoneAverageResult'] = weekoneAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,21 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekDiabeticResultAverage {
dynamic dailyAverageResult;
DateTime? dateChart;
WeekDiabeticResultAverage({this.dailyAverageResult, this.dateChart});
WeekDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
dailyAverageResult = json['DailyAverageResult'];
dateChart = DateUtil.convertStringToDate(json['DateChart']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailyAverageResult'] = dailyAverageResult;
data['DateChart'] = DateUtil.convertDateToString(dateChart!);
return data;
}
}

@ -0,0 +1,35 @@
import 'dart:developer';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearDiabeticResultAverage {
dynamic monthAverageResult;
var monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearDiabeticResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName});
YearDiabeticResultAverage.fromJson(Map<String, dynamic> json) {
try {
monthAverageResult = json['monthAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
} catch (e) {
log(e.toString());
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthAverageResult'] = monthAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -0,0 +1,37 @@
class MonthWeightMeasurementResultAverage {
dynamic weekfourAverageResult;
dynamic weekthreeAverageResult;
dynamic weektwoAverageResult;
dynamic weekoneAverageResult;
dynamic weekAverageResult;
String? weekDesc;
MonthWeightMeasurementResultAverage(
{this.weekfourAverageResult,
this.weekthreeAverageResult,
this.weektwoAverageResult,
this.weekoneAverageResult,
this.weekAverageResult,
this.weekDesc});
MonthWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
weekfourAverageResult = json['weekfourAverageResult'];
weekthreeAverageResult = json['weekthreeAverageResult'];
weektwoAverageResult = json['weektwoAverageResult'];
weekoneAverageResult = json['weekoneAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['weekfourAverageResult'] = weekfourAverageResult;
data['weekthreeAverageResult'] = weekthreeAverageResult;
data['weektwoAverageResult'] = weektwoAverageResult;
data['weekoneAverageResult'] = weekoneAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,21 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeekWeightMeasurementResultAverage {
dynamic dailyAverageResult;
DateTime? weightDate;
WeekWeightMeasurementResultAverage({this.dailyAverageResult, this.weightDate});
WeekWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
dailyAverageResult = json['DailyAverageResult'];
weightDate = DateUtil.convertStringToDate(json['WeightDate']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['DailyAverageResult'] = dailyAverageResult;
data['WeightDate'] = weightDate;
return data;
}
}

@ -0,0 +1,77 @@
import 'dart:developer';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class WeightMeasurementResult {
int? patientID;
int? lineItemNo;
int? weightMeasured;
DateTime? weightDate;
dynamic remark;
bool? isActive;
int? measured;
dynamic unit;
int? chartYear;
dynamic chartMonth;
double? yearAverageResult;
dynamic monthAverageResult;
dynamic weekAverageResult;
dynamic weekDesc;
WeightMeasurementResult(
{this.patientID,
this.lineItemNo,
this.weightMeasured,
this.weightDate,
this.remark,
this.isActive,
this.measured,
this.unit,
this.chartYear,
this.chartMonth,
this.yearAverageResult,
this.monthAverageResult,
this.weekAverageResult,
this.weekDesc});
WeightMeasurementResult.fromJson(Map<String, dynamic> json) {
try {
patientID = json['PatientID'];
lineItemNo = json['LineItemNo'];
weightMeasured = json['WeightMeasured'];
weightDate = DateUtil.convertStringToDate(json['WeightDate']);
remark = json['Remark'];
isActive = json['IsActive'];
measured = json['Measured'];
unit = json['Unit'];
chartYear = json['ChartYear'];
chartMonth = json['ChartMonth'];
// Convert to double safely since API may return int
yearAverageResult = json['YearAverageResult'] != null ? (json['YearAverageResult'] as num).toDouble() : null;
monthAverageResult = json['MonthAverageResult'];
weekAverageResult = json['WeekAverageResult'];
weekDesc = json['WeekDesc'];
} catch (e) {
log(e.toString());
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['PatientID'] = patientID;
data['LineItemNo'] = lineItemNo;
data['WeightMeasured'] = weightMeasured;
data['WeightDate'] = weightDate;
data['Remark'] = remark;
data['IsActive'] = isActive;
data['Measured'] = measured;
data['Unit'] = unit;
data['ChartYear'] = chartYear;
data['ChartMonth'] = chartMonth;
data['YearAverageResult'] = yearAverageResult;
data['MonthAverageResult'] = monthAverageResult;
data['WeekAverageResult'] = weekAverageResult;
data['WeekDesc'] = weekDesc;
return data;
}
}

@ -0,0 +1,29 @@
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
class YearWeightMeasurementResultAverage {
dynamic monthAverageResult;
int? monthNumber;
String? monthName;
String? yearName;
DateTime? date;
YearWeightMeasurementResultAverage({this.monthAverageResult, this.monthNumber, this.monthName, this.yearName});
YearWeightMeasurementResultAverage.fromJson(Map<String, dynamic> json) {
monthAverageResult = json['monthAverageResult'];
monthNumber = json['monthNumber'];
monthName = json['monthName'];
yearName = json['yearName'];
date = DateUtil.getMonthDateTime(monthName!, yearName);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['monthAverageResult'] = monthAverageResult;
data['monthNumber'] = monthNumber;
data['monthName'] = monthName;
data['yearName'] = yearName;
return data;
}
}

@ -0,0 +1,188 @@
class SymptomsUserDetailsResponseModel {
final TokenDetails? tokenDetails;
final UserDetails? userDetails;
final String? sessionId;
SymptomsUserDetailsResponseModel({
this.tokenDetails,
this.userDetails,
this.sessionId,
});
factory SymptomsUserDetailsResponseModel.fromJson(Map<String, dynamic> json) {
return SymptomsUserDetailsResponseModel(
tokenDetails: json['tokenDetails'] != null ? TokenDetails.fromJson(json['tokenDetails']) : null,
userDetails: json['userDetails'] != null ? UserDetails.fromJson(json['userDetails']) : null,
sessionId: json['sessionId'],
);
}
Map<String, dynamic> toJson() {
return {
'tokenDetails': tokenDetails?.toJson(),
'userDetails': userDetails?.toJson(),
'sessionId': sessionId,
};
}
}
class TokenDetails {
final String? id;
final String? authToken;
final int? expiresIn;
TokenDetails({
this.id,
this.authToken,
this.expiresIn,
});
factory TokenDetails.fromJson(Map<String, dynamic> json) {
return TokenDetails(
id: json['id'],
authToken: json['auth_token'],
expiresIn: json['expires_in'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'auth_token': authToken,
'expires_in': expiresIn,
};
}
}
class UserDetails {
final String? fileNo;
final String? nationalId;
final String? email;
final String? dateOfBirth;
final String? dateOfBirthHijri;
final int? age;
final UserName? name;
final int? maritalStatus;
final String? maritalStatusCode;
final String? nationality;
final String? nationalityIsoCode;
final String? occupation;
final int? idType;
final int? gender;
final String? jwtToken;
final String? countryDialCode;
final String? phoneNo;
UserDetails({
this.fileNo,
this.nationalId,
this.email,
this.dateOfBirth,
this.dateOfBirthHijri,
this.age,
this.name,
this.maritalStatus,
this.maritalStatusCode,
this.nationality,
this.nationalityIsoCode,
this.occupation,
this.idType,
this.gender,
this.jwtToken,
this.countryDialCode,
this.phoneNo,
});
factory UserDetails.fromJson(Map<String, dynamic> json) {
return UserDetails(
fileNo: json['FileNo'],
nationalId: json['national_id'],
email: json['email'],
dateOfBirth: json['date_of_birth'],
dateOfBirthHijri: json['date_of_birth_hijri'],
age: json['age'],
name: json['name'] != null ? UserName.fromJson(json['name']) : null,
maritalStatus: json['marital_status'],
maritalStatusCode: json['marital_status_code'],
nationality: json['nationality'],
nationalityIsoCode: json['nationality_iso_code'],
occupation: json['occupation'],
idType: json['id_type'],
gender: json['gender'],
jwtToken: json['jwt_token'],
countryDialCode: json['country_dial_code'],
phoneNo: json['phone_no'],
);
}
Map<String, dynamic> toJson() {
return {
'FileNo': fileNo,
'national_id': nationalId,
'email': email,
'date_of_birth': dateOfBirth,
'date_of_birth_hijri': dateOfBirthHijri,
'age': age,
'name': name?.toJson(),
'marital_status': maritalStatus,
'marital_status_code': maritalStatusCode,
'nationality': nationality,
'nationality_iso_code': nationalityIsoCode,
'occupation': occupation,
'id_type': idType,
'gender': gender,
'jwt_token': jwtToken,
'country_dial_code': countryDialCode,
'phone_no': phoneNo,
};
}
// Helper method to get full name
String getFullName(bool isArabic) {
if (name == null) return '';
if (isArabic) {
return '${name!.firstNameAr ?? ''} ${name!.middleNameAr ?? ''} ${name!.lastNameAr ?? ''}'.trim();
}
return '${name!.firstName ?? ''} ${name!.middleName ?? ''} ${name!.lastName ?? ''}'.trim();
}
}
class UserName {
final String? firstName;
final String? middleName;
final String? lastName;
final String? firstNameAr;
final String? middleNameAr;
final String? lastNameAr;
UserName({
this.firstName,
this.middleName,
this.lastName,
this.firstNameAr,
this.middleNameAr,
this.lastNameAr,
});
factory UserName.fromJson(Map<String, dynamic> json) {
return UserName(
firstName: json['first_name'],
middleName: json['middle_name'],
lastName: json['last_name'],
firstNameAr: json['first_name_ar'],
middleNameAr: json['middle_name_ar'],
lastNameAr: json['last_name_ar'],
);
}
Map<String, dynamic> toJson() {
return {
'first_name': firstName,
'middle_name': middleName,
'last_name': lastName,
'first_name_ar': firstNameAr,
'middle_name_ar': middleNameAr,
'last_name_ar': lastNameAr,
};
}
}

@ -0,0 +1,209 @@
class TriageDataDetails {
final TriageQuestion? question;
final List<TriageCondition>? conditions;
final bool? hasEmergencyEvidence;
final bool? shouldStop;
final String? interviewToken;
final String? message;
final List<String>? errorList;
final int? id;
final String? language;
final String? generalId;
final String? createDate;
final String? lastEditDate;
final String? createdBy;
final String? lastEditBy;
final bool? active;
final int? sortOrder;
final int? userType;
final String? userId;
TriageDataDetails({
this.question,
this.conditions,
this.hasEmergencyEvidence,
this.shouldStop,
this.interviewToken,
this.message,
this.errorList,
this.id,
this.language,
this.generalId,
this.createDate,
this.lastEditDate,
this.createdBy,
this.lastEditBy,
this.active,
this.sortOrder,
this.userType,
this.userId,
});
factory TriageDataDetails.fromJson(Map<String, dynamic> json) {
return TriageDataDetails(
question: json['question'] != null ? TriageQuestion.fromJson(json['question']) : null,
conditions: json['conditions'] != null ? (json['conditions'] as List).map((item) => TriageCondition.fromJson(item)).toList() : null,
hasEmergencyEvidence: json['has_emergency_evidence'],
shouldStop: json['should_stop'],
interviewToken: json['interview_token'],
message: json['Message'],
errorList: json['ErrorList'] != null ? List<String>.from(json['ErrorList']) : null,
id: json['Id'],
language: json['language'],
generalId: json['generalId'],
createDate: json['CreateDate'],
lastEditDate: json['LastEditDate'],
createdBy: json['CreatedBy'],
lastEditBy: json['LastEditBy'],
active: json['Active'],
sortOrder: json['SortOrder'],
userType: json['userType'],
userId: json['userId'],
);
}
Map<String, dynamic> toJson() {
return {
'question': question?.toJson(),
'conditions': conditions?.map((item) => item.toJson()).toList(),
'has_emergency_evidence': hasEmergencyEvidence,
'should_stop': shouldStop,
'interview_token': interviewToken,
'Message': message,
'ErrorList': errorList,
'Id': id,
'language': language,
'generalId': generalId,
'CreateDate': createDate,
'LastEditDate': lastEditDate,
'CreatedBy': createdBy,
'LastEditBy': lastEditBy,
'Active': active,
'SortOrder': sortOrder,
'userType': userType,
'userId': userId,
};
}
}
class TriageQuestion {
final int? type;
final String? text;
final List<TriageQuestionItem>? items;
TriageQuestion({
this.type,
this.text,
this.items,
});
factory TriageQuestion.fromJson(Map<String, dynamic> json) {
return TriageQuestion(
type: json['type'],
text: json['text'],
items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null,
);
}
Map<String, dynamic> toJson() {
return {
'type': type,
'text': text,
'items': items?.map((item) => item.toJson()).toList(),
};
}
}
class TriageQuestionItem {
final String? id;
final String? name;
final List<TriageChoice>? choices;
TriageQuestionItem({
this.id,
this.name,
this.choices,
});
factory TriageQuestionItem.fromJson(Map<String, dynamic> json) {
return TriageQuestionItem(
id: json['id'],
name: json['name'],
choices: json['choices'] != null ? (json['choices'] as List).map((item) => TriageChoice.fromJson(item)).toList() : null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'choices': choices?.map((item) => item.toJson()).toList(),
};
}
}
class TriageChoice {
final String? id;
final String? label;
TriageChoice({
this.id,
this.label,
});
factory TriageChoice.fromJson(Map<String, dynamic> json) {
return TriageChoice(
id: json['id'],
label: json['label'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'label': label,
};
}
}
class TriageCondition {
final String? id;
final String? name;
final String? commonName;
final double? probability;
final dynamic conditionDetails;
TriageCondition({
this.id,
this.name,
this.commonName,
this.probability,
this.conditionDetails,
});
factory TriageCondition.fromJson(Map<String, dynamic> json) {
return TriageCondition(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
probability: json['probability']?.toDouble(),
conditionDetails: json['condition_details'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
'probability': probability,
'condition_details': conditionDetails,
};
}
/// Get probability as percentage
String getProbabilityPercentage() {
if (probability == null) return '0%';
return '${(probability! * 100).toStringAsFixed(1)}%';
}
}

@ -1,5 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
@ -8,25 +7,56 @@ 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/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
required String userName,
required String password,
});
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
}
@ -34,14 +64,79 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
final ApiClient apiClient;
final LoggerService loggerService;
SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService});
SymptomsCheckerRepoImp(
{required this.apiClient, required this.loggerService});
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({required List<String> organNames}) async {
log("GetBodySymptomsByName Request URL: ${ApiConsts.getBodySymptomsByName}");
log("GetBodySymptomsByName Request Body: ${jsonEncode(organNames)}");
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
required String userName,
required String password,
}) async {
Map<String, String> body = {"userName": userName, "password": password};
try {
GenericApiModel<SymptomsUserDetailsResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.symptomsUserLogin,
body: body,
isExternal: true,
isAllowAny: true,
isBodyPlainText: false,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("getUserDetails API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
Map<String, String> headers = {'Content-Type': 'application/json', 'Accept': 'text/plain'};
SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel =
SymptomsUserDetailsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<SymptomsUserDetailsResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: symptomsUserDetailsResponseModel,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing getUserDetails response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getUserDetails: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
}) async {
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
Map<String, dynamic> body = {
'bodyPartName': organNames,
'gender': gender,
};
try {
GenericApiModel<BodySymptomResponseModel>? apiResponse;
@ -50,22 +145,17 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getBodySymptomsByName,
apiHeaders: headers,
body: jsonEncode(organNames),
body: body,
isExternal: true,
isAllowAny: true,
isBodyPlainText: true,
isBodyPlainText: false,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("GetBodySymptomsByName API Failed: $error");
log("GetBodySymptomsByName Failed: $error, Status: $statusCode");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("GetBodySymptomsByName Response Status: $statusCode");
loggerService.logInfo("GetBodySymptomsByName API Success: $response");
log("GetBodySymptomsByName Response: $response");
BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response);
BodySymptomResponseModel bodySymptomResponse =
BodySymptomResponseModel.fromJson(response);
apiResponse = GenericApiModel<BodySymptomResponseModel>(
messageStatus: messageStatus ?? 1,
@ -74,9 +164,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: bodySymptomResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService
.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -88,17 +178,20 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getBodySymptomsByName: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
final Map<String, dynamic> body = {
"age": {
@ -107,6 +200,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"sex": sex,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
@ -115,21 +214,22 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getRiskFactors,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
log("GetRiskFactors Failed: $error, Status: $statusCode");
loggerService.logError("GetRiskFactors API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("GetRiskFactors Response: $response");
// Parse response if it's a string
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -140,7 +240,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetRiskFactors response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -152,17 +251,111 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getRiskFactors: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>?
triageEvidence, // Additional triage-specific evidence
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
// Build evidence list: combine initial symptoms with triage evidence
List<Map<String, dynamic>> evidenceList = [];
// Add initial evidence as simple IDs
for (var id in evidenceIds) {
evidenceList.add({"id": id});
}
// Add triage evidence with proper format (id, choice_id, source)
if (triageEvidence != null && triageEvidence.isNotEmpty) {
evidenceList.addAll(triageEvidence);
}
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"sex": sex,
"evidence": evidenceList,
"language": language,
"suggest_method": "diagnosis",
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
GenericApiModel<TriageDataDetails>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.diagnosis,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("getDiagnosisForTriage API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final updatedResponseData = responseData['dataDetails'];
TriageDataDetails riskFactorsResponse =
TriageDataDetails.fromJson(updatedResponseData);
apiResponse = GenericApiModel<TriageDataDetails>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: riskFactorsResponse,
);
} catch (e, stackTrace) {
loggerService
.logError("Error parsing getDiagnosisForTriage response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getDiagnosisForTriage: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
}) async {
final Map<String, dynamic> body = {
"age": {
@ -171,6 +364,12 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"sex": sex,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
@ -179,21 +378,22 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
await apiClient.post(
ApiConsts.getSuggestions,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
log("getSuggestions Failed: $error, Status: $statusCode");
loggerService.logError("GetSuggestions API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
log("getSuggestions Response: $response");
// Parse response if it's a string
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -202,9 +402,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: riskFactorsResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing getSuggestions response: $e");
loggerService.logError("Error parsing GetSuggestions response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
failure = DataParsingFailure(e.toString());
}
},
@ -216,7 +415,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
} catch (e, stackTrace) {
loggerService.logError("Exception in getSuggestions: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}

@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@ -35,11 +37,23 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
bool isBodySymptomsLoading = false;
bool isRiskFactorsLoading = false;
bool isSuggestionsLoading = false;
bool isTriageDiagnosisLoading = false;
// API data storage - using API models directly
SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel;
BodySymptomResponseModel? bodySymptomResponse;
RiskAndSuggestionsResponseModel? riskFactorsResponse;
RiskAndSuggestionsResponseModel? suggestionsResponse;
TriageDataDetails? triageDataDetails;
// Triage state
int?
_selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility
final Map<String, int> _selectedTriageChoicesByItemId =
{}; // Map of itemId -> choiceIndex for multi-item questions
final List<Map<String, String>> _triageEvidenceList =
[]; // Store triage evidence with proper format
int _triageQuestionCount = 0; // Track number of triage questions answered
// Selected risk factors tracking
final Set<String> _selectedRiskFactorIds = {};
@ -52,7 +66,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// User Info Flow State
int _userInfoCurrentPage = 0;
bool _isSinglePageEditMode = false; // Track if editing single page or full flow
bool _isSinglePageEditMode =
false; // Track if editing single page or full flow
String? _selectedGender;
DateTime? _dateOfBirth;
int? _selectedAge;
@ -92,8 +107,54 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? get tooltipOrganId => _tooltipOrganId;
String get currentSessionAuthToken =>
symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? "";
String get currentSessionId =>
symptomsUserDetailsResponseModel?.sessionId ?? "";
// Triage-related getters
bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false;
bool get hasEmergencyEvidence =>
triageDataDetails?.hasEmergencyEvidence ?? false;
String? get currentInterviewToken => triageDataDetails?.interviewToken;
TriageQuestion? get currentTriageQuestion => triageDataDetails?.question;
List<TriageCondition>? get currentConditions => triageDataDetails?.conditions;
int? get selectedTriageChoiceIndex => _selectedTriageChoiceIndex;
/// Get the number of triage questions answered
int get triageQuestionCount => _triageQuestionCount;
/// Get choice index for a specific item
int? getTriageChoiceForItem(String itemId) {
return _selectedTriageChoicesByItemId[itemId];
}
/// Check if all items in current question have been answered
bool get areAllTriageItemsAnswered {
if (currentTriageQuestion?.items == null ||
currentTriageQuestion!.items!.isEmpty) {
return false;
}
// Check if we have an answer for each item
for (var item in currentTriageQuestion!.items!) {
if (item.id != null &&
!_selectedTriageChoicesByItemId.containsKey(item.id)) {
return false;
}
}
return true;
}
/// Get organs for current view
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
List<OrganModel> get currentOrgans =>
OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> get selectedOrgans {
@ -101,7 +162,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
...OrganData.frontViewOrgans,
...OrganData.backViewOrgans,
];
return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList();
return allOrgans
.where((organ) => _selectedOrganIds.contains(organ.id))
.toList();
}
/// Check if any organs are selected
@ -118,11 +181,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
return _selectedSymptomsByOrgan.values
.fold(0, (sum, symptomIds) => sum + symptomIds.length);
}
bool get hasSelectedSymptoms {
return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty);
return _selectedSymptomsByOrgan.values
.any((symptomIds) => symptomIds.isNotEmpty);
}
/// Get risk factors list
@ -148,7 +213,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
void toggleView() {
_currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front;
_currentView =
_currentView == BodyView.front ? BodyView.back : BodyView.front;
notifyListeners();
}
@ -251,7 +317,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return;
}
List<String> organNames = selectedOrgans.map((organ) => organ.name).toList();
List<String> organNames =
selectedOrgans.map((organ) => organ.name).toList();
await getBodySymptomsByName(
organNames: organNames,
@ -301,7 +368,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
}
if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
if (matchingOrganId != null &&
_selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!;
if (organResult.bodySymptoms != null) {
@ -352,7 +420,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedRiskFactors() {
return riskFactorsList.where((factor) => factor.id != null && _selectedRiskFactorIds.contains(factor.id)).toList();
return riskFactorsList
.where((factor) =>
factor.id != null && _selectedRiskFactorIds.contains(factor.id))
.toList();
}
/// Clear all risk factor selections
@ -385,12 +456,14 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
await getRiskFactors(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
evidenceIds: evidenceIds,
sessionId: currentSessionId,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
@ -409,6 +482,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getRiskFactors({
required int age,
required String sex,
required String sessionId,
required List<String> evidenceIds,
required String language,
Function(RiskAndSuggestionsResponseModel)? onSuccess,
@ -422,6 +496,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
sex: sex,
evidenceIds: evidenceIds,
language: language,
sessionId: sessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -438,8 +515,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
riskFactorsResponse = apiResponse.data;
if (riskFactorsResponse != null && riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
if (riskFactorsResponse != null &&
riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -493,7 +572,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedSuggestions() {
return suggestionsList.where((factor) => factor.id != null && _selectedSuggestionsIds.contains(factor.id)).toList();
return suggestionsList
.where((factor) =>
factor.id != null && _selectedSuggestionsIds.contains(factor.id))
.toList();
}
/// Clear all risk factor selections
@ -502,6 +584,30 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners();
}
/// Get all evidence IDs (symptoms + risk factors + suggestions) for triage/diagnosis
List<String> getAllEvidenceIds() {
List<String> evidenceIds = [];
// Add selected symptoms
final selectedSymptoms = getAllSelectedSymptoms();
evidenceIds
.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!));
// Add selected risk factors (excluding "not_applicable")
final selectedRiskFactors = getAllSelectedRiskFactors();
evidenceIds.addAll(selectedRiskFactors
.where((rf) => rf.id != null && rf.id != "not_applicable")
.map((rf) => rf.id!));
// Add selected suggestions (excluding "not_applicable")
final selectedSuggestions = getAllSelectedSuggestions();
evidenceIds.addAll(selectedSuggestions
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!));
return evidenceIds;
}
/// Fetch risk factors based on selected symptoms
Future<void> fetchSuggestions({
Function()? onSuccess,
@ -526,13 +632,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
// Get all selected symptoms
final selectedRisks = getAllSelectedRiskFactors();
if (selectedRisks.isNotEmpty) {
List<String> evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList();
List<String> evidenceRisksIds = selectedRisks
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!)
.toList();
evidenceIds.addAll(evidenceRisksIds);
}
@ -571,6 +681,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
sex: sex,
evidenceIds: evidenceIds,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -587,8 +700,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
suggestionsResponse = apiResponse.data;
if (suggestionsResponse != null && suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
if (suggestionsResponse != null &&
suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -612,15 +727,155 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
);
}
/// Call Diagnosis API for Triage - This is called iteratively until shouldStop is true
Future<void> getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
Function(TriageDataDetails)? onSuccess,
Function(String)? onError,
}) async {
isTriageDiagnosisLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getDiagnosisForTriage(
age: age,
sex: sex,
evidenceIds: evidenceIds,
triageEvidence: triageEvidence,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
(failure) async {
isTriageDiagnosisLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isTriageDiagnosisLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
triageDataDetails = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch diagnosis');
}
}
},
);
}
/// Convenience method to start or continue the triage process
/// This automatically uses all selected evidence (symptoms + risk factors + suggestions)
Future<void> startOrContinueTriage({
Function()? onSuccess,
Function(String)? onError,
}) async {
// Validate user info
if (_selectedAge == null || _selectedGender == null) {
if (onError != null) {
onError('User information is incomplete');
}
return;
}
// Get all evidence IDs
final evidenceIds = getAllEvidenceIds();
if (evidenceIds.isEmpty) {
if (onError != null) {
onError('No evidence selected');
}
return;
}
await getDiagnosisForTriage(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
evidenceIds: evidenceIds,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
onSuccess();
}
},
onError: (error) {
if (onError != null) {
onError(error);
}
},
);
}
/// Select a triage choice option (for backward compatibility with single-item questions)
void selectTriageChoice(int choiceIndex) {
_selectedTriageChoiceIndex = choiceIndex;
notifyListeners();
}
/// Select a choice for a specific item (for multi-item questions)
void selectTriageChoiceForItem(String itemId, int choiceIndex) {
_selectedTriageChoicesByItemId[itemId] = choiceIndex;
notifyListeners();
}
/// Reset triage choice selection and increment question count
void resetTriageChoice() {
_selectedTriageChoiceIndex = null;
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount++; // Increment question count
notifyListeners();
}
/// Add triage evidence in the proper format
void addTriageEvidence(String itemId, String choiceId) {
_triageEvidenceList.add({
"id": itemId,
"choice_id": choiceId,
"source": "triage",
});
notifyListeners();
}
/// Get all triage evidence
List<Map<String, String>> getTriageEvidence() {
return List.from(_triageEvidenceList);
}
/// Clear triage evidence
void clearTriageEvidence() {
_triageEvidenceList.clear();
notifyListeners();
}
void reset() {
_currentView = BodyView.front;
_selectedOrganIds.clear();
_selectedSymptomsByOrgan.clear();
_selectedRiskFactorIds.clear();
_selectedSuggestionsIds.clear();
_triageEvidenceList.clear();
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount = 0; // Reset question count
bodySymptomResponse = null;
riskFactorsResponse = null;
suggestionsResponse = null;
triageDataDetails = null;
isTriageDiagnosisLoading = false;
_selectedTriageChoiceIndex = null;
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
@ -686,7 +941,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Calculate age from date of birth
final now = DateTime.now();
int age = now.year - dateOfBirth.year;
if (now.month < dateOfBirth.month || (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
if (now.month < dateOfBirth.month ||
(now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
age--;
}
_selectedAge = age;
@ -725,6 +981,44 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
};
}
Future<void> getSymptomsUserDetails({
required String userName,
required String password,
Function()? onSuccess,
Function(String)? onError,
}) async {
isBodySymptomsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getUserDetails(
userName: userName, password: password);
result.fold(
(failure) async {
isBodySymptomsLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isBodySymptomsLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
symptomsUserDetailsResponseModel = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess();
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch symptoms');
}
}
},
);
}
Future<void> getBodySymptomsByName({
required List<String> organNames,
Function(BodySymptomResponseModel)? onSuccess,
@ -735,6 +1029,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getBodySymptomsByName(
organNames: organNames,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(

@ -16,14 +16,19 @@ import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_mode
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class WaterMonitorViewModel extends ChangeNotifier {
WaterMonitorRepo waterMonitorRepo;
ErrorHandlerService errorHandlerService;
WaterMonitorViewModel({required this.waterMonitorRepo});
WaterMonitorViewModel({
required this.waterMonitorRepo,
required this.errorHandlerService,
});
// Controllers
final TextEditingController nameController = TextEditingController();
@ -190,9 +195,9 @@ class WaterMonitorViewModel extends ChangeNotifier {
identificationNo: identification,
);
result.fold((failure) {
log('Error fetching user progress: ${failure.message}');
}, (apiModel) {
result.fold(
(failure) => errorHandlerService.handleError(failure: failure),
(apiModel) {
log("User Progress Data ($_selectedDuration): ${apiModel.data.toString()}");
// Parse the response based on progress type
@ -300,10 +305,13 @@ class WaterMonitorViewModel extends ChangeNotifier {
identificationNo: identification,
);
result.fold((failure) {
_userDetailData = null;
if (onError != null) onError(failure.message);
}, (apiModel) {
result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
_userDetailData = null;
if (onError != null) onError(failure.message);
},
(apiModel) {
_userDetailData = apiModel.data;
// Populate form fields from the fetched data
@ -322,7 +330,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
if (_userDetailData == null) {
try {
_navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsScreen);
_navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsPage);
} catch (navErr) {
log('Navigation to water monitor settings failed: $navErr');
}
@ -686,6 +694,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
_validationError = failure.message;
_isLoading = false;
notifyListeners();
@ -1060,14 +1069,14 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
log('Error inserting user activity: ${failure.message}');
_isLoading = false;
notifyListeners();
return false;
},
(apiModel) {
(apiModel) async {
log("Insert user activity success: ${apiModel.data.toString()}");
// Update consumed amount and goal from the response
if (apiModel.data != null && apiModel.data is List && (apiModel.data as List).isNotEmpty) {
final progressData = (apiModel.data as List).first;
if (progressData is Map) {
@ -1090,7 +1099,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
// Refresh progress data to ensure consistency
fetchUserProgressForMonitoring();
await fetchUserProgressForMonitoring();
}
_isLoading = false;
@ -1135,12 +1144,13 @@ class WaterMonitorViewModel extends ChangeNotifier {
return result.fold(
(failure) {
errorHandlerService.handleError(failure: failure);
log('Error undoing user activity: ${failure.message}');
_isLoading = false;
notifyListeners();
return false;
},
(apiModel) {
(apiModel) async {
log("Undo user activity success: ${apiModel.data.toString()}");
// Update consumed amount and goal from the response
@ -1164,8 +1174,8 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
}
}
await fetchUserProgressForMonitoring();
}
fetchUserProgressForMonitoring();
_isLoading = false;
notifyListeners();
return true;

@ -36,6 +36,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -175,6 +176,9 @@ void main() async {
ChangeNotifierProvider<MyInvoicesViewModel>(
create: (_) => getIt.get<MyInvoicesViewModel>(),
),
ChangeNotifierProvider<HealthTrackersViewModel>(
create: (_) => getIt.get<HealthTrackersViewModel>(),
),
ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(),
)
@ -219,3 +223,4 @@ class MyApp extends StatelessWidget {
}
}
// flutter pub run easy_localization:generate -S assets/langs -f keys -o locale_keys.g.dart

@ -1,4 +1,9 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/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/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart';
@ -12,17 +17,12 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class HealthCalculatorDetailedPage extends StatefulWidget {
HealthCalculatorsTypeEnum calculatorType;
@ -50,8 +50,8 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox()
: Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
@ -172,58 +172,69 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMIWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.calories:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CaloriesWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bmr:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMRWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.idealBodyWeight:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: IdealBodyWeightWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bodyFat:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BodyFatWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.crabsProteinFat:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CrabsWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.ovulation:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: OvulationWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.deliveryDueDate:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: DeliveryDueDWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodSugar:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodSugarWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodCholesterol:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodCholesterolWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.triglycerides:
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: TriglyceridesWidget(onChange: onCalculate),
);
}

@ -282,7 +282,12 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
// IBW
void calculateIBW({required String heightText, required String weightText, required String heightUnit, required String weightUnit, required String bodyFrameSize}) {
void calculateIBW(
{required String heightText,
required String weightText,
required String heightUnit,
required String weightUnit,
required String bodyFrameSize}) {
if (heightText.trim().isEmpty || weightText.trim().isEmpty) {
ibwResult = null;
weightDifference = null;
@ -609,7 +614,20 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
}
String _getMonthName(int month) {
const List<String> months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const List<String> months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
return months[month - 1];
}
@ -828,7 +846,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier {
// expose map-like results for widgets to forward to parent
Map<String, dynamic>? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory};
Map<String, dynamic>? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
Map<String, dynamic>? get caloriesResultMap =>
caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
Map<String, dynamic>? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus};

@ -0,0 +1,567 @@
import 'dart:developer';
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class AddHealthTrackerEntryPage extends StatefulWidget {
final HealthTrackerTypeEnum trackerType;
const AddHealthTrackerEntryPage({
super.key,
required this.trackerType,
});
@override
State<AddHealthTrackerEntryPage> createState() => _AddHealthTrackerEntryPageState();
}
class _AddHealthTrackerEntryPageState extends State<AddHealthTrackerEntryPage> {
late DialogService dialogService;
// Controllers for date and time
final TextEditingController dateController = TextEditingController();
final TextEditingController timeController = TextEditingController();
@override
void initState() {
super.initState();
dialogService = getIt.get<DialogService>();
}
@override
void dispose() {
dateController.dispose();
timeController.dispose();
super.dispose();
}
/// Get page title based on tracker type
String _getPageTitle() {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return "Add Blood Sugar".needTranslation;
case HealthTrackerTypeEnum.bloodPressure:
return "Add Blood Pressure".needTranslation;
case HealthTrackerTypeEnum.weightTracker:
return "Add Weight".needTranslation;
}
}
/// Get success message based on tracker type
String _getSuccessMessage() {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return "Blood Sugar Data saved successfully".needTranslation;
case HealthTrackerTypeEnum.bloodPressure:
return "Blood Pressure Data saved successfully".needTranslation;
case HealthTrackerTypeEnum.weightTracker:
return "Weight Data saved successfully".needTranslation;
}
}
/// Save entry based on tracker type
Future<void> _saveEntry(HealthTrackersViewModel viewModel) async {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
await _saveBloodSugarEntry(viewModel);
break;
case HealthTrackerTypeEnum.bloodPressure:
await _saveBloodPressureEntry(viewModel);
break;
case HealthTrackerTypeEnum.weightTracker:
await _saveWeightEntry(viewModel);
break;
}
}
// Save Blood Sugar entry
Future<void> _saveBloodSugarEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveBloodSugarEntry(
dateTime: dateTime,
measureTime: viewModel.selectedBloodSugarMeasureTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Save Weight entry
Future<void> _saveWeightEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveWeightEntry(
dateTime: dateTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Save Blood Pressure entry
Future<void> _saveBloodPressureEntry(HealthTrackersViewModel viewModel) async {
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
// Combine date and time
final dateTime = "${dateController.text} ${timeController.text}";
// Call ViewModel method with callbacks
await viewModel.saveBloodPressureEntry(
dateTime: dateTime,
onSuccess: () {
LoaderBottomSheet.hideLoader();
_showSuccessAndPop();
},
onFailure: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
// Show success message and pop back
void _showSuccessAndPop() {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(
loadingText: _getSuccessMessage(),
),
callBackFunc: () {
Navigator.pop(context);
},
isCloseButtonVisible: false,
isDismissible: true,
isFullScreen: false,
);
}
// Reusable method to build selection row widget
Widget _buildSelectionRow({
required String value,
required String groupValue,
required VoidCallback onTap,
bool useUpperCase = false,
}) {
return SizedBox(
height: 70.h,
child: Row(
spacing: 8.h,
children: [
Radio<String>(
value: value,
groupValue: groupValue,
activeColor: AppColors.errorColor,
onChanged: (_) => onTap(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
(useUpperCase ? value.toUpperCase() : value.toCamelCase)
.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1)
.expanded,
],
).onPress(onTap),
);
}
// Reusable method to show selection bottom sheet
void _showSelectionBottomSheet({
required BuildContext context,
required String title,
required List<String> items,
required String selectedValue,
required Function(String) onSelected,
bool useUpperCase = false,
}) {
dialogService.showFamilyBottomSheetWithoutHWithChild(
label: title.needTranslation,
message: "",
child: Container(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.7),
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
child: ListView.separated(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return _buildSelectionRow(
value: item,
groupValue: selectedValue,
useUpperCase: useUpperCase,
onTap: () {
onSelected(item);
Navigator.pop(context);
},
);
},
separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor),
),
),
onOkPressed: () {},
);
}
// Blood Sugar unit selection
void _showBloodSugarUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Unit".needTranslation,
items: viewModel.bloodSugarUnit,
selectedValue: viewModel.selectedBloodSugarUnit,
onSelected: viewModel.setBloodSugarUnit,
useUpperCase: false,
);
}
// Blood Sugar measure time selection
void _showBloodSugarEntryTimeBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Measure Time".needTranslation,
items: viewModel.bloodSugarMeasureTimeEnList,
selectedValue: viewModel.selectedBloodSugarMeasureTime,
onSelected: viewModel.setBloodSugarMeasureTime,
useUpperCase: false,
);
}
// Weight unit selection
void _showWeightUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Unit".needTranslation,
items: viewModel.weightUnits,
selectedValue: viewModel.selectedWeightUnit,
onSelected: viewModel.setWeightUnit,
useUpperCase: false,
);
}
// Blood Pressure measured arm selection
void _showMeasuredArmSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) {
FocusScope.of(context).unfocus();
_showSelectionBottomSheet(
context: context,
title: "Select Arm".needTranslation,
items: viewModel.measuredArmList,
selectedValue: viewModel.selectedMeasuredArm,
onSelected: viewModel.setMeasuredArm,
useUpperCase: false,
);
}
// Reusable method to build text field
Widget _buildTextField(TextEditingController controller, String hintText, {TextInputType keyboardType = TextInputType.name}) {
return TextField(
controller: controller,
keyboardType: keyboardType,
maxLines: 1,
cursorHeight: 14.h,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isCollapsed: true,
hintText: hintText,
hintStyle: const TextStyle(color: Colors.grey),
),
style: TextStyle(
fontSize: 14.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
);
}
// Reusable method to build settings row
Widget _buildSettingsRow({
required String icon,
required String label,
String? value,
Widget? inputField,
String? unit,
VoidCallback? onUnitTap,
VoidCallback? onRowTap,
Color? iconColor,
bool showDivider = true,
}) {
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 40.w,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.greyColor,
borderRadius: 10.r,
hasShadow: false,
),
child: Center(child: Utils.buildSvgWithAssets(icon: icon, height: 22.w, width: 22.w, iconColor: iconColor)),
),
SizedBox(width: 12.w),
Expanded(
flex: unit != null ? 2 : 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
label.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
if (inputField != null)
Container(
height: 20.w,
alignment: Alignment.centerLeft,
child: inputField,
)
else if (value != null && value.isNotEmpty)
value.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
],
),
),
if (unit != null) ...[
Container(
width: 1.w,
height: 30.w,
color: AppColors.dividerColor,
).paddingOnly(right: 10.w),
Expanded(
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
"Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
unit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
],
),
SizedBox(width: 12.w),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
],
).onPress(onUnitTap ?? () {}),
),
] else if (onRowTap != null) ...[
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
SizedBox(width: 8.w),
],
],
).paddingSymmetrical(0.w, 16.w).onPress(onRowTap ?? () {}),
if (showDivider) Divider(height: 1, color: AppColors.dividerColor),
],
);
}
/// Build form fields based on tracker type
Widget _buildFormFields(HealthTrackersViewModel viewModel) {
switch (widget.trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return _buildBloodSugarForm(viewModel);
case HealthTrackerTypeEnum.bloodPressure:
return _buildBloodPressureForm(viewModel);
case HealthTrackerTypeEnum.weightTracker:
return _buildWeightForm(viewModel);
}
}
/// Blood Sugar form fields
Widget _buildBloodSugarForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.heightIcon,
label: "Enter Blood Sugar".needTranslation,
inputField: _buildTextField(viewModel.bloodSugarController, '', keyboardType: TextInputType.number),
unit: viewModel.selectedBloodSugarUnit,
onUnitTap: () => _showBloodSugarUnitSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
Divider(height: 1, color: AppColors.dividerColor),
_buildSettingsRow(
icon: AppAssets.weight_tracker_icon,
label: "Select Measure Time".needTranslation,
value: viewModel.selectedBloodSugarMeasureTime,
onRowTap: () => _showBloodSugarEntryTimeBottomSheet(context, viewModel),
),
],
);
}
/// Blood Pressure form fields
Widget _buildBloodPressureForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.bloodPressureIcon,
iconColor: AppColors.greyTextColor,
label: "Enter Systolic Value".needTranslation,
inputField: _buildTextField(viewModel.systolicController, '', keyboardType: TextInputType.number),
),
_buildSettingsRow(
icon: AppAssets.bloodPressureIcon,
iconColor: AppColors.greyTextColor,
label: "Enter Diastolic Value".needTranslation,
inputField: _buildTextField(viewModel.diastolicController, '', keyboardType: TextInputType.number),
),
_buildSettingsRow(
icon: AppAssets.bodyIcon,
iconColor: AppColors.greyTextColor,
label: "Select Arm".needTranslation,
value: viewModel.selectedMeasuredArm,
onRowTap: () => _showMeasuredArmSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
],
);
}
/// Weight form fields
Widget _buildWeightForm(HealthTrackersViewModel viewModel) {
return Column(
children: [
_buildSettingsRow(
icon: AppAssets.weightScale,
label: "Enter Weight".needTranslation,
inputField: _buildTextField(viewModel.weightController, '', keyboardType: TextInputType.number),
unit: viewModel.selectedWeightUnit,
onUnitTap: () => _showWeightUnitSelectionBottomSheet(context, viewModel),
),
_buildDateTimeFields(),
],
);
}
/// Common date and time fields
Widget _buildDateTimeFields() {
return Column(
children: [
TextInputWidget(
controller: dateController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Date",
hintText: "Select date".needTranslation,
focusNode: FocusNode(),
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.calendarGrey,
selectionType: SelectionTypeEnum.calendar,
isHideSwitcher: true,
onCalendarTypeChanged: (val) {},
onChange: (val) {
if (val == null) return;
try {
final parsedDate = DateTime.parse(val);
final formattedDate = DateFormat('dd MMM yyyy').format(parsedDate);
dateController.text = formattedDate;
log("date: $formattedDate");
} catch (e) {
dateController.text = val;
log("date: $val");
}
},
),
TextInputWidget(
controller: timeController,
isReadOnly: true,
isArrowTrailing: true,
labelText: "Time",
hintText: "Select time".needTranslation,
focusNode: FocusNode(),
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.calendarGrey,
selectionType: SelectionTypeEnum.time,
isHideSwitcher: true,
onCalendarTypeChanged: (val) {},
onChange: (val) {
if (val == null) return;
timeController.text = val;
log("time: $val");
},
),
],
);
}
@override
Widget build(BuildContext context) {
final viewModel = context.watch<HealthTrackersViewModel>();
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: _getPageTitle(),
bottomChild: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(24.w),
child: CustomButton(
text: "Save".needTranslation,
onPressed: () async => await _saveEntry(viewModel),
borderRadius: 12.r,
padding: EdgeInsets.symmetric(vertical: 14.h),
),
),
),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h),
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: _buildFormFields(viewModel),
),
),
),
);
}
}

@ -0,0 +1,118 @@
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/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
class HealthTrackersPage extends StatefulWidget {
const HealthTrackersPage({super.key});
@override
State<HealthTrackersPage> createState() => _HealthTrackersPageState();
}
Widget buildHealthTrackerCard({
required String icon,
required String title,
required String description,
required Color iconBgColor,
required VoidCallback onTap,
}) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: iconBgColor, borderRadius: 10.r),
height: 40.w,
width: 40.w,
child: Utils.buildSvgWithAssets(
icon: icon,
fit: BoxFit.none,
height: 22.w,
width: 22.w,
),
),
SizedBox(width: 12.w),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
title.toText16(weight: FontWeight.w600),
description.toText12(
fontWeight: FontWeight.w500,
color: Color(0xFF8F9AA3),
),
],
),
),
SizedBox(width: 12.w),
Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor,
),
],
).paddingAll(16.w),
).onPress(onTap);
}
class _HealthTrackersPageState extends State<HealthTrackersPage> {
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: "Health Trackers".needTranslation,
child: Column(
children: [
buildHealthTrackerCard(
iconBgColor: AppColors.primaryRedColor,
icon: AppAssets.bloodSugarOnlyIcon,
title: "Blood Sugar".needTranslation,
description: "Track your glucose levels, understand trends, and get personalized insights for better health.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.bloodSugar,
);
},
),
SizedBox(height: 16.h),
buildHealthTrackerCard(
iconBgColor: AppColors.infoColor,
icon: AppAssets.bloodPressureIcon,
title: "Blood Pressure".needTranslation,
description: "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.bloodPressure,
);
},
),
SizedBox(height: 16.h),
buildHealthTrackerCard(
iconBgColor: AppColors.successColor,
icon: AppAssets.weightIcon,
title: "Weight".needTranslation,
description: "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.".needTranslation,
onTap: () {
context.navigateWithName(
AppRoutes.healthTrackerDetailPage,
arguments: HealthTrackerTypeEnum.weightTracker,
);
},
),
],
).paddingSymmetrical(20.w, 24.h),
);
}
}

@ -0,0 +1,271 @@
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/enums.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/presentation/health_trackers/health_trackers_view_model.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:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class TrackerLastValueCard extends StatelessWidget {
final HealthTrackerTypeEnum trackerType;
const TrackerLastValueCard({super.key, required this.trackerType});
/// Get status text and color based on blood sugar value
(String status, Color color, Color bgColor) _getBloodSugarStatus(double value) {
if (value < 70) {
return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5));
} else if (value <= 100) {
return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
} else if (value <= 125) {
return ('Pre-diabetic'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4));
} else {
return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4));
}
}
/// Get status text and color based on blood pressure value (systolic)
(String status, Color color, Color bgColor) _getBloodPressureStatus(int systolic) {
if (systolic < 90) {
return ('Low'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.5));
} else if (systolic <= 120) {
return ('Normal'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
} else if (systolic <= 140) {
return ('Elevated'.needTranslation, AppColors.ratingColorYellow, AppColors.errorColor.withValues(alpha: 0.4));
} else {
return ('High'.needTranslation, AppColors.errorColor, AppColors.errorColor.withValues(alpha: 0.4));
}
}
/// Get status for weight (neutral - no good/bad status)
(String status, Color color, Color bgColor) _getWeightStatus() {
return ('Recorded'.needTranslation, AppColors.successColor, AppColors.successLightBgColor);
}
/// Get default unit based on tracker type
String _getDefaultUnit() {
switch (trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
return 'mg/dL';
case HealthTrackerTypeEnum.bloodPressure:
return 'mmHg';
case HealthTrackerTypeEnum.weightTracker:
return 'kg';
}
}
@override
Widget build(BuildContext context) {
return Consumer<HealthTrackersViewModel>(
builder: (context, viewModel, child) {
// Get the last record based on tracker type
dynamic lastRecord;
String displayValue = '--';
String unit = _getDefaultUnit();
DateTime? lastDate;
String status = '';
Color statusColor = AppColors.greyTextColor;
switch (trackerType) {
case HealthTrackerTypeEnum.bloodSugar:
final allResults = [
...viewModel.weekDiabeticPatientResult,
...viewModel.monthDiabeticPatientResult,
...viewModel.yearDiabeticPatientResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.dateChart ?? DateTime(1900);
final dateB = b.dateChart ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final lastValue = lastRecord.resultValue?.toDouble() ?? 0.0;
displayValue = lastValue.toStringAsFixed(0);
unit = lastRecord.unit ?? 'mg/dL';
lastDate = lastRecord.dateChart;
final (s, c, _) = _getBloodSugarStatus(lastValue);
status = s;
statusColor = c;
}
break;
case HealthTrackerTypeEnum.bloodPressure:
final allResults = [
...viewModel.weekBloodPressureResult,
...viewModel.monthBloodPressureResult,
...viewModel.yearBloodPressureResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.bloodPressureDate ?? DateTime(1900);
final dateB = b.bloodPressureDate ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final systolic = lastRecord.systolicePressure ?? 0;
final diastolic = lastRecord.diastolicPressure ?? 0;
displayValue = '$systolic/$diastolic';
unit = 'mmHg';
lastDate = lastRecord.bloodPressureDate;
final (s, c, _) = _getBloodPressureStatus(systolic);
status = s;
statusColor = c;
}
break;
case HealthTrackerTypeEnum.weightTracker:
final allResults = [
...viewModel.weekWeightMeasurementResult,
...viewModel.monthWeightMeasurementResult,
...viewModel.yearWeightMeasurementResult,
];
if (allResults.isNotEmpty) {
allResults.sort((a, b) {
final dateA = a.weightDate ?? DateTime(1900);
final dateB = b.weightDate ?? DateTime(1900);
return dateB.compareTo(dateA);
});
lastRecord = allResults.first;
final weightValue = lastRecord.weightMeasured?.toDouble() ?? 0.0;
displayValue = weightValue.toStringAsFixed(0);
unit = lastRecord.unit ?? 'kg';
lastDate = lastRecord.weightDate;
final (s, c, _) = _getWeightStatus();
status = s;
statusColor = c;
}
break;
}
final formattedDate = lastDate != null ? DateFormat('EEE DD MMM, yy').format(lastDate) : DateFormat('EEE DD MMM, yy').format(DateTime.now());
// Show shimmer while loading
if (viewModel.isLoading) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Shimmer.fromColors(
baseColor: AppColors.shimmerBaseColor,
highlightColor: AppColors.shimmerHighlightColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 40.h,
width: 120.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8.r),
),
),
SizedBox(height: 8.h),
Row(
children: [
Container(
height: 32.h,
width: 150.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(16.r),
),
),
SizedBox(width: 8.w),
Container(
height: 32.h,
width: 80.w,
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(16.r),
),
),
],
),
],
),
),
);
}
// Show empty state if no records
if (lastRecord == null) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
"--".toText32(isBold: true, color: AppColors.greyTextColor),
SizedBox(width: 6.w),
unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h),
],
),
SizedBox(height: 8.h),
AppCustomChipWidget(
labelText: "No records yet".needTranslation,
icon: AppAssets.doctor_calendar_icon,
),
],
),
);
}
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
displayValue.toText32(isBold: true, color: statusColor),
SizedBox(width: 6.w),
unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h),
],
),
SizedBox(height: 8.h),
Row(
children: [
AppCustomChipWidget(
labelText: "${"Last Record".needTranslation}: $formattedDate",
icon: AppAssets.doctor_calendar_icon,
),
SizedBox(width: 8.w),
if (trackerType != HealthTrackerTypeEnum.weightTracker) ...[
AppCustomChipWidget(
labelText: status.needTranslation,
icon: AppAssets.normalStatusGreenIcon,
iconColor: statusColor,
),
]
],
),
],
),
);
},
);
}
}

@ -45,12 +45,13 @@ class ServicesPage extends StatelessWidget {
late final List<HmgServicesComponentModel> hmgServices = [
HmgServicesComponentModel(
11,
"Emergency Services".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true, route: null, onTap: () {
11,
"Emergency Services".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true,
route: null, onTap: () {
getIt.get<EmergencyServicesViewModel>().flushData();
getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
showLoader: false,
@ -172,6 +173,15 @@ class ServicesPage extends StatelessWidget {
];
late final List<HmgServicesComponentModel> hmgHealthToolServices = [
HmgServicesComponentModel(
11,
"Health Trackers".needTranslation,
"".needTranslation,
AppAssets.general_health,
bgColor: AppColors.whiteColor,
true,
route: AppRoutes.healthTrackersPage,
),
HmgServicesComponentModel(
11,
"Daily Water Monitor".needTranslation,
@ -189,14 +199,14 @@ class ServicesPage extends StatelessWidget {
LoaderBottomSheet.hideLoader();
if (userDetail == null) {
waterMonitorVM.populateFromAuthenticatedUser();
context.navigateWithName(AppRoutes.waterMonitorSettingsScreen);
context.navigateWithName(AppRoutes.waterMonitorSettingsPage);
} else {
context.navigateWithName(AppRoutes.waterConsumptionScreen);
context.navigateWithName(AppRoutes.waterConsumptionPage);
}
},
onError: (error) {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.waterConsumptionScreen);
context.navigateWithName(AppRoutes.waterConsumptionPage);
},
);
},

@ -537,7 +537,7 @@ class _LandingPageState extends State<LandingPage> {
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
padding: EdgeInsets.all(16.h),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),

@ -9,10 +9,11 @@ import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_config.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/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
@ -243,9 +244,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
onChipTap: () {
navigationService.pushPage(
page: FamilyMedicalScreen(
profiles: medicalFileViewModel.patientFamilyFiles,
onSelect: (FamilyFileResponseModelLists p1) {},
page: FamilyMedicalScreen(
profiles: medicalFileViewModel.patientFamilyFiles,
onSelect: (FamilyFileResponseModelLists p1) {},
),
);
},
@ -288,7 +289,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
iconSize: 12.w,
backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
backgroundColor:
insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
);
}),
@ -399,9 +401,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w,
height: 8.h,
decoration: BoxDecoration(
color: hmgServicesVM.vitalSignCurrentPage == index
? AppColors.primaryRedColor
: AppColors.dividerColor,
color: hmgServicesVM.vitalSignCurrentPage == index ? AppColors.primaryRedColor : AppColors.dividerColor,
borderRadius: BorderRadius.circular(4.r),
),
),
@ -605,7 +605,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? Container(
padding: EdgeInsets.all(12.w),
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
@ -642,57 +643,58 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
onRescheduleTap: () {
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
},
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
await myAppointmentsViewModel.isDoctorAvailable(
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel:
myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
},
onError: (_) {
LoaderBottomSheet.hideLoader();
print("Doctor is not available");
}
},
onError: (_) {
LoaderBottomSheet.hideLoader();
},
);
},
},
);
},
),
),
),
),
));
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
),
@ -751,116 +753,125 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: Column(
children: [
ListView.separated(
itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2,
itemCount:
prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2,
shrinkWrap: true,
padding: EdgeInsets.only(left: 0, right: 8.w),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Row(
children: [
Image.network(
prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!,
width: 40.w,
height: 40.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Row(
children: [
Image.network(
prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!,
width: 40.w,
height: 40.h,
fit: BoxFit.cover,
).circle(100.r),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true),
SizedBox(height: 4.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(
labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(
prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
),
),
),
],
),
],
],
),
],
),
),
),
// SizedBox(width: 40.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
// SizedBox(width: 40.h),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
width: 15.w,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
SizedBox(width: 6.w),
Expanded(
child: CustomButton(
text: "All Medications".needTranslation,
onPressed: () {},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
],
),
],
),
],
),
],
),
),
).paddingSymmetrical(0.w, 0.h)
@ -914,7 +925,10 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
("Dr. John Smith Smith Smith")
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: true),
],
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -941,58 +955,58 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
shrinkWrap: true,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SizedBox(
// width: 80.w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.w,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
Expanded(
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID,
projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID,
doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
));
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SizedBox(
// width: 80.w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.network(
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.w,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
SizedBox(height: 8.h),
Expanded(
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
).onPress(() async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID,
projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID,
doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID,
));
LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@ -1101,9 +1115,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
@ -1307,7 +1326,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.blood_sugar_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)),
MedicalFileCard(
label: "Blood Pressure".needTranslation,
textColor: AppColors.blackColor,
@ -1315,7 +1334,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.lab_result_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)),
MedicalFileCard(
label: "Weight Tracker".needTranslation,
textColor: AppColors.blackColor,
@ -1323,7 +1342,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.weight_tracker_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {}),
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.weightTracker)),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
@ -1624,7 +1643,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
),
),
],
),
),
@ -1632,6 +1650,3 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}
}

@ -10,9 +10,11 @@ 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/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class OrganSelectorPage extends StatefulWidget {
@ -24,25 +26,38 @@ class OrganSelectorPage extends StatefulWidget {
class _OrganSelectorPageState extends State<OrganSelectorPage> {
late final AppState _appState;
late final DialogService dialogService;
@override
void initState() {
super.initState();
_appState = getIt.get<AppState>();
dialogService = getIt<DialogService>();
}
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
void _onNextPressed(SymptomsCheckerViewModel viewModel) async {
if (!viewModel.validateSelection()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one organ'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one organ'.needTranslation,
);
return;
}
LoaderBottomSheet.showLoader(loadingText: "Please wait".needTranslation);
final String userName = 'guest_user';
final String password = '123456';
context.navigateWithName(AppRoutes.symptomsSelectorScreen);
await viewModel.getSymptomsUserDetails(
userName: userName,
password: password,
onSuccess: () {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.symptomsSelectorPage);
},
onError: (String error) {
LoaderBottomSheet.hideLoader();
},
);
}
@override

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/services/dialog_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/appbar/collapsing_list_view.dart';
@ -18,8 +19,8 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class PossibleConditionsScreen extends StatelessWidget {
const PossibleConditionsScreen({super.key});
class PossibleConditionsPage extends StatelessWidget {
const PossibleConditionsPage({super.key});
Widget _buildLoadingShimmer() {
return ListView.separated(
@ -44,7 +45,7 @@ class PossibleConditionsScreen extends StatelessWidget {
);
}
Widget _buildPredictionsList(List<ConditionsModel> conditions) {
Widget _buildPredictionsList(BuildContext context, List<ConditionsModel> conditions) {
if (conditions.isEmpty) {
return Center(
child: Padding(
@ -60,6 +61,8 @@ class PossibleConditionsScreen extends StatelessWidget {
);
}
final dialogService = getIt<DialogService>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -82,11 +85,8 @@ class PossibleConditionsScreen extends StatelessWidget {
description: conditionModel.description,
possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum,
onActionPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('We are not available for a week. May you Rest In Peace :('),
backgroundColor: AppColors.primaryRedColor,
),
dialogService.showErrorBottomSheet(
message: 'We are not available for a week. May you Rest In Peace :(',
);
},
);
@ -168,7 +168,7 @@ class PossibleConditionsScreen extends StatelessWidget {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) {
return _buildLoadingShimmer();
}
return _buildPredictionsList(dummyConditions);
return _buildPredictionsList(context, dummyConditions);
},
),
),

@ -2,11 +2,13 @@ import 'package:flutter/gestures.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/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -20,9 +22,12 @@ class RiskFactorsScreen extends StatefulWidget {
}
class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Fetch risk factors based on selected symptoms
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -36,13 +41,10 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedRiskFactors) {
context.navigateWithName(AppRoutes.suggestionsScreen);
context.navigateWithName(AppRoutes.suggestionsPage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one risk before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one risk before proceeding'.needTranslation,
);
}
}

@ -1,11 +1,13 @@
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -19,9 +21,12 @@ class SuggestionsScreen extends StatefulWidget {
}
class _SuggestionsScreenState extends State<SuggestionsScreen> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -38,13 +43,10 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSuggestions) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.triageScreen);
context.navigateWithName(AppRoutes.triagePage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one option before proceeding'.needTranslation,
);
}
}

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -15,17 +17,20 @@ import 'package:hmg_patient_app_new/widgets/chip/custom_selectable_chip.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
class SymptomsSelectorScreen extends StatefulWidget {
const SymptomsSelectorScreen({super.key});
class SymptomsSelectorPage extends StatefulWidget {
const SymptomsSelectorPage({super.key});
@override
State<SymptomsSelectorScreen> createState() => _SymptomsSelectorScreenState();
State<SymptomsSelectorPage> createState() => _SymptomsSelectorPageState();
}
class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -36,13 +41,10 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.riskFactorsScreen);
context.navigateWithName(AppRoutes.riskFactorsPage);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one symptom before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
dialogService.showErrorBottomSheet(
message: 'Please select at least one symptom before proceeding'.needTranslation,
);
}
}

@ -1,89 +1,213 @@
import 'dart:developer';
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/data/triage_questions_data.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/triage_question_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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:lottie/lottie.dart';
import 'package:provider/provider.dart';
class TriageScreen extends StatefulWidget {
const TriageScreen({super.key});
class TriagePage extends StatefulWidget {
const TriagePage({super.key});
@override
State<TriageScreen> createState() => _TriageScreenState();
State<TriagePage> createState() => _TriagePageState();
}
class _TriageScreenState extends State<TriageScreen> {
late List<TriageQuestionModel> triageQuestions;
int currentQuestionIndex = 0;
class _TriagePageState extends State<TriagePage> {
late SymptomsCheckerViewModel viewModel;
late DialogService dialogService;
@override
void initState() {
super.initState();
triageQuestions = TriageQuestionsData.getSampleTriageQuestions();
viewModel = context.read<SymptomsCheckerViewModel>();
dialogService = getIt.get<DialogService>();
// Start triage process when screen loads
WidgetsBinding.instance.addPostFrameCallback((_) {
_startTriage();
});
}
TriageQuestionModel get currentQuestion => triageQuestions[currentQuestionIndex];
void _startTriage() {
viewModel.startOrContinueTriage(
onSuccess: () {
_handleTriageResponse();
},
onError: (error) {
dialogService.showErrorBottomSheet(
message: error,
onOkPressed: () => context.pop(),
);
},
);
}
bool get isFirstQuestion => currentQuestionIndex == 0;
void _handleTriageResponse() {
// Case 1: Emergency evidence detected
if (viewModel.hasEmergencyEvidence) {
_showEmergencyDialog();
return;
}
bool get isLastQuestion => currentQuestionIndex == triageQuestions.length - 1;
// Get the highest probability condition
final conditions = viewModel.currentConditions ?? [];
double highestProbability = 0.0;
void _onOptionSelected(int optionIndex) {
setState(() {
currentQuestion.selectOption(optionIndex);
});
if (conditions.isNotEmpty) {
final sortedConditions = List<TriageCondition>.from(conditions);
sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0));
highestProbability = (sortedConditions.first.probability ?? 0.0) * 100;
}
// Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered
if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) {
// Navigate to results/possible conditions screen
context.navigateWithName(AppRoutes.possibleConditionsPage);
return;
}
// Continue triage - question is loaded, reset selection for new question
viewModel.resetTriageChoice();
}
void _showEmergencyDialog() {
showCommonBottomSheetWithoutHeight(
context,
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.primaryRedColor,
borderRadius: 24.h,
),
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".toText14(),
Utils.buildSvgWithAssets(
icon: AppAssets.cancel_circle_icon,
iconColor: AppColors.whiteColor,
width: 24.h,
height: 24.h,
fit: BoxFit.contain,
).onPress(() {
Navigator.of(context).pop();
}),
],
),
Lottie.asset(AppAnimations.ambulanceAlert,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain),
SizedBox(height: 8.h),
"Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true),
SizedBox(height: 8.h),
"Emergency evidence detected. Please seek medical attention."
.needTranslation
.toText14(color: AppColors.whiteColor, weight: FontWeight.w500),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.confirm.tr(context: context),
onPressed: () async => Navigator.of(context).pop(),
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.whiteColor,
textColor: AppColors.primaryRedColor,
icon: AppAssets.checkmark_icon,
iconColor: AppColors.primaryRedColor,
),
SizedBox(height: 8.h),
],
),
),
),
isFullScreen: false,
isCloseButtonVisible: false,
hasBottomPadding: false,
backgroundColor: AppColors.primaryRedColor,
callBackFunc: () {},
);
}
bool get isFirstQuestion => viewModel.getTriageEvidence().isEmpty;
void _onOptionSelectedForItem(String itemId, int choiceIndex) {
viewModel.selectTriageChoiceForItem(itemId, choiceIndex);
}
void _onPreviousPressed() {
if (!isFirstQuestion) {
setState(() {
currentQuestionIndex--;
});
}
context.pop();
}
void _onNextPressed() {
if (currentQuestion.isAnswered) {
currentQuestion.confirmSelection();
if (isLastQuestion) {
context.navigateWithName(AppRoutes.possibleConditionsScreen);
} else {
setState(() {
currentQuestionIndex++;
});
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select an option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
final currentQuestion = viewModel.currentTriageQuestion;
if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) {
dialogService.showErrorBottomSheet(
message: 'No question items available'.needTranslation,
);
return;
}
}
_buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to restart the organ selection?".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
// Check if all items have been answered
if (!viewModel.areAllTriageItemsAnswered) {
dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation);
return;
}
// Collect all evidence from all items
for (var item in currentQuestion.items!) {
final itemId = item.id ?? "";
if (itemId.isEmpty) continue;
final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId);
if (selectedChoiceIndex == null) continue;
if (item.choices != null && selectedChoiceIndex < item.choices!.length) {
final selectedChoice = item.choices![selectedChoiceIndex];
final choiceId = selectedChoice.id ?? "";
if (choiceId.isNotEmpty) {
viewModel.addTriageEvidence(itemId, choiceId);
}
}
}
// Get all evidence: initial symptoms + risk factors + suggestions + triage evidence
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence();
log("initialEvidenceIds: ${initialEvidenceIds.toString()}");
log("triageEvidence: ${triageEvidence.toString()}");
// Call API with updated evidence
viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!,
sex: viewModel.selectedGender!.toLowerCase(),
evidenceIds: initialEvidenceIds,
triageEvidence: triageEvidence,
language: viewModel.appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
_handleTriageResponse();
},
onError: (error) {
dialogService.showErrorBottomSheet(message: error);
},
);
}
@ -91,45 +215,137 @@ class _TriageScreenState extends State<TriageScreen> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: "Triage".needTranslation,
// onLeadingTapped: () => _buildConfirmationBottomSheet(
// context: context,
// onConfirm: () => {
// context.pop(),
// context.pop(),
// }),
leadingCallback: () => context.pop(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
_buildQuestionCard(),
],
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, child) {
// Show normal question UI
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "Triage".needTranslation,
leadingCallback: () => _showConfirmationBeforeExit(context),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
_buildQuestionCard(viewModel),
],
),
),
),
_buildStickyBottomCard(context, viewModel),
],
);
},
),
);
}
Widget _buildLoadingShimmer() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
// Create 2-3 shimmer cards
...List.generate(1, (index) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: _buildShimmerCard(),
);
}),
],
);
}
Widget _buildShimmerCard() {
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Shimmer title
Container(
height: 40.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
).toShimmer2(isShow: true, radius: 24.r),
SizedBox(height: 16.h),
// Shimmer chips
Wrap(
runSpacing: 12.h,
spacing: 8.w,
children: List.generate(4, (index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
border: Border.all(color: AppColors.bottomNAVBorder, width: 1),
),
child: Text(
'Not Applicable Suggestion',
style: TextStyle(fontSize: 14.f, color: AppColors.textColor),
),
).toShimmer2(isShow: true, radius: 24.r);
}),
),
_buildStickyBottomCard(context),
],
),
);
}
Widget _buildQuestionCard() {
void _showConfirmationBeforeExit(BuildContext context) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () {
Navigator.pop(context);
context.pop();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
Widget _buildQuestionCard(SymptomsCheckerViewModel viewModel) {
if (viewModel.isTriageDiagnosisLoading) {
return _buildLoadingShimmer();
}
if (viewModel.currentTriageQuestion == null) {
return Center(
child: "No question available".needTranslation.toText16(weight: FontWeight.w500),
);
}
final question = viewModel.currentTriageQuestion;
if (question == null || question.items == null || question.items!.isEmpty) {
return SizedBox.shrink();
}
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
));
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
),
);
return SlideTransition(
position: offsetAnimation,
@ -140,7 +356,7 @@ class _TriageScreenState extends State<TriageScreen> {
);
},
child: Container(
key: ValueKey<int>(currentQuestionIndex),
key: ValueKey<String>(question.items!.first.id ?? viewModel.getTriageEvidence().length.toString()),
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
@ -148,11 +364,36 @@ class _TriageScreenState extends State<TriageScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
currentQuestion.question.toText16(weight: FontWeight.w500),
// Main question text
(question.text ?? "").toText16(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 24.h),
...List.generate(currentQuestion.options.length, (index) {
bool selected = currentQuestion.selectedOptionIndex == index;
return _buildOptionItem(index, selected, currentQuestion.options[index].text);
// Show all items with dividers
...List.generate(question.items!.length, (itemIndex) {
final item = question.items![itemIndex];
final itemId = item.id ?? "";
final choices = item.choices ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Item name (sub-question)
(item.name ?? "").toText14(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 8.h),
// Choices for this item
...List.generate(choices.length, (choiceIndex) {
bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex;
return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? "");
}),
// Add divider between items (but not after the last one)
if (itemIndex < question.items!.length - 1) ...[
SizedBox(height: 8.h),
Divider(color: AppColors.bottomNAVBorder, thickness: 1),
SizedBox(height: 10.h),
],
],
);
}),
],
),
@ -160,9 +401,9 @@ class _TriageScreenState extends State<TriageScreen> {
);
}
Widget _buildOptionItem(int index, bool selected, String optionText) {
Widget _buildOptionItem(String itemId, int choiceIndex, bool selected, String optionText) {
return GestureDetector(
onTap: () => _onOptionSelected(index),
onTap: () => _onOptionSelectedForItem(itemId, choiceIndex),
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Row(
@ -181,16 +422,29 @@ class _TriageScreenState extends State<TriageScreen> {
child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(child: optionText.toText14(weight: FontWeight.w500)),
Expanded(child: optionText.toText13(weight: FontWeight.w500)),
],
),
),
);
}
Widget _buildStickyBottomCard(BuildContext context) {
final currentScore = TriageQuestionsData.calculateTotalScore(triageQuestions);
final suggestedCondition = TriageQuestionsData.getSuggestedCondition(currentScore);
Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) {
// Get the top condition with highest probability
final conditions = viewModel.currentConditions ?? [];
String suggestedCondition = "Analyzing...";
double probability = 0.0;
if (conditions.isNotEmpty) {
// Sort by probability descending
final sortedConditions = List<TriageCondition>.from(conditions);
sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0));
final topCondition = sortedConditions.first;
suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown";
probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage
}
// final bool isHighConfidence = probability >= 70.0;
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
@ -223,14 +477,14 @@ class _TriageScreenState extends State<TriageScreen> {
),
SizedBox(height: 16.h),
CustomRoundedProgressBar(
percentage: currentScore,
percentage: probability.toInt(),
paddingBetween: 5.h,
color: AppColors.primaryRedColor,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.17),
height: 8.h,
titleWidget: RichText(
text: TextSpan(
text: "$currentScore% ",
text: "${probability.toStringAsFixed(1)}% ",
style: TextStyle(
color: AppColors.primaryRedColor,
fontWeight: FontWeight.w600,
@ -249,6 +503,8 @@ class _TriageScreenState extends State<TriageScreen> {
),
),
),
// Show high confidence message
SizedBox(height: 12.h),
Row(
children: [
@ -256,7 +512,7 @@ class _TriageScreenState extends State<TriageScreen> {
child: CustomButton(
text: "Previous".needTranslation,
onPressed: isFirstQuestion ? () {} : _onPreviousPressed,
isDisabled: isFirstQuestion,
isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
@ -266,7 +522,8 @@ class _TriageScreenState extends State<TriageScreen> {
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: isLastQuestion ? "Finish".needTranslation : "Next".needTranslation,
text: "Next".needTranslation,
isDisabled: viewModel.isTriageDiagnosisLoading,
onPressed: _onNextPressed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -3,6 +3,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/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -90,8 +91,8 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(weight: FontWeight.w500),
subTitle.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500),
title.toText14(weight: FontWeight.w500),
subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
@ -106,7 +107,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Widget _getDivider() {
return Divider(
color: AppColors.dividerColor,
).paddingSymmetrical(0, 16.h);
).paddingSymmetrical(0, 8.h);
}
@override
@ -114,8 +115,25 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
AppState appState = getIt.get<AppState>();
String name = "";
int? userAgeFromDOB;
if (appState.isAuthenticated) {
name = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!} ";
final user = appState.getAuthenticatedUser();
name = "${user!.firstName!} ${user.lastName!} ";
// Calculate age from authenticated user's DOB if available
if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) {
try {
DateTime dob = DateUtil.convertStringToDate(user.dateofBirth!);
final now = DateTime.now();
int age = now.year - dob.year;
if (now.month < dob.month || (now.month == dob.month && now.day < dob.day)) {
age--;
}
userAgeFromDOB = age;
} catch (e) {
// If date parsing fails, ignore
}
}
} else {
name = "Guest";
}
@ -132,8 +150,9 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
// Get display values
String genderText = viewModel.selectedGender ?? "Not set";
// Show age calculated from DOB, not the DOB itself
String ageText = viewModel.selectedAge != null ? "${viewModel.selectedAge} Years" : "Not set";
// Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB)
int? displayAge = viewModel.selectedAge ?? userAgeFromDOB;
String ageText = displayAge != null ? "$displayAge Years" : "Not set";
String heightText =
viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : "Not set";
String weightText =
@ -144,7 +163,7 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
Expanded(
child: CollapsingListView(
title: "Symptoms Checker".needTranslation,
isLeading: Navigator.canPop(context),
isLeading: true,
child: SingleChildScrollView(
child: Column(
children: [
@ -154,11 +173,11 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
children: [
"Hello $name, Is your information up to date?".needTranslation.toText18(
"Hello $name, Is your information up to date?".needTranslation.toText16(
weight: FontWeight.w600,
color: AppColors.textColor,
),
SizedBox(height: 24.h),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,
leadingIcon: AppAssets.genderIcon,
@ -228,48 +247,46 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.all(8.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: CustomButton(
text: "No, Edit all".needTranslation,
icon: AppAssets.edit_icon,
iconColor: AppColors.primaryRedColor,
onPressed: () {
context.read<SymptomsCheckerViewModel>().setUserInfoPage(0, isSinglePageEdit: false);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 24.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "No, Edit all".needTranslation,
icon: AppAssets.edit_icon,
iconColor: AppColors.primaryRedColor,
onPressed: () {
context.read<SymptomsCheckerViewModel>().setUserInfoPage(0, isSinglePageEdit: false);
context.navigateWithName(AppRoutes.userInfoFlowManager);
},
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: "Yes, It is".needTranslation,
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),
backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
fontSize: 16.f,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
text: "Yes, It is".needTranslation,
icon: AppAssets.tickIcon,
iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
onPressed: hasEmptyFields
? () {} // Empty function for disabled state
: () => context.navigateWithName(AppRoutes.organSelectorPage),
backgroundColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
borderColor: hasEmptyFields ? AppColors.greyLightColor : AppColors.primaryRedColor,
textColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor,
fontSize: 16.f,
),
],
),
],
).paddingSymmetrical(24.w, 0),
),
),
],
),
],
).paddingSymmetrical(24.w, 0),
),
);
}

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -35,7 +33,6 @@ class AgeSelectionPage extends StatelessWidget {
initialDate: symptomsViewModel.dateOfBirth ?? DateTime(2000, 1, 1),
onDateChanged: (date) {
symptomsViewModel.setDateOfBirth(date);
log('DOB saved: $date, Age: ${symptomsViewModel.selectedAge}');
},
)
],

@ -21,14 +21,14 @@ import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class WaterConsumptionScreen extends StatefulWidget {
const WaterConsumptionScreen({super.key});
class WaterConsumptionPage extends StatefulWidget {
const WaterConsumptionPage({super.key});
@override
State<WaterConsumptionScreen> createState() => _WaterConsumptionScreenState();
State<WaterConsumptionPage> createState() => _WaterConsumptionPageState();
}
class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
class _WaterConsumptionPageState extends State<WaterConsumptionPage> {
@override
void initState() {
super.initState();
@ -98,37 +98,14 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"History".needTranslation.toText16(isBold: true),
Row(
children: [
InkWell(
onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: Container(
key: ValueKey<bool>(viewModel.isGraphView),
child: Utils.buildSvgWithAssets(
icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
height: 24.h,
width: 24.h,
),
),
),
),
"History".needTranslation.toText16(isBold: true),
SizedBox(width: 8.w),
InkWell(
onTap: () => _showHistoryDurationBottomsheet(context, viewModel),
child: Container(
padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 6.h),
padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 6.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
backgroundColor: AppColors.greyColor,
borderRadius: 8.r,
@ -137,13 +114,36 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
child: Row(
children: [
viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h),
],
),
),
)
],
),
InkWell(
onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: Container(
key: ValueKey<bool>(viewModel.isGraphView),
child: Utils.buildSvgWithAssets(
icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
height: 24.h,
width: 24.h,
),
),
),
),
],
),
if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else ...[SizedBox(height: 16.h), _buildHistoryGraph()]

@ -10,16 +10,25 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart';
class WaterMonitorSettingsScreen extends StatefulWidget {
const WaterMonitorSettingsScreen({super.key});
class WaterMonitorSettingsPage extends StatefulWidget {
const WaterMonitorSettingsPage({super.key});
@override
State<WaterMonitorSettingsScreen> createState() => _WaterMonitorSettingsScreenState();
State<WaterMonitorSettingsPage> createState() => _WaterMonitorSettingsPageState();
}
class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen> {
class _WaterMonitorSettingsPageState extends State<WaterMonitorSettingsPage> {
late DialogService dialogService;
@override
void initState() {
super.initState();
dialogService = getIt.get<DialogService>();
}
// No need to call initialize() here since it's already called in water_consumption_screen
// The ViewModel is shared via Provider, so data is already loaded
@ -59,8 +68,6 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
required Function(String) onSelected,
bool useUpperCase = false,
}) {
final dialogService = getIt.get<DialogService>();
dialogService.showFamilyBottomSheetWithoutHWithChild(
label: title.needTranslation,
message: "",
@ -263,9 +270,20 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
onPressed: () async {
final success = await viewModel.saveSettings();
if (!success && viewModel.validationError != null) {
_showSnackBar(context, viewModel.validationError!);
dialogService.showErrorBottomSheet(
message: viewModel.validationError!,
);
} else if (success) {
_showSnackBar(context, "Settings saved successfully");
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(
loadingText: "Settings saved successfully".needTranslation,
),
callBackFunc: () {},
isCloseButtonVisible: false,
isDismissible: true,
isFullScreen: false,
);
}
},
borderRadius: 12.r,
@ -340,16 +358,4 @@ class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen>
),
);
}
// Show snackbar for validation errors and success messages
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
backgroundColor: message.contains('successfully') ? Colors.green : AppColors.errorColor,
),
);
}
}

@ -18,23 +18,29 @@ class WaterActionButtonsWidget extends StatelessWidget {
return Consumer<WaterMonitorViewModel>(builder: (context, vm, _) {
final cupAmount = vm.selectedCupCapacityMl;
final isGoalAchieved = vm.progressPercent >= 100 || vm.nextDrinkTime.toLowerCase().contains('goal achieved');
final isDisabled = vm.isLoading || isGoalAchieved;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () async {
if (cupAmount > 0) {
await vm.undoUserActivity();
}
},
child: Utils.buildSvgWithAssets(
icon: AppAssets.minimizeIcon,
height: 20.h,
width: 20.h,
iconColor: AppColors.textColor,
Opacity(
opacity: vm.isLoading ? 0.4 : 1.0,
child: InkWell(
onTap: vm.isLoading
? null
: () async {
if (cupAmount > 0) {
await vm.undoUserActivity();
}
},
child: Utils.buildSvgWithAssets(
icon: AppAssets.minimizeIcon,
height: 20.h,
width: 20.h,
iconColor: AppColors.textColor,
),
),
),
Container(
@ -51,9 +57,9 @@ class WaterActionButtonsWidget extends StatelessWidget {
),
),
Opacity(
opacity: isGoalAchieved ? 0.4 : 1.0,
opacity: isDisabled ? 0.4 : 1.0,
child: InkWell(
onTap: isGoalAchieved
onTap: isDisabled
? null
: () async {
if (cupAmount > 0) {
@ -88,7 +94,7 @@ class WaterActionButtonsWidget extends StatelessWidget {
),
_buildActionButton(
context: context,
onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsScreen),
onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsPage),
title: "Settings".needTranslation,
icon: Icon(
Icons.settings,

@ -6,8 +6,12 @@ import 'package:hmg_patient_app_new/presentation/authentication/register_step2.d
import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/covid19test/covid19_landing_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
@ -23,12 +27,10 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_sele
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_screen.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_screen.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
import '../presentation/covid19test/covid19_landing_page.dart';
class AppRoutes {
static const String initialRoute = '/initialRoute';
static const String loginScreen = '/loginScreen';
@ -47,25 +49,29 @@ class AppRoutes {
static const String huaweiHealthExample = '/huaweiHealthExample';
static const String covid19Test = '/covid19Test';
static const String vitalSign = '/vitalSign';
//appointments
static const String bookAppointmentPage = '/bookAppointmentPage';
// Water Monitor
static const String waterConsumptionScreen = '/waterConsumptionScreen';
static const String waterMonitorSettingsScreen = '/waterMonitorSettingsScreen';
static const String waterConsumptionPage = '/waterConsumptionScreen';
static const String waterMonitorSettingsPage = '/waterMonitorSettingsScreen';
// Symptoms Checker
static const String organSelectorPage = '/organSelectorPage';
static const String symptomsSelectorScreen = '/symptomsCheckerScreen';
static const String suggestionsScreen = '/suggestionsScreen';
static const String riskFactorsScreen = '/riskFactorsScreen';
static const String possibleConditionsScreen = '/possibleConditionsScreen';
static const String triageScreen = '/triageProgressScreen';
//UserInfoSelection
static const String symptomsSelectorPage = '/symptomsCheckerScreen';
static const String suggestionsPage = '/suggestionsScreen';
static const String riskFactorsPage = '/riskFactorsScreen';
static const String possibleConditionsPage = '/possibleConditionsScreen';
static const String triagePage = '/triageProgressScreen';
static const String userInfoSelection = '/userInfoSelection';
static const String userInfoFlowManager = '/userInfoFlowManager';
// Health Trackers
static const String healthTrackersPage = '/healthTrackersListScreen';
static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage';
static const String healthTrackerDetailPage = '/healthTrackerDetailPage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
@ -78,11 +84,11 @@ class AppRoutes {
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(),
organSelectorPage: (context) => OrganSelectorPage(),
symptomsSelectorScreen: (context) => SymptomsSelectorScreen(),
riskFactorsScreen: (context) => RiskFactorsScreen(),
suggestionsScreen: (context) => SuggestionsScreen(),
possibleConditionsScreen: (context) => PossibleConditionsScreen(),
triageScreen: (context) => TriageScreen(),
symptomsSelectorPage: (context) => SymptomsSelectorPage(),
riskFactorsPage: (context) => RiskFactorsScreen(),
suggestionsPage: (context) => SuggestionsScreen(),
possibleConditionsPage: (context) => PossibleConditionsPage(),
triagePage: (context) => TriagePage(),
bloodDonationPage: (context) => BloodDonationPage(),
bookAppointmentPage: (context) => BookAppointmentPage(),
userInfoSelection: (context) => UserInfoSelectionScreen(),
@ -90,11 +96,23 @@ class AppRoutes {
smartWatches: (context) => SmartwatchInstructionsPage(),
huaweiHealthExample: (context) => HuaweiHealthExample(),
covid19Test: (context) => Covid19LandingPage(),
//
waterConsumptionScreen: (context) => WaterConsumptionScreen(),
waterMonitorSettingsScreen: (context) => WaterMonitorSettingsScreen(),
waterConsumptionPage: (context) => WaterConsumptionPage(),
waterMonitorSettingsPage: (context) => WaterMonitorSettingsPage(),
healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator),
healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter),
vitalSign: (context) => VitalSignPage()
healthTrackersPage: (context) => HealthTrackersPage(),
vitalSign: (context) => VitalSignPage(),
addHealthTrackerEntryPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return AddHealthTrackerEntryPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
healthTrackerDetailPage: (context) {
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return HealthTrackerDetailPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
};
}

@ -62,18 +62,16 @@ class DialogServiceImp implements DialogService {
message: message,
showCancel: onCancelPressed != null ? true : false,
onOkPressed: () {
print('ok button is pressed');
if (onOkPressed != null) {
print('onOkPressed is not null');
onOkPressed();
}else {
} else {
Navigator.pop(context);
}
},
onCancelPressed: () {
if (onCancelPressed != null) {
onCancelPressed();
}else {
} else {
Navigator.pop(context);
}
},
@ -108,7 +106,8 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
Future<void> showCommonBottomSheetWithoutH(
{String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
@ -162,7 +161,8 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {
Future<void> showPhoneNumberPickerSheet(
{String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(context,
@ -184,7 +184,8 @@ class DialogServiceImp implements DialogService {
}
}
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
Widget exceptionBottomSheetWidget(
{required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
return Column(
children: [
(message).toText16(isBold: false, color: AppColors.textColor),
@ -239,7 +240,8 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin
);
}
Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) {
Widget showPhoneNumberPickerWidget(
{required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) {
return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Column(
children: [

@ -2,111 +2,112 @@ import 'package:flutter/material.dart';
class AppColors {
static const transparent = Colors.transparent;
static const mainPurple = Color(0xFF7954F7);
static const mainPurple = Color(0xFF7954F7); // #7954F7
static const scaffoldBgColor = Color(0xFFF8F8F8);
static const bottomSheetBgColor = Color(0xFFF8F8FA);
static const lightGreyEFColor = Color(0xffeaeaff);
static const greyF7Color = Color(0xffF7F7F7);
static const greyInfoTextColor = Color(0xff777777);
static const lightGrayColor = Color(0xff808080);
static const greyTextColorLight = Color(0xFFA2A2A2);
static const scaffoldBgColor = Color(0xFFF8F8F8); // #F8F8F8
static const bottomSheetBgColor = Color(0xFFF8F8FA); // #F8F8FA
static const lightGreyEFColor = Color(0xffeaeaff); // #EAEAFF
static const greyF7Color = Color(0xffF7F7F7); // #F7F7F7
static const greyInfoTextColor = Color(0xff777777); // #777777
static const lightGrayColor = Color(0xff808080); // #808080
static const greyTextColorLight = Color(0xFFA2A2A2); // #A2A2A2
// New UI Colors
static const whiteColor = Color(0xFFffffff);
static const Color bgScaffoldColor = Color(0xffF8F8F8);
static const Color primaryRedColor = Color(0xFFED1C2B);
static const Color primaryRedBorderColor = Color(0xFFED1C2B);
static const Color secondaryLightRedColor = Color(0xFFFEE9EA);
static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA);
static const Color bgRedLightColor = Color(0xFFFEE9EA);
static const Color bgGreenColor = Color(0xFF18C273);
static const Color textColor = Color(0xFF2E3039);
static const Color borderGrayColor = Color(0x332E3039);
static const Color textColorLight = Color(0xFF5E5E5E);
static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color chipBorderColorOpacity20 = Color(0x332E3039);
static const Color dividerColor = Color(0x40D2D2D2);
static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039);
static const blackColor = textColor;
static const Color inputLabelTextColor = Color(0xff898A8D);
static const Color greyTextColor = Color(0xFF8F9AA3);
static const Color lightGrayBGColor = Color(0x142E3039);
static const Color checkBoxBorderColor = Color(0xffD2D2D2);
static const Color pharmacyBGColor = Color(0xFF359846);
static const lightGreenColor = Color(0xFF0ccedde);
static const textGreenColor = Color(0xFF18C273);
static const Color ratingColorYellow = Color(0xFFFFAF15);
static const Color spacerLineColor = Color(0x2E30391A);
static const whiteColor = Color(0xFFffffff); // #FFFFFF
static const Color bgScaffoldColor = Color(0xffF8F8F8); // #F8F8F8
static const Color primaryRedColor = Color(0xFFED1C2B); // #ED1C2B
static const Color primaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B
static const Color secondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color secondaryLightRedBorderColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color bgRedLightColor = Color(0xFFFEE9EA); // #FEE9EA
static const Color bgGreenColor = Color(0xFF18C273); // #18C273
static const Color textColor = Color(0xFF2E3039); // #2E3039
static const Color borderGrayColor = Color(0x332E3039); // #2E3039 (20% opacity)
static const Color textColorLight = Color(0xFF5E5E5E); // #5E5E5E
static const Color borderOnlyColor = Color(0xFF2E3039); // #2E3039
static const Color chipBorderColorOpacity20 = Color(0x332E3039); // #2E3039 (20% opacity)
static const Color dividerColor = Color(0x40D2D2D2); // #D2D2D2 (25% opacity)
static const Color warningColorYellow = Color(0xFFF4A308); // #F4A308
static const Color blackBgColor = Color(0xFF2E3039); // #2E3039
static const blackColor = textColor; // #2E3039
static const Color inputLabelTextColor = Color(0xff898A8D); // #898A8D
static const Color greyTextColor = Color(0xFF8F9AA3); // #8F9AA3
static const Color lightGrayBGColor = Color(0x142E3039); // #2E3039 (8% opacity)
static const Color checkBoxBorderColor = Color(0xffD2D2D2); // #D2D2D2
static const Color pharmacyBGColor = Color(0xFF359846); // #359846
static const lightGreenColor = Color(0xFF0ccedde); // #0CCEDDE
static const textGreenColor = Color(0xFF18C273); // #18C273
static const Color ratingColorYellow = Color(0xFFFFAF15); // #FFAF15
static const Color spacerLineColor = Color(0x2E30391A); // #2E3039 (10% opacity)
//Chips
static const Color successColor = Color(0xff18C273);
static const Color successLightBgColor = Color(0xffDDF6EA);
static const Color errorColor = Color(0xFFED1C2B);
static const Color alertColor = Color(0xFFD48D05);
static const Color infoColor = Color(0xFF0B85F7);
static const Color warningColor = Color(0xFFFFCC00);
static const Color greyColor = Color(0xFFEFEFF0);
static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B);
static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA);
static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B);
static const Color alertLightColor = Color(0xFFD48D05);
static const Color infoLightColor = Color(0xFF0B85F7);
static const Color warningLightColor = Color(0xFFFFCC00);
static const Color greyLightColor = Color(0xFFEFEFF0);
static const Color thumbColor = Color(0xFF18C273);
static const Color switchBackgroundColor = Color(0x2618C273);
static const Color bottomNAVBorder = Color(0xFFEEEEEE);
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);
static const Color lightRedButtonColor = Color(0x1AED1C2B);
static const Color successColor = Color(0xff18C273); // #18C273
static const Color successLightBgColor = Color(0xffDDF6EA); // #DDF6EA
static const Color errorColor = Color(0xFFED1C2B); // #ED1C2B
static const Color alertColor = Color(0xFFD48D05); // #D48D05
static const Color infoColor = Color(0xFF0B85F7); // #0B85F7
static const Color warningColor = Color(0xFFFFCC00); // #FFCC00
static const Color greyColor = Color(0xFFEFEFF0); // #EFEFF0
static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); // #ED1C2B
static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); // #FEE9EA
// static const Color chipSecondaryLightRedColor = Color(0xFFFF9E15); // #FEE9EA
static const Color successLightColor = Color(0xFF18C273); // #18C273
static const Color errorLightColor = Color(0xFFED1C2B); // #ED1C2B
static const Color alertLightColor = Color(0xFFD48D05); // #D48D05
static const Color infoLightColor = Color(0xFF0B85F7); // #0B85F7
static const Color warningLightColor = Color(0xFFFFCC00); // #FFCC00
static const Color greyLightColor = Color(0xFFEFEFF0); // #EFEFF0
static const Color thumbColor = Color(0xFF18C273); // #18C273
static const Color switchBackgroundColor = Color(0x2618C273); // #18C273 (15% opacity)
static const Color bottomNAVBorder = Color(0xFFEEEEEE); // #EEEEEE
static const Color quickLoginColor = Color(0xFF666666); // #666666
static const Color tooltipTextColor = Color(0xFF414D55); // #414D55
static const Color graphGridColor = Color(0x4D18C273); // #18C273 (30% opacity)
static const Color criticalLowAndHigh = Color(0xFFED1C2B); // #ED1C2B
static const Color highAndLow = Color(0xFFFFAF15); // #FFAF15
static const Color labelTextColor = Color(0xFF838383); // #838383
static const Color calenderTextColor = Color(0xFFD0D0D0); // #D0D0D0
static const Color lightGreenButtonColor = Color(0x2618C273); // #18C273 (15% opacity)
static const Color lightRedButtonColor = Color(0x1AED1C2B); // #ED1C2B (10% opacity)
// Status Colors
static const Color statusPendingColor = Color(0xffCC9B14);
static const Color statusProcessingColor = Color(0xff2E303A);
static const Color statusCompletedColor = Color(0xff359846);
static const Color statusRejectedColor = Color(0xffD02127);
static const Color statusPendingColor = Color(0xffCC9B14); // #CC9B14
static const Color statusProcessingColor = Color(0xff2E303A); // #2E303A
static const Color statusCompletedColor = Color(0xff359846); // #359846
static const Color statusRejectedColor = Color(0xffD02127); // #D02127
// Info Banner Colors
static const Color infoBannerBgColor = Color(0xFFFFF4E6);
static const Color infoBannerBorderColor = Color(0xFFFFE5B4);
static const Color infoBannerIconColor = Color(0xFFCC9B14);
static const Color infoBannerTextColor = Color(0xFF856404);
static const Color infoBannerBgColor = Color(0xFFFFF4E6); // #FFF4E6
static const Color infoBannerBorderColor = Color(0xFFFFE5B4); // #FFE5B4
static const Color infoBannerIconColor = Color(0xFFCC9B14); // #CC9B14
static const Color infoBannerTextColor = Color(0xFF856404); // #856404
// SymptomsChecker
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15);
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103);
static const Color chipColorMonitor = Color(0xFF18C273);
static const Color chipColorEmergency = Color(0xFFED1C2B);
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); // #FFAF15
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); // #AB7103
static const Color chipColorMonitor = Color(0xFF18C273); // #18C273
static const Color chipColorEmergency = Color(0xFFED1C2B); // #ED1C2B
// Services Page Colors
static const Color eReferralCardColor = Color(0xFFFF8012);
static const Color bloodDonationCardColor = Color(0xFFFF5662);
static const Color bookAppointment = Color(0xFF415364);
static const Color eReferralCardColor = Color(0xFFFF8012); // #FF8012
static const Color bloodDonationCardColor = Color(0xFFFF5662); // #FF5662
static const Color bookAppointment = Color(0xFF415364); // #415364
// Water Monitor
static const Color blueColor = Color(0xFF4EB5FF);
static const Color blueGradientColorOne = Color(0xFFF1F7FD);
static const Color blueGradientColorTwo = Color(0xFFD9EFFF);
static const Color blueColor = Color(0xFF4EB5FF); // #4EB5FF
static const Color blueGradientColorOne = Color(0xFFF1F7FD); // #F1F7FD
static const Color blueGradientColorTwo = Color(0xFFD9EFFF); // #D9EFFF
// Shimmer
static const Color shimmerBaseColor = Color(0xFFE0E0E0);
static const Color shimmerHighlightColor = Color(0xFFF5F5F5);
static const Color covid29Color = Color(0xff2563EB);
static const Color shimmerBaseColor = Color(0xFFE0E0E0); // #E0E0E0
static const Color shimmerHighlightColor = Color(0xFFF5F5F5); // #F5F5F5
static const Color covid29Color = Color(0xff2563EB); // #2563EB
}

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
///
/// **Parameters:**
/// - [dataPoints]: List of `DataPoint` objects to plot.
/// - [secondaryDataPoints]: Optional list for a second line (e.g., diastolic in blood pressure).
/// - [leftLabelFormatter]: Function to build left axis labels.
/// - [bottomLabelFormatter]: Function to build bottom axis labels.
/// - [width]: Optional width of the chart.
@ -18,6 +19,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
/// - [maxY], [maxX], [minX]: Axis bounds.
/// - [spotColor]: Color of the touched spot marker.
/// - [graphColor]: Color of the line.
/// - [secondaryGraphColor]: Color of the secondary line.
/// - [graphShadowColor]: Color of the area below the line.
/// - [graphGridColor]: Color of the grid lines.
/// - [bottomLabelColor]: Color of bottom axis labels.
@ -43,6 +45,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
/// )
class CustomGraph extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<DataPoint>? secondaryDataPoints; // For dual-line graphs (e.g., blood pressure)
final double? width;
final double height;
final double? maxY;
@ -50,6 +53,7 @@ class CustomGraph extends StatelessWidget {
final double? minX;
final Color spotColor;
final Color graphColor;
final Color? secondaryGraphColor; // Color for secondary line
final Color graphShadowColor;
final Color graphGridColor;
final Color bottomLabelColor;
@ -79,6 +83,7 @@ class CustomGraph extends StatelessWidget {
const CustomGraph(
{super.key,
required this.dataPoints,
this.secondaryDataPoints,
required this.leftLabelFormatter,
this.width,
required this.scrollDirection,
@ -89,6 +94,7 @@ class CustomGraph extends StatelessWidget {
this.isFullScreeGraph = false,
this.spotColor = AppColors.bgGreenColor,
this.graphColor = AppColors.bgGreenColor,
this.secondaryGraphColor,
this.graphShadowColor = AppColors.graphGridColor,
this.graphGridColor = AppColors.graphGridColor,
this.bottomLabelColor = AppColors.textColor,
@ -225,7 +231,7 @@ class CustomGraph extends StatelessWidget {
return FlSpot(entry.key.toDouble(), value);
}).toList();
var data = [
var data = <LineChartBarData>[
LineChartBarData(
spots: allSpots,
isCurved: true,
@ -254,6 +260,31 @@ class CustomGraph extends StatelessWidget {
)
];
// Add secondary line if provided (for dual-line graphs like blood pressure)
if (secondaryDataPoints != null && secondaryDataPoints!.isNotEmpty) {
final List<FlSpot> secondarySpots = secondaryDataPoints!.asMap().entries.map((entry) {
double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value;
return FlSpot(entry.key.toDouble(), value);
}).toList();
data.add(
LineChartBarData(
spots: secondarySpots,
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
barWidth: 2,
gradient: LinearGradient(
colors: [secondaryGraphColor ?? AppColors.blueColor, secondaryGraphColor ?? AppColors.blueColor],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(show: showLinePoints),
belowBarData: BarAreaData(show: false),
),
);
}
return data;
}
}

@ -3,17 +3,14 @@ import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import '../core/dependencies.dart';
// TODO: Import AppColors if bgRedColor is defined there
// import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart';
import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart';
class TextInputWidget extends StatelessWidget {
final String labelText;
@ -49,6 +46,7 @@ class TextInputWidget extends StatelessWidget {
final int maxLines;
final Color? hintColor;
final bool? isHideSwitcher;
final bool? isArrowTrailing;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
@ -87,6 +85,7 @@ class TextInputWidget extends StatelessWidget {
this.minLines = 3,
this.maxLines = 6,
this.isHideSwitcher,
this.isArrowTrailing,
// this.countryList = const [],
// this.onCountryChange,
});
@ -167,7 +166,8 @@ class TextInputWidget extends StatelessWidget {
],
),
),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context, isArrowTrailing: isArrowTrailing ?? false),
if (selectionType == SelectionTypeEnum.time) _buildTimePickerIcon(context, isArrowTrailing: isArrowTrailing ?? false),
if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context),
],
),
@ -200,7 +200,7 @@ class TextInputWidget extends StatelessWidget {
child: Utils.buildSvgWithAssets(icon: leadingIcon!));
}
Widget _buildTrailingIcon(BuildContext context) {
Widget _buildTrailingIcon(BuildContext context, {bool isArrowTrailing = false}) {
final AppState appState = getIt.get<AppState>();
return Container(
height: 40.h,
@ -233,7 +233,47 @@ class TextInputWidget extends StatelessWidget {
onChange!(picked.toString());
}
},
child: Utils.buildSvgWithAssets(icon: AppAssets.calendar),
child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.calendar),
),
);
}
Widget _buildTimePickerIcon(BuildContext context, {bool isArrowTrailing = false}) {
return Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.zero,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
borderRadius: 12.r,
color: AppColors.whiteColor,
),
child: GestureDetector(
onTap: () async {
// Parse existing time if available
TimeOfDay? initialTime;
if (controller?.text.isNotEmpty ?? false) {
initialTime = TimePickerWidget.parseTime(controller!.text);
}
final picked = await TimePickerWidget.show(
context,
initialTime: initialTime,
use24HourFormat: false, // You can make this configurable if needed
onTimeSelected: (time) {
if (onChange != null) {
final formattedTime = TimePickerWidget.formatTime(time);
onChange!(formattedTime);
}
},
);
// Update controller if time was picked
if (picked != null && controller != null) {
controller!.text = TimePickerWidget.formatTime(picked);
}
},
child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.alarm_clock_icon),
),
);
}

@ -0,0 +1,348 @@
import 'package:flutter/cupertino.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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
/// A reusable time picker widget that can be used anywhere in the app
/// Shows a bottom sheet with iOS-style time picker
class TimePickerWidget {
/// Shows a time picker bottom sheet
///
/// [context] - BuildContext for showing the bottom sheet
/// [initialTime] - Initial time to display (defaults to current time)
/// [use24HourFormat] - Whether to use 24-hour format (defaults to false)
/// [onTimeSelected] - Callback when time is selected
///
/// Returns the selected TimeOfDay or null if cancelled
static Future<TimeOfDay?> show(
BuildContext context, {
TimeOfDay? initialTime,
bool use24HourFormat = false,
bool displaySelectedTime = false,
Function(TimeOfDay)? onTimeSelected,
}) async {
final selectedTime = initialTime ?? TimeOfDay.now();
final result = await showModalBottomSheet<TimeOfDay>(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (BuildContext context) {
return _TimePickerBottomSheet(
initialTime: selectedTime,
use24HourFormat: use24HourFormat,
displaySelectedTime: displaySelectedTime,
onTimeChanged: (time) {
// Time is being changed in real-time
},
);
},
);
if (result != null && onTimeSelected != null) {
onTimeSelected(result);
}
return result;
}
/// Formats TimeOfDay to string (HH:mm format)
static String formatTime(TimeOfDay time, {bool use24HourFormat = false}) {
if (use24HourFormat) {
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
} else {
final hour = time.hourOfPeriod == 0 ? 12 : time.hourOfPeriod;
final period = time.period == DayPeriod.am ? 'AM' : 'PM';
return '${hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')} $period';
}
}
/// Parses time string to TimeOfDay
static TimeOfDay? parseTime(String timeString) {
try {
final parts = timeString.split(':');
if (parts.length == 2) {
final hour = int.parse(parts[0]);
final minute = int.parse(parts[1].split(' ')[0]);
return TimeOfDay(hour: hour, minute: minute);
}
} catch (e) {
return null;
}
return null;
}
}
class _TimePickerBottomSheet extends StatefulWidget {
final TimeOfDay initialTime;
final bool use24HourFormat;
final bool displaySelectedTime;
final Function(TimeOfDay) onTimeChanged;
const _TimePickerBottomSheet({
required this.initialTime,
required this.use24HourFormat,
required this.displaySelectedTime,
required this.onTimeChanged,
});
@override
State<_TimePickerBottomSheet> createState() => _TimePickerBottomSheetState();
}
class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> {
late int selectedHour;
late int selectedMinute;
late DayPeriod selectedPeriod;
@override
void initState() {
super.initState();
selectedHour = widget.use24HourFormat ? widget.initialTime.hour : widget.initialTime.hourOfPeriod;
if (selectedHour == 0 && !widget.use24HourFormat) selectedHour = 12;
selectedMinute = widget.initialTime.minute;
selectedPeriod = widget.initialTime.period;
}
TimeOfDay _getCurrentTime() {
if (widget.use24HourFormat) {
return TimeOfDay(hour: selectedHour, minute: selectedMinute);
} else {
int hour = selectedHour;
if (selectedPeriod == DayPeriod.pm && hour != 12) {
hour += 12;
} else if (selectedPeriod == DayPeriod.am && hour == 12) {
hour = 0;
}
return TimeOfDay(hour: hour, minute: selectedMinute);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
),
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.dividerColor,
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Select Time".needTranslation.toText18(
weight: FontWeight.w600,
color: AppColors.textColor,
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Utils.buildSvgWithAssets(
icon: AppAssets.cancel,
width: 24.h,
height: 24.h,
iconColor: AppColors.textColor,
),
),
],
),
),
// Time Picker
SizedBox(
height: 250.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Hour Picker
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: widget.use24HourFormat ? selectedHour : (selectedHour - 1),
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
if (widget.use24HourFormat) {
selectedHour = index;
} else {
selectedHour = index + 1;
}
widget.onTimeChanged(_getCurrentTime());
});
},
children: List.generate(
widget.use24HourFormat ? 24 : 12,
(index) {
final hour = widget.use24HourFormat ? index : index + 1;
return Center(
child: Text(
hour.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
);
},
),
),
),
// Separator
Text(
':',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
// Minute Picker
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedMinute,
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
selectedMinute = index;
widget.onTimeChanged(_getCurrentTime());
});
},
children: List.generate(
60,
(index) => Center(
child: Text(
index.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
),
),
),
// AM/PM Picker (only for 12-hour format)
if (!widget.use24HourFormat)
Expanded(
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedPeriod == DayPeriod.am ? 0 : 1,
),
itemExtent: 50.h,
onSelectedItemChanged: (index) {
setState(() {
selectedPeriod = index == 0 ? DayPeriod.am : DayPeriod.pm;
widget.onTimeChanged(_getCurrentTime());
});
},
children: [
Center(
child: Text(
'AM',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
Center(
child: Text(
'PM',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
),
),
],
),
),
],
),
),
if (widget.displaySelectedTime)
// Current Time Display
Container(
margin: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
padding: EdgeInsets.symmetric(vertical: 12.h),
decoration: BoxDecoration(
color: AppColors.lightGrayBGColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: TimePickerWidget.formatTime(
_getCurrentTime(),
use24HourFormat: widget.use24HourFormat,
).toText20(
weight: FontWeight.w600,
color: AppColors.textColor,
),
),
),
// Action Buttons
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Row(
children: [
Expanded(
child: CustomButton(
height: 56.h,
text: "Cancel".needTranslation,
onPressed: () => Navigator.pop(context),
textColor: AppColors.textColor,
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
),
),
SizedBox(width: 12.w),
Expanded(
child: CustomButton(
height: 56.h,
text: "Confirm".needTranslation,
onPressed: () {
Navigator.pop(context, _getCurrentTime());
},
textColor: AppColors.whiteColor,
backgroundColor: AppColors.primaryRedColor,
),
),
],
),
),
],
),
),
);
}
}

@ -0,0 +1,165 @@
// Example usage of TimePickerWidget
//
// This file demonstrates how to use the TimePickerWidget in your Flutter app.
// The TimePickerWidget is a reusable component that shows a bottom sheet with
// an iOS-style time picker.
// ============================================================================
// EXAMPLE 1: Using with TextInputWidget
// ============================================================================
/*
TextInputWidget(
labelText: "Appointment Time",
hintText: "Select time",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
print("Selected time: $value");
},
)
*/
// ============================================================================
// EXAMPLE 2: Direct usage with custom button
// ============================================================================
/*
ElevatedButton(
onPressed: () async {
final selectedTime = await TimePickerWidget.show(
context,
initialTime: TimeOfDay.now(),
use24HourFormat: false,
onTimeSelected: (time) {
print("Time selected: ${TimePickerWidget.formatTime(time)}");
},
);
if (selectedTime != null) {
print("Final time: ${TimePickerWidget.formatTime(selectedTime)}");
}
},
child: Text("Pick Time"),
)
*/
// ============================================================================
// EXAMPLE 3: Using with 24-hour format
// ============================================================================
/*
TextInputWidget(
labelText: "Meeting Time",
hintText: "Select time (24h)",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
// The value will be formatted as "14:30" for 2:30 PM in 24h format
print("Selected time (24h): $value");
},
)
// Or programmatically:
final time = await TimePickerWidget.show(
context,
use24HourFormat: true, // Enable 24-hour format
);
*/
// ============================================================================
// EXAMPLE 4: Parsing and formatting times
// ============================================================================
/*
// Parse time string to TimeOfDay
String timeString = "02:30 PM";
TimeOfDay? parsedTime = TimePickerWidget.parseTime(timeString);
// Format TimeOfDay to string
TimeOfDay time = TimeOfDay(hour: 14, minute: 30);
String formatted12h = TimePickerWidget.formatTime(time); // "02:30 PM"
String formatted24h = TimePickerWidget.formatTime(time, use24HourFormat: true); // "14:30"
*/
// ============================================================================
// EXAMPLE 5: Complete form example with date and time
// ============================================================================
/*
class AppointmentForm extends StatefulWidget {
@override
_AppointmentFormState createState() => _AppointmentFormState();
}
class _AppointmentFormState extends State<AppointmentForm> {
final TextEditingController dateController = TextEditingController();
final TextEditingController timeController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column(
children: [
// Date picker
TextInputWidget(
labelText: "Appointment Date",
hintText: "Select date",
controller: dateController,
selectionType: SelectionTypeEnum.calendar,
isReadOnly: true,
onChange: (value) {
print("Date selected: $value");
},
),
SizedBox(height: 16),
// Time picker
TextInputWidget(
labelText: "Appointment Time",
hintText: "Select time",
controller: timeController,
selectionType: SelectionTypeEnum.time,
isReadOnly: true,
onChange: (value) {
print("Time selected: $value");
},
),
],
);
}
}
*/
// ============================================================================
// Features:
// ============================================================================
// iOS-style cupertino picker (works on both iOS and Android)
// Support for 12-hour format (with AM/PM)
// Support for 24-hour format
// Beautiful bottom sheet UI matching app design
// Real-time preview of selected time
// Confirm/Cancel buttons
// Easy integration with TextInputWidget
// Parse and format time utilities
// Fully customizable and reusable
// ============================================================================
// API Reference:
// ============================================================================
// TimePickerWidget.show() - Shows the time picker bottom sheet
// Parameters:
// - context: BuildContext (required)
// - initialTime: TimeOfDay? (optional, defaults to current time)
// - use24HourFormat: bool (optional, defaults to false)
// - onTimeSelected: Function(TimeOfDay)? (optional callback)
// Returns: Future<TimeOfDay?>
//
// TimePickerWidget.formatTime() - Formats TimeOfDay to string
// Parameters:
// - time: TimeOfDay (required)
// - use24HourFormat: bool (optional, defaults to false)
// Returns: String (e.g., "02:30 PM" or "14:30")
//
// TimePickerWidget.parseTime() - Parses time string to TimeOfDay
// Parameters:
// - timeString: String (required, e.g., "02:30 PM")
// Returns: TimeOfDay?
Loading…
Cancel
Save