diff --git a/assets/images/svg/approximate_ovulation_accordion.svg b/assets/images/svg/approximate_ovulation_accordion.svg new file mode 100644 index 0000000..a721d61 --- /dev/null +++ b/assets/images/svg/approximate_ovulation_accordion.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/bloodcholestrol.svg b/assets/images/svg/bloodcholestrol.svg new file mode 100644 index 0000000..8a77bb5 --- /dev/null +++ b/assets/images/svg/bloodcholestrol.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/bloodsugar.svg b/assets/images/svg/bloodsugar.svg new file mode 100644 index 0000000..a97032c --- /dev/null +++ b/assets/images/svg/bloodsugar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/bulb.svg b/assets/images/svg/bulb.svg new file mode 100644 index 0000000..94553a5 --- /dev/null +++ b/assets/images/svg/bulb.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/due_date_accordion.svg b/assets/images/svg/due_date_accordion.svg new file mode 100644 index 0000000..828a1c9 --- /dev/null +++ b/assets/images/svg/due_date_accordion.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/fertile_window_accordion.svg b/assets/images/svg/fertile_window_accordion.svg new file mode 100644 index 0000000..63f0173 --- /dev/null +++ b/assets/images/svg/fertile_window_accordion.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/svg/next_period_accordion.svg b/assets/images/svg/next_period_accordion.svg new file mode 100644 index 0000000..4ff65db --- /dev/null +++ b/assets/images/svg/next_period_accordion.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/pregnancy_test_day_accordion.svg b/assets/images/svg/pregnancy_test_day_accordion.svg new file mode 100644 index 0000000..5a29588 --- /dev/null +++ b/assets/images/svg/pregnancy_test_day_accordion.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/switch.svg b/assets/images/svg/switch.svg new file mode 100644 index 0000000..1db8753 --- /dev/null +++ b/assets/images/svg/switch.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/triglycerides.svg b/assets/images/svg/triglycerides.svg new file mode 100644 index 0000000..0f8facd --- /dev/null +++ b/assets/images/svg/triglycerides.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bbe7d75..9ea7fcc 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -247,6 +247,21 @@ class AppAssets { static const String refreshIcon = '$svgBasePath/refresh.svg'; static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg'; + + + static const String bloodSugar = '$svgBasePath/bloodsugar.svg'; + static const String bloodCholestrol = '$svgBasePath/bloodcholestrol.svg'; + static const String triglycerides = '$svgBasePath/triglycerides.svg'; + static const String bulb = '$svgBasePath/bulb.svg'; + static const String switchBtn = '$svgBasePath/switch.svg'; + + + static const String ovulationAccordion = '$svgBasePath/approximate_ovulation_accordion.svg'; + static const String nextPeriodAccordion = '$svgBasePath/next_period_accordion.svg'; + static const String fertileAccordion = '$svgBasePath/fertile_window_accordion.svg'; + static const String pregnancyDayAccordion = '$svgBasePath/pregnancy_test_day_accordion.svg'; + static const String pregnancyDueDateAccordion = '$svgBasePath/due_date_accordion.svg'; + // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; static const String liveCareService = '$pngBasePath/livecare_service.png'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 054e1fa..5d98a78 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -45,7 +45,6 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_r import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_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'; @@ -59,6 +58,8 @@ import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + GetIt getIt = GetIt.instance; class AppDependencies { @@ -135,15 +136,13 @@ class AppDependencies { () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), ); - getIt.registerLazySingleton( - () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: 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( - () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); + getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel( diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 3a9effb..8fd4818 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -34,9 +34,11 @@ enum FamilyFileEnum { active, inactive, blocked, deleted, pending, rejected } enum BodyView { front, back } +enum HealthCalConEnum { calculator, converter } + enum HealthCalculatorEnum { general, women } -enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate } +enum HealthCalculatorsTypeEnum { bmi, calories, bmr, idealBodyWeight, bodyFat, crabsProteinFat, ovulation, deliveryDueDate, bloodSugar, bloodCholesterol, triglycerides } extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { String get displayName { @@ -59,6 +61,12 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { return isArabic ? "فترة الإباضة" : "Ovulation Period"; case HealthCalculatorsTypeEnum.deliveryDueDate: return isArabic ? "تاريخ استحقاق التسليم" : "Delivery Due Date"; + case HealthCalculatorsTypeEnum.bloodSugar: + return isArabic ? "سكر الدم" : "Blood Sugar"; + case HealthCalculatorsTypeEnum.bloodCholesterol: + return isArabic ? "كوليسترول الدم" : "Blood Cholesterol"; + case HealthCalculatorsTypeEnum.triglycerides: + return isArabic ? "الدهون الثلاثية في الدم" : "Triglycerides Fat Blood"; } } diff --git a/lib/main.dart b/lib/main.dart index ed69150..fd7654f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,7 +32,7 @@ import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/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'; diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index df2f85a..d801bdb 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/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'; @@ -27,6 +28,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.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/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; @@ -36,8 +38,10 @@ import 'package:provider/provider.dart'; class SelectClinicPage extends StatefulWidget { bool isFromRegionFlow; + HealthCalculatorsTypeEnum? calculatorType; + dynamic calculatedResult; - SelectClinicPage({super.key, this.isFromRegionFlow = false}); + SelectClinicPage({super.key, this.isFromRegionFlow = false, this.calculatorType, this.calculatedResult}); @override State createState() => _SelectClinicPageState(); @@ -69,131 +73,872 @@ class _SelectClinicPageState extends State { bookAppointmentsViewModel = Provider.of(context, listen: false); regionalViewModel = Provider.of(context, listen: true); appState = getIt.get(); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Consumer(builder: (context, bookAppointmentsVM, child) { - return Column( - children: [ - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: LocaleKeys.clinicName.tr(context: context), - controller: searchEditingController, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - focusNode: textFocusNode, - suffix: searchEditingController.text.isNotEmpty - ? GestureDetector( - onTap: () { - searchEditingController.clear(); - bookAppointmentsViewModel.filterClinics(""); - textFocusNode.unfocus(); - }, - child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), - ) - : null, - onChange: (value) { - bookAppointmentsViewModel.filterClinics(value!); - }, - padding: EdgeInsets.symmetric( - vertical: ResponsiveExtension(10).h, - horizontal: ResponsiveExtension(15).h, + if (widget.calculatorType != null) { + return CollapsingListView( + title: "Your ${widget.calculatorType!.name.toCamelCase}", + 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: LocaleKeys.bookAppo.tr(), + onPressed: () { + onClinicSelected(GetClinicsListResponseModel(clinicID: 108, clinicDescription: "asdfds", clinicDescriptionN: "asdfds")); + }, + icon: null, + fontSize: 16.f, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + borderRadius: 12.r, + fontWeight: FontWeight.w500), + ), + child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h), + ); + } else { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: bookAppointmentsViewModel.isLiveCareSchedule ? "Select LiveCare Clinic".needTranslation : LocaleKeys.selectClinic.tr(context: context), + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.clinicName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + bookAppointmentsViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), ), - ), - bookAppointmentsVM.isLiveCareSchedule - ? ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); - }), + bookAppointmentsVM.isLiveCareSchedule + ? ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }), + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ) - : ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); - }), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ) + : ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); + }), + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + }), + ), + ), + ), + ); + } + } + + Widget getCalculatorResultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { + switch (widget.calculatorType!) { + case HealthCalculatorsTypeEnum.bmi: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.calories: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bmr: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.idealBodyWeight: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bodyFat: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.crabsProteinFat: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.ovulation: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.deliveryDueDate: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bloodSugar: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.bloodCholesterol: + return resultWidget(type: widget.calculatorType!, calculatedResult: calculatedResult); + case HealthCalculatorsTypeEnum.triglycerides: + return resultWidget(type: widget.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; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + 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; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + 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; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + 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; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + 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; + })(), + chipsItems: [ + ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), + ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), + ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + ], + color: Colors.orange, + hide: true, + icon: Icons.warning_rounded, + ), + ] + ] else if (type == HealthCalculatorsTypeEnum.ovulation) ...[ + Column( + children: [ + 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( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.nextPeriodAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], ), - ], - ); - }), + ], + ), + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["lastPeriodDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + SizedBox(height: 16.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["ovulationDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + SizedBox(height: 16.h), + "Fertility Window".toText16(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range." + .toText12(height: 1.5, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + ], + ), + ), + SizedBox( + height: 16.h, + ), + 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( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bulb), + SizedBox( + width: 8.w, + ), + Row( + children: [ + "Tips to keep in mind".toText16(color: AppColors.textColor, weight: FontWeight.w600), + ], + ), + ], + ), + SizedBox(height: 8.h), + Column( + children: [ + _buildTipRow("Drink before you feel thirsty."), + SizedBox(height: 4.h), + _buildTipRow("Keep a refillable bottle next to you."), + SizedBox(height: 4.h), + _buildTipRow("Track your daily intake to stay motivated."), + SizedBox(height: 4.h), + _buildTipRow("Choose sparkling water instead of soda."), + ], + ).paddingOnly(left: 4.w, right: 4.w) + ], + ), + ), + ], + ) + ] else if (type == HealthCalculatorsTypeEnum.deliveryDueDate) ...[ + Column( + children: [ + 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( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDueDateAccordion, width: 30.h, height: 30.h), + SizedBox(width: 8.w), + calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + Expanded(child: SizedBox(width: 8.w)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["dueDateDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + ], + ), + ), + SizedBox(height: 16.h), + 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( + children: [ + "First Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["firstTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + + SizedBox(height: 16.h), + 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( + children: [ + "Second Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["secondTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + SizedBox(height: 16.h), + 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( + children: [ + "Third Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Expanded(child: SizedBox(width: 8.h)), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["thirdTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDayAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + + + ], + ) + ], + ], + ); + } + + Widget _buildTipRow(String text) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 6.h, right: 8.w), + child: CircleAvatar( + radius: 3.r, + backgroundColor: AppColors.textColorLight, ), ), + Expanded( + child: text.toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w500, height: 1.2.h), + ), + ], + ); + } + + Widget calorieWidget({required String title, required dynamic calories, required String description, required Color color, required IconData icon, bool hide = false, List? chipsItems}) { + 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(title), isBold: true), + SizedBox(width: 10.w), + hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600) + ], + ), + _buildStatusIcon(title) + ], + ), + SizedBox(height: 16.h), + if (chipsItems != null && chipsItems.isNotEmpty) ...[ + Wrap( + spacing: 8.0, // Horizontal space between chips + runSpacing: 4.0, // Vertical space between rows of chips + children: chipsItems.map((item) { + return CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: item, + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor) + .paddingOnly(bottom: 4.h); + }).toList(), + ), + SizedBox(height: 8.h), + ], + title.toText16(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox(height: 8.h), + description.toText12(height: 1.2.h, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + ], ), ); } + Widget _buildStatusIcon(String title) { + 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(String title) { + 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; + } + } + void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) { bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic); bookAppointmentsViewModel.setIsDoctorsListLoading(true); @@ -210,7 +955,8 @@ class _SelectClinicPageState extends State { if (clinic.isLiveCareClinicAndOnline ?? false) { Navigator.of(context).push( CustomPageRoute( - page: SelectLivecareClinicPage(onNegativeClicked: (){ + page: SelectLivecareClinicPage( + onNegativeClicked: () { handleDoctorScreen(clinic); }, selectedClinic: clinic, @@ -228,9 +974,7 @@ class _SelectClinicPageState extends State { //Dental Clinic Flow if (clinic.clinicID == 17) { if (appState.isAuthenticated) { - initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel - .currentlySelectedHospitalFromRegionFlow ?? - "0")); + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); return; } else { bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); @@ -241,8 +985,7 @@ class _SelectClinicPageState extends State { ); return; } - } - else if (clinic.clinicID == 253) { + } else if (clinic.clinicID == 253) { bookAppointmentsViewModel.resetLaserData(); bookAppointmentsViewModel.getLaserClinic(); Navigator.push( @@ -269,8 +1012,7 @@ class _SelectClinicPageState extends State { } } - void openRegionListBottomSheet( - BuildContext context, RegionBottomSheetType type) { + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { bookAppointmentsViewModel.setProjectID(null); regionalViewModel.flush(); @@ -278,12 +1020,11 @@ class _SelectClinicPageState extends State { // AppointmentViaRegionViewmodel? viewmodel = null; showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (_, data, __) { - return getRegionalSelectionWidget(data); - }), callBackFunc: () { - }); + return getRegionalSelectionWidget(data); + }), callBackFunc: () {}); } - Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) { return RegionBottomSheetBody(); } @@ -308,7 +1049,7 @@ class _SelectClinicPageState extends State { if (appState.isAuthenticated) { initDentalAppointment(); return SizedBox.shrink(); - }else { + } else { bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); } } @@ -321,8 +1062,7 @@ class _SelectClinicPageState extends State { } bookAppointmentsViewModel.setProjectID(id); return SizedBox.shrink(); - } - else { + } else { return SizedBox.shrink(); } return SizedBox.shrink(); @@ -476,9 +1216,7 @@ class _SelectClinicPageState extends State { void initDentalAppointment() async { await Future.delayed(Duration(milliseconds: 300)); - initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel - .currentlySelectedHospitalFromRegionFlow ?? - "0")); + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); return; } } diff --git a/lib/presentation/health_calculators/health_calculator_result.dart b/lib/presentation/health_calculators/health_calculator_result.dart index a0a46ae..e69de29 100644 --- a/lib/presentation/health_calculators/health_calculator_result.dart +++ b/lib/presentation/health_calculators/health_calculator_result.dart @@ -1,473 +0,0 @@ -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/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/book_appointments/book_appointments_view_model.dart'; -import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; -import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.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:provider/provider.dart'; - -import '../../widgets/common_bottom_sheet.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}", - 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: LocaleKeys.bookAppo.tr(), - onPressed: () { - // Navigate to booking page - var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; - openRegionListBottomSheet(context, bottomSheetType); - getIt.get().setSelectedClinic( - GetClinicsListResponseModel(clinicID: 108, clinicDescription: "Bariatics", clinicDescriptionN: "Bariatics"), - ); - getIt.get().setIsDoctorsListLoading(true); - }, - icon: null, - fontSize: 16.f, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - borderRadius: 12.r, - fontWeight: FontWeight.w500), - ), - child: getCalculatorResultWidget(type: calculatorType, calculatedResult: calculatedResult).paddingSymmetrical(18.w, 24.h), - ); - } - - void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { - getIt.get().setProjectID(null); - - getIt.get().flush(); - getIt.get().setBottomSheetType(type); - showCommonBottomSheetWithoutHeight(context, - title: "", - titleWidget: Consumer(builder: (_, data, __) => LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600)), - isDismissible: false, child: Consumer(builder: (_, data, __) { - return RegionBottomSheetBody(); - }), callBackFunc: () {}); - } - - 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_calculators_page.dart b/lib/presentation/health_calculators/health_calculators_page.dart deleted file mode 100644 index 8ac7a22..0000000 --- a/lib/presentation/health_calculators/health_calculators_page.dart +++ /dev/null @@ -1,157 +0,0 @@ -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/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/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/routes/custom_page_route.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/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart similarity index 61% rename from lib/presentation/health_calculators/health_calculator_detailed_page.dart rename to lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index ae7d46b..e0f3a7a 100644 --- a/lib/presentation/health_calculators/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -1,25 +1,28 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bf.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/blood_sugar.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmi.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/bmr.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/calories.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/crabs.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/dduedate.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.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; @@ -40,24 +43,34 @@ class _HealthCalculatorDetailedPageState extends State(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)), + bottomChild: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar || + widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol || + widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides + ? SizedBox() + : 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: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar || + widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol || + widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides + ? "Convert".needTranslation + : "Calculate".needTranslation, + onPressed: () { + if (calculatedResult == null) return; + + DialogService dialogService = getIt.get(); + Navigator.of(context).push(CustomPageRoute( + page: SelectClinicPage(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) { @@ -85,6 +98,12 @@ class _HealthCalculatorDetailedPageState extends State? deliveryResult; + Map? bloodSugarResult; + Map? bloodCholesterolResult; + Map? triglyceridesResult; + + String? _mgdlValue; + + String? get mgdlValue => _mgdlValue; + + String? _mmolValue; + + String? get mmolValue => _mmolValue; + + String _activeUnit = 'mg/dL'; // current source unit + String get activeUnit => _activeUnit; + + + // ================== BLOOD CHOLESTEROL ================== + String? _cholMgdlValue; + String? _cholMmolValue; + String _cholActiveUnit = 'mg/dL'; + + String? get cholMgdlValue => _cholMgdlValue; + String? get cholMmolValue => _cholMmolValue; + String get cholActiveUnit => _cholActiveUnit; + + // ================== TRIGLYCERIDES ================== + String? _triMgdlValue; + String? _triMmolValue; + String _triActiveUnit = 'mg/dL'; + + String? get triMgdlValue => _triMgdlValue; + String? get triMmolValue => _triMmolValue; + String get triActiveUnit => _triActiveUnit; + + // Generic helpers void calculateBMI({required String heightText, required String weightText, required String heightUnit, required String weightUnit}) { @@ -113,7 +148,15 @@ class HealthCalcualtorViewModel extends ChangeNotifier { } // 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}) { + 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; @@ -303,7 +346,16 @@ class HealthCalcualtorViewModel extends ChangeNotifier { } // 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}) { + 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; @@ -548,6 +600,29 @@ class HealthCalcualtorViewModel extends ChangeNotifier { return months[month - 1]; } + String _getDayName(int weekday) { + // DateTime.weekday returns 1 for Monday and 7 for Sunday. + const List days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + return days[weekday - 1]; + } + + String _formatDateWithDayName(DateTime? date) { + if (date == null) return ''; + String dayName = _getDayName(date.weekday); // Gets 'Monday', 'Tuesday', etc. + return dayName; + } + + Map convertBloodSugar(double value, String fromUnit) { + if (fromUnit == 'mg/dL') { + final mmolL = value / 18.0182; + return {'mgdL': value, 'mmolL': mmolL}; + } else if (fromUnit == 'mmol/L') { + final mgdL = value * 18.0182; + return {'mgdL': mgdL, 'mmolL': value}; + } + return {'mgdL': 0, 'mmolL': 0}; + } + String _determineCyclePhase(int daysSincePeriod, int follicularPhase, int cycleLength) { int normalizedDay = daysSincePeriod % cycleLength; @@ -600,6 +675,8 @@ class HealthCalcualtorViewModel extends ChangeNotifier { String cyclePhase = _determineCyclePhase(daysSincePeriod, follicularPhase, cycleLength); ovulationResult = { + 'lastPeriodDay': _formatDateWithDayName(parsedDate), + 'ovulationDay': _formatDateWithDayName(ovulationDate), 'lastPeriodDate': _formatDate(parsedDate), 'ovulationDate': _formatDate(ovulationDate), 'fertileWindowStart': _formatDate(fertileStart), @@ -629,7 +706,7 @@ class HealthCalcualtorViewModel extends ChangeNotifier { DateTime? lmp; try { lmp = DateTime.tryParse(isoDateString) ?? _tryParseDate(isoDateString); - } catch (e) { + } catch (_) { lmp = null; } @@ -639,19 +716,293 @@ class HealthCalcualtorViewModel extends ChangeNotifier { return; } - DateTime dueDate = lmp.add(Duration(days: 280)); - String dueDateStr = _formatDate(dueDate); - String lmpStr = _formatDate(lmp); + // Pregnancy constants + const int totalPregnancyDays = 280; + + // Calculate main dates + final DateTime dueDate = lmp.add(const Duration(days: totalPregnancyDays)); + + // Trimester calculations + final DateTime firstTrimesterStart = lmp; + final DateTime firstTrimesterEnd = lmp.add(const Duration(days: 83)); + + final DateTime secondTrimesterStart = + firstTrimesterEnd.add(const Duration(days: 1)); + final DateTime secondTrimesterEnd = + lmp.add(const Duration(days: 195)); + + final DateTime thirdTrimesterStart = + secondTrimesterEnd.add(const Duration(days: 1)); + final DateTime thirdTrimesterEnd = dueDate; + + deliveryResult = { + 'lmpDate': _formatDate(lmp), + 'dueDate': _formatDate(dueDate), + // Raw DateTime (useful for logic/UI) + 'lmpDateTime': lmp, + 'dueDateTime': dueDate, + 'dueDateDay' : _formatDateWithDayName(dueDate), + // Trimester info + 'firstTrimester': { + 'start': _formatDate(firstTrimesterStart), + 'end': _formatDate(firstTrimesterEnd), + 'weeks': '1–12', + 'startDateTime': firstTrimesterStart, + 'endDateTime': firstTrimesterEnd, + }, + 'secondTrimester': { + 'start': _formatDate(secondTrimesterStart), + 'end': _formatDate(secondTrimesterEnd), + 'weeks': '13–27', + 'startDateTime': secondTrimesterStart, + 'endDateTime': secondTrimesterEnd, + }, + 'thirdTrimester': { + 'start': _formatDate(thirdTrimesterStart), + 'end': _formatDate(thirdTrimesterEnd), + 'weeks': '28–40', + 'startDateTime': thirdTrimesterStart, + 'endDateTime': thirdTrimesterEnd, + }, + }; - deliveryResult = {'lmpDate': lmpStr, 'dueDate': dueDateStr, 'lmpDateTime': lmp, 'dueDateTime': dueDate}; notifyListeners(); } + + // Blood sugar conversions + void calculateBloodSugar({required String valueText, required String unit}) { + if (valueText.trim().isEmpty) { + bloodSugarResult = null; + notifyListeners(); + return; + } + + try { + double value = double.parse(valueText.trim()); + + if (value <= 0) { + bloodSugarResult = null; + notifyListeners(); + return; + } + + double converted; + String convertedUnit; + + if (unit == 'mg/dL' || unit == 'MG/DL' || unit.toLowerCase().contains('mg')) { + // mg/dL -> mmol/L (glucose): divide by 18.0182 + converted = value / 18.0182; + convertedUnit = 'mmol/L'; + } else { + // mmol/L -> mg/dL + converted = value * 18.0182; + convertedUnit = 'mg/dL'; + } + + // round to sensible precision + double convertedRounded = double.parse(converted.toStringAsFixed(2)); + + bloodSugarResult = { + 'input': value, + 'inputUnit': unit, + 'converted': convertedRounded, + 'convertedUnit': convertedUnit, + }; + notifyListeners(); + } catch (e) { + bloodSugarResult = null; + 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}; -} + Map? get macrosResultMap => carbsCalories == null + ? null + : { + 'totalCalories': carbsCalories! + proteinCalories! + fatCalories!, + 'carbsGrams': carbsGrams, + 'carbsCalories': carbsCalories, + 'proteinGrams': proteinGrams, + 'proteinCalories': proteinCalories, + 'fatGrams': fatGrams, + 'fatCalories': fatCalories, + 'dietType': dietType + }; + + + + void onBloodSugarChanged(String value, String fromUnit) { + _activeUnit = fromUnit; + + if (value.isEmpty) { + _mgdlValue = null; + _mmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _mgdlValue = value; + _mmolValue = (parsed / 18.0182).toStringAsFixed(1); + } else { + _mmolValue = value; + _mgdlValue = (parsed * 18.0182).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchBloodSugarValues() { + if (_activeUnit == 'mg/dL') { + final mmol = double.tryParse(_mmolValue ?? ''); + if (mmol == null) return; + + _activeUnit = 'mmol/L'; + + // mmol stays as source + _mmolValue = mmol.toStringAsFixed(1); + _mgdlValue = (mmol * 18.0182).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_mgdlValue ?? ''); + if (mgdl == null) return; + + _activeUnit = 'mg/dL'; + + // mg/dL stays as source + _mgdlValue = mgdl.toStringAsFixed(0); + _mmolValue = (mgdl / 18.0182).toStringAsFixed(1); + } + + notifyListeners(); + } + // --- NEW: Method to clear the values --- + void clearBloodSugar() { + _mgdlValue = null; + _mmolValue = null; + notifyListeners(); + } + + + + void onBloodCholesterolChanged(String value, String fromUnit) { + _cholActiveUnit = fromUnit; + + if (value.isEmpty) { + _cholMgdlValue = null; + _cholMmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _cholMgdlValue = value; + _cholMmolValue = (parsed / 38.67).toStringAsFixed(2); + } else { + _cholMmolValue = value; + _cholMgdlValue = (parsed * 38.67).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchBloodCholesterolValues() { + if (_cholActiveUnit == 'mg/dL') { + final mmol = double.tryParse(_cholMmolValue ?? ''); + if (mmol == null) return; + + _cholActiveUnit = 'mmol/L'; + + _cholMmolValue = mmol.toStringAsFixed(2); + _cholMgdlValue = (mmol * 38.67).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_cholMgdlValue ?? ''); + if (mgdl == null) return; + + _cholActiveUnit = 'mg/dL'; + + _cholMgdlValue = mgdl.toStringAsFixed(0); + _cholMmolValue = (mgdl / 38.67).toStringAsFixed(2); + } + + notifyListeners(); + } + + + void clearBloodCholesterol() { + _cholMgdlValue = null; + _cholMmolValue = null; + _cholActiveUnit = 'mg/dL'; + notifyListeners(); + } + + + + void onTriglyceridesChanged(String value, String fromUnit) { + _triActiveUnit = fromUnit; + + if (value.isEmpty) { + _triMgdlValue = null; + _triMmolValue = null; + notifyListeners(); + return; + } + + final parsed = double.tryParse(value); + if (parsed == null) return; + + if (fromUnit == 'mg/dL') { + _triMgdlValue = value; + _triMmolValue = (parsed / 88.57).toStringAsFixed(2); + } else { + _triMmolValue = value; + _triMgdlValue = (parsed * 88.57).toStringAsFixed(0); + } + + notifyListeners(); + } + + void switchTriglyceridesValues() { + if (_triActiveUnit == 'mg/dL') { + final mmol = double.tryParse(_triMmolValue ?? ''); + if (mmol == null) return; + + _triActiveUnit = 'mmol/L'; + _triMmolValue = mmol.toStringAsFixed(2); + _triMgdlValue = (mmol * 88.57).toStringAsFixed(0); + } else { + final mgdl = double.tryParse(_triMgdlValue ?? ''); + if (mgdl == null) return; + + _triActiveUnit = 'mg/dL'; + _triMgdlValue = mgdl.toStringAsFixed(0); + _triMmolValue = (mgdl / 88.57).toStringAsFixed(2); + } + + notifyListeners(); + } + + void clearTriglycerides() { + _triMgdlValue = null; + _triMmolValue = null; + _triActiveUnit = 'mg/dL'; + notifyListeners(); + } + + + +} diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart new file mode 100644 index 0000000..702e04c --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.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_and_converts/health_calculator_detailed_page.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/health_card.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/routes/custom_page_route.dart'; + +class HealthCalculatorsPage extends StatefulWidget { + HealthCalConEnum type; + + HealthCalculatorsPage({super.key, required this.type}); + + @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: widget.type == HealthCalConEnum.calculator ? "Health Calculators".needTranslation : "Health Converters".needTranslation, + child: widget.type == HealthCalConEnum.calculator + ? 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) + : Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Blood Sugar".needTranslation.toText16(weight: FontWeight.w600), + "Track your glucose levels, understand trends, and get personalized insights for better 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(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodSugar), + ), + ); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Blood Cholesterol".needTranslation.toText16(weight: FontWeight.w600), + "Monitor your cholesterol levels, track your LDL, HDL, and triglycerides. Get personalized recommendations for a healthy heart." + .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(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.bloodCholesterol), + ), + ); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 20.r), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Triglycerides Fat Blood".needTranslation.toText16(weight: FontWeight.w600), + "Manage triglycerides, a key blood fat. Understand levels, diet impacts, and heart health strategies." + .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(() { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage(calculatorType: HealthCalculatorsTypeEnum.triglycerides), + ), + ); + }), + ], + ).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_and_converts/widgets/bf.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bf.dart rename to lib/presentation/health_calculators_and_converts/widgets/bf.dart index bbab9a5..3a37036 100644 --- a/lib/presentation/health_calculators/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'dart:math'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,7 +10,6 @@ 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; diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart new file mode 100644 index 0000000..161f318 --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart @@ -0,0 +1,180 @@ +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/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/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class BloodCholesterolWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const BloodCholesterolWidget({super.key, this.onChange}); + + @override + State createState() => _BloodCholesterolWidgetState(); +} + +class _BloodCholesterolWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearBloodCholesterol(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + final mgdlText = provider.cholMgdlValue ?? ''; + final mmolText = provider.cholMmolValue ?? ''; + + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "120", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onBloodCholesterolChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchBloodCholesterolValues(); + if (provider.cholActiveUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + _buildInputField( + label: "MMOL/L", + hint: "6.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + + provider.onBloodCholesterolChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + const 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: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + /// Reusable Input Field + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + maxLines: 1, + onChanged: onChanged, + cursorHeight: 35.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, + height: 1.h, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart new file mode 100644 index 0000000..86b4c1e --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart @@ -0,0 +1,190 @@ +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/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/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class BloodSugarWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const BloodSugarWidget({super.key, this.onChange}); + + @override + State createState() => _BloodSugarWidgetState(); +} + +class _BloodSugarWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearBloodSugar(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + final mgdlText = provider.mgdlValue ?? ''; + final mmolText = provider.mmolValue ?? ''; + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "120", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onBloodSugarChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchBloodSugarValues(); + if (provider.activeUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + _buildInputField( + label: "MMOL/L", + hint: "6.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + + provider.onBloodSugarChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + const 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: "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)".toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + /// Reusable Input Field + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + maxLines: 1, + onChanged: onChanged, + cursorHeight: 35.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, + height: 1.h, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/health_calculators/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bmi.dart rename to lib/presentation/health_calculators_and_converts/widgets/bmi.dart index be87d0d..49b77c0 100644 --- a/lib/presentation/health_calculators/widgets/bmi.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart @@ -8,7 +8,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class BMIWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/bmr.dart rename to lib/presentation/health_calculators_and_converts/widgets/bmr.dart index 771191e..138fd3e 100644 --- a/lib/presentation/health_calculators/widgets/bmr.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart @@ -10,7 +10,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class BMRWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/calories.dart rename to lib/presentation/health_calculators_and_converts/widgets/calories.dart index 0d7aab7..bfaad3e 100644 --- a/lib/presentation/health_calculators/widgets/calories.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart @@ -10,7 +10,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class CaloriesWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart similarity index 98% rename from lib/presentation/health_calculators/widgets/crabs.dart rename to lib/presentation/health_calculators_and_converts/widgets/crabs.dart index 3fa1bdf..5fe1215 100644 --- a/lib/presentation/health_calculators/widgets/crabs.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart @@ -7,11 +7,11 @@ 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/presentation/health_calculators_and_converts/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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class CrabsWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/dduedate.dart b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart similarity index 94% rename from lib/presentation/health_calculators/widgets/dduedate.dart rename to lib/presentation/health_calculators_and_converts/widgets/dduedate.dart index 91057aa..15c0a0c 100644 --- a/lib/presentation/health_calculators/widgets/dduedate.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/dduedate.dart @@ -8,7 +8,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class DeliveryDueDWidget extends StatefulWidget { final Function(dynamic result)? onChange; @@ -35,7 +35,7 @@ class _DeliveryDueDWidgetState extends State { child: Column( children: [ TextInputWidget( - labelText: "Date", + labelText: "Last Period Date", hintText: "11 July, 1994".needTranslation, controller: _date, focusNode: FocusNode(), @@ -56,8 +56,6 @@ class _DeliveryDueDWidgetState extends State { selectedDate = Utils.formatDateToDisplay(val); _date.text = selectedDate; }); - - // provider expects ISO date string to calculate due date provider.calculateDueDate(isoDateString: val); if (widget.onChange != null) { diff --git a/lib/presentation/health_calculators/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart similarity index 100% rename from lib/presentation/health_calculators/widgets/health_card.dart rename to lib/presentation/health_calculators_and_converts/widgets/health_card.dart diff --git a/lib/presentation/health_calculators/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart similarity index 99% rename from lib/presentation/health_calculators/widgets/ibw.dart rename to lib/presentation/health_calculators_and_converts/widgets/ibw.dart index 0e3f725..5f67511 100644 --- a/lib/presentation/health_calculators/widgets/ibw.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart @@ -8,7 +8,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class IdealBodyWeightWidget extends StatefulWidget { Function(dynamic result)? onChange; diff --git a/lib/presentation/health_calculators/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart similarity index 91% rename from lib/presentation/health_calculators/widgets/ovulation.dart rename to lib/presentation/health_calculators_and_converts/widgets/ovulation.dart index 476bf72..de209cb 100644 --- a/lib/presentation/health_calculators/widgets/ovulation.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart @@ -8,7 +8,7 @@ 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'; +import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; class OvulationWidget extends StatefulWidget { Function(dynamic result)? onChange; @@ -78,7 +78,7 @@ class _OvulationWidgetState extends State { onChange: (val) { if (val == null) return; setState(() { - _ageController.text = selectedDate = Utils.formatDateToDisplay(val!); + _ageController.text = selectedDate = Utils.formatDateToDisplay(val); }); _onInputChanged(); }, @@ -109,7 +109,7 @@ class _OvulationWidgetState extends State { border: InputBorder.none, contentPadding: EdgeInsets.zero, isCollapsed: true, - hintText: '15', + hintText: '28', hintStyle: TextStyle(color: Colors.grey), ), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), @@ -146,7 +146,7 @@ class _OvulationWidgetState extends State { border: InputBorder.none, contentPadding: EdgeInsets.zero, isCollapsed: true, - hintText: '10', + hintText: '14', hintStyle: TextStyle(color: Colors.grey), ), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), @@ -157,17 +157,6 @@ class _OvulationWidgetState extends State { ), ], ).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: [ diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart new file mode 100644 index 0000000..17ad1f9 --- /dev/null +++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart @@ -0,0 +1,194 @@ +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/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/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; + +class TriglyceridesWidget extends StatefulWidget { + final Function(dynamic result)? onChange; + + const TriglyceridesWidget({super.key, this.onChange}); + + @override + State createState() => _TriglyceridesWidgetState(); +} + +class _TriglyceridesWidgetState extends State { + final TextEditingController _mgdlController = TextEditingController(); + final TextEditingController _mmolController = TextEditingController(); + final FocusNode _mgdlFocus = FocusNode(); + final FocusNode _mmolFocus = FocusNode(); + bool _isProgrammaticChange = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of( + context, + listen: false, + ).clearTriglycerides(); + }); + } + + @override + void dispose() { + _mgdlController.dispose(); + _mmolController.dispose(); + _mgdlFocus.dispose(); + _mmolFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, _) { + _isProgrammaticChange = true; + + final mgdlText = provider.triMgdlValue ?? ''; + final mmolText = provider.triMmolValue ?? ''; + + if (_mgdlController.text != mgdlText) { + _mgdlController.text = mgdlText; + _mgdlController.selection = TextSelection.fromPosition( + TextPosition(offset: _mgdlController.text.length), + ); + } + + if (_mmolController.text != mmolText) { + _mmolController.text = mmolText; + _mmolController.selection = TextSelection.fromPosition( + TextPosition(offset: _mmolController.text.length), + ); + } + + _isProgrammaticChange = false; + + return Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + _buildInputField( + label: "MG/DL", + hint: "150", + controller: _mgdlController, + focusNode: _mgdlFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onTriglyceridesChanged(value, 'mg/dL'); + }, + ).paddingOnly(top: 16.h), + + Row( + children: [ + const Expanded( + flex: 3, + child: Divider(height: 1, color: Color(0xFFEEEEEE)), + ), + SizedBox(width: 8.w), + Utils.buildSvgWithAssets( + icon: AppAssets.switchBtn, + width: 40.h, + height: 40.h, + ).onPress(() { + provider.switchTriglyceridesValues(); + + if (provider.triActiveUnit == 'mg/dL') { + _mgdlFocus.requestFocus(); + } else { + _mmolFocus.requestFocus(); + } + }), + ], + ), + + _buildInputField( + label: "MMOL/L", + hint: "1.7", + controller: _mmolController, + focusNode: _mmolFocus, + onChanged: (value) { + if (_isProgrammaticChange) return; + provider.onTriglyceridesChanged(value, 'mmol/L'); + }, + ).paddingOnly(bottom: 16.h), + + const 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: + "Convert triglyceride values between mg/dL and mmol/L." + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + ), + ], + ).paddingSymmetrical(0.w, 16.w), + ], + ), + ); + }, + ); + } + + Widget _buildInputField({ + required String label, + required String hint, + required TextEditingController controller, + required FocusNode focusNode, + required ValueChanged onChanged, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + label.toText12( + fontWeight: FontWeight.w500, + color: AppColors.inputLabelTextColor, + ), + SizedBox( + height: 40.h, + child: TextField( + controller: controller, + focusNode: focusNode, + + keyboardType: + const TextInputType.numberWithOptions(decimal: true), + onChanged: onChanged, + cursorHeight: 35.h, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index b321ff3..8b10ab0 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -149,7 +149,7 @@ class ServicesPage extends StatelessWidget { AppAssets.health_converters_icon, bgColor: AppColors.whiteColor, true, - route: AppRoutes.comprehensiveCheckupPage, + route: AppRoutes.healthConvertersPage, ), HmgServicesComponentModel( 11, diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 8e452ff..cc3c881 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -36,7 +36,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( - serviceName: "health_calculators", + serviceName: "health_calculators_and_converts", icon: AppAssets.health_calculators_icon, title: "Health", subtitle: "Calculators", diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index d25a9de..f335f34 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; @@ -6,7 +7,7 @@ import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_p import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.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/health_calculators_and_converts/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'; @@ -33,6 +34,7 @@ class AppRoutes { static const String eReferralPage = '/erReferralPage'; static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; static const String healthCalculatorsPage = '/healthCalculatorsPage'; + static const String healthConvertersPage = '/healthConvertersPage'; static const String homeHealthCarePage = '/homeHealthCarePage'; static const String zoomCallPage = '/zoomCallPage'; static const String bloodDonationPage = '/bloodDonationPage'; @@ -79,7 +81,7 @@ class AppRoutes { huaweiHealthExample: (context) => HuaweiHealthExample(), // - healthCalculatorsPage: (context) => HealthCalculatorsPage() - + healthCalculatorsPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.calculator), + healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter) }; } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 7329de9..ca40c32 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -47,6 +48,8 @@ class CollapsingListView extends StatelessWidget { this.physics, }); + final ScrollController _controller = ScrollController(); + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -56,15 +59,31 @@ class CollapsingListView extends StatelessWidget { children: [ CustomScrollView( physics: physics, + controller: _controller, slivers: [ SliverAppBar( - automaticallyImplyLeading: false, + automaticallyImplyLeading: isLeading, pinned: true, - expandedHeight: MediaQuery.of(context).size.height * 0.11.h, - stretch: true, + // toolbarHeight: isLeading ? 24.h : kToolbarHeight, + leadingWidth: isLeading ? null : double.infinity, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, backgroundColor: AppColors.bgScaffoldColor, + bottom: isLeading + ? ScrollAnimatedTitle( + title: title, + showBack: true, + controller: _controller, + search: search, + report: report, + logout: logout, + history: history, + instructions: instructions, + requests: requests, + bottomChild: bottomChild, + trailing: trailing, + ) + : null, leading: isLeading ? Transform.flip( flipX: appState.isArabic(), @@ -81,68 +100,19 @@ class CollapsingListView extends StatelessWidget { highlightColor: Colors.transparent, ), ) - : SizedBox.shrink(), - flexibleSpace: LayoutBuilder( - builder: (context, constraints) { - final double maxHeight = 100.h; - final double minHeight = kToolbarHeight; - double t = (constraints.maxHeight - minHeight) / (maxHeight - minHeight); - t = t - 1; - if (t < 0.7) t = 0.7; - t = t.clamp(0.0, 1.0); - - final double fontSize = lerpDouble(14, 18, t)!; - final double bottomPadding = lerpDouble(0, 0, t)!; - final double leftPadding = lerpDouble(150, 24, t)!; - - return Stack( - children: [ - Align( - alignment: Alignment.lerp( - Alignment.center, - Alignment.bottomLeft, - t, - )!, - child: Padding( - padding: EdgeInsets.only( - left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), - child: Row( - spacing: 4.h, - children: [ - Text( - title, - maxLines: 1, - style: TextStyle( - fontSize: (27 - (5 * (2 - t))).f, - fontWeight: FontWeight.lerp( - FontWeight.w300, - FontWeight.w600, - t, - )!, - color: AppColors.blackColor, - letterSpacing: -0.5), - ).expanded, - if (logout != null) - actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), - if (report != null) - actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(report!), - if (history != null) - actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(history!), - if (instructions != null) - actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), - if (requests != null) - actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(requests!), - if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), - if (trailing != null) trailing!, - ], - )), - ), - ], - ); - }, - ), + : ScrollAnimatedTitle( + title: title, + showBack: false, + controller: _controller, + search: search, + report: report, + logout: logout, + history: history, + instructions: instructions, + requests: requests, + bottomChild: bottomChild, + trailing: trailing, + ), ), SliverList( delegate: SliverChildBuilderDelegate( @@ -158,13 +128,161 @@ class CollapsingListView extends StatelessWidget { ); } +// Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { +// return AnimatedSize( +// duration: Duration(milliseconds: 150), +// child: Container( +// height: 40.h, +// padding: EdgeInsets.all(8.w), +// margin: EdgeInsets.only(right: 24.w), +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration( +// color: AppColors.secondaryLightRedColor, +// borderRadius: 10.r, +// ), +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.center, +// mainAxisAlignment: MainAxisAlignment.center, +// mainAxisSize: MainAxisSize.min, +// spacing: 8.h, +// children: [ +// Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), +// if (t == 1) +// Text( +// title, +// style: context.dynamicTextStyle( +// color: AppColors.primaryRedColor, +// letterSpacing: -0.4, +// fontSize: (12 - (2 * (1 - t))).f, +// fontWeight: FontWeight.lerp( +// FontWeight.w300, +// FontWeight.w500, +// t, +// )!, +// ), +// ), +// ], +// ), +// ), +// ); +// } +} +// +// class ActionModel { +// bool requireT; +// +// } + +class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget { + final String title; + final bool showBack; + final ScrollController controller; + + VoidCallback? search; + VoidCallback? report; + VoidCallback? logout; + VoidCallback? history; + VoidCallback? instructions; + VoidCallback? requests; + Widget? bottomChild; + Widget? trailing; + + ScrollAnimatedTitle({ + super.key, + required this.title, + required this.controller, + required this.showBack, + this.search, + this.report, + this.logout, + this.history, + this.instructions, + this.requests, + this.bottomChild, + this.trailing, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + State createState() => _ScrollAnimatedTitleState(); +} + +class _ScrollAnimatedTitleState extends State { + static const double _maxFont = 24; + static const double _minFont = 14; + + static const double _maxHeight = 80; + static const double _minHeight = 56; + + double _fontSize = _maxFont; + + @override + void initState() { + super.initState(); + widget.controller.addListener(_onScroll); + } + + @override + void dispose() { + widget.controller.removeListener(_onScroll); + super.dispose(); + } + + double t = 0; + + void _onScroll() { + final double offset = widget.controller.offset; + + // control animation range + const double range = 120; + + final double t = (1 - (offset / range)).clamp(0.0, 1.0); + this.t = t; + setState(() { + _fontSize = _minFont + (_maxFont - _minFont) * t; + }); + } + + @override + Widget build(BuildContext context) { + final isRtl = Directionality.of(context) == TextDirection.rtl; + return Container( + height: (widget.preferredSize.height - _fontSize / 2).h, + alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), + padding: EdgeInsets.fromLTRB(24, 0, 24, 0), + child: Row( + spacing: 4.h, + children: [ + Text( + widget.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: _fontSize, + fontWeight: FontWeight.bold, + ), + ).expanded, + ...[ + if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), + if (widget.trailing != null) widget.trailing!, + ] + ], + ), + ); + } + Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { return AnimatedSize( duration: Duration(milliseconds: 150), child: Container( - height: 40.h, + height: 36.h + (4.h * t), padding: EdgeInsets.all(8.w), - margin: EdgeInsets.only(right: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.secondaryLightRedColor, borderRadius: 10.r, @@ -176,7 +294,7 @@ class CollapsingListView extends StatelessWidget { spacing: 8.h, children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), - if (t == 1) + if (t >= .5) Text( title, style: context.dynamicTextStyle( @@ -184,7 +302,7 @@ class CollapsingListView extends StatelessWidget { letterSpacing: -0.4, fontSize: (12 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( - FontWeight.w300, + FontWeight.w400, FontWeight.w500, t, )!,