fixes and updates

pull/229/head
Sultan khan 3 days ago
parent 207b84e493
commit 3d226c39d0

@ -301,6 +301,12 @@
"medicinesSubtitle": "الوصفات", "medicinesSubtitle": "الوصفات",
"vitalSigns": "علامات حيوية", "vitalSigns": "علامات حيوية",
"vitalSignsSubTitle": "التقارير", "vitalSignsSubTitle": "التقارير",
"vitalSignNormal": "طبيعي",
"vitalSignLow": "منخفض",
"vitalSignHigh": "مرتفع",
"vitalSignObese": "بدين",
"vitalSignOverweight": "زيادة الوزن",
"vitalSignUnderweight": "نقص الوزن",
"myMedical": "نشط", "myMedical": "نشط",
"myMedicalSubtitle": "الأدوية", "myMedicalSubtitle": "الأدوية",
"myDoctor": "أطبائي", "myDoctor": "أطبائي",

@ -300,6 +300,12 @@
"medicinesSubtitle": "Prescriptions", "medicinesSubtitle": "Prescriptions",
"vitalSigns": "Vital Signs", "vitalSigns": "Vital Signs",
"vitalSignsSubTitle": "Reports", "vitalSignsSubTitle": "Reports",
"vitalSignNormal": "Normal",
"vitalSignLow": "Low",
"vitalSignHigh": "High",
"vitalSignObese": "Obese",
"vitalSignOverweight": "Overweight",
"vitalSignUnderweight": "Underweight",
"myMedical": "Active", "myMedical": "Active",
"myMedicalSubtitle": "Medications", "myMedicalSubtitle": "Medications",
"myDoctor": "My Doctors", "myDoctor": "My Doctors",

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.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/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/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_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/req_models/check_activation_e_referral_req_model.dart';
import 'models/resq_models/get_covid_payment_info_resp.dart'; import 'models/resq_models/get_covid_payment_info_resp.dart';
@ -632,6 +634,9 @@ class HmgServicesViewModel extends ChangeNotifier {
Function(GenericApiModel)? onSuccess, Function(GenericApiModel)? onSuccess,
Function(String)? onError, Function(String)? onError,
}) async { }) async {
// Get and set the SMS signature for auto-fill
requestModel.sMSSignature = await getSignature();
notifyListeners(); notifyListeners();
final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel); 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({ Future<void> checkEReferralActivationCode({
required CheckActivationCodeForEReferralRequestModel requestModel, required CheckActivationCodeForEReferralRequestModel requestModel,
Function(GenericApiModel)? onSuccess, Function(GenericApiModel)? onSuccess,

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

@ -1,6 +1,18 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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'; 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. /// UI-only helper model for Vital Sign cards.
/// ///
/// Keeps presentation logic (chip colors, icon colors, simple status rules) /// Keeps presentation logic (chip colors, icon colors, simple status rules)
@ -25,8 +37,7 @@ class VitalSignUiModel {
/// - High, Obese, Overweight => red scheme. /// - High, Obese, Overweight => red scheme.
/// - Low, Underweight => yellow scheme. /// - Low, Underweight => yellow scheme.
/// - Otherwise => green scheme (Normal). /// - Otherwise => green scheme (Normal).
static VitalSignUiModel scheme({required String? status, required String label}) { static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) {
final s = (status ?? '').toLowerCase();
final l = label.toLowerCase(); final l = label.toLowerCase();
// Height should always be blue. // Height should always be blue.
@ -40,7 +51,9 @@ class VitalSignUiModel {
} }
// High, Obese, Overweight => red scheme (health concerns) // High, Obese, Overweight => red scheme (health concerns)
if (s.contains('high') || s.contains('obese') || s.contains('overweight')) { if (status == VitalSignStatus.high ||
status == VitalSignStatus.obese ||
status == VitalSignStatus.overweight) {
return VitalSignUiModel( return VitalSignUiModel(
iconBg: AppColors.chipSecondaryLightRedColor, iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor, iconFg: AppColors.primaryRedColor,
@ -50,7 +63,7 @@ class VitalSignUiModel {
} }
// Low, Underweight => yellow scheme (warning) // Low, Underweight => yellow scheme (warning)
if (s.contains('low') || s.contains('underweight')) { if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) {
final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12); final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12);
return VitalSignUiModel( return VitalSignUiModel(
iconBg: yellowBg, iconBg: yellowBg,
@ -70,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: /// Simple, user-friendly classification:
/// - Low: systolic < 90 OR diastolic < 60 /// - Low: systolic < 90 OR diastolic < 60
/// - High: systolic >= 140 OR diastolic >= 90 /// - High: systolic >= 140 OR diastolic >= 90
/// - Normal: otherwise /// - Normal: otherwise
/// Returns null if values are missing/unparseable. /// 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? s = toIntOrNull(systolic);
final int? d = toIntOrNull(diastolic); final int? d = toIntOrNull(diastolic);
if (s == null || d == null) return null; if (s == null || d == null) return null;
if (s < 90 || d < 60) return 'Low'; if (s < 90 || d < 60) return VitalSignStatus.low;
if (s >= 140 || d >= 90) return 'High'; if (s >= 140 || d >= 90) return VitalSignStatus.high;
return 'Normal'; return VitalSignStatus.normal;
} }
static int? toIntOrNull(dynamic v) { static int? toIntOrNull(dynamic v) {
@ -92,7 +123,7 @@ class VitalSignUiModel {
return int.tryParse(v.toString()); return int.tryParse(v.toString());
} }
static String? bmiStatus(dynamic bmi) { static VitalSignStatus? bmiStatus(dynamic bmi) {
// Return null if BMI is not available or is 0 // Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0; final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
@ -100,27 +131,23 @@ class VitalSignUiModel {
return null; return null;
} }
String bmiStatus = 'Normal';
// BMI >= 25 (Overweight or Obese) => High // BMI >= 25 (Overweight or Obese) => High
if (bmiResult >= 25) { if (bmiResult >= 25) {
bmiStatus = 'High'; return VitalSignStatus.high;
} }
// BMI >= 18.5 and < 25 => Normal // BMI >= 18.5 and < 25 => Normal
else if (bmiResult >= 18.5) { else if (bmiResult >= 18.5) {
bmiStatus = 'Normal'; return VitalSignStatus.normal;
} }
// BMI < 18.5 (Underweight) => Low // BMI < 18.5 (Underweight) => Low
else { else {
bmiStatus = 'Low'; return VitalSignStatus.low;
} }
return bmiStatus;
} }
/// Weight status based on BMI with detailed classification /// Weight status based on BMI with detailed classification
/// Returns: Obese, Overweight, Normal, Underweight /// Returns: Obese, Overweight, Normal, Underweight
static String? weightStatus(dynamic bmi) { static VitalSignStatus? weightStatus(dynamic bmi) {
// Return null if BMI is not available or is 0 // Return null if BMI is not available or is 0
final double bmiResult = double.tryParse(bmi.toString()) ?? 0; final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
@ -128,19 +155,15 @@ class VitalSignUiModel {
return null; return null;
} }
String status = 'Normal';
if (bmiResult >= 30) { if (bmiResult >= 30) {
status = 'Obese'; return VitalSignStatus.obese;
} else if (bmiResult >= 25) { } else if (bmiResult >= 25) {
status = 'Overweight'; return VitalSignStatus.overweight;
} else if (bmiResult >= 18.5) { } else if (bmiResult >= 18.5) {
status = 'Normal'; return VitalSignStatus.normal;
} else { } else {
status = 'Underweight'; return VitalSignStatus.underweight;
} }
return status;
} }
} }

File diff suppressed because it is too large Load Diff

@ -302,6 +302,12 @@ abstract class LocaleKeys {
static const medicinesSubtitle = 'medicinesSubtitle'; static const medicinesSubtitle = 'medicinesSubtitle';
static const vitalSigns = 'vitalSigns'; static const vitalSigns = 'vitalSigns';
static const vitalSignsSubTitle = 'vitalSignsSubTitle'; 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 myMedical = 'myMedical';
static const myMedicalSubtitle = 'myMedicalSubtitle'; static const myMedicalSubtitle = 'myMedicalSubtitle';
static const myDoctor = 'myDoctor'; static const myDoctor = 'myDoctor';

@ -154,7 +154,8 @@ class _AppointmentRatingWidgetState extends State<AppointmentRatingWidget> {
Expanded( Expanded(
child: CustomButton( child: CustomButton(
text: LocaleKeys.submit.tr(context: context), 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 // Set up clinic rating and show clinic rating view
// appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),); // appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),);
// appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),); // appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),);

@ -56,11 +56,11 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
child: CollapsingListView( child: CollapsingListView(
title: LocaleKeys.bloodDonation.tr(), title: LocaleKeys.bloodDonation.tr(),
trailing: CustomButton( trailing: CustomButton(
text: "Book", text: LocaleKeys.book.tr(context: context),
onPressed: () { onPressed: () {
if (bloodDonationVM.isUserAuthanticated()) { if (bloodDonationVM.isUserAuthanticated()) {
bloodDonationVM.fetchHospitalsList().then((value) { 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( return HospitalBottomSheetBodySelection(
isHideTitle: true, isHideTitle: true,
onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) { onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) {

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

@ -63,28 +63,12 @@ class OTPService {
Navigator.pop(context); Navigator.pop(context);
final hmgServicesViewModel = context.read<HmgServicesViewModel>(); final hmgServicesViewModel = context.read<HmgServicesViewModel>();
LoaderBottomSheet.showLoader(); // Start listening for SMS before sending OTP
hmgServicesViewModel.eReferralSendActivationCode( _startSmsListener(
requestModel: SendActivationCodeForEReferralRequestModel( context: context,
patientMobileNumber: int.parse(formManager.formData.requesterPhone), hmgServicesViewModel: hmgServicesViewModel,
zipCode: formManager.formData.countryEnum.countryCode, formManager: formManager,
patientOutSA: formManager.formData.countryEnum.countryCode == '966' ? 0 : 1, onSuccess: onSuccess,
),
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)));
},
); );
} }
}, },
@ -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( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( // Row(
children: [ // children: [
Expanded( // // Expanded(
child: Container( // // child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, // // color: AppColors.whiteColor,
borderRadius: 12.h, // // borderRadius: 12.h,
hasShadow: false, // // hasShadow: false,
), // // ),
child: Padding( // // child: Padding(
padding: EdgeInsets.all(16.h), // // padding: EdgeInsets.all(16.h),
child: Row( // // child: Row(
children: [ // // children: [
Utils.buildSvgWithAssets( // // Utils.buildSvgWithAssets(
icon: AppAssets.virtual_tour_icon, // // icon: AppAssets.virtual_tour_icon,
width: 32.w, // // width: 32.w,
height: 32.h, // // height: 32.h,
fit: BoxFit.contain, // // fit: BoxFit.contain,
), // // ),
SizedBox(width: 8.w), // // SizedBox(width: 8.w),
LocaleKeys.virtualTour.tr().toText14(isBold: true) // // LocaleKeys.virtualTour.tr().toText14(isBold: true)
], // // ],
), // // ),
), // // ),
).onPress(() { // // ).onPress(() {
Utils.openWebView( // // Utils.openWebView(
url: 'https://hmgwebservices.com/vt_mobile/html/index.html', // // url: 'https://hmgwebservices.com/vt_mobile/html/index.html',
); // // );
}), // // }),
), // // ),
SizedBox(width: 16.w), // SizedBox(width: 16.w),
Expanded( // Expanded(
child: Container( // child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, // color: AppColors.whiteColor,
borderRadius: 12.h, // borderRadius: 12.h,
hasShadow: false, // hasShadow: false,
), // ),
child: Padding( // child: Padding(
padding: EdgeInsets.all(16.h), // padding: EdgeInsets.all(16.h),
child: Row( // child: Row(
children: [ // children: [
Utils.buildSvgWithAssets( // Utils.buildSvgWithAssets(
icon: AppAssets.car_parking_icon, // icon: AppAssets.car_parking_icon,
width: 32.w, // width: 32.w,
height: 32.h, // height: 32.h,
fit: BoxFit.contain, // fit: BoxFit.contain,
), // ),
SizedBox(width: 8.w), // SizedBox(width: 8.w),
LocaleKeys.carParking.tr().toText14(isBold: true) // LocaleKeys.carParking.tr().toText14(isBold: true)
], // ],
).onPress(() { // ).onPress(() {
Navigator.push( // Navigator.push(
context, // context,
MaterialPageRoute( // MaterialPageRoute(
builder: (_) => ChangeNotifierProvider( // builder: (_) => ChangeNotifierProvider(
create: (_) => getIt<QrParkingViewModel>(), // create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(), // child: const ParkingPage(),
), // ),
), // ),
); // );
}), // }),
), // ),
), // ),
), // ),
], // ],
), // ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Row( Row(
children: [ children: [

@ -57,6 +57,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves
import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_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/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_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/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_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
@ -1362,28 +1363,24 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
// Health Trackers // Health Trackers
MedicalFileCard( HealthToolsCard(
label: LocaleKeys.healthTrackers.tr(context: context), label: LocaleKeys.healthTrackers.tr(context: context),
textColor: AppColors.blackColor, textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.general_health, svgIcon: AppAssets.general_health,
iconColor: null, iconColor:null,
isLargeText: true, iconSize: 24.w,
iconSize: 36.w,
).onPress(() { ).onPress(() {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
context.navigateWithName(AppRoutes.healthTrackersPage); context.navigateWithName(AppRoutes.healthTrackersPage);
} }
}), }),
// Daily Water Monitor // Daily Water Monitor
MedicalFileCard( HealthToolsCard(
label: LocaleKeys.dailyWaterMonitor.tr(context: context), label: LocaleKeys.dailyWaterMonitor.tr(context: context),
textColor: AppColors.blackColor, textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.daily_water_monitor_icon, svgIcon: AppAssets.daily_water_monitor_icon,
iconColor: AppColors.infoColor, iconColor: AppColors.infoColor,
isLargeText: true, iconSize: 24.w,
iconSize: 36.w,
).onPress(() async { ).onPress(() async {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr());
@ -1406,38 +1403,32 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
} }
}), }),
// Health Calculators // Health Calculators
MedicalFileCard( HealthToolsCard(
label: LocaleKeys.healthCalculatorsServices.tr(context: context), label: LocaleKeys.healthCalculatorsServices.tr(context: context),
textColor: AppColors.blackColor, textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.health_calculators_services_icon, svgIcon: AppAssets.health_calculators_services_icon,
iconColor: AppColors.successColor, iconColor: AppColors.successColor,
isLargeText: true, iconSize: 24.w,
iconSize: 36.w,
).onPress(() { ).onPress(() {
context.navigateWithName(AppRoutes.healthCalculatorsPage); context.navigateWithName(AppRoutes.healthCalculatorsPage);
}), }),
// Health Converters // Health Converters
MedicalFileCard( HealthToolsCard(
label: LocaleKeys.healthConvertersServices.tr(context: context), label: LocaleKeys.healthConvertersServices.tr(context: context),
textColor: AppColors.blackColor, textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.health_converters_icon, svgIcon: AppAssets.health_converters_icon,
iconColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor,
isLargeText: true, iconSize: 24.w,
iconSize: 36.w,
).onPress(() { ).onPress(() {
context.navigateWithName(AppRoutes.healthConvertersPage); context.navigateWithName(AppRoutes.healthConvertersPage);
}), }),
// Smart Watches // Smart Watches
MedicalFileCard( HealthToolsCard(
label: LocaleKeys.smartWatchesServices.tr(context: context), label: LocaleKeys.smartWatchesServices.tr(context: context),
textColor: AppColors.blackColor, textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.smartwatch_icon, svgIcon: AppAssets.smartwatch_icon,
iconColor: AppColors.warningColorYellow, iconColor: AppColors.warningColorYellow,
isLargeText: true, iconSize: 24.w,
iconSize: 36.w,
).onPress(() { ).onPress(() {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
context.navigateWithName(AppRoutes.smartWatches); context.navigateWithName(AppRoutes.smartWatches);
@ -1607,15 +1598,15 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
]; ];
} }
String? _getBMIStatus(dynamic bmi) { VitalSignStatus? _getBMIStatus(dynamic bmi) {
return VitalSignUiModel.bmiStatus(bmi); return VitalSignUiModel.bmiStatus(bmi);
} }
String? _getWeightStatus(dynamic bmi) { VitalSignStatus? _getWeightStatus(dynamic bmi) {
return VitalSignUiModel.weightStatus(bmi); return VitalSignUiModel.weightStatus(bmi);
} }
String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) { VitalSignStatus? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic); return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
} }
@ -1636,7 +1627,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
required String label, required String label,
required String value, required String value,
required String unit, required String unit,
required String? status, required VitalSignStatus? status,
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label); final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
@ -1716,7 +1707,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
if (status != null) ...[ if (status != null) ...[
SizedBox(width: 4.w), SizedBox(width: 4.w),
AppCustomChipWidget( AppCustomChipWidget(
labelText: status, labelText: VitalSignUiModel.statusToString(status),
backgroundColor: scheme.chipBg, backgroundColor: scheme.chipBg,
textColor: scheme.chipFg, textColor: scheme.chipFg,
), ),

@ -35,10 +35,10 @@ class _VaccineListPageState extends State<VaccineListPage> {
medicalFileViewModel.setIsPatientVaccineListLoading(true); medicalFileViewModel.setIsPatientVaccineListLoading(true);
medicalFileViewModel.getPatientVaccinesList( medicalFileViewModel.getPatientVaccinesList(
onSuccess: (data) { onSuccess: (data) {
print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items"); // print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items");
}, },
onError: (error) { onError: (error) {
print("❌ Vaccine error: $error"); //print("❌ Vaccine error: $error");
}, },
); );
}); });
@ -141,11 +141,18 @@ class _VaccineListPageState extends State<VaccineListPage> {
// height: 63.h, // height: 63.h,
// fit: BoxFit.fill, // fit: BoxFit.fill,
// ).circle(100).toShimmer2(isShow: false), // ).circle(100).toShimmer2(isShow: false),
SizedBox(width: 16.h),
SizedBox(width: 8.h),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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), (medicalFileVM.patientVaccineList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Wrap( Wrap(
@ -156,7 +163,7 @@ class _VaccineListPageState extends State<VaccineListPage> {
AppCustomChipWidget( AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon, icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(medicalFileVM.patientVaccineList[index].vaccinationDate), false)), 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].clinicName).toShimmer2(isShow: false, width: 16.h),
AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].projectName).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),
],
),
],
),
);
}
}

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

