|
|
|
|
@ -15,6 +15,9 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
|
|
|
|
|
@ -48,6 +51,7 @@ import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_
|
|
|
|
|
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/services/dialog_service.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/services/navigation_service.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
|
|
|
|
@ -79,9 +83,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
late MedicalFileViewModel medicalFileViewModel;
|
|
|
|
|
late BookAppointmentsViewModel bookAppointmentsViewModel;
|
|
|
|
|
late LabViewModel labViewModel;
|
|
|
|
|
late HmgServicesViewModel hmgServicesViewModel;
|
|
|
|
|
|
|
|
|
|
int currentIndex = 0;
|
|
|
|
|
|
|
|
|
|
// Used to make the PageView height follow the card's intrinsic height
|
|
|
|
|
final GlobalKey _vitalSignMeasureKey = GlobalKey();
|
|
|
|
|
double? _vitalSignMeasuredHeight;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
appState = getIt.get<AppState>();
|
|
|
|
|
@ -92,11 +101,29 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
|
|
|
|
|
medicalFileViewModel.getPatientSickLeaveList();
|
|
|
|
|
medicalFileViewModel.onTabChanged(0);
|
|
|
|
|
// Load vital signs
|
|
|
|
|
hmgServicesViewModel.getPatientVitalSign();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
super.initState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _scheduleVitalSignMeasure() {
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
final ctx = _vitalSignMeasureKey.currentContext;
|
|
|
|
|
if (ctx == null) return;
|
|
|
|
|
final box = ctx.findRenderObject();
|
|
|
|
|
if (box is RenderBox) {
|
|
|
|
|
final h = box.size.height;
|
|
|
|
|
if (h > 0 && h != _vitalSignMeasuredHeight) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_vitalSignMeasuredHeight = h;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
labViewModel = Provider.of<LabViewModel>(context, listen: false);
|
|
|
|
|
@ -104,6 +131,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
|
|
|
|
|
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
|
|
|
|
|
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
|
|
|
|
|
hmgServicesViewModel = Provider.of<HmgServicesViewModel>(context, listen: false);
|
|
|
|
|
NavigationService navigationService = getIt.get<NavigationService>();
|
|
|
|
|
return CollapsingListView(
|
|
|
|
|
title: "Medical File".needTranslation,
|
|
|
|
|
@ -250,6 +278,111 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
),
|
|
|
|
|
).paddingSymmetrical(24.w, 0.0),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
|
|
|
|
|
// Vital Signs Section
|
|
|
|
|
Consumer<HmgServicesViewModel>(builder: (context, hmgServicesVM, child) {
|
|
|
|
|
return Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
"Vital Signs".needTranslation.toText16(weight: FontWeight.w500, letterSpacing: -0.2),
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
|
|
|
|
|
SizedBox(width: 2.h),
|
|
|
|
|
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
).paddingSymmetrical(0.w, 0.h).onPress(() {
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
CustomPageRoute(
|
|
|
|
|
page: VitalSignPage(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
// Make this section dynamic-height (no fixed 160.h)
|
|
|
|
|
LayoutBuilder(
|
|
|
|
|
builder: (context, constraints) {
|
|
|
|
|
if (hmgServicesVM.isVitalSignLoading) {
|
|
|
|
|
return _buildVitalSignShimmer();
|
|
|
|
|
}
|
|
|
|
|
if (hmgServicesVM.vitalSignList.isEmpty) {
|
|
|
|
|
return Container(
|
|
|
|
|
padding: EdgeInsets.all(16.w),
|
|
|
|
|
width: MediaQuery.of(context).size.width,
|
|
|
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
|
borderRadius: 12.r,
|
|
|
|
|
hasShadow: false,
|
|
|
|
|
),
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.call_for_vitals, width: 32.h, height: 32.h),
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
"No vital signs recorded yet".needTranslation.toText12(isCenter: true),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The cards define their own height; measure the first rendered page once
|
|
|
|
|
_scheduleVitalSignMeasure();
|
|
|
|
|
final double hostHeight = _vitalSignMeasuredHeight ?? (160.h);
|
|
|
|
|
|
|
|
|
|
return SizedBox(
|
|
|
|
|
height: hostHeight,
|
|
|
|
|
child: PageView(
|
|
|
|
|
controller: hmgServicesVM.vitalSignPageController,
|
|
|
|
|
onPageChanged: (index) {
|
|
|
|
|
hmgServicesVM.setVitalSignCurrentPage(index);
|
|
|
|
|
_scheduleVitalSignMeasure();
|
|
|
|
|
},
|
|
|
|
|
children: _buildVitalSignPages(
|
|
|
|
|
vitalSign: hmgServicesVM.vitalSignList.first,
|
|
|
|
|
onTap: () {
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
CustomPageRoute(
|
|
|
|
|
page: VitalSignPage(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
measureKey: _vitalSignMeasureKey,
|
|
|
|
|
currentPageIndex: hmgServicesVM.vitalSignCurrentPage,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
if (!hmgServicesVM.isVitalSignLoading && hmgServicesVM.vitalSignList.isNotEmpty) ...[
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: List.generate(
|
|
|
|
|
2, // 2 pages (BMI+Height on page 1, Weight+BP on page 2)
|
|
|
|
|
(index) => Container(
|
|
|
|
|
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
|
|
|
|
width: hmgServicesVM.vitalSignCurrentPage == index ? 24.w : 8.w,
|
|
|
|
|
height: 8.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: hmgServicesVM.vitalSignCurrentPage == index
|
|
|
|
|
? AppColors.primaryRedColor
|
|
|
|
|
: AppColors.dividerColor,
|
|
|
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
).paddingSymmetrical(24.w, 0.0);
|
|
|
|
|
}),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
|
|
|
|
|
TextInputWidget(
|
|
|
|
|
labelText: LocaleKeys.search.tr(context: context),
|
|
|
|
|
hintText: "Type any record".needTranslation,
|
|
|
|
|
@ -268,7 +401,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
// Using CustomExpandableList
|
|
|
|
|
CustomExpandableList(
|
|
|
|
|
expansionMode: ExpansionMode.exactlyOne,
|
|
|
|
|
dividerColor: Color(0xFF2B353E1A),
|
|
|
|
|
dividerColor: Color(0xff2b353e1a),
|
|
|
|
|
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
|
|
|
|
|
items: [
|
|
|
|
|
ExpandableListItem(
|
|
|
|
|
@ -485,47 +618,51 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
horizontalOffset: 100.0,
|
|
|
|
|
child: FadeInAnimation(
|
|
|
|
|
child: AnimatedContainer(
|
|
|
|
|
duration: Duration(milliseconds: 300),
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
child: MedicalFileAppointmentCard(
|
|
|
|
|
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
|
|
|
|
|
myAppointmentsViewModel: myAppointmentsViewModel,
|
|
|
|
|
onRescheduleTap: () {
|
|
|
|
|
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
|
|
|
|
|
},
|
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
child: MedicalFileAppointmentCard(
|
|
|
|
|
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
|
|
|
|
|
myAppointmentsViewModel: myAppointmentsViewModel,
|
|
|
|
|
onRescheduleTap: () {
|
|
|
|
|
openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]);
|
|
|
|
|
},
|
|
|
|
|
onAskDoctorTap: () async {
|
|
|
|
|
LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation);
|
|
|
|
|
await myAppointmentsViewModel.isDoctorAvailable(
|
|
|
|
|
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
|
|
|
|
|
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
|
|
|
|
|
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
|
|
|
|
|
onSuccess: (value) async {
|
|
|
|
|
if (value) {
|
|
|
|
|
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
|
|
|
|
|
LoaderBottomSheet.hideLoader();
|
|
|
|
|
showCommonBottomSheetWithoutHeight(
|
|
|
|
|
context,
|
|
|
|
|
title: LocaleKeys.askDoctor.tr(context: context),
|
|
|
|
|
child: AskDoctorRequestTypeSelect(
|
|
|
|
|
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
|
|
|
|
|
myAppointmentsViewModel: myAppointmentsViewModel,
|
|
|
|
|
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
|
|
|
|
|
),
|
|
|
|
|
callBackFunc: () {},
|
|
|
|
|
isFullScreen: false,
|
|
|
|
|
isCloseButtonVisible: true,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
print("Doctor is not available");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID,
|
|
|
|
|
doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID,
|
|
|
|
|
clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID,
|
|
|
|
|
onSuccess: (value) async {
|
|
|
|
|
if (value) {
|
|
|
|
|
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
|
|
|
|
|
LoaderBottomSheet.hideLoader();
|
|
|
|
|
showCommonBottomSheetWithoutHeight(
|
|
|
|
|
context,
|
|
|
|
|
title: LocaleKeys.askDoctor.tr(context: context),
|
|
|
|
|
child: AskDoctorRequestTypeSelect(
|
|
|
|
|
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
|
|
|
|
|
myAppointmentsViewModel: myAppointmentsViewModel,
|
|
|
|
|
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
|
|
|
|
|
),
|
|
|
|
|
callBackFunc: () {},
|
|
|
|
|
isFullScreen: false,
|
|
|
|
|
isCloseButtonVisible: true,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
LoaderBottomSheet.hideLoader();
|
|
|
|
|
print("Doctor is not available");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onError: (_) {
|
|
|
|
|
LoaderBottomSheet.hideLoader();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h),
|
|
|
|
|
),
|
|
|
|
|
@ -643,58 +780,57 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
);
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
const Divider(color: AppColors.dividerColor),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: CustomButton(
|
|
|
|
|
text: "All Prescriptions".needTranslation,
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
CustomPageRoute(
|
|
|
|
|
page: PrescriptionsListPage(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
backgroundColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
borderColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
textColor: AppColors.primaryRedColor,
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
borderRadius: 12.r,
|
|
|
|
|
height: 40.h,
|
|
|
|
|
icon: AppAssets.requests,
|
|
|
|
|
iconColor: AppColors.primaryRedColor,
|
|
|
|
|
iconSize: 16.w,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
const Divider(color: AppColors.dividerColor),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: CustomButton(
|
|
|
|
|
text: "All Prescriptions".needTranslation,
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
CustomPageRoute(
|
|
|
|
|
page: PrescriptionsListPage(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
backgroundColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
borderColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
textColor: AppColors.primaryRedColor,
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
borderRadius: 12.r,
|
|
|
|
|
height: 40.h,
|
|
|
|
|
icon: AppAssets.requests,
|
|
|
|
|
iconColor: AppColors.primaryRedColor,
|
|
|
|
|
iconSize: 16.w,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 6.w),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: CustomButton(
|
|
|
|
|
text: "All Medications".needTranslation,
|
|
|
|
|
onPressed: () {},
|
|
|
|
|
backgroundColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
borderColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
textColor: AppColors.primaryRedColor,
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
borderRadius: 12.h,
|
|
|
|
|
height: 40.h,
|
|
|
|
|
icon: AppAssets.all_medications_icon,
|
|
|
|
|
iconColor: AppColors.primaryRedColor,
|
|
|
|
|
iconSize: 16.h,
|
|
|
|
|
SizedBox(width: 6.w),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: CustomButton(
|
|
|
|
|
text: "All Medications".needTranslation,
|
|
|
|
|
onPressed: () {},
|
|
|
|
|
backgroundColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
borderColor: AppColors.secondaryLightRedColor,
|
|
|
|
|
textColor: AppColors.primaryRedColor,
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
borderRadius: 12.h,
|
|
|
|
|
height: 40.h,
|
|
|
|
|
icon: AppAssets.all_medications_icon,
|
|
|
|
|
iconColor: AppColors.primaryRedColor,
|
|
|
|
|
iconSize: 16.h,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
).paddingSymmetrical(0.w, 0.h)
|
|
|
|
|
@ -826,8 +962,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
|
|
|
|
|
),
|
|
|
|
|
@ -1065,7 +1200,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
backgroundColor: AppColors.whiteColor,
|
|
|
|
|
svgIcon: AppAssets.medical_reports_icon,
|
|
|
|
|
isLargeText: true,
|
|
|
|
|
iconSize: 36.h,
|
|
|
|
|
iconSize: 36.w,
|
|
|
|
|
).onPress(() {
|
|
|
|
|
medicalFileViewModel.setIsPatientMedicalReportsLoading(true);
|
|
|
|
|
medicalFileViewModel.getPatientMedicalReportList();
|
|
|
|
|
@ -1185,4 +1320,265 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
|
|
|
|
|
return Container();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build shimmer for vital signs
|
|
|
|
|
Widget _buildVitalSignShimmer() {
|
|
|
|
|
return Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(child: _buildSingleShimmerCard()),
|
|
|
|
|
SizedBox(width: 12.w),
|
|
|
|
|
Expanded(child: _buildSingleShimmerCard()),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildSingleShimmerCard() {
|
|
|
|
|
return Container(
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
// Icon shimmer at top
|
|
|
|
|
Container(
|
|
|
|
|
width: 44.w,
|
|
|
|
|
height: 44.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
|
|
|
),
|
|
|
|
|
).toShimmer(),
|
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
|
// Label shimmer
|
|
|
|
|
Container(
|
|
|
|
|
width: 70.w,
|
|
|
|
|
height: 12.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
|
|
|
),
|
|
|
|
|
).toShimmer(),
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
// Value shimmer (larger)
|
|
|
|
|
Container(
|
|
|
|
|
width: 60.w,
|
|
|
|
|
height: 32.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
|
|
|
),
|
|
|
|
|
).toShimmer(),
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
// Bottom row with chip and arrow
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
Container(
|
|
|
|
|
width: 60.w,
|
|
|
|
|
height: 20.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
|
|
|
),
|
|
|
|
|
).toShimmer(),
|
|
|
|
|
Container(
|
|
|
|
|
width: 16.w,
|
|
|
|
|
height: 16.h,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
borderRadius: BorderRadius.circular(2.r),
|
|
|
|
|
),
|
|
|
|
|
).toShimmer(),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build pages with 2 cards each
|
|
|
|
|
List<Widget> _buildVitalSignPages({
|
|
|
|
|
required VitalSignResModel vitalSign,
|
|
|
|
|
required VoidCallback onTap,
|
|
|
|
|
required GlobalKey measureKey,
|
|
|
|
|
required int currentPageIndex,
|
|
|
|
|
}) {
|
|
|
|
|
return [
|
|
|
|
|
// Page 1: BMI + Height
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: _buildVitalSignCard(
|
|
|
|
|
icon: AppAssets.bmiVital,
|
|
|
|
|
label: "BMI",
|
|
|
|
|
value: vitalSign.bodyMassIndex?.toString() ?? '--',
|
|
|
|
|
unit: '',
|
|
|
|
|
status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null,
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 12.w),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: _buildVitalSignCard(
|
|
|
|
|
icon: AppAssets.heightVital,
|
|
|
|
|
label: "Height",
|
|
|
|
|
value: vitalSign.heightCm?.toString() ?? '--',
|
|
|
|
|
unit: 'cm',
|
|
|
|
|
status: null,
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
// Page 2: Weight + Blood Pressure
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: _buildVitalSignCard(
|
|
|
|
|
icon: AppAssets.weightVital,
|
|
|
|
|
label: "Weight",
|
|
|
|
|
value: vitalSign.weightKg?.toString() ?? '--',
|
|
|
|
|
unit: 'kg',
|
|
|
|
|
status: vitalSign.weightKg != null ? "Normal" : null,
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 12.w),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: _buildVitalSignCard(
|
|
|
|
|
icon: AppAssets.bloodPressure,
|
|
|
|
|
label: "Blood Pressure",
|
|
|
|
|
value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null
|
|
|
|
|
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
|
|
|
|
|
: '--',
|
|
|
|
|
unit: '',
|
|
|
|
|
status: _getBloodPressureStatus(
|
|
|
|
|
systolic: vitalSign.bloodPressureHigher,
|
|
|
|
|
diastolic: vitalSign.bloodPressureLower,
|
|
|
|
|
),
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _getBMIStatus(dynamic bmi) {
|
|
|
|
|
return VitalSignUiModel.bmiStatus(bmi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) {
|
|
|
|
|
return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildVitalSignCard({
|
|
|
|
|
required String icon,
|
|
|
|
|
required String label,
|
|
|
|
|
required String value,
|
|
|
|
|
required String unit,
|
|
|
|
|
required String? status,
|
|
|
|
|
required VoidCallback onTap,
|
|
|
|
|
}) {
|
|
|
|
|
final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
|
|
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
child: Container(
|
|
|
|
|
// Same styling used originally for vitals in MedicalFilePage
|
|
|
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
|
color: AppColors.whiteColor,
|
|
|
|
|
borderRadius: 16.r,
|
|
|
|
|
hasShadow: false,
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.all(16.w),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
Container(
|
|
|
|
|
padding: EdgeInsets.all(10.h),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: scheme.iconBg,
|
|
|
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
|
|
|
),
|
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
|
icon: icon,
|
|
|
|
|
width: 20.w,
|
|
|
|
|
height: 20.h,
|
|
|
|
|
iconColor: scheme.iconFg,
|
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 10.w),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: label.toText14(
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
weight: FontWeight.w600,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 14.h),
|
|
|
|
|
|
|
|
|
|
Container(
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: AppColors.bgScaffoldColor,
|
|
|
|
|
borderRadius: BorderRadius.circular(10.r),
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
|
|
children: [
|
|
|
|
|
value.toText17(
|
|
|
|
|
isBold: true,
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
),
|
|
|
|
|
if (unit.isNotEmpty) ...[
|
|
|
|
|
SizedBox(width: 3.w),
|
|
|
|
|
unit.toText12(
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
if (status != null)
|
|
|
|
|
AppCustomChipWidget(
|
|
|
|
|
labelText: status,
|
|
|
|
|
backgroundColor: scheme.chipBg,
|
|
|
|
|
textColor: scheme.chipFg,
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
const SizedBox.shrink(),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
|
|
|
|
|
Align(
|
|
|
|
|
alignment: AlignmentDirectional.centerEnd,
|
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
|
icon: AppAssets.arrow_forward,
|
|
|
|
|
width: 18.w,
|
|
|
|
|
height: 18.h,
|
|
|
|
|
iconColor: AppColors.textColorLight,
|
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|