dev_sultan #229

Merged
Haroon6138 merged 6 commits from dev_sultan into master 2 days ago

@ -1,3 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.835 8.71465C29 10.2375 29 12.157 29 14.6057L29 17.393V17.3931C29 19.8418 29 21.7612 28.835 23.284C28.6667 24.8381 28.3169 26.0973 27.5358 27.1724C27.0629 27.8232 26.4905 28.3956 25.8397 28.8685C24.7646 29.6496 23.5054 29.9993 21.9514 30.1677C20.4285 30.3327 18.5091 30.3327 16.0604 30.3327H16.0603H15.9397C13.491 30.3327 11.5715 30.3327 10.0486 30.1677C8.49458 29.9993 7.23541 29.6496 6.16031 28.8685C5.50945 28.3956 4.93708 27.8232 4.4642 27.1724C3.6831 26.0973 3.33334 24.8381 3.16497 23.284C2.99998 21.7612 2.99999 19.8418 3 17.3931V17.393L3 14.6056V14.6056C2.99999 12.1569 2.99998 10.2375 3.16497 8.71465C3.33334 7.1606 3.6831 5.90143 4.4642 4.82633C4.93708 4.17547 5.50945 3.6031 6.16032 3.13022C7.23541 2.34911 8.49459 1.99936 10.0486 1.83099C11.5715 1.666 13.4909 1.666 15.9395 1.66602H15.9396H16.0604H16.0605C18.5091 1.66601 20.4285 1.666 21.9514 1.83099C23.5054 1.99936 24.7646 2.34911 25.8397 3.13022C26.4905 3.6031 27.0629 4.17547 27.5358 4.82633C28.3169 5.90143 28.6667 7.1606 28.835 8.71465ZM16 5.66602C16.7364 5.66602 17.3333 6.26297 17.3333 6.99935V9.24299C17.6276 9.59842 17.8786 9.66602 18 9.66602C18.1485 9.66602 18.4909 9.56487 18.8693 8.95942C19.2595 8.33494 20.0821 8.14507 20.7066 8.53531C21.3311 8.92555 21.521 9.74814 21.1307 10.3726C20.4782 11.4167 19.4088 12.3327 18 12.3327C17.7685 12.3327 17.5462 12.3079 17.3333 12.262L17.3333 13.0409C17.8948 13.4337 18.584 13.666 19.3333 13.666C20.2769 13.666 21.1252 13.2976 21.7432 12.7042C22.2744 12.1942 23.1185 12.2114 23.6285 12.7426C24.1385 13.2738 24.1213 14.1178 23.5901 14.6278C22.49 15.6841 20.9852 16.3327 19.3333 16.3327C18.6338 16.3327 17.9606 16.2163 17.3333 16.002V18.0531C18.0392 17.9934 18.7108 17.8873 19.335 17.7423C21.0296 17.3485 23 18.5587 23 20.5393C23 21.5265 22.4537 22.5524 21.4063 22.9419C20.935 23.1172 20.4345 23.269 19.911 23.3954C19.3839 23.5228 18.9627 23.8554 18.7618 24.3012L18.6199 24.6159C18.1546 25.6483 17.1409 26.3327 16 26.3327C14.8591 26.3327 13.8454 25.6483 13.3801 24.6159L13.2382 24.3012C13.0373 23.8554 12.6161 23.5228 12.089 23.3954C11.5655 23.269 11.065 23.1172 10.5937 22.9419C9.54632 22.5524 9 21.5265 9 20.5393C9 18.5587 10.9704 17.3485 12.665 17.7423C13.2892 17.8873 13.9608 17.9934 14.6667 18.0531V16.002C14.0394 16.2164 13.3662 16.3327 12.6667 16.3327C11.0148 16.3327 9.51001 15.6841 8.4099 14.6278C7.87872 14.1178 7.86154 13.2738 8.37154 12.7426C8.88155 12.2114 9.72559 12.1942 10.2568 12.7042C10.8748 13.2976 11.7231 13.666 12.6667 13.666C13.416 13.666 14.1052 13.4337 14.6667 13.0409V12.262C14.4538 12.3079 14.2315 12.3327 14 12.3327C12.5912 12.3327 11.5218 11.4167 10.8693 10.3726C10.4791 9.74814 10.6689 8.92555 11.2934 8.53531C11.9179 8.14507 12.7405 8.33494 13.1307 8.95942C13.5091 9.56487 13.8515 9.66602 14 9.66602C14.1214 9.66602 14.3724 9.59842 14.6667 9.24299L14.6667 6.99935C14.6667 6.26297 15.2636 5.66602 16 5.66602Z" fill="#2E3039"/>
<svg width="26" height="29" viewBox="0 0 26 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 4C13.5523 4 14 4.44772 14 5V7.35861C14.3647 7.84353 14.7315 7.99997 15.0001 7.99997C15.3062 7.99997 15.7395 7.79698 16.1521 7.13669C16.4448 6.66833 17.0617 6.52593 17.5301 6.81861C17.9984 7.11129 18.1408 7.72823 17.8482 8.19659C17.2299 9.18588 16.2514 9.99997 15.0001 9.99997C14.644 9.99997 14.3099 9.93402 14 9.81685V11.2101C14.6402 11.7041 15.4494 12 16.3333 12C17.3655 12 18.2958 11.5965 18.9741 10.9453C19.3725 10.5628 20.0055 10.5757 20.388 10.9741C20.7705 11.3725 20.7576 12.0055 20.3592 12.388C19.3194 13.3864 17.8967 14 16.3333 14C15.5039 14 14.7141 13.8273 14 13.516V16.4116C14.8282 16.3596 15.6132 16.244 16.335 16.0762C18.0296 15.6825 20 16.8927 20 18.8733C20 19.8605 19.4537 20.8864 18.4063 21.2759C17.935 21.4511 17.4345 21.603 16.911 21.7294C16.3839 21.8567 15.9627 22.1893 15.7618 22.6351L15.6199 22.9499C15.1546 23.9822 14.1409 24.6667 13 24.6667C11.8591 24.6667 10.8454 23.9822 10.3801 22.9499L10.2382 22.6351C10.0373 22.1893 9.61608 21.8567 9.08902 21.7294C8.56548 21.603 8.06504 21.4511 7.59375 21.2759C6.54632 20.8864 6 19.8605 6 18.8733C6 16.8927 7.97038 15.6825 9.66501 16.0762C10.3868 16.244 11.1718 16.3596 12 16.4116V13.516C11.2859 13.8273 10.4961 14 9.66667 14C8.1033 14 6.6806 13.3864 5.64076 12.388C5.24237 12.0055 5.22949 11.3725 5.61199 10.9741C5.99449 10.5757 6.62753 10.5628 7.02591 10.9453C7.70416 11.5965 8.63452 12 9.66667 12C10.5506 12 11.3598 11.7041 12 11.2101V9.81695C11.6901 9.93406 11.3562 9.99997 11.0001 9.99997C9.74886 9.99997 8.77033 9.18588 8.1521 8.19659C7.85942 7.72823 8.00183 7.11129 8.47018 6.81861C8.93854 6.52593 9.55548 6.66833 9.84816 7.13669C10.2608 7.79698 10.6941 7.99997 11.0001 7.99997C11.2687 7.99997 11.6354 7.84361 12 7.35897V5C12 4.44772 12.4477 4 13 4Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26 15.727V12.9397C26 10.491 26 8.57148 25.835 7.04863C25.6667 5.49458 25.3169 4.23541 24.5358 3.16031C24.0629 2.50945 23.4905 1.93708 22.8397 1.4642C21.7646 0.683098 20.5054 0.333341 18.9514 0.16497C17.4285 -1.87904e-05 15.5091 -1.04558e-05 13.0604 1.75524e-07H12.9396C10.491 -1.06751e-05 8.57146 -1.91778e-05 7.04863 0.16497C5.49459 0.33334 4.23541 0.683097 3.16032 1.4642C2.50945 1.93708 1.93708 2.50945 1.4642 3.16031C0.683099 4.23541 0.333342 5.49458 0.164971 7.04863C-1.6585e-05 8.57145 -9.22962e-06 10.4909 1.48706e-07 12.9395V15.727C-9.56553e-06 18.1757 -1.7181e-05 20.0952 0.164971 21.618C0.333341 23.1721 0.683098 24.4313 1.4642 25.5064C1.93708 26.1572 2.50945 26.7296 3.16031 27.2025C4.23541 27.9836 5.49458 28.3333 7.04863 28.5017C8.57148 28.6667 10.491 28.6667 12.9397 28.6667H13.0603C15.509 28.6667 17.4285 28.6667 18.9514 28.5017C20.5054 28.3333 21.7646 27.9836 22.8397 27.2025C23.4905 26.7296 24.0629 26.1572 24.5358 25.5064C25.3169 24.4313 25.6667 23.1721 25.835 21.618C26 20.0952 26 18.1757 26 15.727ZM23.8467 21.4026C23.6967 22.7865 23.4099 23.6534 22.9178 24.3308C22.5682 24.8119 22.1452 25.2349 21.6641 25.5844C20.9868 26.0765 20.1198 26.3634 18.7359 26.5133C17.3333 26.6653 15.5221 26.6667 13 26.6667C10.4779 26.6667 8.66666 26.6653 7.26406 26.5133C5.88018 26.3634 5.01322 26.0765 4.33588 25.5844C3.85481 25.2349 3.43176 24.8119 3.08224 24.3308C2.59012 23.6534 2.30327 22.7865 2.15334 21.4026C2.00137 20 2 18.1888 2 15.6667V13C2 10.4779 2.00137 8.66665 2.15334 7.26406C2.30327 5.88017 2.59012 5.01322 3.08224 4.33588C3.43176 3.85481 3.85481 3.43175 4.33589 3.08224C5.01322 2.59012 5.88018 2.30327 7.26406 2.15333C8.66666 2.00137 10.4779 2 13 2C15.5221 2 17.3333 2.00137 18.7359 2.15334C20.1198 2.30327 20.9868 2.59012 21.6641 3.08224C22.1452 3.43175 22.5682 3.85481 22.9178 4.33588C23.4099 5.01322 23.6967 5.88018 23.8467 7.26406C23.9986 8.66665 24 10.4779 24 13V15.6667C24 18.1888 23.9986 20 23.8467 21.4026Z" fill="#2E3039"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -301,6 +301,12 @@
"medicinesSubtitle": "الوصفات",
"vitalSigns": "علامات حيوية",
"vitalSignsSubTitle": "التقارير",
"vitalSignNormal": "طبيعي",
"vitalSignLow": "منخفض",
"vitalSignHigh": "مرتفع",
"vitalSignObese": "بدين",
"vitalSignOverweight": "زيادة الوزن",
"vitalSignUnderweight": "نقص الوزن",
"myMedical": "نشط",
"myMedicalSubtitle": "الأدوية",
"myDoctor": "أطبائي",
@ -717,6 +723,11 @@
"bloodDonation": "التبرع بالدم",
"bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.",
"bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم",
"aboutBloodDonation": "عن التبرع بالدم",
"bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.",
"bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)",
"bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع",
"bloodDonationBenefit3": "فحص صحي مجاني لدمك",
"viewTerms": "لعرض الشروط والأحكام",
"wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية",
"failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية",

