fixes and updates

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

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

@ -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",

@ -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;
}
}

@ -1,6 +1,18 @@
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';
/// 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)
@ -25,8 +37,7 @@ class VitalSignUiModel {
/// - 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.
@ -40,7 +51,9 @@ class VitalSignUiModel {
}
// 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(
iconBg: AppColors.chipSecondaryLightRedColor,
iconFg: AppColors.primaryRedColor,
@ -50,7 +63,7 @@ class VitalSignUiModel {
}
// 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);
return VitalSignUiModel(
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:
/// - 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) {
@ -92,7 +123,7 @@ class VitalSignUiModel {
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
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
@ -100,27 +131,23 @@ class VitalSignUiModel {
return null;
}
String bmiStatus = 'Normal';
// BMI >= 25 (Overweight or Obese) => High
if (bmiResult >= 25) {
bmiStatus = 'High';
return VitalSignStatus.high;
}
// BMI >= 18.5 and < 25 => Normal
else if (bmiResult >= 18.5) {
bmiStatus = 'Normal';
return VitalSignStatus.normal;
}
// BMI < 18.5 (Underweight) => Low
else {
bmiStatus = 'Low';
return VitalSignStatus.low;
}
return bmiStatus;
}
/// Weight status based on BMI with detailed classification
/// 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
final double bmiResult = double.tryParse(bmi.toString()) ?? 0;
@ -128,19 +155,15 @@ class VitalSignUiModel {
return null;
}
String status = 'Normal';
if (bmiResult >= 30) {
status = 'Obese';
return VitalSignStatus.obese;
} else if (bmiResult >= 25) {
status = 'Overweight';
return VitalSignStatus.overweight;
} else if (bmiResult >= 18.5) {
status = 'Normal';
return VitalSignStatus.normal;
} 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 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';

@ -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) {

@ -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: [

@ -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/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';
@ -1362,28 +1363,24 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
shrinkWrap: true,
children: [
// Health Trackers
MedicalFileCard(
HealthToolsCard(
label: LocaleKeys.healthTrackers.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.general_health,
iconColor: null,
isLargeText: true,
iconSize: 36.w,
iconColor:null,
iconSize: 24.w,
).onPress(() {
if (getIt.get<AppState>().isAuthenticated) {
context.navigateWithName(AppRoutes.healthTrackersPage);
}
}),
// Daily Water Monitor
MedicalFileCard(
HealthToolsCard(
label: LocaleKeys.dailyWaterMonitor.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.daily_water_monitor_icon,
iconColor: AppColors.infoColor,
isLargeText: true,
iconSize: 36.w,
iconSize: 24.w,
).onPress(() async {
if (getIt.get<AppState>().isAuthenticated) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr());
@ -1406,38 +1403,32 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}
}),
// Health Calculators
MedicalFileCard(
HealthToolsCard(
label: LocaleKeys.healthCalculatorsServices.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.health_calculators_services_icon,
iconColor: AppColors.successColor,
isLargeText: true,
iconSize: 36.w,
iconSize: 24.w,
).onPress(() {
context.navigateWithName(AppRoutes.healthCalculatorsPage);
}),
// Health Converters
MedicalFileCard(
HealthToolsCard(
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,
iconSize: 24.w,
).onPress(() {
context.navigateWithName(AppRoutes.healthConvertersPage);
}),
// Smart Watches
MedicalFileCard(
HealthToolsCard(
label: LocaleKeys.smartWatchesServices.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.smartwatch_icon,
iconColor: AppColors.warningColorYellow,
isLargeText: true,
iconSize: 36.w,
iconSize: 24.w,
).onPress(() {
if (getIt.get<AppState>().isAuthenticated) {
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);
}
String? _getWeightStatus(dynamic 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);
}
@ -1636,7 +1627,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
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);
@ -1716,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,
),

@ -35,10 +35,10 @@ class _VaccineListPageState extends State<VaccineListPage> {
medicalFileViewModel.setIsPatientVaccineListLoading(true);
medicalFileViewModel.getPatientVaccinesList(
onSuccess: (data) {
print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items");
// print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items");
},
onError: (error) {
print("❌ Vaccine error: $error");
//print("❌ Vaccine error: $error");
},
);
});
@ -141,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(
@ -156,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),
],
),
],
),
);
}
}

@ -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) {
@ -678,9 +685,11 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
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;
}
}
@ -755,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),
);

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

Loading…
Cancel
Save