diff --git a/assets/images/png/bmi_image_1.png b/assets/images/png/bmi_image_1.png
new file mode 100644
index 0000000..db3a613
Binary files /dev/null and b/assets/images/png/bmi_image_1.png differ
diff --git a/assets/images/svg/blood_pressure.svg b/assets/images/svg/blood_pressure.svg
new file mode 100644
index 0000000..67badbe
--- /dev/null
+++ b/assets/images/svg/blood_pressure.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bmi_2.svg b/assets/images/svg/bmi_2.svg
new file mode 100644
index 0000000..38468d7
--- /dev/null
+++ b/assets/images/svg/bmi_2.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/heart_rate.svg b/assets/images/svg/heart_rate.svg
new file mode 100644
index 0000000..15c754f
--- /dev/null
+++ b/assets/images/svg/heart_rate.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/height_2.svg b/assets/images/svg/height_2.svg
new file mode 100644
index 0000000..a1c361a
--- /dev/null
+++ b/assets/images/svg/height_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/resp_rate.svg b/assets/images/svg/resp_rate.svg
new file mode 100644
index 0000000..7038793
--- /dev/null
+++ b/assets/images/svg/resp_rate.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/temperature.svg b/assets/images/svg/temperature.svg
new file mode 100644
index 0000000..14c7da4
--- /dev/null
+++ b/assets/images/svg/temperature.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/weight_2.svg b/assets/images/svg/weight_2.svg
new file mode 100644
index 0000000..c22441f
--- /dev/null
+++ b/assets/images/svg/weight_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 4d5535f..6ba2980 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -284,6 +284,19 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
+
+ //vital sign
+
+ static const String heartRate = '$svgBasePath/heart_rate.svg';
+ static const String respRate = '$svgBasePath/resp_rate.svg';
+ static const String weightVital = '$svgBasePath/weight_2.svg';
+ static const String bmiVital = '$svgBasePath/bmi_2.svg';
+ static const String heightVital = '$svgBasePath/height_2.svg';
+ static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
+ static const String temperature = '$svgBasePath/temperature.svg';
+
+
+
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@@ -309,7 +322,7 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
-
+ static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}
diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart
index 37a813d..85e6018 100644
--- a/lib/features/hmg_services/hmg_services_repo.dart
+++ b/lib/features/hmg_services/hmg_services_repo.dart
@@ -935,8 +935,8 @@ class HmgServicesRepoImp implements HmgServicesRepo {
try {
List vitalSignList = [];
- if (response['PatientVitalSignList'] != null && response['PatientVitalSignList'] is List) {
- final vitalSignsList = response['PatientVitalSignList'] as List;
+ if (response['List_DoctorPatientVitalSign'] != null && response['List_DoctorPatientVitalSign'] is List) {
+ final vitalSignsList = response['List_DoctorPatientVitalSign'] as List;
for (var vitalSignJson in vitalSignsList) {
if (vitalSignJson is Map) {
diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart
index b620c6d..c55a11c 100644
--- a/lib/features/hmg_services/hmg_services_view_model.dart
+++ b/lib/features/hmg_services/hmg_services_view_model.dart
@@ -51,6 +51,19 @@ class HmgServicesViewModel extends ChangeNotifier {
HospitalsModel? selectedHospital;
List vitalSignList = [];
+ // Vital Sign PageView Controller
+ PageController _vitalSignPageController = PageController();
+ PageController get vitalSignPageController => _vitalSignPageController;
+
+ int _vitalSignCurrentPage = 0;
+ int get vitalSignCurrentPage => _vitalSignCurrentPage;
+
+ void setVitalSignCurrentPage(int page) {
+ _vitalSignCurrentPage = page;
+ notifyListeners();
+ }
+
+
// HHC specific lists
List hhcOrdersList = [];
List hhcServicesList = [];
@@ -896,4 +909,10 @@ class HmgServicesViewModel extends ChangeNotifier {
},
);
}
+
+ @override
+ void dispose() {
+ _vitalSignPageController.dispose();
+ super.dispose();
+ }
}
diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
new file mode 100644
index 0000000..45b0ab6
--- /dev/null
+++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
@@ -0,0 +1,102 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+/// UI-only helper model for Vital Sign cards.
+///
+/// Keeps presentation logic (chip colors, icon colors, simple status rules)
+/// in one place so it can be reused across multiple pages.
+class VitalSignUiModel {
+ final Color iconBg;
+ final Color iconFg;
+ final Color chipBg;
+ final Color chipFg;
+
+ const VitalSignUiModel({
+ required this.iconBg,
+ required this.iconFg,
+ required this.chipBg,
+ required this.chipFg,
+ });
+
+ /// Returns a color scheme for a card based on its [status] and [label].
+ ///
+ /// Rules (mirrors existing behavior in Medical File page):
+ /// - Height is always blue.
+ /// - High => red scheme.
+ /// - Low => yellow scheme.
+ /// - Otherwise => green scheme (Normal).
+ static VitalSignUiModel scheme({required String? status, required String label}) {
+ final s = (status ?? '').toLowerCase();
+ final l = label.toLowerCase();
+
+ // Height should always be blue.
+ if (l.contains('height')) {
+ return VitalSignUiModel(
+ iconBg: AppColors.infoColor.withValues(alpha: 0.12),
+ iconFg: AppColors.infoColor,
+ chipBg: AppColors.infoColor.withValues(alpha: 0.12),
+ chipFg: AppColors.infoColor,
+ );
+ }
+
+ if (s.contains('high')) {
+ return const VitalSignUiModel(
+ iconBg: AppColors.chipSecondaryLightRedColor,
+ iconFg: AppColors.primaryRedColor,
+ chipBg: AppColors.chipSecondaryLightRedColor,
+ chipFg: AppColors.primaryRedColor,
+ );
+ }
+
+ if (s.contains('low')) {
+ final Color yellowBg = AppColors.warningColor.withValues(alpha: 0.12);
+ return VitalSignUiModel(
+ iconBg: yellowBg,
+ iconFg: AppColors.warningColor,
+ chipBg: yellowBg,
+ chipFg: AppColors.warningColor,
+ );
+ }
+
+ // Normal (green)
+ final Color greenBg = AppColors.lightGreenColor;
+ return VitalSignUiModel(
+ iconBg: greenBg,
+ iconFg: AppColors.bgGreenColor,
+ chipBg: greenBg,
+ chipFg: AppColors.bgGreenColor,
+ );
+ }
+
+ /// 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}) {
+ 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';
+ }
+
+ static int? toIntOrNull(dynamic v) {
+ if (v == null) return null;
+ if (v is int) return v;
+ if (v is double) return v.round();
+ return int.tryParse(v.toString());
+ }
+
+ static String bmiStatus(dynamic bmi) {
+ if (bmi == null) return 'N/A';
+ final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
+ if (bmiValue < 18.5) return 'Underweight';
+ if (bmiValue < 25) return 'Normal';
+ if (bmiValue < 30) return 'Overweight';
+ return 'High';
+ }
+}
+
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index b4a25ee..9bd48ec 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -127,6 +127,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
initAppointmentsViewModel() {
if (isAppointmentDataToBeLoaded) {
+ // Default view is grouped by clinic on first open.
+ isAppointmentsSortByClinic = true;
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
@@ -269,6 +271,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
+ // Build grouped list immediately so the UI has data for the default (By Clinic) view.
+ _groupAppointmentsByClinicAndHospital();
+
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
@@ -280,6 +285,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
+ notifyListeners();
}
void getFiltersForSelectedAppointmentList(List filteredAppointmentList) {
diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart
index eb72bc6..b4c3630 100644
--- a/lib/presentation/appointments/my_appointments_page.dart
+++ b/lib/presentation/appointments/my_appointments_page.dart
@@ -4,9 +4,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
-import 'package:hmg_patient_app_new/core/app_state.dart';
-import 'package:hmg_patient_app_new/core/dependencies.dart';
-import 'package:hmg_patient_app_new/core/utils/date_util.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';
diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart
index 3740a4a..2c5d1b0 100644
--- a/lib/presentation/appointments/my_doctors_page.dart
+++ b/lib/presentation/appointments/my_doctors_page.dart
@@ -1,8 +1,6 @@
import 'package:easy_localization/easy_localization.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/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';
@@ -43,8 +41,8 @@ class _MyDoctorsPageState extends State {
bookAppointmentsViewModel = Provider.of(context, listen: false);
return CollapsingListView(
- title: LocaleKeys.myDoctor.tr(context: context),
- child: Consumer(builder: (context, myAppointmentsVM, child) {
+ title: LocaleKeys.myDoctor.tr(context: context),
+ child: Consumer(builder: (context, myAppointmentsVM, child) {
// build grouped lists from the flat list
final clinicMap = >{};
final hospitalMap = >{};
@@ -171,129 +169,121 @@ class _MyDoctorsPageState extends State {
final displayName = isSortByClinic ? (group.first.clinicName ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
final isExpanded = expandedIndex == index;
- return Container(
- key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
- margin: EdgeInsets.only(bottom: 12.h),
- padding: EdgeInsets.all(16.h),
+ return AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- GestureDetector(
- onTap: () {
- setState(() {
- expandedIndex = isExpanded ? null : index;
- });
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final key = _groupKeys[index];
- if (key != null && key.currentContext != null && expandedIndex == index) {
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
+ if (key.currentContext != null && expandedIndex == index) {
+ Future.delayed(const Duration(milliseconds: 450), () {
+ if (key.currentContext != null) {
Scrollable.ensureVisible(
key.currentContext!,
- duration: Duration(milliseconds: 350),
+ duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
- alignment: 0.1,
+ alignment: 0.0,
);
}
});
- },
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- CustomButton(
- text: "${group.length} ${'doctors'.needTranslation}",
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 30.h,
- ),
- Icon(isExpanded ? Icons.expand_less : Icons.chevron_right, color: AppColors.greyColor),
- ],
- ),
- SizedBox(height: 8.h),
- Text(
- displayName,
- style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
- overflow: TextOverflow.ellipsis,
- ),
- ],
+ }
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AppCustomChipWidget(labelText: "${group.length} ${'doctors'.needTranslation}"),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Text(
+ displayName,
+ style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
),
- ),
- AnimatedSwitcher(
- duration: Duration(milliseconds: 400),
- child: isExpanded
- ? Container(
- key: ValueKey(index),
- padding: EdgeInsets.only(top: 12.h),
- child: Column(
- children: group.map((doctor) {
- return Container(
- margin: EdgeInsets.only(bottom: 12.h),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 12.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
- child: Column(
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...group.map((doctor) {
+ return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
- crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
children: [
Image.network(
(doctor?.doctorImageURL ?? doctor?.doctorImage ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png"),
- width: 24.h,
+ width: 24.w,
height: 24.h,
fit: BoxFit.cover,
).circle(100),
SizedBox(width: 8.h),
Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
- SizedBox(height: 6.h),
- ],
- ),
+ child: (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
),
],
),
SizedBox(height: 8.h),
- Row(
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 4.h,
+ runSpacing: 4.h,
children: [
- CustomButton(
- text: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
+ AppCustomChipWidget(
+ labelText: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""),
),
],
),
- SizedBox(height: 8.h),
+ SizedBox(height: 12.h),
Row(
children: [
Expanded(
- flex: 6,
+ flex: 2,
child: CustomButton(
+ icon: AppAssets.view_report_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
text: "View Profile".needTranslation.tr(context: context),
onPressed: () async {
bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
@@ -320,75 +310,31 @@ class _MyDoctorsPageState extends State {
);
});
},
- backgroundColor: AppColors.bgRedLightColor,
- borderColor: AppColors.primaryRedColor,
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14,
fontWeight: FontWeight.w500,
borderRadius: 12,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
+ padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
),
- SizedBox(width: 8.h),
- Expanded(
- flex: 1,
- child: Container(
- height: 40.h,
- width: 40.w,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.textColor,
- borderRadius: 12,
- ),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Transform.flip(
- flipX: getIt().isArabic(),
- child: Utils.buildSvgWithAssets(
- icon: AppAssets.forward_arrow_icon_small,
- iconColor: AppColors.whiteColor,
- fit: BoxFit.contain,
- ),
- ),
- ),
- ).onPress(() async {
- bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel(
- clinicID: doctor?.clinicID ?? 0,
- projectID: doctor?.projectID ?? 0,
- doctorID: doctor?.doctorID ?? 0,
- ));
- LoaderBottomSheet.showLoader();
- await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) {
- LoaderBottomSheet.hideLoader();
- Navigator.of(context).push(
- CustomPageRoute(
- page: DoctorProfilePage(),
- ),
- );
- }, onError: (err) {
- LoaderBottomSheet.hideLoader();
- showCommonBottomSheetWithoutHeight(
- context,
- child: Utils.getErrorWidget(loadingText: err),
- callBackFunc: () {},
- isFullScreen: false,
- isCloseButtonVisible: true,
- );
- });
- }),
- ),
],
),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
],
- ),
- ),
- );
- }).toList(),
- ),
- )
- : SizedBox.shrink(),
- ),
- ],
+ );
+ }).toList(),
+ ],
+ ),
+ )
+ : const SizedBox.shrink(),
+ ),
+ ],
+ ),
),
);
},
diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart
index 8ff71e3..34d9e17 100644
--- a/lib/presentation/medical_file/medical_file_page.dart
+++ b/lib/presentation/medical_file/medical_file_page.dart
@@ -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';
@@ -50,6 +53,7 @@ import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.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';
@@ -82,9 +86,14 @@ class _MedicalFilePageState extends State {
late BookAppointmentsViewModel bookAppointmentsViewModel;
late LabViewModel labViewModel;
late MyInvoicesViewModel myInvoicesViewModel;
+ 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();
@@ -95,11 +104,29 @@ class _MedicalFilePageState extends State {
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(context, listen: false);
@@ -108,6 +135,7 @@ class _MedicalFilePageState extends State {
medicalFileViewModel = Provider.of(context, listen: false);
bookAppointmentsViewModel = Provider.of(context, listen: false);
myInvoicesViewModel = Provider.of(context, listen: false);
+ hmgServicesViewModel = Provider.of(context, listen: false);
NavigationService navigationService = getIt.get();
return CollapsingListView(
// title: "Medical File".needTranslation,
@@ -258,6 +286,111 @@ class _MedicalFilePageState extends State {
),
).paddingSymmetrical(24.w, 0.0),
SizedBox(height: 16.h),
+
+ // Vital Signs Section
+ Consumer(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,
@@ -276,7 +409,7 @@ class _MedicalFilePageState extends State {
// Using CustomExpandableList
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
- dividerColor: Color(0xFF2B353E1A),
+ dividerColor: Color(0xff2b353e1a),
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: [
ExpandableListItem(
@@ -493,47 +626,51 @@ class _MedicalFilePageState extends State {
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),
),
@@ -651,58 +788,57 @@ class _MedicalFilePageState extends State {
);
}),
),
+ ));
+ },
+ 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)
@@ -834,8 +970,7 @@ class _MedicalFilePageState extends State {
});
}),
),
- ),
- );
+ ));
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h),
),
@@ -1079,7 +1214,7 @@ class _MedicalFilePageState extends State {
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.medical_reports_icon,
isLargeText: true,
- iconSize: 36.h,
+ iconSize: 36.w,
).onPress(() {
medicalFileViewModel.setIsPatientMedicalReportsLoading(true);
medicalFileViewModel.getPatientMedicalReportList();
@@ -1199,4 +1334,265 @@ class _MedicalFilePageState extends State {
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 _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,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
}
+
+
+
diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart
index c0eb782..fb153ea 100644
--- a/lib/presentation/radiology/radiology_orders_page.dart
+++ b/lib/presentation/radiology/radiology_orders_page.dart
@@ -190,231 +190,173 @@ class _RadiologyOrdersPageState extends State {
itemBuilder: (context, index) {
final group = model.patientRadiologyOrdersViewList[index];
final displayName = model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown');
+ final isExpanded = expandedIndex == index;
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 400),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // Group header container with key so we can scroll to it
- GestureDetector(
- onTap: () {
- setState(() {
- expandedIndex = expandedIndex == index ? null : index;
- });
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
- if (key.currentContext != null && expandedIndex == index) {
- // Delay scrolling to wait for expansion animation
- Future.delayed(Duration(milliseconds: 450), () {
- if (key.currentContext != null) {
- Scrollable.ensureVisible(
- key.currentContext!,
- duration: Duration(milliseconds: 350),
- curve: Curves.easeInOut,
- alignment: 0.0,
- );
- }
- });
- }
- });
- },
- child: Container(
- key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
- margin: EdgeInsets.only(bottom: 8.h),
- padding: EdgeInsets.all(12.h),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(8.h),
- boxShadow: [
- BoxShadow(
- color: AppColors.blackColor.withValues(alpha: 0.03),
- blurRadius: 6,
- offset: Offset(0, 2),
- )
- ],
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ margin: EdgeInsets.symmetric(vertical: 8.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: InkWell(
+ onTap: () {
+ setState(() {
+ expandedIndex = isExpanded ? null : index;
+ });
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final key = _groupKeys.putIfAbsent(index, () => GlobalKey());
+ if (key.currentContext != null && expandedIndex == index) {
+ Future.delayed(const Duration(milliseconds: 450), () {
+ if (key.currentContext != null) {
+ Scrollable.ensureVisible(
+ key.currentContext!,
+ duration: const Duration(milliseconds: 350),
+ curve: Curves.easeInOut,
+ alignment: 0.0,
+ );
+ }
+ });
+ }
+ });
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ key: _groupKeys.putIfAbsent(index, () => GlobalKey()),
+ padding: EdgeInsets.all(16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- CustomButton(
- text: "${group.length} ${'results'.needTranslation}",
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 30.h,
- ),
- Icon(expandedIndex == index ? Icons.expand_less : Icons.expand_more),
- ],
- ),
- SizedBox(height: 8.h),
- Text(
- displayName,
- style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
- overflow: TextOverflow.ellipsis,
- ),
+ AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"),
+ Icon(isExpanded ? Icons.expand_less : Icons.expand_more),
],
),
- ),
- ],
+ SizedBox(height: 8.h),
+ Text(
+ displayName,
+ style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
),
- ),
- ),
- AnimatedSwitcher(
- duration: Duration(milliseconds: 400),
- child: expandedIndex == index
- ? Container(
- key: ValueKey(index),
- padding: EdgeInsets.only(top: 12.h),
- child: Column(
- children: group.map((order) {
- return Container(
- margin: EdgeInsets.only(bottom: 12.h),
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 12.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(14.h),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Image.network(
- order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
- width: 24.h,
- height: 24.h,
- fit: BoxFit.cover,
- ).circle(100),
- SizedBox(width: 8.h),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
-
- children: [
- (order.doctorName ?? "").toString().toText14(weight: FontWeight.w500),
- SizedBox(height: 6.h),
-
-
- ],
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 500),
+ switchInCurve: Curves.easeIn,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: SizeTransition(
+ sizeFactor: animation,
+ axisAlignment: 0.0,
+ child: child,
+ ),
+ );
+ },
+ child: isExpanded
+ ? Container(
+ key: ValueKey(index),
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...group.map((order) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Image.network(
+ order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
+ width: 24.w,
+ height: 24.h,
+ fit: BoxFit.cover,
+ ).circle(100),
+ SizedBox(width: 8.h),
+ Expanded(
+ child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500),
),
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(children: [
- CustomButton(
- text: order.description!,
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- )
-
- ]
- ),
- SizedBox(height: 6.h),
-
- Row(
- children: [
- CustomButton(
- text: DateUtil.formatDateToDate(order.orderDate ?? order.appointmentDate ?? "", false),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- ),
- SizedBox(width: 8.h),
- CustomButton(
- text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""),
- onPressed: () {},
- backgroundColor: AppColors.greyColor,
- borderColor: AppColors.greyColor,
- textColor: AppColors.blackColor,
- fontSize: 10,
- fontWeight: FontWeight.w500,
- borderRadius: 8,
- padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
- height: 24.h,
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(
- children: [
- Expanded(
- flex: 6,
- child: SizedBox(),
- ),
- SizedBox(width: 8.h),
- Expanded(
- flex: 1,
- child: Container(
- height: 40.h,
- width: 40.w,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.textColor,
- borderRadius: 12,
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Wrap(
+ direction: Axis.horizontal,
+ spacing: 4.h,
+ runSpacing: 4.h,
+ children: [
+ if ((order.description ?? '').isNotEmpty)
+ AppCustomChipWidget(
+ labelText: (order.description ?? '').toString(),
),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Transform.flip(
- flipX: false,
- child: Utils.buildSvgWithAssets(
- icon: AppAssets.forward_arrow_icon_small,
- iconColor: AppColors.whiteColor,
- fit: BoxFit.contain,
- ),
- ),
+ AppCustomChipWidget(
+ labelText: DateUtil.formatDateToDate(
+ (order.orderDate ?? order.appointmentDate),
+ false,
),
- ).onPress(() {
- model.navigationService.push(
- CustomPageRoute(
- page: RadiologyResultPage(patientRadiologyResponseModel: order),
- ),
- );
- }),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }).toList(),
- ),
- )
- : SizedBox.shrink(),
+ ),
+ AppCustomChipWidget(
+ labelText: model.isSortByClinic ? (order.clinicDescription ?? '') : (order.projectName ?? ''),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Row(
+ children: [
+ Expanded(flex: 2, child: const SizedBox()),
+ Expanded(
+ flex: 2,
+ child: CustomButton(
+ icon: AppAssets.view_report_icon,
+ iconColor: AppColors.primaryRedColor,
+ iconSize: 16.h,
+ text: "View Results".needTranslation,
+ onPressed: () {
+ model.navigationService.push(
+ CustomPageRoute(
+ page: RadiologyResultPage(patientRadiologyResponseModel: order),
+ ),
+ );
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ borderRadius: 12,
+ padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
+ height: 40.h,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 12.h),
+ Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h),
+ SizedBox(height: 12.h),
+ ],
+ );
+ }).toList(),
+ ],
+ ),
+ )
+ : const SizedBox.shrink(),
+ ),
+ ],
),
- ],
+ ),
),
),
),
diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart
index 858e801..bd25641 100644
--- a/lib/presentation/vital_sign/vital_sign_page.dart
+++ b/lib/presentation/vital_sign/vital_sign_page.dart
@@ -1,4 +1,4 @@
-import 'dart:async';
+import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vit
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
-import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart';
import 'package:provider/provider.dart';
class VitalSignPage extends StatefulWidget {
@@ -22,22 +22,10 @@ class VitalSignPage extends StatefulWidget {
}
class _VitalSignPageState extends State {
+
@override
void initState() {
super.initState();
- final HmgServicesViewModel hmgServicesViewModel = context.read();
-
- scheduleMicrotask(() async {
- LoaderBottomSheet.showLoader(loadingText: 'Loading Vital Signs...');
- await hmgServicesViewModel.getPatientVitalSign(
- onSuccess: (_) {
- LoaderBottomSheet.hideLoader();
- },
- onError: (_) {
- LoaderBottomSheet.hideLoader();
- },
- );
- });
}
@override
@@ -72,48 +60,40 @@ class _VitalSignPageState extends State {
children: [
// BMI Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
+ icon: AppAssets.bmiVital,
label: 'BMI',
value: latestVitalSign?.bodyMassIndex?.toString() ?? '--',
unit: '',
- chipText: _getBMIStatus(latestVitalSign?.bodyMassIndex),
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex),
+ onTap: () {},
),
SizedBox(height: 16.h),
// Height Card
_buildVitalSignCard(
- icon: AppAssets.height,
- iconColor: AppColors.infoColor,
- iconBgColor: AppColors.infoColor.withValues(alpha: 0.1),
+ icon: AppAssets.heightVital,
label: 'Height',
value: latestVitalSign?.heightCm?.toString() ?? '--',
unit: 'cm',
+ status: null,
+ onTap: () {},
),
SizedBox(height: 16.h),
// Weight Card
_buildVitalSignCard(
- icon: AppAssets.weight,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
+ icon: AppAssets.weightVital,
label: 'Weight',
value: latestVitalSign?.weightKg?.toString() ?? '--',
unit: 'kg',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ status: (latestVitalSign?.weightKg != null) ? 'Normal' : null,
+ onTap: () {},
),
SizedBox(height: 16.h),
// Blood Pressure Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.warningColor,
- iconBgColor: AppColors.warningColor.withValues(alpha: 0.1),
+ icon: AppAssets.bloodPressure,
label: 'Blood Pressure',
value: latestVitalSign != null &&
latestVitalSign.bloodPressureHigher != null &&
@@ -121,17 +101,22 @@ class _VitalSignPageState extends State {
? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}'
: '--',
unit: '',
+ status: VitalSignUiModel.bloodPressureStatus(
+ systolic: latestVitalSign?.bloodPressureHigher,
+ diastolic: latestVitalSign?.bloodPressureLower,
+ ),
+ onTap: () {},
),
SizedBox(height: 16.h),
// Temperature Card
_buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.errorColor,
- iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
+ icon: AppAssets.temperature,
label: 'Temperature',
value: latestVitalSign?.temperatureCelcius?.toString() ?? '--',
unit: '°C',
+ status: null,
+ onTap: () {},
),
],
),
@@ -143,54 +128,80 @@ class _VitalSignPageState extends State {
Expanded(
child: Column(
children: [
- // Body anatomy image
- Container(
- height: 280.h,
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Center(
- child: Image.asset(
- AppAssets.fullBodyFront,
- height: 260.h,
- fit: BoxFit.contain,
- ),
- ),
- ),
- SizedBox(height: 16.h),
+ // Body anatomy image with Heart Rate card overlaid at bottom
+ SizedBox(
+ height: 480.h,
+ width: double.infinity,
+ child: Stack(
+ clipBehavior: Clip.none,
+ children: [
+ // Image
+ Positioned.fill(
+ child: Stack(
+ fit: StackFit.expand,
+ children: [
+ Image.asset(
+ AppAssets.bmiFullBody,
+ fit: BoxFit.cover,
+ alignment: Alignment.topCenter,
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: SizedBox(
+ height: 420.h,
+ child: ImageFiltered(
+ imageFilter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ AppColors.whiteColor.withValues(alpha: 0.0),
+ AppColors.whiteColor.withValues(alpha: 0.97),
+ AppColors.whiteColor,
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
- // Heart Rate Card
- _buildVitalSignCard(
- icon: AppAssets.heart,
- iconColor: AppColors.errorColor,
- iconBgColor: AppColors.errorColor.withValues(alpha: 0.1),
- label: 'Heart Rate',
- value: latestVitalSign?.heartRate?.toString() ??
- latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
- unit: 'bpm',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
+ // Overlay Heart Rate card
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 12.h,
+ child: _buildVitalSignCard(
+ icon: AppAssets.heart,
+ label: 'Heart Rate',
+ value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
+ unit: 'bpm',
+ status: 'Normal',
+ onTap: () {},
+ ),
+ ),
+ ],
+ ),
),
- SizedBox(height: 16.h),
+ SizedBox(height: 12.h),
- // Respiratory rate Card
- _buildVitalSignCard(
- icon: AppAssets.activity,
- iconColor: AppColors.successColor,
- iconBgColor: AppColors.successColor.withValues(alpha: 0.1),
- label: 'Respiratory rate',
- value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
- unit: 'bpm',
- chipText: 'Normal',
- chipBgColor: AppColors.successColor.withValues(alpha: 0.1),
- chipTextColor: AppColors.successColor,
- ),
- ],
- ),
- ),
+ // Respiratory rate Card
+ _buildVitalSignCard(
+ icon: AppAssets.respRate,
+ label: 'Respiratory rate',
+ value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
+ unit: 'bpm',
+ status: 'Normal',
+ onTap: () {},
+ ),
+ ],
+ ),
+ ),
],
),
),
@@ -205,104 +216,106 @@ class _VitalSignPageState extends State {
);
}
- String? _getBMIStatus(dynamic bmi) {
- if (bmi == null) return null;
- double bmiValue = double.tryParse(bmi.toString()) ?? 0;
- if (bmiValue < 18.5) return 'Underweight';
- if (bmiValue < 25) return 'Normal';
- if (bmiValue < 30) return 'Overweight';
- return 'Obese';
- }
-
Widget _buildVitalSignCard({
required String icon,
- required Color iconColor,
- required Color iconBgColor,
required String label,
required String value,
required String unit,
- String? chipText,
- Color? chipBgColor,
- Color? chipTextColor,
+ required String? status,
+ required VoidCallback onTap,
}) {
- return Container(
- decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
- color: AppColors.whiteColor,
- borderRadius: 20.h,
- hasShadow: true,
- ),
- child: Padding(
- padding: EdgeInsets.all(12.h),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- // Icon with background
- Container(
- padding: EdgeInsets.all(8.h),
- decoration: BoxDecoration(
- color: iconBgColor,
- borderRadius: BorderRadius.circular(12.r),
+ final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label);
+
+ return GestureDetector(
+ onTap: onTap,
+ child: Container(
+ 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,
+ ),
),
- child: Utils.buildSvgWithAssets(
- icon: icon,
- width: 16.w,
- height: 16.h,
- iconColor: iconColor,
- 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),
),
- SizedBox(width: 8.w),
- Expanded(
- child: label.toText10(
- color: AppColors.textColorLight,
- weight: FontWeight.w500,
- ),
+ 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(),
+ ],
),
- // Forward arrow
- Utils.buildSvgWithAssets(
+ ),
+ SizedBox(height: 8.h),
+ Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_forward,
- width: 16.w,
- height: 16.h,
+ width: 18.w,
+ height: 18.h,
iconColor: AppColors.textColorLight,
fit: BoxFit.contain,
),
- ],
- ),
- SizedBox(height: 12.h),
-
- // Value
- Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- value.toText18(
- isBold: true,
- color: AppColors.textColor,
- ),
- if (unit.isNotEmpty) ...[
- SizedBox(width: 4.w),
- unit.toText12(
- color: AppColors.textColorLight,
- ),
- ],
- ],
- ),
-
- // Chip if available
- if (chipText != null) ...[
- SizedBox(height: 8.h),
- AppCustomChipWidget(
- labelText: chipText,
- backgroundColor: chipBgColor,
- textColor: chipTextColor,
- padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
),
],
- ],
+ ),
),
),
);
}
}
-
diff --git a/lib/widgets/shimmer/vital_sign_shimmer_widget.dart b/lib/widgets/shimmer/vital_sign_shimmer_widget.dart
new file mode 100644
index 0000000..2ab4741
--- /dev/null
+++ b/lib/widgets/shimmer/vital_sign_shimmer_widget.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+class VitalSignShimmerWidget extends StatelessWidget {
+ const VitalSignShimmerWidget({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ // BMI Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Height Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Weight Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ SizedBox(width: 8.w),
+ // Blood Pressure Card Shimmer
+ Expanded(child: _buildShimmerCard()),
+ ],
+ );
+ }
+
+ Widget _buildShimmerCard() {
+ return Container(
+ decoration: BoxDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(12.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // Icon shimmer
+ Container(
+ width: 32.w,
+ height: 32.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 8.h),
+ // Label shimmer
+ Container(
+ width: 50.w,
+ height: 10.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Value shimmer
+ Container(
+ width: 40.w,
+ height: 16.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Chip shimmer
+ Container(
+ width: 45.w,
+ height: 18.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ ).toShimmer(),
+ SizedBox(height: 4.h),
+ // Arrow shimmer
+ Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: Container(
+ width: 10.w,
+ height: 10.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(2.r),
+ ),
+ ).toShimmer(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+