@ -300,6 +300,12 @@
"medicinesSubtitle": "Prescriptions",
"vitalSigns": "Vital Signs",
"vitalSignsSubTitle": "Reports",
"vitalSignNormal": "Normal",
"vitalSignLow": "Low",
"vitalSignHigh": "High",
"vitalSignObese": "Obese",
"vitalSignOverweight": "Overweight",
"vitalSignUnderweight": "Underweight",
"myMedical": "Active",
"myMedicalSubtitle": "Medications",
"myDoctor": "My Doctors",
@ -710,6 +716,11 @@
"bloodDonation": "Blood Donation",
"bloodDonationInfo": "Through this service, you can register your name as a blood donor where the blood bank in the Habib Medical Group will communicate you in case of need for blood type.",
"bloodInstruction": "Enter the required information, in order to register for Blood Donation Service",
"aboutBloodDonation": "About Blood Donation",
"bloodDonationDescription": "Blood donation is a noble act that saves lives. Every single donation can save up to three people. By donating blood, you help patients in need of transfusions, emergencies, and medical treatments.",
"bloodDonationBenefit1": "Safe and quick process (10-15 minutes)",
"bloodDonationBenefit2": "You can donate every 8-12 weeks",
"bloodDonationBenefit3": "Free health check of your blood",
"viewTerms": "To view the terms and conditions",
"wantConnectHmgNetwork": "Dear customer there is no internet access, do you want to connect with HMG network to use our app, make sure you are in range of HMG network",
"failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services",

@ -667,6 +667,11 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
// Refresh privileges after user switching to ensure correct privileges are loaded
if (isSwitchUser) {
print("🔄 Switching user - refreshing privileges");
await getServicePrivilege();
}
// Fetch patient blood type from new API
// await getPatientBloodTypeNew();