@ -276,7 +276,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
label: LocaleKeys.heart.tr(context: context), label: LocaleKeys.heart.tr(context: context),
value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute), value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute),
unit: 'bpm', unit: 'bpm',
status: 'Normal', status: _isValidValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null,
onTap: () => _openDetails( onTap: () => _openDetails(
VitalSignDetailsArgs( VitalSignDetailsArgs(
metric: VitalSignMetric.heartRate, metric: VitalSignMetric.heartRate,
@ -300,7 +300,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
label: LocaleKeys.respirationRate.tr(context: context), label: LocaleKeys.respirationRate.tr(context: context),
value: _formatValue(latestVitalSign?.respirationBeatPerMinute), value: _formatValue(latestVitalSign?.respirationBeatPerMinute),
unit: 'bpm', unit: 'bpm',
status: 'Normal', status: _isValidValue(latestVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null,
onTap: () => _openDetails( onTap: () => _openDetails(
VitalSignDetailsArgs( VitalSignDetailsArgs(
metric: VitalSignMetric.respiratoryRate, metric: VitalSignMetric.respiratoryRate,
@ -334,7 +334,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
required String label, required String label,
required String value, required String value,
required String unit, required String unit,
required String? status, required VitalSignStatus? status,
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label); final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
@ -414,7 +414,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
), ),
if (status != null) if (status != null)
AppCustomChipWidget( AppCustomChipWidget(
labelText: status, labelText: VitalSignUiModel.statusToString(status),
backgroundColor: scheme.chipBg, backgroundColor: scheme.chipBg,
textColor: scheme.chipFg, textColor: scheme.chipFg,

Loading…
Cancel
Save