From 5f82f0db2d36cfc4164e7f0f286d30145e7a831d Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Fri, 10 Apr 2026 22:02:32 +0300 Subject: [PATCH] fixes and updates --- assets/images/svg/my_radiology_icon.svg | 5 +- assets/langs/ar-SA.json | 5 + assets/langs/en-US.json | 5 + .../authentication_view_model.dart | 5 + .../emergency_services_repo.dart | 2 +- .../models/ui_models/vital_sign_ui_model.dart | 74 +++++-- lib/generated/locale_keys.g.dart | 5 + .../blood_donation/blood_donation_page.dart | 103 +++++++++ .../medical_file/medical_file_page.dart | 209 ++++++++++-------- .../widgets/health_tracker_menu_card.dart | 86 +++++++ .../widgets/medical_file_card.dart | 2 + .../rate_appointment_doctor.dart | 23 +- .../vital_sign/vital_sign_details_page.dart | 3 +- .../vital_sign/vital_sign_page.dart | 40 +++- lib/widgets/buttons/custom_button.dart | 14 +- 15 files changed, 429 insertions(+), 152 deletions(-) create mode 100644 lib/presentation/medical_file/widgets/health_tracker_menu_card.dart diff --git a/assets/images/svg/my_radiology_icon.svg b/assets/images/svg/my_radiology_icon.svg index 7b5ebe46..1848607f 100644 --- a/assets/images/svg/my_radiology_icon.svg +++ b/assets/images/svg/my_radiology_icon.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 4ac9878c..2d8af237 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -717,6 +717,11 @@ "bloodDonation": "التبرع بالدم", "bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.", "bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم", + "aboutBloodDonation": "عن التبرع بالدم", + "bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.", + "bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)", + "bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع", + "bloodDonationBenefit3": "فحص صحي مجاني لدمك", "viewTerms": "لعرض الشروط والأحكام", "wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية", "failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 84d51a6c..faff11d1 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -710,6 +710,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", diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index d94b100b..45c2d620 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -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(); diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index f8eb896f..10c5541a 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -183,7 +183,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { @override Future>>> getProjectList() async { - Map request = {'IsOnlineCheckIn': false, 'IsAdvancePayment': false, 'IsForER': true}; + Map request = {'IsOnlineCheckIn': true, 'IsAdvancePayment': false, 'IsForER': true}; try { GenericApiModel>? apiResponse; diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart index 90c513c4..48f9024b 100644 --- a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart +++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart @@ -1,6 +1,4 @@ -import 'package:easy_localization/easy_localization.dart'; 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'; /// UI-only helper model for Vital Sign cards. @@ -22,10 +20,10 @@ 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(); @@ -41,7 +39,8 @@ class VitalSignUiModel { ); } - if (s.contains('high')) { + // High, Obese, Overweight => red scheme (health concerns) + if (s.contains('high') || s.contains('obese') || s.contains('overweight')) { return VitalSignUiModel( iconBg: AppColors.chipSecondaryLightRedColor, iconFg: AppColors.primaryRedColor, @@ -50,8 +49,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 (s.contains('low') || s.contains('underweight')) { final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12); return VitalSignUiModel( iconBg: yellowBg, @@ -93,28 +92,55 @@ class VitalSignUiModel { return int.tryParse(v.toString()); } - static String bmiStatus(dynamic bmi) { - String bmiStatus = 'Normal'; + static String? 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; } - // 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'; + String bmiStatus = 'Normal'; + + // BMI >= 25 (Overweight or Obese) => High + if (bmiResult >= 25) { + bmiStatus = 'High'; + } + // BMI >= 18.5 and < 25 => Normal + else if (bmiResult >= 18.5) { + bmiStatus = 'Normal'; + } + // BMI < 18.5 (Underweight) => Low + else { + bmiStatus = 'Low'; + } return bmiStatus; } + + /// Weight status based on BMI with detailed classification + /// Returns: Obese, Overweight, Normal, Underweight + static String? weightStatus(dynamic bmi) { + // Return null if BMI is not available or is 0 + final double bmiResult = double.tryParse(bmi.toString()) ?? 0; + + if (bmiResult == 0 || bmi == null) { + return null; + } + + String status = 'Normal'; + + if (bmiResult >= 30) { + status = 'Obese'; + } else if (bmiResult >= 25) { + status = 'Overweight'; + } else if (bmiResult >= 18.5) { + status = 'Normal'; + } else { + status = 'Underweight'; + } + + return status; + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1e9b1349..b04d457b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -716,6 +716,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'; diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index 277d78c0..e058833e 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -224,6 +224,109 @@ class _BloodDonationPageState extends State { 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, + ), + ), + ], + ), + ], + ), + ), ], ), ), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 7bbfc936..cb050907 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -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,7 @@ 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/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 +526,7 @@ class _MedicalFilePageState extends State { ], ), 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), @@ -1278,7 +1280,7 @@ class _MedicalFilePageState extends State { width: 62.w, height: 62.h, ), - ).paddingSymmetrical(0.w, 0.h); + ).paddingSymmetrical(0.w, 0.h); }), SizedBox(height: 16.h), GridView( @@ -1292,21 +1294,21 @@ class _MedicalFilePageState extends State { 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, @@ -1343,95 +1345,103 @@ class _MedicalFilePageState extends State { ], ); 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: [ + // Health Trackers MedicalFileCard( - label: LocaleKeys.bloodSugar.tr(context: context), + label: LocaleKeys.healthTrackers.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.blood_sugar_icon, + svgIcon: AppAssets.general_health, + iconColor: null, isLargeText: true, iconSize: 36.w, - ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)), + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.healthTrackersPage); + } + }), + // Daily Water Monitor MedicalFileCard( - label: LocaleKeys.bloodPressure.tr(context: context), + label: LocaleKeys.dailyWaterMonitor.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, + svgIcon: AppAssets.daily_water_monitor_icon, + iconColor: AppColors.infoColor, isLargeText: true, iconSize: 36.w, - ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)), + ).onPress(() async { + if (getIt.get().isAuthenticated) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); + final waterMonitorVM = getIt.get(); + 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 MedicalFileCard( - label: LocaleKeys.weightTracker.tr(context: context), + label: LocaleKeys.healthCalculatorsServices.tr(context: context), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.weight_tracker_icon, + svgIcon: AppAssets.health_calculators_services_icon, + iconColor: AppColors.successColor, 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().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(() {}), + ).onPress(() { + context.navigateWithName(AppRoutes.healthCalculatorsPage); + }), + // Health Converters + MedicalFileCard( + label: LocaleKeys.healthConvertersServices.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.health_converters_icon, + iconColor: AppColors.primaryRedColor, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + context.navigateWithName(AppRoutes.healthConvertersPage); + }), + // Smart Watches + MedicalFileCard( + label: LocaleKeys.smartWatchesServices.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.smartwatch_icon, + iconColor: AppColors.warningColorYellow, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.smartWatches); + } + }), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), @@ -1535,7 +1545,7 @@ class _MedicalFilePageState extends State { 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", @@ -1547,7 +1557,7 @@ class _MedicalFilePageState extends State { 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, @@ -1565,7 +1575,7 @@ class _MedicalFilePageState extends State { 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, @@ -1576,12 +1586,18 @@ class _MedicalFilePageState extends State { 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, ), ), @@ -1590,19 +1606,30 @@ class _MedicalFilePageState extends State { ]; } - String _getBMIStatus(dynamic bmi) { + String? _getBMIStatus(dynamic bmi) { return VitalSignUiModel.bmiStatus(bmi); } String? _getWeightStatus(dynamic bmi) { - if (bmi == null) return 'Normal'; - return VitalSignUiModel.bmiStatus(bmi); + return VitalSignUiModel.weightStatus(bmi); } String? _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, @@ -1675,7 +1702,7 @@ class _MedicalFilePageState extends State { isEnglishOnly: true ), ), - if (unit.isNotEmpty) ...[ + if (unit.isNotEmpty && value != '--' && value != '0') ...[ SizedBox(width: 3.w), unit.toText12( color: AppColors.textColor, diff --git a/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart b/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart new file mode 100644 index 00000000..6284a827 --- /dev/null +++ b/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart @@ -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().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor, + ), + ), + ], + ), + ); + } +} + diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 40ae17d4..a79d8aa9 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -64,3 +64,5 @@ class MedicalFileCard extends StatelessWidget { ); } } + + diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index 0020624c..9a3925a1 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -193,20 +193,15 @@ class _RateAppointmentDoctorState extends State { 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, ), ), ], diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index ff1daaf9..d6dbd753 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -674,8 +674,7 @@ class _VitalSignDetailsPageState extends State { 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: diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 2e6e98ed..2ba18db2 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -41,6 +41,18 @@ class _VitalSignPageState extends State { 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 { _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 { _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 { _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 { ? '${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 { _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,7 +274,7 @@ class _VitalSignPageState extends State { 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', onTap: () => _openDetails( @@ -282,7 +298,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.respRate, label: LocaleKeys.respirationRate.tr(context: context), - value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', + value: _formatValue(latestVitalSign?.respirationBeatPerMinute), unit: 'bpm', status: 'Normal', onTap: () => _openDetails( @@ -387,7 +403,7 @@ class _VitalSignPageState extends State { color: AppColors.textColor, isEnglishOnly: true ), - if (unit.isNotEmpty) ...[ + if (unit.isNotEmpty && value != '--' && value != '0') ...[ SizedBox(width: 3.w), unit.toText12( color: AppColors.textColor, diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index ae1d3643..cf9799f0 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -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, ),