@ -183,7 +183,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
@override
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList() async {
Map<String, dynamic> request = {'IsOnlineCheckIn': false, 'IsAdvancePayment': false, 'IsForER': true};
Map<String, dynamic> request = {'IsOnlineCheckIn': true, 'IsAdvancePayment': false, 'IsForER': true};
try {
GenericApiModel<List<HospitalsModel>>? apiResponse;

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
@ -21,6 +22,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/req_models/check_activation_e_referral_req_model.dart';
import 'models/resq_models/get_covid_payment_info_resp.dart';
@ -632,6 +634,9 @@ class HmgServicesViewModel extends ChangeNotifier {
Function(GenericApiModel)? onSuccess,
Function(String)? onError,
}) async {
// Get and set the SMS signature for auto-fill
requestModel.sMSSignature = await getSignature();
notifyListeners();
final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel);
@ -660,6 +665,14 @@ class HmgServicesViewModel extends ChangeNotifier {
);
}
Future<String?> getSignature() async {
if (Platform.isAndroid) {
return await SmsVerification.getAppSignature();
} else {
return null;
}
}
Future<void> checkEReferralActivationCode({
required CheckActivationCodeForEReferralRequestModel requestModel,
Function(GenericApiModel)? onSuccess,

@ -10,6 +10,7 @@ class SendActivationCodeForEReferralRequestModel {
dynamic sessionID;
bool? isDentalAllowedBackend;
int? deviceTypeID;
String? sMSSignature;
SendActivationCodeForEReferralRequestModel(
{this.patientMobileNumber,
@ -22,7 +23,8 @@ class SendActivationCodeForEReferralRequestModel {
this.patientOutSA,
this.sessionID,
this.isDentalAllowedBackend,
this.deviceTypeID});
this.deviceTypeID,
this.sMSSignature});
SendActivationCodeForEReferralRequestModel.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber'];
@ -36,6 +38,7 @@ class SendActivationCodeForEReferralRequestModel {
sessionID = json['SessionID'];
isDentalAllowedBackend = json['isDentalAllowedBackend'];
deviceTypeID = json['DeviceTypeID'];
sMSSignature = json['SMSSignature'];
}
Map<String, dynamic> toJson() {
@ -51,6 +54,7 @@ class SendActivationCodeForEReferralRequestModel {
data['SessionID'] = this.sessionID;
data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
data['DeviceTypeID'] = this.deviceTypeID;
data['SMSSignature'] = this.sMSSignature;
return data;
}
}

@ -3,6 +3,16 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
/// Enum for vital sign status to avoid string-based comparisons
enum VitalSignStatus {
normal,
high,
low,
obese,
overweight,
underweight,
}
/// UI-only helper model for Vital Sign cards.
///
/// Keeps presentation logic (chip colors, icon colors, simple status rules)
@ -22,13 +32,12 @@ class VitalSignUiModel {
/// Returns a color scheme for a card based on its [status] and [label].
///
/// Rules (mirrors existing behavior in Medical File page):
/// Rules:
/// - Height is always blue.
/// - High => red scheme.
/// - Low => yellow scheme.
/// - High, Obese, Overweight => red scheme.
/// - Low, Underweight => yellow scheme.
/// - Otherwise => green scheme (Normal).
static VitalSignUiModel scheme({required String? status, required String label}) {
final s = (status ?? '').toLowerCase();
static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) {
final l = label.toLowerCase();
// Height should always be blue.
@ -41,7 +50,10 @@ class VitalSignUiModel {
);
}
if (s.contains('high')) {
// High, Obese, Overweight => red scheme (health concerns)
if (status == VitalSignStatus.high ||
status == VitalSignStatus.obese ||
status == VitalSignStatus.overweight) {
return VitalSignUiModel(
iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor,
@ -50,8 +62,8 @@ class VitalSignUiModel {
);
}
// Warning for both low and overweight/underweight BMI, since they can indicate potential health issues.
if (s.contains('low') || s.contains("OverWeight") || s.contains("وزن زائد") || s.contains("UnderWeight") || s.contains("وزن ناقص")) {
// Low, Underweight => yellow scheme (warning)
if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) {
final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12);
return VitalSignUiModel(
iconBg: yellowBg,
@ -71,19 +83,37 @@ class VitalSignUiModel {
);
}
/// Convert VitalSignStatus enum to localized string
static String statusToString(VitalSignStatus status) {
switch (status) {
case VitalSignStatus.normal:
return LocaleKeys.vitalSignNormal.tr();
case VitalSignStatus.high:
return LocaleKeys.vitalSignHigh.tr();
case VitalSignStatus.low:
return LocaleKeys.vitalSignLow.tr();
case VitalSignStatus.obese:
return LocaleKeys.vitalSignObese.tr();
case VitalSignStatus.overweight:
return LocaleKeys.vitalSignOverweight.tr();
case VitalSignStatus.underweight:
return LocaleKeys.vitalSignUnderweight.tr();
}
}
/// Simple, user-friendly classification:
/// - Low: systolic < 90 OR diastolic < 60
/// - High: systolic >= 140 OR diastolic >= 90
/// - Normal: otherwise
/// Returns null if values are missing/unparseable.
static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
static VitalSignStatus? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
final int? s = toIntOrNull(systolic);
final int? d = toIntOrNull(diastolic);
if (s == null || d == null) return null;
if (s < 90 || d < 60) return 'Low';
if (s >= 140 || d >= 90) return 'High';
return 'Normal';
if (s < 90 || d < 60) return VitalSignStatus.low;
if (s >= 140 || d >= 90) return VitalSignStatus.high;
return VitalSignStatus.normal;
}
static int? toIntOrNull(dynamic v) {
@ -93,28 +123,47 @@ class VitalSignUiModel {
return int.tryParse(v.toString());
}
static String bmiStatus(dynamic bmi) {
String bmiStatus = 'Normal';
static VitalSignStatus? bmiStatus(dynamic bmi) {
// Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
if (bmiResult >= 30) {
bmiStatus = LocaleKeys.obese.tr();
} else if (bmiResult < 30 && bmiResult >= 25) {
bmiStatus = LocaleKeys.overWeight.tr();
} else if (bmiResult < 25 && bmiResult >= 18.5) {
bmiStatus = LocaleKeys.normal.tr();
} else if (bmiResult < 18.5) {
bmiStatus = LocaleKeys.underWeight.tr();
if (bmiResult == 0 || bmi == null) {
return null;
}
// BMI >= 25 (Overweight or Obese) => High
if (bmiResult >= 25) {
return VitalSignStatus.high;
}
// BMI >= 18.5 and < 25 => Normal
else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
}
// BMI < 18.5 (Underweight) => Low
else {
return VitalSignStatus.low;
}
}
/// Weight status based on BMI with detailed classification
/// Returns: Obese, Overweight, Normal, Underweight
static VitalSignStatus? weightStatus(dynamic bmi) {
// Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
// if (bmi == null) return 'N/A';
// final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
// if (bmiValue < 18.5) return 'Underweight';
// if (bmiValue < 25) return 'Normal';
// if (bmiValue < 30) return 'Overweight';
// return 'High';
if (bmiResult == 0 || bmi == null) {
return null;
}
return bmiStatus;
if (bmiResult >= 30) {
return VitalSignStatus.obese;
} else if (bmiResult >= 25) {
return VitalSignStatus.overweight;
} else if (bmiResult >= 18.5) {
return VitalSignStatus.normal;
} else {
return VitalSignStatus.underweight;
}
}
}

@ -215,27 +215,40 @@ class MedicalFileViewModel extends ChangeNotifier {
final result = await medicalFileRepo.getPatientVaccinesList();
result.fold(
// (failure) async => await errorHandlerService.handleError(
// failure: failure,
// onOkPressed: () {
// onError!(failure.message);
// },
// ),
(failure) async {
// onError!(failure.message);
print("❌ Vaccine API Failure: ${failure.message}");
isPatientVaccineListLoading = false;
notifyListeners();
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
print("📦 Vaccine API Response - Status: ${apiResponse.messageStatus}, Data: ${apiResponse.data}");
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
print("⚠️ Vaccine API Error: ${apiResponse.errorMessage}");
isPatientVaccineListLoading = false;
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
patientVaccineList = apiResponse.data!;
if (apiResponse.data != null) {
patientVaccineList = apiResponse.data!;
print("✅ Vaccine data loaded: ${patientVaccineList.length} items");
} else {
print("⚠️ Vaccine API returned success but data is null");
}
isPatientVaccineListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
} else {
print("⚠️ Unexpected messageStatus: ${apiResponse.messageStatus}");
isPatientVaccineListLoading = false;
notifyListeners();
}
},
);

@ -1,160 +1,169 @@
import 'dart:convert';
class PatientVaccineResponseModel {
String? setupID;
int? projectID;
int? patientID;
String? setupId;
int? projectId;
int? patientId;
int? invoiceNo;
String? procedureID;
String? procedureId;
String? vaccineName;
Null? vaccineNameN;
dynamic vaccineNameN;
String? invoiceDate;
int? doctorID;
int? clinicID;
int? doctorId;
int? clinicId;
String? firstName;
String? middleName;
String? lastName;
Null? firstNameN;
Null? middleNameN;
Null? lastNameN;
dynamic firstNameN;
dynamic middleNameN;
dynamic lastNameN;
String? dateofBirth;
int? actualDoctorRate;
String? age;
String? clinicName;
Null? decimalDoctorRate;
Null? doctorImageURL;
double? decimalDoctorRate;
dynamic doctorImageUrl;
String? doctorName;
int? doctorRate;
int? doctorStarsRate;
double? doctorStarsRate;
String? doctorTitle;
int? gender;
Null? genderDescription;
Null? invoiceNoVP;
dynamic genderDescription;
dynamic invoiceNoVp;
bool? isActiveDoctorProfile;
bool? isDoctorAllowVedioCall;
bool? isExecludeDoctor;
int? noOfPatientsRate;
String? patientName;
String? projectName;
String? qR;
String? qr;
List<dynamic>? speciality;
String? vaccinationDate;
PatientVaccineResponseModel(
{this.setupID,
this.projectID,
this.patientID,
this.invoiceNo,
this.procedureID,
this.vaccineName,
this.vaccineNameN,
this.invoiceDate,
this.doctorID,
this.clinicID,
this.firstName,
this.middleName,
this.lastName,
this.firstNameN,
this.middleNameN,
this.lastNameN,
this.dateofBirth,
this.actualDoctorRate,
this.age,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageURL,
this.doctorName,
this.doctorRate,
this.doctorStarsRate,
this.doctorTitle,
this.gender,
this.genderDescription,
this.invoiceNoVP,
this.isActiveDoctorProfile,
this.isDoctorAllowVedioCall,
this.isExecludeDoctor,
this.noOfPatientsRate,
this.patientName,
this.projectName,
this.qR,
this.vaccinationDate});
PatientVaccineResponseModel({
this.setupId,
this.projectId,
this.patientId,
this.invoiceNo,
this.procedureId,
this.vaccineName,
this.vaccineNameN,
this.invoiceDate,
this.doctorId,
this.clinicId,
this.firstName,
this.middleName,
this.lastName,
this.firstNameN,
this.middleNameN,
this.lastNameN,
this.dateofBirth,
this.actualDoctorRate,
this.age,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageUrl,
this.doctorName,
this.doctorRate,
this.doctorStarsRate,
this.doctorTitle,
this.gender,
this.genderDescription,
this.invoiceNoVp,
this.isActiveDoctorProfile,
this.isDoctorAllowVedioCall,
this.isExecludeDoctor,
this.noOfPatientsRate,
this.patientName,
this.projectName,
this.qr,
this.speciality,
this.vaccinationDate,
});
factory PatientVaccineResponseModel.fromRawJson(String str) => PatientVaccineResponseModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
PatientVaccineResponseModel.fromJson(Map<String, dynamic> json) {
setupID = json['SetupID'];
projectID = json['ProjectID'];
patientID = json['PatientID'];
invoiceNo = json['InvoiceNo'];
procedureID = json['ProcedureID'];
vaccineName = json['VaccineName'];
vaccineNameN = json['VaccineNameN'];
invoiceDate = json['InvoiceDate'];
doctorID = json['DoctorID'];
clinicID = json['ClinicID'];
firstName = json['FirstName'];
middleName = json['MiddleName'];
lastName = json['LastName'];
firstNameN = json['FirstNameN'];
middleNameN = json['MiddleNameN'];
lastNameN = json['LastNameN'];
dateofBirth = json['DateofBirth'];
actualDoctorRate = json['ActualDoctorRate'];
age = json['Age'];
clinicName = json['ClinicName'];
decimalDoctorRate = json['DecimalDoctorRate'];
doctorImageURL = json['DoctorImageURL'];
doctorName = json['DoctorName'];
doctorRate = json['DoctorRate'];
doctorStarsRate = json['DoctorStarsRate'];
doctorTitle = json['DoctorTitle'];
gender = json['Gender'];
genderDescription = json['GenderDescription'];
invoiceNoVP = json['InvoiceNo_VP'];
isActiveDoctorProfile = json['IsActiveDoctorProfile'];
isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall'];
isExecludeDoctor = json['IsExecludeDoctor'];
noOfPatientsRate = json['NoOfPatientsRate'];
patientName = json['PatientName'];
projectName = json['ProjectName'];
qR = json['QR'];
vaccinationDate = json['VaccinationDate'];
}
factory PatientVaccineResponseModel.fromJson(Map<String, dynamic> json) => PatientVaccineResponseModel(
setupId: json["SetupID"],
projectId: json["ProjectID"],
patientId: json["PatientID"],
invoiceNo: json["InvoiceNo"],
procedureId: json["ProcedureID"],
vaccineName: json["VaccineName"],
vaccineNameN: json["VaccineNameN"],
invoiceDate: json["InvoiceDate"],
doctorId: json["DoctorID"],
clinicId: json["ClinicID"],
firstName: json["FirstName"],
middleName: json["MiddleName"],
lastName: json["LastName"],
firstNameN: json["FirstNameN"],
middleNameN: json["MiddleNameN"],
lastNameN: json["LastNameN"],
dateofBirth: json["DateofBirth"],
actualDoctorRate: json["ActualDoctorRate"],
age: json["Age"],
clinicName: json["ClinicName"],
decimalDoctorRate: json["DecimalDoctorRate"]?.toDouble(),
doctorImageUrl: json["DoctorImageURL"],
doctorName: json["DoctorName"],
doctorRate: json["DoctorRate"],
doctorStarsRate: json["DoctorStarsRate"]?.toDouble(),
doctorTitle: json["DoctorTitle"],
gender: json["Gender"],
genderDescription: json["GenderDescription"],
invoiceNoVp: json["InvoiceNo_VP"],
isActiveDoctorProfile: json["IsActiveDoctorProfile"],
isDoctorAllowVedioCall: json["IsDoctorAllowVedioCall"],
isExecludeDoctor: json["IsExecludeDoctor"],
noOfPatientsRate: json["NoOfPatientsRate"],
patientName: json["PatientName"],
projectName: json["ProjectName"],
qr: json["QR"],
speciality: json["Speciality"] == null ? [] : List<dynamic>.from(json["Speciality"]!.map((x) => x)),
vaccinationDate: json["VaccinationDate"],
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupID'] = this.setupID;
data['ProjectID'] = this.projectID;
data['PatientID'] = this.patientID;
data['InvoiceNo'] = this.invoiceNo;
data['ProcedureID'] = this.procedureID;
data['VaccineName'] = this.vaccineName;
data['VaccineNameN'] = this.vaccineNameN;
data['InvoiceDate'] = this.invoiceDate;
data['DoctorID'] = this.doctorID;
data['ClinicID'] = this.clinicID;
data['FirstName'] = this.firstName;
data['MiddleName'] = this.middleName;
data['LastName'] = this.lastName;
data['FirstNameN'] = this.firstNameN;
data['MiddleNameN'] = this.middleNameN;
data['LastNameN'] = this.lastNameN;
data['DateofBirth'] = this.dateofBirth;
data['ActualDoctorRate'] = this.actualDoctorRate;
data['Age'] = this.age;
data['ClinicName'] = this.clinicName;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorName'] = this.doctorName;
data['DoctorRate'] = this.doctorRate;
data['DoctorStarsRate'] = this.doctorStarsRate;
data['DoctorTitle'] = this.doctorTitle;
data['Gender'] = this.gender;
data['GenderDescription'] = this.genderDescription;
data['InvoiceNo_VP'] = this.invoiceNoVP;
data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile;
data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall;
data['IsExecludeDoctor'] = this.isExecludeDoctor;
data['NoOfPatientsRate'] = this.noOfPatientsRate;
data['PatientName'] = this.patientName;
data['ProjectName'] = this.projectName;
data['QR'] = this.qR;
data['VaccinationDate'] = this.vaccinationDate;
return data;
}
Map<String, dynamic> toJson() => {
"SetupID": setupId,
"ProjectID": projectId,
"PatientID": patientId,
"InvoiceNo": invoiceNo,
"ProcedureID": procedureId,
"VaccineName": vaccineName,
"VaccineNameN": vaccineNameN,
"InvoiceDate": invoiceDate,
"DoctorID": doctorId,
"ClinicID": clinicId,
"FirstName": firstName,
"MiddleName": middleName,
"LastName": lastName,
"FirstNameN": firstNameN,
"MiddleNameN": middleNameN,
"LastNameN": lastNameN,
"DateofBirth": dateofBirth,
"ActualDoctorRate": actualDoctorRate,
"Age": age,
"ClinicName": clinicName,
"DecimalDoctorRate": decimalDoctorRate,
"DoctorImageURL": doctorImageUrl,
"DoctorName": doctorName,
"DoctorRate": doctorRate,
"DoctorStarsRate": doctorStarsRate,
"DoctorTitle": doctorTitle,
"Gender": gender,
"GenderDescription": genderDescription,
"InvoiceNo_VP": invoiceNoVp,
"IsActiveDoctorProfile": isActiveDoctorProfile,
"IsDoctorAllowVedioCall": isDoctorAllowVedioCall,
"IsExecludeDoctor": isExecludeDoctor,
"NoOfPatientsRate": noOfPatientsRate,
"PatientName": patientName,
"ProjectName": projectName,
"QR": qr,
"Speciality": speciality == null ? [] : List<dynamic>.from(speciality!.map((x) => x)),
"VaccinationDate": vaccinationDate,
};
}

File diff suppressed because it is too large Load Diff

@ -302,6 +302,12 @@ abstract class LocaleKeys {
static const medicinesSubtitle = 'medicinesSubtitle';
static const vitalSigns = 'vitalSigns';
static const vitalSignsSubTitle = 'vitalSignsSubTitle';
static const vitalSignNormal = 'vitalSignNormal';
static const vitalSignLow = 'vitalSignLow';
static const vitalSignHigh = 'vitalSignHigh';
static const vitalSignObese = 'vitalSignObese';
static const vitalSignOverweight = 'vitalSignOverweight';
static const vitalSignUnderweight = 'vitalSignUnderweight';
static const myMedical = 'myMedical';
static const myMedicalSubtitle = 'myMedicalSubtitle';
static const myDoctor = 'myDoctor';
@ -716,6 +722,11 @@ abstract class LocaleKeys {
static const bloodDonation = 'bloodDonation';
static const bloodDonationInfo = 'bloodDonationInfo';
static const bloodInstruction = 'bloodInstruction';
static const aboutBloodDonation = 'aboutBloodDonation';
static const bloodDonationDescription = 'bloodDonationDescription';
static const bloodDonationBenefit1 = 'bloodDonationBenefit1';
static const bloodDonationBenefit2 = 'bloodDonationBenefit2';
static const bloodDonationBenefit3 = 'bloodDonationBenefit3';
static const viewTerms = 'viewTerms';
static const wantConnectHmgNetwork = 'wantConnectHmgNetwork';
static const failedToAccessHmgServices = 'failedToAccessHmgServices';

@ -154,7 +154,8 @@ class _AppointmentRatingWidgetState extends State<AppointmentRatingWidget> {
Expanded(
child: CustomButton(
text: LocaleKeys.submit.tr(context: context),
onPressed: () async {
isDisabled: rating == 0,
onPressed: rating == 0 ? null : () async {
// Set up clinic rating and show clinic rating view
// appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
// appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);

@ -56,11 +56,11 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
child: CollapsingListView(
title: LocaleKeys.bloodDonation.tr(),
trailing: CustomButton(
text: "Book",
text: LocaleKeys.book.tr(context: context),
onPressed: () {
if (bloodDonationVM.isUserAuthanticated()) {
bloodDonationVM.fetchHospitalsList().then((value) {
showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.selectHospital.tr(context: context), isDismissible: false, child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
return HospitalBottomSheetBodySelection(
isHideTitle: true,
onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) {
@ -224,6 +224,109 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
SizedBox(height: 24.h),
Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.bgRedLightColor.withValues(alpha: 0.1),
borderRadius: 12.r,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(8.h),
decoration: BoxDecoration(
color: AppColors.primaryRedColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: Utils.buildSvgWithAssets(
icon: AppAssets.blood_icon,
width: 20.h,
height: 20.h,
iconColor: AppColors.primaryRedColor,
),
),
SizedBox(width: 12.w),
Expanded(
child: LocaleKeys.aboutBloodDonation.tr(context: context).toText16(
color: AppColors.primaryRedColor,
isBold: true,
),
),
],
),
SizedBox(height: 12.h),
LocaleKeys.bloodDonationDescription.tr(context: context).toText14(
color: AppColors.greyTextColor,
isBold: true,
),
SizedBox(height: 12.h),
Row(
children: [
Container(
width: 4.w,
height: 4.h,
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
shape: BoxShape.circle,
),
),
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit1.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Container(
width: 4.w,
height: 4.h,
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
shape: BoxShape.circle,
),
),
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit2.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Container(
width: 4.w,
height: 4.h,
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
shape: BoxShape.circle,
),
),
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit3.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
),
],
),
],
),
),
],
),
),

@ -104,10 +104,9 @@ class _NewReferralPageState extends State<NewReferralPage> {
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
// setState(() {
_currentStep++;
// });
// widget.onStepChanged(_currentStep);
setState(() {
_currentStep++;
});
}
void _submitReferral() {
@ -133,8 +132,10 @@ class _NewReferralPageState extends State<NewReferralPage> {
hmgServicesVM.createEReferral(
requestModel: createReferralRequestModel,
onSuccess: (GenericApiModel response) {
showSuccessBottomSheet(int.parse(response.data), hmgServicesVM);
LoaderBottomSheet.hideLoader();
showSuccessBottomSheet(int.parse(response.data), hmgServicesVM);
},
onError: (errorMessage) {
// Handle error (e.g., show error message)
@ -190,7 +191,8 @@ class _NewReferralPageState extends State<NewReferralPage> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (index) {
if (_currentStep == index) {
// Fill current step and all previous completed steps
if (index <= _currentStep) {
return StepperWidget(widthOfOneState, AppColors.primaryRedColor, true, 4.h);
} else {
return StepperWidget(widthOfOneState, AppColors.greyLightColor, false, 4.h);
@ -200,8 +202,8 @@ class _NewReferralPageState extends State<NewReferralPage> {
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (index) => {
// setState(() => _currentStep = index)
onPageChanged: (index) {
setState(() => _currentStep = index);
},
children: [
RequesterFormStep(),

@ -63,28 +63,12 @@ class OTPService {
Navigator.pop(context);
final hmgServicesViewModel = context.read<HmgServicesViewModel>();
LoaderBottomSheet.showLoader();
hmgServicesViewModel.eReferralSendActivationCode(
requestModel: SendActivationCodeForEReferralRequestModel(
patientMobileNumber: int.parse(formManager.formData.requesterPhone),
zipCode: formManager.formData.countryEnum.countryCode,
patientOutSA: formManager.formData.countryEnum.countryCode == '966' ? 0 : 1,
),
onSuccess: (GenericApiModel response) {
LoaderBottomSheet.hideLoader();
hmgServicesViewModel.navigateToOTPScreen(
otpTypeEnum: OTPTypeEnum.sms,
phoneNumber: formManager.formData.requesterPhone,
loginToken: response.data,
onSuccess: () {
Navigator.pop(context);
onSuccess();
});
},
onError: (String errorMessage) {
LoaderBottomSheet.hideLoader();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage)));
},
// Start listening for SMS before sending OTP
_startSmsListener(
context: context,
hmgServicesViewModel: hmgServicesViewModel,
formManager: formManager,
onSuccess: onSuccess,
);
}
},
@ -100,4 +84,37 @@ class OTPService {
)),
);
}
static void _startSmsListener({
required BuildContext context,
required HmgServicesViewModel hmgServicesViewModel,
required ReferralFormManager formManager,
required Function onSuccess,
}) async {
LoaderBottomSheet.showLoader();
// Send OTP after starting listener
hmgServicesViewModel.eReferralSendActivationCode(
requestModel: SendActivationCodeForEReferralRequestModel(
patientMobileNumber: int.parse(formManager.formData.requesterPhone),
zipCode: formManager.formData.countryEnum.countryCode,
patientOutSA: formManager.formData.countryEnum.countryCode == '966' ? 0 : 1,
),
onSuccess: (GenericApiModel response) {
LoaderBottomSheet.hideLoader();
hmgServicesViewModel.navigateToOTPScreen(
otpTypeEnum: OTPTypeEnum.sms,
phoneNumber: formManager.formData.requesterPhone,
loginToken: response.data,
onSuccess: () {
Navigator.pop(context);
onSuccess();
});
},
onError: (String errorMessage) {
LoaderBottomSheet.hideLoader();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage)));
},
);
}
}

@ -636,73 +636,73 @@ class ServicesPage extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.virtual_tour_icon,
width: 32.w,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
LocaleKeys.virtualTour.tr().toText14(isBold: true)
],
),
),
).onPress(() {
Utils.openWebView(
url: 'https://hmgwebservices.com/vt_mobile/html/index.html',
);
}),
),
SizedBox(width: 16.w),
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.car_parking_icon,
width: 32.w,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
LocaleKeys.carParking.tr().toText14(isBold: true)
],
).onPress(() {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChangeNotifierProvider(
create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(),
),
),
);
}),
),
),
),
],
),
// Row(
// children: [
// // Expanded(
// // child: Container(
// // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// // color: AppColors.whiteColor,
// // borderRadius: 12.h,
// // hasShadow: false,
// // ),
// // child: Padding(
// // padding: EdgeInsets.all(16.h),
// // child: Row(
// // children: [
// // Utils.buildSvgWithAssets(
// // icon: AppAssets.virtual_tour_icon,
// // width: 32.w,
// // height: 32.h,
// // fit: BoxFit.contain,
// // ),
// // SizedBox(width: 8.w),
// // LocaleKeys.virtualTour.tr().toText14(isBold: true)
// // ],
// // ),
// // ),
// // ).onPress(() {
// // Utils.openWebView(
// // url: 'https://hmgwebservices.com/vt_mobile/html/index.html',
// // );
// // }),
// // ),
// SizedBox(width: 16.w),
// Expanded(
// child: Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 12.h,
// hasShadow: false,
// ),
// child: Padding(
// padding: EdgeInsets.all(16.h),
// child: Row(
// children: [
// Utils.buildSvgWithAssets(
// icon: AppAssets.car_parking_icon,
// width: 32.w,
// height: 32.h,
// fit: BoxFit.contain,
// ),
// SizedBox(width: 8.w),
// LocaleKeys.carParking.tr().toText14(isBold: true)
// ],
// ).onPress(() {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (_) => ChangeNotifierProvider(
// create: (_) => getIt<QrParkingViewModel>(),
// child: const ParkingPage(),
// ),
// ),
// );
// }),
// ),
// ),
// ),
// ],
// ),
SizedBox(height: 16.h),
Row(
children: [

@ -34,6 +34,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.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_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/active_medication/active_medication_page.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
@ -55,6 +56,8 @@ import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_a
import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tracker_menu_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tools_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
@ -524,7 +527,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
),
ExpandableListItem(
title: LocaleKeys.healthTrackers.tr(context: context).toText18(isBold: true),
title: LocaleKeys.healthTools.tr(context: context).toText18(isBold: true),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
@ -1279,7 +1282,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 62.w,
height: 62.h,
),
).paddingSymmetrical(0.w, 0.h);
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 16.h),
GridView(
@ -1293,21 +1296,21 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.monthlyReports.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.monthly_reports_icon,
isLargeText: true,
iconSize: 36.h,
).onPress(() {
monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false);
Navigator.of(context).push(
CustomPageRoute(
page: MonthlyReport(),
),
);
}),
// MedicalFileCard(
// label: LocaleKeys.monthlyReports.tr(context: context),
// textColor: AppColors.blackColor,
// backgroundColor: AppColors.whiteColor,
// svgIcon: AppAssets.monthly_reports_icon,
// isLargeText: true,
// iconSize: 36.h,
// ).onPress(() {
// monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false);
// Navigator.of(context).push(
// CustomPageRoute(
// page: MonthlyReport(),
// ),
// );
// }),
MedicalFileCard(
label: LocaleKeys.medicalReports.tr(context: context),
textColor: AppColors.blackColor,
@ -1344,95 +1347,93 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
);
case 3:
// Trackers Tab Data
// Health Tools Tab Data
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
// LocaleKeys.healthTrackers.tr(context: context).toText16(isBold: true, color: AppColors.textColor),
// ],
// ),
// SizedBox(height: 16.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
mainAxisExtent: 120.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
padding: EdgeInsets.only(top: 12.h),
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.bloodSugar.tr(context: context),
// Health Trackers
HealthToolsCard(
label: LocaleKeys.healthTrackers.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.blood_sugar_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)),
MedicalFileCard(
label: LocaleKeys.bloodPressure.tr(context: context),
svgIcon: AppAssets.general_health,
iconColor:null,
iconSize: 24.w,
).onPress(() {
if (getIt.get<AppState>().isAuthenticated) {
context.navigateWithName(AppRoutes.healthTrackersPage);
}
}),
// Daily Water Monitor
HealthToolsCard(
label: LocaleKeys.dailyWaterMonitor.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.lab_result_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)),
MedicalFileCard(
label: LocaleKeys.weightTracker.tr(context: context),
svgIcon: AppAssets.daily_water_monitor_icon,
iconColor: AppColors.infoColor,
iconSize: 24.w,
).onPress(() async {
if (getIt.get<AppState>().isAuthenticated) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr());
final waterMonitorVM = getIt.get<WaterMonitorViewModel>();
await waterMonitorVM.fetchUserDetailsForMonitoring(
onSuccess: (userDetail) {
LoaderBottomSheet.hideLoader();
if (userDetail == null) {
waterMonitorVM.populateFromAuthenticatedUser();
context.navigateWithName(AppRoutes.waterMonitorSettingsPage);
} else {
context.navigateWithName(AppRoutes.waterConsumptionPage);
}
},
onError: (error) {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.waterConsumptionPage);
},
);
}
}),
// Health Calculators
HealthToolsCard(
label: LocaleKeys.healthCalculatorsServices.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.weight_tracker_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.weightTracker)),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
// Row(
// children: [
// LocaleKeys.others.tr(context: context).toText16(isBold: true, color: AppColors.textColor),
// ],
// ),
SizedBox(height: 16.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
// MedicalFileCard(
// label: LocaleKeys.askYourDoctor.tr(context: context),
// textColor: AppColors.blackColor,
// backgroundColor: AppColors.whiteColor,
// svgIcon: AppAssets.ask_doctor_medical_file_icon,
// isLargeText: true,
// iconSize: 36.w,
// ).onPress(() {
// getIt.get<AskDoctorViewModel>().initAskDoctorViewModel();
// Navigator.of(context).push(
// CustomPageRoute(
// page: AskDoctorPage(),
// ),
// );
// }),
// MedicalFileCard(
// label: LocaleKeys.internetPairing.tr(context: context),
// textColor: AppColors.blackColor,
// backgroundColor: AppColors.whiteColor,
// svgIcon: AppAssets.internet_pairing_icon,
// isLargeText: true,
// iconSize: 36.w,
// ).onPress(() {}),
svgIcon: AppAssets.health_calculators_services_icon,
iconColor: AppColors.successColor,
iconSize: 24.w,
).onPress(() {
context.navigateWithName(AppRoutes.healthCalculatorsPage);
}),
// Health Converters
HealthToolsCard(
label: LocaleKeys.healthConvertersServices.tr(context: context),
textColor: AppColors.blackColor,
svgIcon: AppAssets.health_converters_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 24.w,
).onPress(() {
context.navigateWithName(AppRoutes.healthConvertersPage);
}),
// Smart Watches
HealthToolsCard(
label: LocaleKeys.smartWatchesServices.tr(context: context),
textColor: AppColors.blackColor,
svgIcon: AppAssets.smartwatch_icon,
iconColor: AppColors.warningColorYellow,
iconSize: 24.w,
).onPress(() {
if (getIt.get<AppState>().isAuthenticated) {
context.navigateWithName(AppRoutes.smartWatches);
}
}),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 24.h),
@ -1536,7 +1537,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: _buildVitalSignCard(
icon: AppAssets.bmiVital,
label: LocaleKeys.bmi.tr(context: context),
value: vitalSign.bodyMassIndex?.toString() ?? '--',
value: _formatValue(vitalSign.bodyMassIndex),
unit: '',
status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null,
// status: "Overweight",
@ -1548,7 +1549,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: _buildVitalSignCard(
icon: AppAssets.heightVital,
label: LocaleKeys.height.tr(context: context),
value: vitalSign.heightCm?.toString() ?? '--',
value: _formatValue(vitalSign.heightCm),
unit: 'cm',
status: null,
onTap: onTap,
@ -1566,7 +1567,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: _buildVitalSignCard(
icon: AppAssets.weightVital,
label: LocaleKeys.weight.tr(context: context),
value: vitalSign.weightKg?.toString() ?? '--',
value: _formatValue(vitalSign.weightKg),
unit: 'kg',
status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null,
onTap: onTap,
@ -1577,12 +1578,18 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: _buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: LocaleKeys.bloodPressure.tr(context: context),
value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" : '--',
value: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
: '--',
unit: '',
status: _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
),
status: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
? _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
)
: null,
onTap: onTap,
),
),
@ -1591,25 +1598,36 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
];
}
String _getBMIStatus(dynamic bmi) {
VitalSignStatus? _getBMIStatus(dynamic bmi) {
return VitalSignUiModel.bmiStatus(bmi);
}
String? _getWeightStatus(dynamic bmi) {
if (bmi == null) return 'Normal';
return VitalSignUiModel.bmiStatus(bmi);
VitalSignStatus? _getWeightStatus(dynamic bmi) {
return VitalSignUiModel.weightStatus(bmi);
}
String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
VitalSignStatus? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
}
// Helper function to format vital sign values, showing '--' instead of 0 or null
String _formatValue(dynamic value) {
if (value == null) return '--';
if (value is num && value == 0) return '--';
if (value is String) {
if (value.trim().isEmpty) return '--';
final parsed = double.tryParse(value);
if (parsed == null || parsed == 0) return '--';
}
return value.toString();
}
Widget _buildVitalSignCard({
required String icon,
required String label,
required String value,
required String unit,
required String? status,
required VitalSignStatus? status,
required VoidCallback onTap,
}) {
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
@ -1676,7 +1694,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
isEnglishOnly: true
),
),
if (unit.isNotEmpty) ...[
if (unit.isNotEmpty && value != '--' && value != '0') ...[
SizedBox(width: 3.w),
unit.toText12(
color: AppColors.textColor,
@ -1689,7 +1707,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
if (status != null) ...[
SizedBox(width: 4.w),
AppCustomChipWidget(
labelText: status,
labelText: VitalSignUiModel.statusToString(status),
backgroundColor: scheme.chipBg,
textColor: scheme.chipFg,
),

@ -29,11 +29,19 @@ class _VaccineListPageState extends State<VaccineListPage> {
@override
void initState() {
super.initState();
scheduleMicrotask(() {
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
medicalFileViewModel.setIsPatientVaccineListLoading(true);
medicalFileViewModel.getPatientVaccinesList();
medicalFileViewModel.getPatientVaccinesList(
onSuccess: (data) {
// print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items");
},
onError: (error) {
//print("❌ Vaccine error: $error");
},
);
});
super.initState();
}
@override
@ -133,11 +141,18 @@ class _VaccineListPageState extends State<VaccineListPage> {
// height: 63.h,
// fit: BoxFit.fill,
// ).circle(100).toShimmer2(isShow: false),
SizedBox(width: 16.h),
SizedBox(width: 8.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppCustomChipWidget(
backgroundColor: AppColors.lightGreenButtonColor,
textColor: AppColors.textGreenColor,
padding: EdgeInsets.all(0),
labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h, ),
SizedBox(height:10.h),
(medicalFileVM.patientVaccineList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false),
SizedBox(height: 8.h),
Wrap(
@ -148,7 +163,7 @@ class _VaccineListPageState extends State<VaccineListPage> {
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(medicalFileVM.patientVaccineList[index].vaccinationDate), false)),
AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h),
AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].clinicName).toShimmer2(isShow: false, width: 16.h),
AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].projectName).toShimmer2(isShow: false, width: 16.h),
],

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class HealthToolsCard extends StatelessWidget {
final String label;
final Color textColor;
final String svgIcon;
final double? iconSize;
final Color? iconColor;
const HealthToolsCard({
super.key,
required this.label,
required this.textColor,
required this.svgIcon,
this.iconSize,
this.iconColor,
});
@override
Widget build(BuildContext context) {
final iconS = iconSize ?? 24.w;
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
padding: EdgeInsets.all(12.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icon container with white background and border
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
side: BorderSide(
color: AppColors.borderOnlyColor.withValues(alpha: 0.1),
width: 1,
),
),
height: 48.w,
width: 48.w,
child: Center(
child: Utils.buildSvgWithAssets(
icon: svgIcon,
width: iconS,
height: iconS,
fit: BoxFit.contain,
applyThemeColor: false,
iconColor: iconColor,
),
),
),
SizedBox(height: 6.h),
label.toText13(color: textColor, isBold: true, maxLine: 2),
],
),
],
),
);
}
}

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class HealthTrackerMenuCard extends StatelessWidget {
final String label;
final Color textColor;
final Color backgroundColor;
final String svgIcon;
final double? iconSize;
final Color? iconColor;
final Color iconBgColor;
const HealthTrackerMenuCard({
super.key,
required this.label,
required this.textColor,
required this.backgroundColor,
required this.svgIcon,
required this.iconBgColor,
this.iconSize,
this.iconColor,
});
@override
Widget build(BuildContext context) {
final iconS = iconSize ?? 22.w;
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: backgroundColor,
borderRadius: 20.r,
hasShadow: false
),
padding: EdgeInsets.all(16.w),
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: svgIcon,
fit: BoxFit.none,
height: iconS,
width: iconS,
applyThemeColor: false,
iconColor: iconColor,
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
label.toText14(color: textColor, isBold: true),
],
),
),
SizedBox(width: 12.w),
Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor,
),
),
],
),
);
}
}

