diff --git a/assets/images/svg/activity.svg b/assets/images/svg/activity.svg
new file mode 100644
index 0000000..7e1c342
--- /dev/null
+++ b/assets/images/svg/activity.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/images/svg/age_icon.svg b/assets/images/svg/age_icon.svg
new file mode 100644
index 0000000..8acfad3
--- /dev/null
+++ b/assets/images/svg/age_icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/images/svg/bmi.svg b/assets/images/svg/bmi.svg
new file mode 100644
index 0000000..7ee99db
--- /dev/null
+++ b/assets/images/svg/bmi.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/bmr.svg b/assets/images/svg/bmr.svg
new file mode 100644
index 0000000..6b797e4
--- /dev/null
+++ b/assets/images/svg/bmr.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/calories.svg b/assets/images/svg/calories.svg
new file mode 100644
index 0000000..9f8d2b5
--- /dev/null
+++ b/assets/images/svg/calories.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/gender_icon.svg b/assets/images/svg/gender_icon.svg
new file mode 100644
index 0000000..4573125
--- /dev/null
+++ b/assets/images/svg/gender_icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/general_health.svg b/assets/images/svg/general_health.svg
new file mode 100644
index 0000000..0102df1
--- /dev/null
+++ b/assets/images/svg/general_health.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/height.svg b/assets/images/svg/height.svg
new file mode 100644
index 0000000..f275d34
--- /dev/null
+++ b/assets/images/svg/height.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/ibw.svg b/assets/images/svg/ibw.svg
new file mode 100644
index 0000000..2f11ca4
--- /dev/null
+++ b/assets/images/svg/ibw.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/trade_down_red.svg b/assets/images/svg/trade_down_red.svg
new file mode 100644
index 0000000..7c77c8e
--- /dev/null
+++ b/assets/images/svg/trade_down_red.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/trade_down_yellow.svg b/assets/images/svg/trade_down_yellow.svg
new file mode 100644
index 0000000..93c6805
--- /dev/null
+++ b/assets/images/svg/trade_down_yellow.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/weight.svg b/assets/images/svg/weight.svg
new file mode 100644
index 0000000..6c42c17
--- /dev/null
+++ b/assets/images/svg/weight.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/women_health.svg b/assets/images/svg/women_health.svg
new file mode 100644
index 0000000..5eca669
--- /dev/null
+++ b/assets/images/svg/women_health.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index f3ff8fd..c9aabc6 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -178,7 +178,21 @@ class AppAssets {
static const String doctor_profile_rating_icon = '$svgBasePath/doctor_profile_rating_icon.svg';
static const String doctor_profile_reviews_icon = '$svgBasePath/doctor_profile_reviews_icon.svg';
static const String waiting_appointment_icon = '$svgBasePath/waitingAppo.svg';
+ static const String bmi = '$svgBasePath/bmi.svg';
+ static const String bmr = '$svgBasePath/bmr.svg';
+ static const String calories = '$svgBasePath/calories.svg';
+ static const String ibw = '$svgBasePath/ibw.svg';
+ static const String general_health = '$svgBasePath/general_health.svg';
+ static const String women_health = '$svgBasePath/women_health.svg';
+ static const String height = '$svgBasePath/height.svg';
+ static const String weight = '$svgBasePath/weight.svg';
+ static const String activity = '$svgBasePath/activity.svg';
+ static const String age = '$svgBasePath/age_icon.svg';
+ static const String gender = '$svgBasePath/gender_icon.svg';
+
+ static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg';
+ static const String trade_down_red = '$svgBasePath/trade_down_red.svg';
//bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg';
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 30b4ab7..753ab11 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -39,6 +39,7 @@ import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@@ -123,19 +124,14 @@ class AppDependencies {
getIt.registerLazySingleton(() => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()));
getIt.registerLazySingleton(
- () => RadiologyViewModel(
- radiologyRepo: getIt(),
- errorHandlerService: getIt(),
- navigationService: getIt()
- ),
+ () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
- getIt.registerLazySingleton(
- () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
+ getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton(
() => PayfortViewModel(
@@ -160,12 +156,7 @@ class AppDependencies {
getIt.registerLazySingleton(
() => BookAppointmentsViewModel(
- bookAppointmentsRepo: getIt(),
- errorHandlerService: getIt(),
- navigationService: getIt(),
- myAppointmentsViewModel: getIt(),
- locationUtils: getIt(),
- dialogService: getIt()),
+ bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()),
);
getIt.registerLazySingleton(
@@ -179,13 +170,7 @@ class AppDependencies {
getIt.registerLazySingleton(
() => AuthenticationViewModel(
- authenticationRepo: getIt(),
- cacheService: getIt(),
- navigationService: getIt(),
- dialogService: getIt(),
- appState: getIt(),
- errorHandlerService: getIt(),
- localAuthService: getIt()),
+ authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()),
);
getIt.registerLazySingleton(() => ProfileSettingsViewModel());
@@ -199,13 +184,7 @@ class AppDependencies {
getIt.registerLazySingleton(
() => EmergencyServicesViewModel(
- locationUtils: getIt(),
- navServices: getIt(),
- emergencyServicesRepo: getIt(),
- appState: getIt(),
- errorHandlerService: getIt(),
- appointmentRepo: getIt(),
- dialogService: getIt()),
+ locationUtils: getIt(), navServices: getIt(), emergencyServicesRepo: getIt(), appState: getIt(), errorHandlerService: getIt(), appointmentRepo: getIt(), dialogService: getIt()),
);
getIt.registerLazySingleton(
@@ -216,6 +195,8 @@ class AppDependencies {
() => ContactUsViewModel(contactUsRepo: getIt(), appState: getIt(), errorHandlerService: getIt()),
);
+ getIt.registerLazySingleton(() => HealthCalcualtorViewModel());
+
getIt.registerLazySingleton(
() => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()),
);
diff --git a/lib/core/enums.dart b/lib/core/enums.dart
index 4151bed..47be218 100644
--- a/lib/core/enums.dart
+++ b/lib/core/enums.dart
@@ -32,6 +32,50 @@ enum AppEnvironmentTypeEnum { dev, uat, preProd, qa, staging, prod }
enum FamilyFileEnum { active, inactive, blocked, deleted, pending, rejected }
+enum HealthCalculatorEnum { general, women }
+
+enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate }
+
+extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum {
+ String get displayName {
+ AppState appState = getIt.get();
+ bool isArabic = appState.getLanguageID() == 1 ? true : false;
+ switch (this) {
+ case HealthCalculatorsTypeEnum.bmi:
+ return isArabic ? "حاسبة مؤشر كتلة الجسم" : "BMI Calculator";
+ case HealthCalculatorsTypeEnum.calories:
+ return isArabic ? "حاسبة السعرات الحرارية" : "Calories Calculator";
+ case HealthCalculatorsTypeEnum.bmr:
+ return isArabic ? "حاسبة معدل الأيض الأساسي" : "BMR Calculator";
+ case HealthCalculatorsTypeEnum.idealBodyWeight:
+ return isArabic ? "الوزن المثالي للجسم" : "Ideal Body Weight Calculator";
+ case HealthCalculatorsTypeEnum.bodyFat:
+ return isArabic ? "حاسبة الدهون في الجسم" : "Body Fat Calculator";
+ case HealthCalculatorsTypeEnum.crabsProteinFat:
+ return isArabic ? "حاسبة البروتين والدهون في سرطان البحر" : "Crabs Protein & Fat Calculator";
+ case HealthCalculatorsTypeEnum.ovulation:
+ return isArabic ? "فترة الإباضة" : "Ovulation Period";
+ case HealthCalculatorsTypeEnum.deliveryDueDate:
+ return isArabic ? "تاريخ استحقاق التسليم" : "Delivery Due Date";
+ }
+ }
+
+ static LoginTypeEnum? fromValue(int value) {
+ switch (value) {
+ case 1:
+ return LoginTypeEnum.sms;
+ case 2:
+ return LoginTypeEnum.fingerprint;
+ case 3:
+ return LoginTypeEnum.face;
+ case 4:
+ return LoginTypeEnum.whatsapp;
+ default:
+ return null;
+ }
+ }
+}
+
extension CalenderExtension on CalenderEnum {
int get toInt {
switch (this) {
@@ -172,7 +216,7 @@ extension FamilyFileEnumExtenshion on FamilyFileEnum {
return isArabic ? 'محذوف' : 'Deleted';
case FamilyFileEnum.pending:
return isArabic ? 'قيد الانتظار' : 'Pending';
- case FamilyFileEnum.rejected:
+ case FamilyFileEnum.rejected:
return isArabic ? 'مرفوض' : 'Rejected';
}
}
diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart
index 05448b1..7455273 100644
--- a/lib/core/utils/calender_utils_new.dart
+++ b/lib/core/utils/calender_utils_new.dart
@@ -43,7 +43,8 @@ class CalenderUtilsNew {
description: description,
startDate: scheduleDateTime!,
endDate: scheduleDateTime!.add(Duration(minutes: 30)),
- reminderMinutes: reminderMinutes);
+ // reminderMinutes: reminderMinutes
+ );
return eventResult.isNotEmpty;
// }
diff --git a/lib/main.dart b/lib/main.dart
index 1af80b6..23ccfae 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@@ -145,6 +146,9 @@ void main() async {
),
ChangeNotifierProvider(
create: (_) => getIt.get(),
+ ),
+ ChangeNotifierProvider(
+ create: (_) => getIt.get(),
)
], child: MyApp()),
),
diff --git a/lib/presentation/health_calculators/health_calculator_detailed_page.dart b/lib/presentation/health_calculators/health_calculator_detailed_page.dart
new file mode 100644
index 0000000..ae7d46b
--- /dev/null
+++ b/lib/presentation/health_calculators/health_calculator_detailed_page.dart
@@ -0,0 +1,142 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/enums.dart';
+import 'package:hmg_patient_app_new/core/utils/size_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/presentation/health_calculators/health_calculator_result.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bf.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmi.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/bmr.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/calories.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/crabs.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/dduedate.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ibw.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/ovulation.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+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/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class HealthCalculatorDetailedPage extends StatefulWidget {
+ HealthCalculatorsTypeEnum calculatorType;
+
+ HealthCalculatorDetailedPage({super.key, required this.calculatorType});
+
+ @override
+ State createState() => _HealthCalculatorDetailedPageState();
+}
+
+class _HealthCalculatorDetailedPageState extends State {
+ dynamic calculatedResult;
+
+ @override
+ Widget build(BuildContext context) {
+ return ChangeNotifierProvider(
+ create: (_) => HealthCalcualtorViewModel(),
+ child: Consumer(builder: (context, provider, _) {
+ return CollapsingListView(
+ title: widget.calculatorType.displayName,
+ bottomChild: Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
+ padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
+ child: CustomButton(
+ text: "Calculate".needTranslation,
+ onPressed: () {
+ if (calculatedResult == null) return;
+ DialogService dialogService = getIt.get();
+ Navigator.of(context).push(CustomPageRoute(
+ page: HealthCalculatorResultPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
+ ));
+ },
+ icon: null,
+ fontSize: 16.f,
+ backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
+ borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
+ borderRadius: 12.r,
+ fontWeight: FontWeight.w500)),
+ child: getCalculatorWidget(
+ type: widget.calculatorType,
+ onCalculate: (result) {
+ // result may be directly provided by widget, but prefer provider maps if available
+ switch (widget.calculatorType) {
+ case HealthCalculatorsTypeEnum.bmi:
+ calculatedResult = provider.bmiResultMap ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.calories:
+ case HealthCalculatorsTypeEnum.bmr:
+ calculatedResult = provider.caloriesResultMap ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.idealBodyWeight:
+ calculatedResult = provider.ibwResultMap ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.bodyFat:
+ calculatedResult = provider.bodyFatResultMap ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.crabsProteinFat:
+ calculatedResult = provider.macrosResultMap ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.ovulation:
+ calculatedResult = provider.ovulationResult ?? result;
+ break;
+ case HealthCalculatorsTypeEnum.deliveryDueDate:
+ calculatedResult = provider.deliveryResult ?? result;
+ break;
+ }
+ setState(() {});
+ },
+ ).paddingSymmetrical(20.w, 24.h),
+ );
+ }),
+ );
+ }
+
+ Widget getCalculatorWidget({required HealthCalculatorsTypeEnum type, required Function(dynamic result) onCalculate}) {
+ switch (widget.calculatorType) {
+ case HealthCalculatorsTypeEnum.bmi:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: BMIWidget(onChange: onCalculate),
+ );
+
+ case HealthCalculatorsTypeEnum.calories:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: CaloriesWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.bmr:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: BMRWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.idealBodyWeight:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: IdealBodyWeightWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.bodyFat:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: BodyFatWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.crabsProteinFat:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: CrabsWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.ovulation:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: OvulationWidget(onChange: onCalculate),
+ );
+ case HealthCalculatorsTypeEnum.deliveryDueDate:
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ child: DeliveryDueDWidget(onChange: onCalculate),
+ );
+ }
+ }
+}
diff --git a/lib/presentation/health_calculators/health_calculator_result.dart b/lib/presentation/health_calculators/health_calculator_result.dart
new file mode 100644
index 0000000..5315a89
--- /dev/null
+++ b/lib/presentation/health_calculators/health_calculator_result.dart
@@ -0,0 +1,428 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/enums.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';
+import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
+
+class HealthCalculatorResultPage extends StatelessWidget {
+ HealthCalculatorsTypeEnum calculatorType;
+ dynamic calculatedResult;
+
+ HealthCalculatorResultPage({super.key, required this.calculatorType, this.calculatedResult});
+
+ @override
+ Widget build(BuildContext context) {
+ return CollapsingListView(
+ title: "Your ${calculatorType.name.toCamelCase}",
+ child: getCalculatorResultWidget(type: calculatorType, calculatedResult: calculatedResult).paddingSymmetrical(18.w, 24.h),
+ );
+ }
+
+ Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
+ switch (calculatorType) {
+ case HealthCalculatorsTypeEnum.bmi:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.calories:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bmr:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.idealBodyWeight:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.bodyFat:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.crabsProteinFat:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.ovulation:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ case HealthCalculatorsTypeEnum.deliveryDueDate:
+ return resultWidget(type: calculatorType, calculatedResult: calculatedResult);
+ }
+ }
+
+ Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) {
+ print(calculatedResult);
+ return Column(
+ children: [
+ if (type == HealthCalculatorsTypeEnum.bmi) ...[
+ if (calculatedResult["bmiCategory"] == "Normal") ...[
+ CalorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.',
+ color: Colors.green,
+ icon: Icons.scale,
+ hide: true,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[
+ CalorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.',
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[
+ CalorieWidget(
+ title: calculatedResult["bmiCategory"],
+ calories: calculatedResult["bmiResult"],
+ description:
+ 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.',
+ color: Colors.red,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.calories) ...[
+ if (calculatedResult["calorieRange"] == "Normal") ...[
+ CalorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.',
+ color: Colors.green,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["calorieRange"] == "Low") ...[
+ CalorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.',
+ color: Colors.orange,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["calorieRange"] == "High") ...[
+ CalorieWidget(
+ title: calculatedResult["calorieRange"],
+ calories: calculatedResult["calories"],
+ description:
+ 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.',
+ color: Colors.red,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[
+ if (calculatedResult["bmrRange"] == "Normal") ...[
+ CalorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.',
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmrRange"] == "Low") ...[
+ CalorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.',
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["bmrRange"] == "High") ...[
+ CalorieWidget(
+ title: calculatedResult["bmrRange"],
+ calories: calculatedResult["bmr"],
+ description:
+ 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.',
+ color: Colors.red,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[
+ if (calculatedResult["status"] == "Normal") ...[
+ CalorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ if (diff == 0) {
+ return 'Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.';
+ } else if (diff > 0) {
+ return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.';
+ } else {
+ return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.';
+ }
+ }
+ return 'Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.';
+ })(),
+ color: Colors.green,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["status"] == "Underweight") ...[
+ CalorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ return 'You are below the ideal weight by ${diff.toStringAsFixed(1)} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.';
+ }
+ return 'You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.';
+ })(),
+ color: Colors.orange,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["status"] == "Overweight") ...[
+ CalorieWidget(
+ title: calculatedResult["status"],
+ calories: calculatedResult["ibw"],
+ hide: true,
+ description: (() {
+ final diff = calculatedResult["difference"];
+ if (diff is num) {
+ return 'You are above the ideal weight by ${diff.abs().toStringAsFixed(1)} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.';
+ }
+ return 'You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.';
+ })(),
+ color: Colors.red,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[
+ if (calculatedResult["dietType"] == "Very Low Crabs") ...[
+ CalorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.';
+ if (total is num) {
+ return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ }
+ return base;
+ })(),
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Low Crabs") ...[
+ CalorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.';
+ if (total is num) {
+ return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ }
+ return base;
+ })(),
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.trending_down,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[
+ CalorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.';
+ if (total is num) {
+ return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ }
+ return base;
+ })(),
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[
+ CalorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.';
+ if (total is num) {
+ return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ }
+ return base;
+ })(),
+ color: Colors.green,
+ hide: true,
+ icon: Icons.scale,
+ ),
+ SizedBox(height: 16.h),
+ ] else if (calculatedResult["dietType"] == "Zone Diet") ...[
+ CalorieWidget(
+ title: calculatedResult["dietType"].toString(),
+ calories: calculatedResult["totalCalories"],
+ description: (() {
+ final total = calculatedResult["totalCalories"];
+ final carbsG = calculatedResult["carbsGrams"];
+ final carbsC = calculatedResult["carbsCalories"];
+ final protG = calculatedResult["proteinGrams"];
+ final protC = calculatedResult["proteinCalories"];
+ final fatG = calculatedResult["fatGrams"];
+ final fatC = calculatedResult["fatCalories"];
+ final base =
+ 'Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.';
+ if (total is num) {
+ return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).';
+ }
+ return base;
+ })(),
+ color: Colors.orange,
+ hide: true,
+ icon: Icons.warning_rounded,
+ ),
+ ]
+ ]
+ ],
+ );
+ }
+}
+
+class CalorieWidget extends StatelessWidget {
+ final String title;
+ final dynamic calories;
+ final String description;
+ final Color color;
+ final IconData icon;
+ final bool hide;
+
+ const CalorieWidget({super.key, required this.title, required this.calories, required this.description, required this.color, required this.icon, this.hide = false});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(), isBold: true),
+ SizedBox(width: 10.w),
+ hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600)
+ ],
+ ),
+ _buildStatusIcon()
+ ],
+ ),
+ SizedBox(height: 16.h),
+ title.toText16(color: AppColors.textColor, weight: FontWeight.w600),
+ SizedBox(height: 8.h),
+ Text(
+ description,
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.grey[700],
+ height: 1.5,
+ ),
+ textAlign: TextAlign.start,
+ ),
+ ],
+ ),
+ );
+ }
+ //
+ // Widget _buildStatusIcon() {
+ // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') {
+ // return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
+ // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') {
+ // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
+ // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") {
+ // return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
+ // } else {
+ // return SizedBox();
+ // }
+ // }
+ //
+ // Color getTextColor() {
+ // if (title == 'Maintenance Calories' || title == 'Normal' || title == 'Ideal Weight') {
+ // return AppColors.successColor;
+ // } else if (title == 'Moderate Deficit' || title == 'Low' || title == 'Underweight') {
+ // return AppColors.warningColor;
+ // } else if (title == 'Aggressive Deficit' || title == 'High' || title == 'Overweight' || title == "Obese") {
+ // return AppColors.errorColor;
+ // } else {
+ // return Colors.black;
+ // }
+ // }
+
+ Widget _buildStatusIcon() {
+ final key = title.trim();
+ if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
+ return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h);
+ } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
+ return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h);
+ } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
+ return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h);
+ } else {
+ return SizedBox();
+ }
+ }
+
+ Color getTextColor() {
+ final key = title.trim();
+ if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') {
+ return AppColors.successColor;
+ } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') {
+ return AppColors.warningColor;
+ } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") {
+ return AppColors.errorColor;
+ } else {
+ return Colors.black;
+ }
+ }
+}
diff --git a/lib/presentation/health_calculators/health_calculator_view_model.dart b/lib/presentation/health_calculators/health_calculator_view_model.dart
new file mode 100644
index 0000000..aa6c157
--- /dev/null
+++ b/lib/presentation/health_calculators/health_calculator_view_model.dart
@@ -0,0 +1,657 @@
+import 'dart:math';
+
+import 'package:flutter/foundation.dart';
+
+class HealthCalcualtorViewModel extends ChangeNotifier {
+ // BMI
+ double? bmiResult;
+ String? bmiCategory;
+
+ // BMR / Calories
+ double? bmrResult;
+ double? caloriesResult;
+ String? bmrRange;
+ String? calorieRange;
+
+ // IBW
+ double? ibwResult;
+ double? weightDifference;
+ String? weightStatus;
+
+ // Body Fat
+ double? bodyFatResult;
+ String? bodyFatCategory;
+
+ // Crabs/Protein/Fat
+ double? carbsCalories;
+ double? proteinCalories;
+ double? fatCalories;
+ double? carbsGrams;
+ double? proteinGrams;
+ double? fatGrams;
+ String? dietType;
+
+ // Ovulation
+ Map? ovulationResult;
+
+ // Delivery
+ Map? deliveryResult;
+
+ // Generic helpers
+
+ void calculateBMI({required String heightText, required String weightText, required String heightUnit, required String weightUnit}) {
+ if (heightText.trim().isEmpty || weightText.trim().isEmpty) {
+ bmiResult = null;
+ bmiCategory = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double height = double.parse(heightText.trim());
+ double weight = double.parse(weightText.trim());
+
+ double heightInMeters = _convertHeightToMeters(height, heightUnit);
+ double weightInKg = _convertWeightToKg(weight, weightUnit);
+
+ if (heightInMeters <= 0 || weightInKg <= 0) {
+ bmiResult = null;
+ bmiCategory = null;
+ notifyListeners();
+ return;
+ }
+
+ double bmi = weightInKg / (heightInMeters * heightInMeters);
+ String category = _getBMICategory(bmi);
+
+ bmiResult = bmi;
+ bmiCategory = category;
+ notifyListeners();
+ } catch (e) {
+ bmiResult = null;
+ bmiCategory = null;
+ notifyListeners();
+ }
+ }
+
+ double _convertHeightToMeters(double height, String unit) {
+ switch (unit) {
+ case 'cm':
+ return height / 100;
+ case 'm':
+ return height;
+ case 'ft':
+ return height * 0.3048;
+ case 'in':
+ return height * 0.0254;
+ default:
+ return height / 100;
+ }
+ }
+
+ double _convertWeightToKg(double weight, String unit) {
+ switch (unit) {
+ case 'kg':
+ return weight;
+ case 'lb':
+ return weight * 0.453592;
+ default:
+ return weight;
+ }
+ }
+
+ String _getBMICategory(double bmi) {
+ if (bmi < 18.5) {
+ return 'Underweight';
+ } else if (bmi >= 18.5 && bmi < 25) {
+ return 'Normal';
+ } else if (bmi >= 25 && bmi < 30) {
+ return 'Overweight';
+ } else {
+ return 'Obese';
+ }
+ }
+
+ // BMR / Calories (shared logic)
+ void calculateBMRAndCalories({required String heightText, required String weightText, required String ageText, required String heightUnit, required String weightUnit, required String gender, required String activityLevel, bool forCaloriesOnly = false}) {
+ if (heightText.trim().isEmpty || weightText.trim().isEmpty || ageText.trim().isEmpty) {
+ bmrResult = null;
+ caloriesResult = null;
+ bmrRange = null;
+ calorieRange = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double height = double.parse(heightText.trim());
+ double weight = double.parse(weightText.trim());
+ int age = int.parse(ageText.trim());
+
+ if (age < 11 || age > 120) {
+ bmrResult = null;
+ caloriesResult = null;
+ bmrRange = null;
+ calorieRange = null;
+ notifyListeners();
+ return;
+ }
+
+ double heightInCm = _convertHeightToCm(height, heightUnit);
+ double weightInKg = _convertWeightToKg(weight, weightUnit);
+
+ if (heightInCm <= 0 || weightInKg <= 0) {
+ bmrResult = null;
+ caloriesResult = null;
+ bmrRange = null;
+ calorieRange = null;
+ notifyListeners();
+ return;
+ }
+
+ double bmr = _calculateBMR(heightInCm, weightInKg, age, gender);
+ double activityMultiplier = _getActivityMultiplier(activityLevel);
+ double calories = bmr * activityMultiplier;
+
+ bmrResult = bmr;
+ caloriesResult = calories;
+
+ // ranges
+ if (bmr < 1200) {
+ bmrRange = 'Low';
+ } else if (bmr < 1600) {
+ bmrRange = 'Normal';
+ } else if (bmr < 2000) {
+ bmrRange = 'High';
+ } else {
+ bmrRange = 'Very High';
+ }
+
+ if (calories < 1800) {
+ calorieRange = 'Low';
+ } else if (calories < 2500) {
+ calorieRange = 'Normal';
+ } else if (calories < 3200) {
+ calorieRange = 'High';
+ } else {
+ calorieRange = 'High';
+ }
+
+ notifyListeners();
+ } catch (e) {
+ bmrResult = null;
+ caloriesResult = null;
+ bmrRange = null;
+ calorieRange = null;
+ notifyListeners();
+ }
+ }
+
+ double _convertHeightToCm(double height, String unit) {
+ switch (unit) {
+ case 'cm':
+ return height;
+ case 'm':
+ return height * 100;
+ case 'ft':
+ return height * 30.48;
+ case 'in':
+ return height * 2.54;
+ default:
+ return height;
+ }
+ }
+
+ double _calculateBMR(double heightCm, double weightKg, int age, String gender) {
+ if (gender == "Male") {
+ return 88.362 + (13.397 * weightKg) + (4.799 * heightCm) - (5.677 * age);
+ } else {
+ return 447.593 + (9.247 * weightKg) + (3.098 * heightCm) - (4.330 * age);
+ }
+ }
+
+ double _getActivityMultiplier(String activityLevel) {
+ switch (activityLevel) {
+ case "Almost Inactive (no exercise)":
+ return 1.2;
+ case "Lightly active":
+ return 1.375;
+ case "Lightly active (1-3) days per week":
+ return 1.55;
+ case "Super active (very hard exercise)":
+ return 1.725;
+ default:
+ return 1.375;
+ }
+ }
+
+ // IBW
+ void calculateIBW({required String heightText, required String weightText, required String heightUnit, required String weightUnit, required String bodyFrameSize}) {
+ if (heightText.trim().isEmpty || weightText.trim().isEmpty) {
+ ibwResult = null;
+ weightDifference = null;
+ weightStatus = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double height = double.parse(heightText.trim());
+ double weight = double.parse(weightText.trim());
+
+ double heightInCm = _convertHeightToCm(height, heightUnit);
+ double weightInKg = _convertWeightToKg(weight, weightUnit);
+
+ if (heightInCm <= 0 || weightInKg <= 0) {
+ ibwResult = null;
+ weightDifference = null;
+ weightStatus = null;
+ notifyListeners();
+ return;
+ }
+
+ double ibw = _calculateIBWValue(heightInCm);
+ double adjusted = _applyFrameSizeAdjustment(ibw, bodyFrameSize);
+ double difference = weightInKg - adjusted;
+ String status = _getWeightStatus(difference);
+
+ ibwResult = adjusted;
+ weightDifference = difference;
+ weightStatus = status;
+ notifyListeners();
+ } catch (e) {
+ ibwResult = null;
+ weightDifference = null;
+ weightStatus = null;
+ notifyListeners();
+ }
+ }
+
+ double _calculateIBWValue(double heightCm) {
+ double baseHeight = 152.4;
+ double baseWeight = 50.0;
+ double heightMultiplier = 2.3;
+
+ if (heightCm <= baseHeight) {
+ return baseWeight - ((baseHeight - heightCm) * 0.5);
+ }
+
+ return baseWeight + ((heightCm - baseHeight) * heightMultiplier / 2.54);
+ }
+
+ double _applyFrameSizeAdjustment(double ibw, String selectedBodyFrameSize) {
+ switch (selectedBodyFrameSize) {
+ case "Small (fingers overlaps)":
+ return ibw * 0.9;
+ case "Medium (fingers touch)":
+ return ibw;
+ case "Large (fingers don't touch)":
+ return ibw * 1.1;
+ default:
+ return ibw;
+ }
+ }
+
+ String _getWeightStatus(double difference) {
+ if (difference.abs() < 2) {
+ return 'Normal';
+ } else if (difference > 0) {
+ return 'Overweight';
+ } else {
+ return 'Underweight';
+ }
+ }
+
+ // Body Fat
+ void calculateBodyFat({required String heightText, required String neckText, required String waistText, required String hipText, required String heightUnit, required String neckUnit, required String waistUnit, required String hipUnit, required String gender}) {
+ if (heightText.trim().isEmpty || neckText.trim().isEmpty || waistText.trim().isEmpty) {
+ bodyFatResult = null;
+ bodyFatCategory = null;
+ notifyListeners();
+ return;
+ }
+
+ if (gender == "Female" && hipText.trim().isEmpty) {
+ bodyFatResult = null;
+ bodyFatCategory = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double height = double.parse(heightText.trim());
+ double neck = double.parse(neckText.trim());
+ double waist = double.parse(waistText.trim());
+ double hip = gender == "Female" ? double.parse(hipText.trim()) : 0;
+
+ double heightInCm = _convertHeightToCm(height, heightUnit);
+ double neckInCm = _convertLengthToCm(neck, neckUnit);
+ double waistInCm = _convertLengthToCm(waist, waistUnit);
+ double hipInCm = gender == "Female" ? _convertLengthToCm(hip, hipUnit) : 0;
+
+ if (heightInCm <= 0 || neckInCm <= 0 || waistInCm <= 0) {
+ bodyFatResult = null;
+ bodyFatCategory = null;
+ notifyListeners();
+ return;
+ }
+
+ double bodyFat = _calculateBodyFatPercentage(heightInCm, neckInCm, waistInCm, hipInCm, gender == "Male");
+ String category = _getBodyFatCategory(bodyFat, gender == "Male");
+
+ bodyFatResult = bodyFat;
+ bodyFatCategory = category;
+ notifyListeners();
+ } catch (e) {
+ bodyFatResult = null;
+ bodyFatCategory = null;
+ notifyListeners();
+ }
+ }
+
+ double _convertLengthToCm(double length, String unit) {
+ switch (unit) {
+ case 'cm':
+ return length;
+ case 'm':
+ return length * 100;
+ case 'ft':
+ return length * 30.48;
+ case 'in':
+ return length * 2.54;
+ default:
+ return length;
+ }
+ }
+
+ double _calculateBodyFatPercentage(double heightCm, double neckCm, double waistCm, double hipCm, bool isMale) {
+ if (isMale) {
+ double waistMinusNeck = waistCm - neckCm;
+ if (waistMinusNeck <= 0) return 0;
+ double log10WaistNeck = _log10(waistMinusNeck);
+ double log10Height = _log10(heightCm);
+ double bodyDensity = 1.0324 - (0.19077 * log10WaistNeck) + (0.15456 * log10Height);
+ double bodyFat = (495 / bodyDensity) - 450;
+ return bodyFat.clamp(0, 100);
+ } else {
+ double waistHipMinusNeck = waistCm + hipCm - neckCm;
+ if (waistHipMinusNeck <= 0) return 0;
+ double log10WaistHipNeck = _log10(waistHipMinusNeck);
+ double log10Height = _log10(heightCm);
+ double bodyDensity = 1.29579 - (0.35004 * log10WaistHipNeck) + (0.22100 * log10Height);
+ double bodyFat = (495 / bodyDensity) - 450;
+ return bodyFat.clamp(0, 100);
+ }
+ }
+
+ double _log10(double x) {
+ return (x <= 0) ? 0 : (log(x) / log(10));
+ }
+
+ String _getBodyFatCategory(double bodyFat, bool isMale) {
+ if (isMale) {
+ if (bodyFat < 6) return 'Essential Fat';
+ if (bodyFat < 14) return 'Athletes';
+ if (bodyFat < 18) return 'Fitness';
+ if (bodyFat < 25) return 'Average';
+ return 'Obese';
+ } else {
+ if (bodyFat < 14) return 'Essential Fat';
+ if (bodyFat < 21) return 'Athletes';
+ if (bodyFat < 25) return 'Fitness';
+ if (bodyFat < 32) return 'Average';
+ return 'Obese';
+ }
+ }
+
+ // Crabs / Macros
+ void calculateMacros({required String caloriesText, required String selectedDietType}) {
+ if (caloriesText.trim().isEmpty) {
+ carbsCalories = null;
+ proteinCalories = null;
+ fatCalories = null;
+ carbsGrams = null;
+ proteinGrams = null;
+ fatGrams = null;
+ dietType = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ double totalCalories = double.parse(caloriesText.trim());
+ if (totalCalories <= 0) {
+ carbsCalories = null;
+ proteinCalories = null;
+ fatCalories = null;
+ carbsGrams = null;
+ proteinGrams = null;
+ fatGrams = null;
+ dietType = null;
+ notifyListeners();
+ return;
+ }
+
+ Map macroPercentages = _getMacroPercentages(selectedDietType);
+ double carbsPercent = macroPercentages['carbs']!;
+ double proteinPercent = macroPercentages['protein']!;
+ double fatPercent = macroPercentages['fat']!;
+
+ double cCalories = totalCalories * (carbsPercent / 100);
+ double pCalories = totalCalories * (proteinPercent / 100);
+ double fCalories = totalCalories * (fatPercent / 100);
+
+ double cGrams = cCalories / 4;
+ double pGrams = pCalories / 4;
+ double fGrams = fCalories / 9;
+
+ carbsCalories = cCalories;
+ proteinCalories = pCalories;
+ fatCalories = fCalories;
+ carbsGrams = cGrams;
+ proteinGrams = pGrams;
+ fatGrams = fGrams;
+ dietType = selectedDietType;
+ notifyListeners();
+ } catch (e) {
+ carbsCalories = null;
+ proteinCalories = null;
+ fatCalories = null;
+ carbsGrams = null;
+ proteinGrams = null;
+ fatGrams = null;
+ dietType = null;
+ notifyListeners();
+ }
+ }
+
+ Map _getMacroPercentages(String dietType) {
+ switch (dietType) {
+ case "Very Low Crabs":
+ return {'carbs': 5, 'protein': 30, 'fat': 65};
+ case "Low Crabs":
+ return {'carbs': 20, 'protein': 35, 'fat': 45};
+ case "Moderate Crabs":
+ return {'carbs': 40, 'protein': 30, 'fat': 30};
+ case "USDA Guidelines ":
+ return {'carbs': 55, 'protein': 15, 'fat': 30};
+ case "Zone Diet":
+ return {'carbs': 40, 'protein': 30, 'fat': 30};
+ default:
+ return {'carbs': 40, 'protein': 30, 'fat': 30};
+ }
+ }
+
+ // Ovulation
+ DateTime? _parseCustomDate(String dateString) {
+ try {
+ List parts = dateString.split(' ');
+ if (parts.length >= 3) {
+ int day = int.parse(parts[0]);
+ String month = parts[1].replaceAll(',', '');
+ int year = int.parse(parts[2]);
+
+ Map months = {
+ 'January': 1,
+ 'February': 2,
+ 'March': 3,
+ 'April': 4,
+ 'May': 5,
+ 'June': 6,
+ 'July': 7,
+ 'August': 8,
+ 'September': 9,
+ 'October': 10,
+ 'November': 11,
+ 'December': 12,
+ 'Jan': 1,
+ 'Feb': 2,
+ 'Mar': 3,
+ 'Apr': 4,
+ 'Jun': 6,
+ 'Jul': 7,
+ 'Aug': 8,
+ 'Sep': 9,
+ 'Oct': 10,
+ 'Nov': 11,
+ 'Dec': 12,
+ };
+
+ int? monthNum = months[month];
+ if (monthNum != null) {
+ return DateTime(year, monthNum, day);
+ }
+ }
+ return null;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ DateTime? _tryParseDate(String dateString) {
+ try {
+ // try ISO like
+ return DateTime.tryParse(dateString) ?? _parseCustomDate(dateString);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ String _formatDate(DateTime? date) {
+ if (date == null) return '';
+ String day = date.day.toString().padLeft(2, '0');
+ return '$day ${_getMonthName(date.month)}, ${date.year}';
+ }
+
+ String _getMonthName(int month) {
+ const List months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+ return months[month - 1];
+ }
+
+ String _determineCyclePhase(int daysSincePeriod, int follicularPhase, int cycleLength) {
+ int normalizedDay = daysSincePeriod % cycleLength;
+
+ if (normalizedDay < follicularPhase - 1) {
+ return 'Follicular Phase';
+ } else if (normalizedDay < follicularPhase + 2) {
+ return 'Ovulation';
+ } else if (normalizedDay < cycleLength) {
+ return 'Luteal Phase';
+ } else {
+ return 'Menstruation';
+ }
+ }
+
+ void calculateOvulation({required String dateText, required String cycleLengthText, required String lutealPhaseText}) {
+ if (dateText.trim().isEmpty || cycleLengthText.trim().isEmpty || lutealPhaseText.trim().isEmpty) {
+ ovulationResult = null;
+ notifyListeners();
+ return;
+ }
+
+ DateTime? parsedDate = _tryParseDate(dateText);
+ if (parsedDate == null) {
+ ovulationResult = null;
+ notifyListeners();
+ return;
+ }
+
+ try {
+ int cycleLength = int.parse(cycleLengthText.trim());
+ int lutealPhase = int.parse(lutealPhaseText.trim());
+
+ if (cycleLength < 16 || cycleLength > 40 || lutealPhase < 8 || lutealPhase > 16) {
+ ovulationResult = null;
+ notifyListeners();
+ return;
+ }
+
+ if (cycleLength <= lutealPhase) {
+ ovulationResult = null;
+ notifyListeners();
+ return;
+ }
+
+ int follicularPhase = cycleLength - lutealPhase;
+ DateTime ovulationDate = parsedDate.add(Duration(days: follicularPhase - 1));
+ DateTime fertileStart = ovulationDate.subtract(Duration(days: 5));
+ DateTime fertileEnd = ovulationDate.add(Duration(days: 1));
+ int daysSincePeriod = DateTime.now().difference(parsedDate).inDays;
+ String cyclePhase = _determineCyclePhase(daysSincePeriod, follicularPhase, cycleLength);
+
+ ovulationResult = {
+ 'lastPeriodDate': _formatDate(parsedDate),
+ 'ovulationDate': _formatDate(ovulationDate),
+ 'fertileWindowStart': _formatDate(fertileStart),
+ 'fertileWindowEnd': _formatDate(fertileEnd),
+ 'cycleLength': cycleLength,
+ 'lutealPhase': lutealPhase,
+ 'follicularPhase': follicularPhase,
+ 'cyclePhase': cyclePhase,
+ 'nextPeriodDate': _formatDate(parsedDate.add(Duration(days: cycleLength))),
+ };
+
+ notifyListeners();
+ } catch (e) {
+ ovulationResult = null;
+ notifyListeners();
+ }
+ }
+
+ // Delivery due date
+ void calculateDueDate({required String isoDateString}) {
+ if (isoDateString.trim().isEmpty) {
+ deliveryResult = null;
+ notifyListeners();
+ return;
+ }
+
+ DateTime? lmp;
+ try {
+ lmp = DateTime.tryParse(isoDateString) ?? _tryParseDate(isoDateString);
+ } catch (e) {
+ lmp = null;
+ }
+
+ if (lmp == null) {
+ deliveryResult = null;
+ notifyListeners();
+ return;
+ }
+
+ DateTime dueDate = lmp.add(Duration(days: 280));
+ String dueDateStr = _formatDate(dueDate);
+ String lmpStr = _formatDate(lmp);
+
+ deliveryResult = {'lmpDate': lmpStr, 'dueDate': dueDateStr, 'lmpDateTime': lmp, 'dueDateTime': dueDate};
+ notifyListeners();
+ }
+
+ // expose map-like results for widgets to forward to parent
+ Map? get bmiResultMap => bmiResult == null ? null : {'bmiResult': bmiResult, 'bmiCategory': bmiCategory};
+ Map? get caloriesResultMap => caloriesResult == null ? null : {'calories': caloriesResult, 'bmr': bmrResult, 'calorieRange': calorieRange, 'bmrRange': bmrRange};
+ Map? get ibwResultMap => ibwResult == null ? null : {'ibw': ibwResult, 'difference': weightDifference, 'status': weightStatus};
+ Map? get bodyFatResultMap => bodyFatResult == null ? null : {'fatPercentage': bodyFatResult, 'fatCategory': bodyFatCategory};
+ Map? get macrosResultMap => carbsCalories == null ? null : {'totalCalories': carbsCalories! + proteinCalories! + fatCalories!, 'carbsGrams': carbsGrams, 'carbsCalories': carbsCalories, 'proteinGrams': proteinGrams, 'proteinCalories': proteinCalories, 'fatGrams': fatGrams, 'fatCalories': fatCalories, 'dietType': dietType};
+}
+
diff --git a/lib/presentation/health_calculators/health_calculators_page.dart b/lib/presentation/health_calculators/health_calculators_page.dart
new file mode 100644
index 0000000..ab37c2c
--- /dev/null
+++ b/lib/presentation/health_calculators/health_calculators_page.dart
@@ -0,0 +1,170 @@
+import 'dart:async';
+
+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/enums.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/features/hmg_services/hmg_services_view_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart';
+import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_order_detail_page.dart';
+import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_selection_review_page.dart';
+import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_detailed_page.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/widgets/health_card.dart';
+import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+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/buttons/custom_button.dart';
+import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
+import 'package:hmg_patient_app_new/widgets/media_viewer/full_screen_image_viewer.dart';
+import 'package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:provider/provider.dart';
+import 'package:shimmer/shimmer.dart';
+
+class HealthCalculatorsPage extends StatefulWidget {
+ const HealthCalculatorsPage({super.key});
+
+ @override
+ State createState() => _HealthCalculatorsPageState();
+}
+
+class _HealthCalculatorsPageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt.get();
+ return CollapsingListView(
+ title: "Health Calculators".needTranslation,
+ child: Column(
+ children: [
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "General Health".needTranslation.toText16(weight: FontWeight.w600),
+ "Related To BMI, calories, body fat, etc to stay updated with your health.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(
+ width: 12.w,
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Calculator".needTranslation,
+ message: "",
+ child: showCalculatorsItems(type: HealthCalculatorEnum.general),
+ onOkPressed: () {},
+ );
+ }),
+ SizedBox(height: 16.h),
+ Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r),
+ child: Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none),
+ SizedBox(width: 12.w),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ "Women's Health".needTranslation.toText16(weight: FontWeight.w600),
+ "Related To periods, ovulation, pregnancy, and other topics.".needTranslation.toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3))
+ ],
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
+ ],
+ ).paddingAll(16.w))
+ .onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Calculator".needTranslation,
+ message: "",
+ child: showCalculatorsItems(type: HealthCalculatorEnum.women),
+ onOkPressed: () {},
+ );
+ }),
+ ],
+ ).paddingSymmetrical(20.w, 24.h));
+ }
+
+ Widget showCalculatorsItems({required HealthCalculatorEnum type}) {
+ return GridView.builder(
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 3, // 4 icons per row
+ crossAxisSpacing: 16.w,
+ mainAxisSpacing: 16.w,
+ childAspectRatio: 0.80),
+ physics: NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length,
+ padding: EdgeInsets.zero,
+ itemBuilder: (BuildContext context, int index) {
+ return HealthCard(
+ icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon,
+ labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title,
+ onTap: () {
+ Navigator.of(context).push(
+ CustomPageRoute(
+ page: HealthCalculatorDetailedPage(
+ calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
+ ),
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ final List generalHealthServices = [
+ HealthComponentModel(title: "BMI\nCalculator".needTranslation, icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi),
+ HealthComponentModel(title: "Calories\nCalculator".needTranslation, icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories),
+ HealthComponentModel(title: "BMR\nCalculator".needTranslation, icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr),
+ HealthComponentModel(title: "Ideal Body\nWeight".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight),
+ HealthComponentModel(title: "Body Fat\nCalculator".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat),
+ HealthComponentModel(title: "Carbs\nProtein & Fat".needTranslation, icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat),
+ ];
+
+ final List womenHealthServices = [
+ HealthComponentModel(title: "Ovulation\nPeriod".needTranslation, icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation),
+ HealthComponentModel(title: "Delivery\nDue Date".needTranslation, icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate),
+ ];
+}
+
+class HealthComponentModel {
+ String title;
+ String? subTitle;
+ String icon;
+ Color? iconColor;
+ Color? bgColor;
+ Color? textColor;
+ HealthCalculatorsTypeEnum type;
+
+ HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type});
+}
diff --git a/lib/presentation/health_calculators/widgets/bf.dart b/lib/presentation/health_calculators/widgets/bf.dart
new file mode 100644
index 0000000..bbab9a5
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/bf.dart
@@ -0,0 +1,642 @@
+import 'package:flutter/material.dart';
+import 'dart:math';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class BodyFatWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ BodyFatWidget({super.key, required this.onChange});
+
+ @override
+ _BodyFatWidgetState createState() => _BodyFatWidgetState();
+}
+
+class _BodyFatWidgetState extends State {
+ final TextEditingController _heightController = TextEditingController();
+ final TextEditingController _neckController = TextEditingController();
+ final TextEditingController _waistController = TextEditingController();
+ final TextEditingController _hipController = TextEditingController();
+ final List _heightUnits = ['cm', 'm', 'ft', 'in'];
+ String selectedHeightUnit = 'cm';
+ String selectedNeckUnit = 'in';
+ String selectedWaistUnit = 'in';
+ String selectedHipUnit = 'in';
+ String selectedGender = "Male";
+
+ @override
+ void initState() {
+ super.initState();
+ _heightController.addListener(_onInputChanged);
+ _neckController.addListener(_onInputChanged);
+ _waistController.addListener(_onInputChanged);
+ _hipController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _heightController.removeListener(_onInputChanged);
+ _neckController.removeListener(_onInputChanged);
+ _waistController.removeListener(_onInputChanged);
+ _hipController.removeListener(_onInputChanged);
+ _heightController.dispose();
+ _neckController.dispose();
+ _waistController.dispose();
+ _hipController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateBodyFat(
+ heightText: _heightController.text,
+ neckText: _neckController.text,
+ waistText: _waistController.text,
+ hipText: _hipController.text,
+ heightUnit: selectedHeightUnit,
+ neckUnit: selectedNeckUnit,
+ waistUnit: selectedWaistUnit,
+ hipUnit: selectedHipUnit,
+ gender: selectedGender,
+ );
+ if (widget.onChange != null) widget.onChange!(provider.bodyFatResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.gender, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedGender.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _genders = ["Male", "Female"];
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Gender".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _genders.length,
+ itemBuilder: (context, index) {
+ final unit = _genders[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedGender,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedGender = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedGender = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.height, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _heightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '175',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHeightUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Neck".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _neckController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '7',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedNeckUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedNeckUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedNeckUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedNeckUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Waist".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _waistController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '30',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedWaistUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedWaistUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedWaistUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedWaistUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Hip".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _hipController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '0',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHipUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHipUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHipUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Estimate the total body fat based on the size.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/bmi.dart b/lib/presentation/health_calculators/widgets/bmi.dart
new file mode 100644
index 0000000..be87d0d
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/bmi.dart
@@ -0,0 +1,309 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class BMIWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ BMIWidget({super.key, required this.onChange});
+
+ @override
+ _BMIWidgetState createState() => _BMIWidgetState();
+}
+
+class _BMIWidgetState extends State {
+ final TextEditingController _heightController = TextEditingController();
+ final TextEditingController _weightController = TextEditingController();
+ final List _heightUnits = ['cm', 'm', 'ft', 'in'];
+ final List _weightUnits = ['kg', 'lb'];
+ String selectedHeightUnit = 'cm';
+ String selectedWeightUnit = 'kg';
+
+ @override
+ void initState() {
+ super.initState();
+ _heightController.addListener(_onInputChanged);
+ _weightController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _heightController.removeListener(_onInputChanged);
+ _weightController.removeListener(_onInputChanged);
+ _heightController.dispose();
+ _weightController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateBMI(heightText: _heightController.text, weightText: _weightController.text, heightUnit: selectedHeightUnit, weightUnit: selectedWeightUnit);
+ // notify parent
+ if (widget.onChange != null) widget.onChange!(provider.bmiResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.height, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _heightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '175',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHeightUnit.toUpperCase().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHeightUnit = value;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHeightUnit = unit;
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _weightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '75',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedWeightUnit.toUpperCase().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _weightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _weightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedWeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedWeightUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedWeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculate the BMI value and weight status to identify the healthy weight. NOt appropriate for children and women who are pregnant or breastfeeding."
+ .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/bmr.dart b/lib/presentation/health_calculators/widgets/bmr.dart
new file mode 100644
index 0000000..771191e
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/bmr.dart
@@ -0,0 +1,523 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.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/enums.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/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class BMRWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ BMRWidget({super.key, required this.onChange});
+
+ @override
+ _BMRWidgetState createState() => _BMRWidgetState();
+}
+
+class _BMRWidgetState extends State {
+ final TextEditingController _heightController = TextEditingController();
+ final TextEditingController _weightController = TextEditingController();
+ final TextEditingController _ageController = TextEditingController();
+ final List _heightUnits = ['cm', 'm', 'ft', 'in'];
+ final List _weightUnits = ['kg', 'lb'];
+ String selectedHeightUnit = 'cm';
+ String selectedWeightUnit = 'kg';
+ String selectedActivityLevel = 'Lightly active';
+ String selectedGender = "Male";
+
+ @override
+ void initState() {
+ super.initState();
+ _heightController.addListener(_onInputChanged);
+ _weightController.addListener(_onInputChanged);
+ _ageController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _heightController.removeListener(_onInputChanged);
+ _weightController.removeListener(_onInputChanged);
+ _ageController.removeListener(_onInputChanged);
+ _heightController.dispose();
+ _weightController.dispose();
+ _ageController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateBMRAndCalories(
+ heightText: _heightController.text,
+ weightText: _weightController.text,
+ ageText: _ageController.text,
+ heightUnit: selectedHeightUnit,
+ weightUnit: selectedWeightUnit,
+ gender: selectedGender,
+ activityLevel: selectedActivityLevel,
+ );
+ if (widget.onChange != null) widget.onChange!(provider.caloriesResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+ AppState appState = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.gender, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedGender.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _genders = ["Male", "Female"];
+
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Gender".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _genders.length,
+ itemBuilder: (context, index) {
+ final unit = _genders[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedGender,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ print(value);
+ setState(() {
+ selectedGender = value;
+ });
+ _onInputChanged();
+ Navigator.of(context).pop();
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedGender = unit;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.age, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Age (11-120) yrs".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _ageController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '20',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.height, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _heightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '175',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHeightUnit = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _weightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '75',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _weightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _weightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedWeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedWeightUnit = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedWeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.activity, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Activity Level".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"];
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Activity Level".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _activity.length,
+ itemBuilder: (context, index) {
+ final unit = _activity[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedActivityLevel,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedActivityLevel = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedActivityLevel = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity."
+ .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ]));
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/calories.dart b/lib/presentation/health_calculators/widgets/calories.dart
new file mode 100644
index 0000000..0d7aab7
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/calories.dart
@@ -0,0 +1,524 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.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/enums.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/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class CaloriesWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ CaloriesWidget({super.key, required this.onChange});
+
+ @override
+ _CaloriesWidgetState createState() => _CaloriesWidgetState();
+}
+
+class _CaloriesWidgetState extends State {
+ final TextEditingController _heightController = TextEditingController();
+ final TextEditingController _weightController = TextEditingController();
+ final TextEditingController _ageController = TextEditingController();
+ final List _heightUnits = ['cm', 'm', 'ft', 'in'];
+ final List _weightUnits = ['kg', 'lb'];
+ String selectedHeightUnit = 'cm';
+ String selectedWeightUnit = 'kg';
+ String selectedActivityLevel = 'Lightly active';
+ String selectedGender = "Male";
+
+ @override
+ void initState() {
+ super.initState();
+ _heightController.addListener(_onInputChanged);
+ _weightController.addListener(_onInputChanged);
+ _ageController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _heightController.removeListener(_onInputChanged);
+ _weightController.removeListener(_onInputChanged);
+ _ageController.removeListener(_onInputChanged);
+ _heightController.dispose();
+ _weightController.dispose();
+ _ageController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateBMRAndCalories(
+ heightText: _heightController.text,
+ weightText: _weightController.text,
+ ageText: _ageController.text,
+ heightUnit: selectedHeightUnit,
+ weightUnit: selectedWeightUnit,
+ gender: selectedGender,
+ activityLevel: selectedActivityLevel,
+ );
+ if (widget.onChange != null) widget.onChange!(provider.caloriesResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+ AppState appState = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.gender, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedGender.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _genders = ["Male", "Female"];
+
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Gender".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _genders.length,
+ itemBuilder: (context, index) {
+ final unit = _genders[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedGender,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedGender = value;
+ });
+ _onInputChanged();
+ Navigator.of(context).pop();
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedGender = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.age, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Age (11-120) yrs".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _ageController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '20',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.height, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _heightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '175',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHeightUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _weightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '75',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _weightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _weightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedWeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedWeightUnit = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedWeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.activity, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Activity Level".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"];
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Activity Level".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _activity.length,
+ itemBuilder: (context, index) {
+ final unit = _activity[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedActivityLevel,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedActivityLevel = value;
+ });
+ _onInputChanged();
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedActivityLevel = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity."
+ .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/crabs.dart b/lib/presentation/health_calculators/widgets/crabs.dart
new file mode 100644
index 0000000..3fa1bdf
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/crabs.dart
@@ -0,0 +1,198 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/enums.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/presentation/health_calculators/health_calculator_detailed_page.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class CrabsWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ CrabsWidget({super.key, required this.onChange});
+
+ @override
+ _CrabsWidgetState createState() => _CrabsWidgetState();
+}
+
+class _CrabsWidgetState extends State {
+ final TextEditingController _caloriesController = TextEditingController();
+ String selectedDietType = 'Very Low Crabs';
+ Function(String)? onUnitSelected;
+
+ @override
+ void initState() {
+ super.initState();
+ _caloriesController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _caloriesController.removeListener(_onInputChanged);
+ _caloriesController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateMacros(caloriesText: _caloriesController.text, selectedDietType: selectedDietType);
+ if (widget.onChange != null) widget.onChange!(provider.macrosResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.age, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Calories Per Day".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _caloriesController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '2000',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon).onPress(() {
+ Navigator.pop(context);
+ Navigator.of(context).push(CustomPageRoute(page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.calories)));
+ }),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.activity, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Diet Type".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedDietType.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _activity = ["Very Low Crabs", "Low Crabs", "Moderate Crabs", "USDA Guidelines ", "Zone Diet"];
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Diet Type".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _activity.length,
+ itemBuilder: (context, index) {
+ final unit = _activity[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedDietType,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedDietType = value;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedDietType = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio."
+ .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/dduedate.dart b/lib/presentation/health_calculators/widgets/dduedate.dart
new file mode 100644
index 0000000..91057aa
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/dduedate.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/enums.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';
+import 'package:hmg_patient_app_new/widgets/input_widget.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class DeliveryDueDWidget extends StatefulWidget {
+ final Function(dynamic result)? onChange;
+
+ DeliveryDueDWidget({super.key, required this.onChange});
+
+ @override
+ _DeliveryDueDWidgetState createState() => _DeliveryDueDWidgetState();
+}
+
+class _DeliveryDueDWidgetState extends State {
+ final TextEditingController _date = TextEditingController();
+ String selectedDate = '';
+ Function(String)? onUnitSelected;
+
+ @override
+ Widget build(BuildContext context) {
+ final provider = Provider.of(context);
+
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 0.h, bottom: 0.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ TextInputWidget(
+ labelText: "Date",
+ hintText: "11 July, 1994".needTranslation,
+ controller: _date,
+ focusNode: FocusNode(),
+ isEnable: true,
+ prefix: null,
+ isAllowRadius: true,
+ isBorderAllowed: false,
+ isAllowLeadingIcon: true,
+ padding: EdgeInsets.symmetric(vertical: 8.h),
+ leadingIcon: AppAssets.birthday_cake,
+ selectionType: SelectionTypeEnum.calendar,
+ isHideSwitcher: true,
+ onCalendarTypeChanged: (val) {},
+ onChange: (val) {
+ if (val == null) return;
+
+ setState(() {
+ selectedDate = Utils.formatDateToDisplay(val);
+ _date.text = selectedDate;
+ });
+
+ // provider expects ISO date string to calculate due date
+ provider.calculateDueDate(isoDateString: val);
+
+ if (widget.onChange != null) {
+ widget.onChange!(provider.deliveryResult);
+ }
+ },
+ ).withVerticalPadding(4.h),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/health_card.dart b/lib/presentation/health_calculators/widgets/health_card.dart
new file mode 100644
index 0000000..c8603e9
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/health_card.dart
@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+class HealthCard extends StatelessWidget {
+ const HealthCard({
+ super.key,
+ required this.icon,
+ required this.labelText,
+ this.onTap,
+ });
+
+ final String icon;
+ final String labelText;
+ final VoidCallback? onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.r,
+ hasShadow: false,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(
+ icon: icon,
+ width: 40.w,
+ height: 40.h,
+ fit: BoxFit.cover,
+ ).toShimmer2(isShow: false, radius: 12.r),
+ SizedBox(height: 14.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Flexible(
+ child: labelText.toText12(
+ fontWeight: FontWeight.w600,
+ maxLine: 2,
+ )),
+ ],
+ ),
+ ],
+ ).paddingAll(16.w).onPress(() {
+ onTap!();
+ }),
+ );
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/ibw.dart b/lib/presentation/health_calculators/widgets/ibw.dart
new file mode 100644
index 0000000..0e3f725
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/ibw.dart
@@ -0,0 +1,393 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class IdealBodyWeightWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ IdealBodyWeightWidget({super.key, required this.onChange});
+
+ @override
+ _IdealBodyWeightWidgetState createState() => _IdealBodyWeightWidgetState();
+}
+
+class _IdealBodyWeightWidgetState extends State {
+ final TextEditingController _heightController = TextEditingController();
+ final TextEditingController _weightController = TextEditingController();
+ final List _heightUnits = ['cm', 'm', 'ft', 'in'];
+ final List _weightUnits = ['kg', 'lb'];
+ String selectedHeightUnit = 'cm';
+ String selectedWeightUnit = 'kg';
+ String selectedBodyFrameSize = 'Medium (fingers touch)';
+ Function(String)? onUnitSelected;
+
+ @override
+ void initState() {
+ super.initState();
+ _heightController.addListener(_onInputChanged);
+ _weightController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _heightController.removeListener(_onInputChanged);
+ _weightController.removeListener(_onInputChanged);
+ _heightController.dispose();
+ _weightController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ provider.calculateIBW(
+ heightText: _heightController.text,
+ weightText: _weightController.text,
+ heightUnit: selectedHeightUnit,
+ weightUnit: selectedWeightUnit,
+ bodyFrameSize: selectedBodyFrameSize,
+ );
+ if (widget.onChange != null) widget.onChange!(provider.ibwResultMap);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ DialogService dialogService = getIt();
+
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.height, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _heightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '175',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _heightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _heightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedHeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedHeightUnit = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedHeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.weight, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _weightController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '75',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ height: 1.0,
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ width: 1.w,
+ height: 30.w,
+ color: Color(0xFFEAEAEB),
+ ).paddingOnly(right: 10.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ SizedBox(width: 12.w),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down)
+ ],
+ ).onPress(() {
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Unit".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _weightUnits.length,
+ itemBuilder: (context, index) {
+ final unit = _weightUnits[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedWeightUnit,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedWeightUnit = value;
+ });
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedWeightUnit = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.activity, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Row(
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Body Frame Size".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ selectedBodyFrameSize.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.arrow_down),
+ SizedBox(width: 4.w),
+ ],
+ ).paddingSymmetrical(0.w, 16.w).onPress(() {
+ List _activity = ["Small (fingers overlaps)", "Medium (fingers touch)", "Large (fingers don't touch)"];
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: "Select Body Frame Size".needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: _activity.length,
+ itemBuilder: (context, index) {
+ final unit = _activity[index];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 70,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: unit,
+ groupValue: selectedBodyFrameSize,
+ activeColor: AppColors.errorColor,
+ onChanged: (String? value) {
+ if (value == null) return;
+ setState(() {
+ selectedBodyFrameSize = value;
+ });
+ Navigator.pop(context);
+ },
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
+ ],
+ ).onPress(() {
+ setState(() {
+ selectedBodyFrameSize = unit;
+ _onInputChanged();
+ });
+ Navigator.pop(context);
+ }),
+ ),
+ ],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Divider(height: 1, color: Color(0xFFEEEEEE));
+ },
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculates the ideal body weight based on height, weight and body size.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/health_calculators/widgets/ovulation.dart b/lib/presentation/health_calculators/widgets/ovulation.dart
new file mode 100644
index 0000000..476bf72
--- /dev/null
+++ b/lib/presentation/health_calculators/widgets/ovulation.dart
@@ -0,0 +1,186 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/enums.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';
+import 'package:hmg_patient_app_new/widgets/input_widget.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+
+class OvulationWidget extends StatefulWidget {
+ Function(dynamic result)? onChange;
+
+ OvulationWidget({super.key, required this.onChange});
+
+ @override
+ _OvulationWidgetState createState() => _OvulationWidgetState();
+}
+
+class _OvulationWidgetState extends State {
+ final TextEditingController _ageController = TextEditingController();
+ final TextEditingController _cycleLengthController = TextEditingController();
+ final TextEditingController _lutealPhaseLengthController = TextEditingController();
+ Function(String)? onUnitSelected;
+ String selectedDate = '';
+
+ @override
+ void initState() {
+ super.initState();
+ _ageController.addListener(_onInputChanged);
+ _cycleLengthController.addListener(_onInputChanged);
+ _lutealPhaseLengthController.addListener(_onInputChanged);
+ }
+
+ @override
+ void dispose() {
+ _ageController.removeListener(_onInputChanged);
+ _cycleLengthController.removeListener(_onInputChanged);
+ _lutealPhaseLengthController.removeListener(_onInputChanged);
+ _ageController.dispose();
+ _cycleLengthController.dispose();
+ _lutealPhaseLengthController.dispose();
+ super.dispose();
+ }
+
+ void _onInputChanged() {
+ final provider = Provider.of(context, listen: false);
+ final dateText = selectedDate.isEmpty ? _ageController.text.trim() : selectedDate;
+ provider.calculateOvulation(dateText: dateText, cycleLengthText: _cycleLengthController.text, lutealPhaseText: _lutealPhaseLengthController.text);
+ if (widget.onChange != null) widget.onChange!(provider.ovulationResult);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(builder: (context, provider, _) {
+ return Container(
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
+ child: Column(
+ children: [
+ TextInputWidget(
+ labelText: "Date",
+ hintText: "11 July, 1994".needTranslation,
+ controller: _ageController,
+ isEnable: true,
+ prefix: null,
+ isAllowRadius: true,
+ isBorderAllowed: false,
+ isAllowLeadingIcon: true,
+ padding: EdgeInsets.symmetric(vertical: 8.h),
+ leadingIcon: AppAssets.birthday_cake,
+ selectionType: SelectionTypeEnum.calendar,
+ isHideSwitcher: true,
+ onCalendarTypeChanged: (val) {},
+ onChange: (val) {
+ if (val == null) return;
+ setState(() {
+ _ageController.text = selectedDate = Utils.formatDateToDisplay(val!);
+ });
+ _onInputChanged();
+ },
+ ).withVerticalPadding(8),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.age, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Average Cycle Length (Usually 28 days)".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _cycleLengthController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '15',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.age, width: 40.w, height: 40.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ flex: 3,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ "Average Luteal Phase Length(Usually 14 days)".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ Container(
+ height: 20.w,
+ alignment: Alignment.centerLeft,
+ child: TextField(
+ controller: _lutealPhaseLengthController,
+ keyboardType: TextInputType.number,
+ maxLines: 1,
+ cursorHeight: 14,
+ textAlignVertical: TextAlignVertical.center,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.zero,
+ isCollapsed: true,
+ hintText: '10',
+ hintStyle: TextStyle(color: Colors.grey),
+ ),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0),
+ ),
+ )
+ ],
+ ),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ if (provider.ovulationResult != null) ...[
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ // show result UI same as before; keeping unchanged design
+ Row(
+ children: [
+ Expanded(child: Text('Last Period: ${provider.ovulationResult!['lastPeriodDate'] ?? ''}')),
+ Expanded(child: Text('Ovulation: ${provider.ovulationResult!['ovulationDate'] ?? ''}')),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ Divider(height: 1, color: Color(0xFFEEEEEE)),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: "Calculate ovulation and fertile window based on last period, cycle length and luteal phase.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor),
+ ),
+ ],
+ ).paddingSymmetrical(0.w, 16.w),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart
index fd6e976..18cf614 100644
--- a/lib/presentation/hmg_services/services_page.dart
+++ b/lib/presentation/hmg_services/services_page.dart
@@ -51,11 +51,21 @@ class ServicesPage extends StatelessWidget {
bgColor: Colors.orange,
textColor: AppColors.blackColor,
route: null,
- onTap:(){
+ onTap: () {
Utils.openWebView(
url: 'https://hmgwebservices.com/vt_mobile/html/index.html',
);
},
+ ),
+ HmgServicesComponentModel(
+ 12,
+ "Health Calculators".needTranslation,
+ "".needTranslation,
+ AppAssets.calendar,
+ true,
+ bgColor: Colors.blueAccent,
+ textColor: AppColors.blackColor,
+ route: AppRoutes.healthCalculatorsPage,
)
];
@@ -65,7 +75,7 @@ class ServicesPage extends StatelessWidget {
title: "Explore Services".needTranslation,
isLeading: Navigator.canPop(context),
child: Padding(
- padding: EdgeInsets.symmetric(horizontal: 24.h),
+ padding: EdgeInsets.symmetric(horizontal: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart
index 2cd923f..e443647 100644
--- a/lib/presentation/hmg_services/services_view.dart
+++ b/lib/presentation/hmg_services/services_view.dart
@@ -49,7 +49,7 @@ class ServiceGridViewItem extends StatelessWidget {
hmgServiceComponentModel.title.toText12(
fontWeight: FontWeight.w500,
color: hmgServiceComponentModel.textColor,
- maxLine: 1,
+ maxLine: 2,
),
],
));
diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart
index ed5c0db..556d535 100644
--- a/lib/routes/app_routes.dart
+++ b/lib/routes/app_routes.dart
@@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
+import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculators_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
@@ -19,6 +20,7 @@ class AppRoutes {
static const String medicalFilePage = '/medicalFilePage';
static const String eReferralPage = '/erReferralPage';
static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage';
+ static const String healthCalculatorsPage = '/healthCalculatorsPage';
static const String homeHealthCarePage = '/homeHealthCarePage';
static const String zoomCallPage = '/zoomCallPage';
@@ -32,6 +34,8 @@ class AppRoutes {
eReferralPage: (context) => NewReferralPage(),
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(),
- zoomCallPage: (context) => CallScreen()
+ zoomCallPage: (context) => CallScreen(),
+ healthCalculatorsPage: (context) => HealthCalculatorsPage()
+
};
}
diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart
index c1a38ab..635a0bb 100644
--- a/lib/widgets/input_widget.dart
+++ b/lib/widgets/input_widget.dart
@@ -48,6 +48,7 @@ class TextInputWidget extends StatelessWidget {
final int minLines;
final int maxLines;
final Color? hintColor;
+ final bool? isHideSwitcher;
// final List countryList;
// final Function(Country)? onCountryChange;
@@ -85,6 +86,7 @@ class TextInputWidget extends StatelessWidget {
this.isMultiline = false,
this.minLines = 3,
this.maxLines = 6,
+ this.isHideSwitcher,
// this.countryList = const [],
// this.onCountryChange,
});
@@ -214,6 +216,7 @@ class TextInputWidget extends StatelessWidget {
switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
language: appState.getLanguageCode()!,
initialDate: DateTime.now(),
+ showCalendarToggle: isHideSwitcher == true ? false : true,
fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins",
okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),