From a29c0dd6bc9780c2200cc51b8010f96784f607d0 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Thu, 1 Jan 2026 12:37:14 +0300 Subject: [PATCH] fixed the suggested design changes. (Not reminder ones) --- .../water_monitor_view_model.dart | 67 ++++++++++++++++-- .../hmg_services/services_page.dart | 70 ++++++++++++++----- .../water_consumption_screen.dart | 33 ++++++--- .../water_monitor_settings_screen.dart | 22 ++++-- .../widgets/cup_bottomsheet_widgets.dart | 1 + .../widgets/water_action_buttons_widget.dart | 51 +++++--------- .../widgets/water_intake_summary_widget.dart | 15 +++- lib/widgets/chip/app_custom_chip_widget.dart | 4 +- lib/widgets/graph/custom_graph.dart | 10 +-- 9 files changed, 190 insertions(+), 83 deletions(-) diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index 787dc05..01167fe 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class WaterMonitorViewModel extends ChangeNotifier { WaterMonitorRepo waterMonitorRepo; @@ -277,7 +278,7 @@ class WaterMonitorViewModel extends ChangeNotifier { } } - Future fetchUserDetailsForMonitoring() async { + Future fetchUserDetailsForMonitoring({Function(dynamic)? onSuccess, Function(String)? onError}) async { try { _isLoading = true; @@ -287,14 +288,21 @@ class WaterMonitorViewModel extends ChangeNotifier { if (authenticated == null) { _isLoading = false; notifyListeners(); + if (onError != null) onError('User not authenticated'); return; } final mobile = (authenticated.mobileNumber ?? '').replaceAll('+', ''); final identification = authenticated.patientIdentificationNo ?? ''; - final result = await waterMonitorRepo.getUserDetailsForWaterMonitoring(progress: 1, mobileNumber: mobile, identificationNo: identification); + final result = await waterMonitorRepo.getUserDetailsForWaterMonitoring( + progress: 1, + mobileNumber: mobile, + identificationNo: identification, + ); + result.fold((failure) { _userDetailData = null; + if (onError != null) onError(failure.message); }, (apiModel) { _userDetailData = apiModel.data; @@ -302,9 +310,12 @@ class WaterMonitorViewModel extends ChangeNotifier { if (_userDetailData != null) { _populateFormFields(_userDetailData); } + + if (onSuccess != null) onSuccess(_userDetailData); }); } catch (e) { _userDetailData = null; + if (onError != null) onError(e.toString()); } finally { _isLoading = false; notifyListeners(); @@ -424,6 +435,48 @@ class WaterMonitorViewModel extends ChangeNotifier { } } + /// Populate form fields from authenticated user data (for new users) + void populateFromAuthenticatedUser() { + try { + final authenticated = _appState.getAuthenticatedUser(); + if (authenticated == null) return; + + // Name - use firstName if available + if (authenticated.firstName != null && authenticated.firstName!.isNotEmpty) { + nameController.text = authenticated.firstName!; + } + + // Gender - map from patient gender + if (authenticated.gender != null) { + final gender = (authenticated.gender == 1 ? 'male' : 'female').toLowerCase(); + if (gender.contains('m') || gender == 'male') { + _selectedGender = 'Male'; + } else if (gender.contains('f') || gender == 'female') { + _selectedGender = 'Female'; + } + } + + // Age - calculate from DOB if available + if (authenticated.dateofBirth != null && authenticated.dateofBirth!.isNotEmpty) { + final age = _calculateAgeFromDOB(authenticated.dateofBirth!); + if (age > 0) { + ageController.text = age.toString(); + } + } + + // Set default units and activity level + _selectedHeightUnit = 'cm'; + _selectedWeightUnit = 'kg'; + _selectedActivityLevel = 'Lightly active'; + _selectedNumberOfReminders = '3 Time'; + + log('Form fields populated from authenticated user: ${authenticated.firstName}'); + notifyListeners(); + } catch (e) { + log('Error populating from authenticated user: $e'); + } + } + // Reset all fields to default void resetFields() { nameController.clear(); @@ -755,15 +808,15 @@ class WaterMonitorViewModel extends ChangeNotifier { final percent = progressPercent; if (percent >= 90) { - return const Color(0xFF00C853); // Dark Green + return AppColors.successColor; // Dark Green } else if (percent >= 70) { - return const Color(0xFF4CAF50); // Green + return AppColors.successColor; // Green } else if (percent >= 50) { - return const Color(0xFFFFC107); // Amber + return AppColors.warningColorYellow; //orange } else if (percent >= 30) { - return const Color(0xFFFF9800); // Orange + return AppColors.warningColorYellow; // Orange } else { - return const Color(0xFFF44336); // Red + return AppColors.errorColor; // Red } } diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index bf0b7f5..ee9542b 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -6,12 +6,14 @@ 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.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'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; @@ -77,29 +79,31 @@ class ServicesPage extends StatelessWidget { route: AppRoutes.eReferralPage, ), HmgServicesComponentModel( - 3, - "Blood Donation".needTranslation, - "".needTranslation, - AppAssets.blood_donation_icon, - bgColor: AppColors.bloodDonationCardColor, - true, - route: null, onTap: () async { - LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); - await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { - // await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { + 3, + "Blood Donation".needTranslation, + "".needTranslation, + AppAssets.blood_donation_icon, + bgColor: AppColors.bloodDonationCardColor, + true, + route: null, + onTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Data..."); + await bloodDonationViewModel.getRegionSelectedClinics(onSuccess: (val) async { + // await bloodDonationViewModel.getPatientBloodGroupDetails(onSuccess: (val) { LoaderBottomSheet.hideLoader(); Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( CustomPageRoute( page: BloodDonationPage(), ), ); - // }, onError: (err) { - // LoaderBottomSheet.hideLoader(); - // }); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - }); - }), + // }, onError: (err) { + // LoaderBottomSheet.hideLoader(); + // }); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + }); + }, + ), HmgServicesComponentModel( 11, "Covid 19 Test".needTranslation, @@ -143,7 +147,37 @@ class ServicesPage extends StatelessWidget { AppAssets.daily_water_monitor_icon, bgColor: AppColors.whiteColor, true, - route: AppRoutes.waterConsumptionScreen, + route: null, // Set to null since we handle navigation in onTap + onTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Fetching your water intake details.".needTranslation); + + final waterMonitorVM = getIt.get(); + final context = getIt.get().navigatorKey.currentContext!; + + // Fetch user details first to check if user has data + await waterMonitorVM.fetchUserDetailsForMonitoring( + onSuccess: (userDetail) { + LoaderBottomSheet.hideLoader(); + + // Navigate based on whether user has data or not + if (userDetail == null) { + // No data found, populate form with authenticated user data + waterMonitorVM.populateFromAuthenticatedUser(); + // Navigate to settings screen + context.navigateWithName(AppRoutes.waterMonitorSettingsScreen); + } else { + // Data exists, navigate to consumption screen + context.navigateWithName(AppRoutes.waterConsumptionScreen); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + + // On error, navigate to consumption screen (it will handle the error state) + context.navigateWithName(AppRoutes.waterConsumptionScreen); + }, + ); + }, ), HmgServicesComponentModel( 11, diff --git a/lib/presentation/water_monitor/water_consumption_screen.dart b/lib/presentation/water_monitor/water_consumption_screen.dart index e91abf6..5d08bcd 100644 --- a/lib/presentation/water_monitor/water_consumption_screen.dart +++ b/lib/presentation/water_monitor/water_consumption_screen.dart @@ -126,14 +126,27 @@ class _WaterConsumptionScreenState extends State { ), SizedBox(width: 8.w), InkWell( - onTap: () => _showHistoryDurationBottomsheet(context, viewModel), - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_calendar_icon, height: 24.h, width: 24.h)) + onTap: () => _showHistoryDurationBottomsheet(context, viewModel), + child: Container( + padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 6.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + backgroundColor: AppColors.greyColor, + borderRadius: 8.r, + hasShadow: true, + ), + child: Row( + children: [ + viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down), + ], + ), + ), + ) ], ), ], ), - SizedBox(height: 12.h), - if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else _buildHistoryGraph() + if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else ...[SizedBox(height: 16.h), _buildHistoryGraph()] ], ); }), @@ -450,6 +463,7 @@ class _WaterConsumptionScreenState extends State { showGridLines: true, maxY: maxY, minY: minY, + showLinePoints: true, maxX: dataPoints.length > 1 ? dataPoints.length.toDouble() - 0.75 : 1.0, horizontalInterval: horizontalInterval, leftLabelInterval: leftLabelInterval, @@ -515,7 +529,7 @@ class _WaterConsumptionScreenState extends State { // For daily, show all 7 time labels (last 7 entries) if (selectedDuration == 'Daily' && index < 7) { return Padding( - padding: EdgeInsets.only(top: 5.h), + padding: EdgeInsets.only(top: 10.h), child: data[index].label.toText8( fontWeight: FontWeight.w600, color: AppColors.labelTextColor, @@ -526,7 +540,7 @@ class _WaterConsumptionScreenState extends State { // For weekly, show all 7 days (today + last 6 days) if (selectedDuration == 'Weekly' && index < 7) { return Padding( - padding: EdgeInsets.only(top: 5.h), + padding: EdgeInsets.only(top: 10.h), child: data[index].label.toText10( weight: FontWeight.w600, color: AppColors.labelTextColor, @@ -537,11 +551,8 @@ class _WaterConsumptionScreenState extends State { // For monthly, show all 7 months (current month + last 6 months) if (selectedDuration == 'Monthly' && index < 7) { return Padding( - padding: EdgeInsets.only(top: 5.h), - child: data[index].label.toText10( - weight: FontWeight.w600, - color: AppColors.labelTextColor, - ), + padding: EdgeInsets.only(top: 10.h), + child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), ); } diff --git a/lib/presentation/water_monitor/water_monitor_settings_screen.dart b/lib/presentation/water_monitor/water_monitor_settings_screen.dart index 4e89d71..1b783ca 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_screen.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_screen.dart @@ -142,10 +142,10 @@ class _WaterMonitorSettingsScreenState extends State } // Reusable method to build text field - Widget _buildTextField(TextEditingController controller, String hintText) { + Widget _buildTextField(TextEditingController controller, String hintText, {TextInputType keyboardType = TextInputType.name}) { return TextField( controller: controller, - keyboardType: TextInputType.number, + keyboardType: keyboardType, maxLines: 1, cursorHeight: 14.h, textAlignVertical: TextAlignVertical.center, @@ -293,19 +293,31 @@ class _WaterMonitorSettingsScreenState extends State _buildSettingsRow( icon: AppAssets.calendarGrey, label: "Age (11-120) yrs".needTranslation, - inputField: _buildTextField(viewModel.ageController, '20'), + inputField: _buildTextField( + viewModel.ageController, + '20', + keyboardType: TextInputType.number, + ), ), _buildSettingsRow( icon: AppAssets.heightIcon, label: "Height".needTranslation, - inputField: _buildTextField(viewModel.heightController, '175'), + inputField: _buildTextField( + viewModel.heightController, + '175', + keyboardType: TextInputType.number, + ), unit: viewModel.selectedHeightUnit, onUnitTap: () => _showHeightUnitSelectionBottomSheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.weightScaleIcon, label: "Weight".needTranslation, - inputField: _buildTextField(viewModel.weightController, '75'), + inputField: _buildTextField( + viewModel.weightController, + '75', + keyboardType: TextInputType.number, + ), unit: viewModel.selectedWeightUnit, onUnitTap: () => _showWeightUnitSelectionBottomsheet(context, viewModel), ), diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 013ac7b..4ffa30d 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -313,6 +313,7 @@ class _CustomizeCupBottomSheetState extends State { viewModel.selectCup(newCup.id); Navigator.pop(context); + Navigator.pop(context); }, backgroundColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, diff --git a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart index 31f8fdb..6546f77 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -17,6 +17,8 @@ class WaterActionButtonsWidget extends StatelessWidget { Widget build(BuildContext context) { return Consumer(builder: (context, vm, _) { final cupAmount = vm.selectedCupCapacityMl; + final isGoalAchieved = vm.progressPercent >= 100 || vm.nextDrinkTime.toLowerCase().contains('goal achieved'); + return Column( children: [ Row( @@ -48,16 +50,21 @@ class WaterActionButtonsWidget extends StatelessWidget { color: AppColors.whiteColor, ), ), - InkWell( - onTap: () async { - if (cupAmount > 0) { - await vm.insertUserActivity(quantityIntake: cupAmount); - } - }, - child: Utils.buildSvgWithAssets( - icon: AppAssets.addIconDark, - height: 20.h, - width: 20.h, + Opacity( + opacity: isGoalAchieved ? 0.4 : 1.0, + child: InkWell( + onTap: isGoalAchieved + ? null + : () async { + if (cupAmount > 0) { + await vm.insertUserActivity(quantityIntake: cupAmount); + } + }, + child: Utils.buildSvgWithAssets( + icon: AppAssets.addIconDark, + height: 20.h, + width: 20.h, + ), ), ), ], @@ -75,29 +82,7 @@ class WaterActionButtonsWidget extends StatelessWidget { ), _buildActionButton( context: context, - onTap: () async { - final success = await vm.scheduleTestNotification(); - if (success) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Test notification will appear in 5 seconds!'.needTranslation), - backgroundColor: AppColors.blueColor, - behavior: SnackBarBehavior.floating, - margin: EdgeInsets.all(16.w), - duration: const Duration(seconds: 2), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Failed to schedule test notification'.needTranslation), - backgroundColor: AppColors.errorColor, - behavior: SnackBarBehavior.floating, - margin: EdgeInsets.all(16.w), - ), - ); - } - }, + onTap: () async {}, title: "Plain Water".needTranslation, icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), diff --git a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart index 094cc38..137f6a3 100644 --- a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart @@ -47,8 +47,19 @@ class WaterIntakeSummaryWidget extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Next Drink Time".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor), - vm.nextDrinkTime.toText32(weight: FontWeight.w600, color: AppColors.blueColor), + // Don't show label if goal is achieved + if (!vm.nextDrinkTime.toLowerCase().contains('goal achieved')) + // Show "Tomorrow" if nextDrinkTime contains "tomorrow", otherwise "Next Drink Time" + (vm.nextDrinkTime.toLowerCase().contains('tomorrow') ? "Tomorrow" : "Next Drink Time") + .needTranslation + .toText18(weight: FontWeight.w600, color: AppColors.textColor), + + // Extract only time if "tomorrow" is present, otherwise show as is + (vm.nextDrinkTime.toLowerCase().contains('tomorrow') + ? vm.nextDrinkTime.replaceAll(RegExp(r'tomorrow', caseSensitive: false), '').trim() + : vm.nextDrinkTime) + .toText32(weight: FontWeight.w600, color: AppColors.blueColor), + SizedBox(height: 12.h), _buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"), SizedBox(height: 8.h), diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 69d82b7..8597b3e 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -78,7 +78,7 @@ class AppCustomChipWidget extends StatelessWidget { fit: BoxFit.contain, ) : SizedBox.shrink(), - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), @@ -104,7 +104,7 @@ class AppCustomChipWidget extends StatelessWidget { ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor, isCenter: true), + label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor, isCenter: true), padding: EdgeInsets.zero, backgroundColor: backgroundColor, shape: shape ?? diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index e04d978..1f5d806 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -63,6 +63,7 @@ class CustomGraph extends StatelessWidget { final double? horizontalInterval; final double? minY; final bool showShadow; + final bool showLinePoints; final double? cutOffY; final RangeAnnotations? rangeAnnotations; @@ -104,6 +105,7 @@ class CustomGraph extends StatelessWidget { this.horizontalInterval, this.minY, this.showShadow = false, + this.showLinePoints = false, this.cutOffY = 0, this.rangeAnnotations}); @@ -195,7 +197,7 @@ class CustomGraph extends StatelessWidget { top: BorderSide.none, ), ), - lineBarsData: _buildColoredLineSegments(dataPoints), + lineBarsData: _buildColoredLineSegments(dataPoints, showLinePoints), gridData: FlGridData( show: showGridLines ?? true, drawVerticalLine: false, @@ -217,7 +219,7 @@ class CustomGraph extends StatelessWidget { ); } - List _buildColoredLineSegments(List dataPoints) { + List _buildColoredLineSegments(List dataPoints, bool showLinePoints) { final List allSpots = dataPoints.asMap().entries.map((entry) { double value = (makeGraphBasedOnActualValue) ? double.tryParse(entry.value.actualValue) ?? 0.0 : entry.value.value; return FlSpot(entry.key.toDouble(), value); @@ -235,9 +237,7 @@ class CustomGraph extends StatelessWidget { begin: Alignment.centerLeft, end: Alignment.centerRight, ), - dotData: FlDotData( - show: false, - ), + dotData: FlDotData(show: showLinePoints), belowBarData: BarAreaData( show: showShadow, applyCutOffY: cutOffY != null,