@ -64,3 +64,5 @@ class MedicalFileCard extends StatelessWidget {
);
}
}

@ -193,20 +193,15 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
Expanded(
child: CustomButton(
text: LocaleKeys.submit.tr(context: context),
onPressed: () async {
// Set up clinic rating and show clinic rating view
// appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
// appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);
// appointmentRatingViewModel!.setClinicOrDoctor(true);
LoaderBottomSheet.showLoader();
await appointmentRatingViewModel!.submitDoctorRating(docRate: rating, docNote: note);
// await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note);
LoaderBottomSheet.hideLoader();
Navigator.pop(context);
setState(() {});
},
onPressed: rating > 0
? () {
LoaderBottomSheet.showLoader();
appointmentRatingViewModel!.submitDoctorRating(docRate: rating, docNote: note).then((_) {
LoaderBottomSheet.hideLoader();
Navigator.pop(context);
});
}
: null,
),
),
],

@ -2,7 +2,9 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -119,7 +121,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
required String title,
required String icon,
required String valueText,
required String? status,
required VitalSignStatus? status,
required VitalSignUiModel scheme,
required DateTime? latestDate,
}) {
@ -168,17 +170,20 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
),
SizedBox(width: 4.h),
if (status != null)
Column(
spacing: 6.h,
children: [
status.toText10(isBold: true, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(
icon: AppAssets.lab_result_indicator,
width: 21,
height: 23,
iconColor: scheme.iconFg,
),
],
Directionality(
textDirection: ui.TextDirection.ltr,
child: Column(
spacing: 6.h,
children: [
VitalSignUiModel.statusToString(status).toText10(isBold: true, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(
icon: AppAssets.lab_result_indicator,
width: 21,
height: 23,
iconColor: scheme.iconFg,
),
],
),
),
],
),
@ -331,6 +336,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
final minY = _minY(history, secondaryHistory: secondaryHistory);
final maxY = _maxY(history, secondaryHistory: secondaryHistory);
final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title);
final isRTL = getIt.get<AppState>().isArabic();
return CustomGraph(
dataPoints: history,
@ -339,6 +345,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
leftLabelReservedSize: 40,
showGridLines: true,
showShadow: true,
isRTL: isRTL,
leftLabelInterval: _leftInterval(history, secondaryHistory: secondaryHistory),
maxY: maxY,
minY: minY,
@ -663,7 +670,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
}
}
String? _statusForLatest(VitalSignResModel? latest) {
VitalSignStatus? _statusForLatest(VitalSignResModel? latest) {
if (latest == null) return null;
switch (args.metric) {
@ -674,14 +681,15 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
case VitalSignMetric.height:
return null;
case VitalSignMetric.weight:
// return latest.weightKg != null ? 'Normal' : null;
return VitalSignUiModel.bmiStatus(latest.bodyMassIndex);
return VitalSignUiModel.weightStatus(latest.bodyMassIndex);
case VitalSignMetric.temperature:
return null;
case VitalSignMetric.heartRate:
return (latest.heartRate ?? latest.pulseBeatPerMinute) != null ? 'Normal' : null;
final heartRateValue = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute);
return (heartRateValue != null && heartRateValue > 0) ? VitalSignStatus.normal : null;
case VitalSignMetric.respiratoryRate:
return latest.respirationBeatPerMinute != null ? 'Normal' : null;
final respiratoryValue = _toDouble(latest.respirationBeatPerMinute);
return (respiratoryValue != null && respiratoryValue > 0) ? VitalSignStatus.normal : null;
}
}
@ -756,9 +764,9 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
Widget _bottomLabel(String label, {bool isLast = false}) {
return Padding(
padding: EdgeInsets.only(
padding: EdgeInsetsDirectional.only(
top: 8.0,
right: isLast ? 16.h : 0,
end: isLast ? 16.h : 0,
),
child: label.toText10(isBold: true, isEnglishOnly: true),
);

@ -41,6 +41,18 @@ class _VitalSignPageState extends State<VitalSignPage> {
return false;
}
// Helper function to format vital sign values, showing '--' instead of 0 or null
String _formatValue(dynamic value) {
if (value == null) return '--';
if (value is num && value == 0) return '--';
if (value is String) {
if (value.trim().isEmpty) return '--';
final parsed = double.tryParse(value);
if (parsed == null || parsed == 0) return '--';
}
return value.toString();
}
void _openDetails(VitalSignDetailsArgs args) {
Navigator.of(context).push(
CustomPageRoute(
@ -100,7 +112,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.bmiVital,
label: LocaleKeys.bmi.tr(context: context),
value: latestVitalSign?.bodyMassIndex?.toString() ?? '--',
value: _formatValue(latestVitalSign?.bodyMassIndex),
unit: '',
status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex),
onTap: () => _openDetails(
@ -118,7 +130,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.heightVital,
label: LocaleKeys.height.tr(context: context),
value: latestVitalSign?.heightCm?.toString() ?? '--',
value: _formatValue(latestVitalSign?.heightCm),
unit: 'cm',
status: null,
onTap: () => _openDetails(
@ -136,10 +148,10 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.weightVital,
label: LocaleKeys.weight.tr(context: context),
value: latestVitalSign?.weightKg?.toString() ?? '--',
value: _formatValue(latestVitalSign?.weightKg),
unit: 'kg',
status: (latestVitalSign?.weightKg != null)
? VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex)
? VitalSignUiModel.weightStatus(latestVitalSign?.bodyMassIndex)
: null,
onTap: () => _openDetails(
VitalSignDetailsArgs(
@ -162,10 +174,14 @@ class _VitalSignPageState extends State<VitalSignPage> {
? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}'
: '--',
unit: '',
status: VitalSignUiModel.bloodPressureStatus(
systolic: latestVitalSign?.bloodPressureHigher,
diastolic: latestVitalSign?.bloodPressureLower,
),
status: (latestVitalSign != null &&
_isValidValue(latestVitalSign.bloodPressureHigher) &&
_isValidValue(latestVitalSign.bloodPressureLower))
? VitalSignUiModel.bloodPressureStatus(
systolic: latestVitalSign.bloodPressureHigher,
diastolic: latestVitalSign.bloodPressureLower,
)
: null,
onTap: () => _openDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.bloodPressure,
@ -183,7 +199,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.temperature,
label: LocaleKeys.temperature.tr(context: context),
value: latestVitalSign?.temperatureCelcius?.toString() ?? '--',
value: _formatValue(latestVitalSign?.temperatureCelcius),
unit: '°C',
status: null,
onTap: () => _openDetails(
@ -258,9 +274,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
child: _buildVitalSignCard(
icon: AppAssets.heart,
label: LocaleKeys.heart.tr(context: context),
value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute),
unit: 'bpm',
status: 'Normal',
status: _isValidValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null,
onTap: () => _openDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.heartRate,
@ -282,9 +298,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
_buildVitalSignCard(
icon: AppAssets.respRate,
label: LocaleKeys.respirationRate.tr(context: context),
value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
value: _formatValue(latestVitalSign?.respirationBeatPerMinute),
unit: 'bpm',
status: 'Normal',
status: _isValidValue(latestVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null,
onTap: () => _openDetails(
VitalSignDetailsArgs(
metric: VitalSignMetric.respiratoryRate,
@ -318,7 +334,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
required String label,
required String value,
required String unit,
required String? status,
required VitalSignStatus? status,
required VoidCallback onTap,
}) {
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
@ -387,7 +403,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
color: AppColors.textColor,
isEnglishOnly: true
),
if (unit.isNotEmpty) ...[
if (unit.isNotEmpty && value != '--' && value != '0') ...[
SizedBox(width: 3.w),
unit.toText12(
color: AppColors.textColor,
@ -398,7 +414,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
),
if (status != null)
AppCustomChipWidget(
labelText: status,
labelText: VitalSignUiModel.statusToString(status),
backgroundColor: scheme.chipBg,
textColor: scheme.chipFg,

@ -8,7 +8,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
class CustomButton extends StatelessWidget {
final String text;
final String? icon;
final VoidCallback onPressed;
final VoidCallback? onPressed;
final Color backgroundColor;
final Color borderColor;
final Color textColor;
@ -58,8 +58,10 @@ class CustomButton extends StatelessWidget {
final radius = borderRadius ?? (12.r);
final iconS = iconSize ?? (24.h);
final fontS = fontSize ?? (16.f);
final isButtonDisabled = isDisabled || onPressed == null;
return GestureDetector(
onTap: isDisabled ? null : onPressed,
onTap: isButtonDisabled ? null : onPressed,
child: Container(
height: height == null
? 56.h
@ -69,10 +71,10 @@ class CustomButton extends StatelessWidget {
width: width,
padding: padding,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: isDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor,
color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor,
borderRadius: radius,
customBorder: BorderRadius.circular(radius),
side: borderSide ?? BorderSide(width: borderWidth.h, color: isDisabled ? Colors.transparent : borderColor)),
side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
@ -80,7 +82,7 @@ class CustomButton extends StatelessWidget {
if (icon != null)
Padding(
padding: text.isNotEmpty ? EdgeInsets.only(right: 4.w, left: 4.w) : EdgeInsets.zero,
child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconS, height: iconS, applyThemeColor: applyThemeColor),
child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isButtonDisabled, width: iconS, height: iconS, applyThemeColor: applyThemeColor),
),
if (text.isNotEmpty)
Text(
@ -90,7 +92,7 @@ class CustomButton extends StatelessWidget {
textAlign: TextAlign.center,
style: context.dynamicTextStyle(
fontSize: fontS,
color: isDisabled ? AppColors.greyTextColor : textColor,
color: isButtonDisabled ? AppColors.greyTextColor : textColor,
letterSpacing: 0,
fontWeight: isBold ? FontWeight.w700 : fontWeight,
),

Loading…
Cancel
Save