diff --git a/lib/features/water_monitor/water_monitor_repo.dart b/lib/features/water_monitor/water_monitor_repo.dart index 73c2ea2..14a74a7 100644 --- a/lib/features/water_monitor/water_monitor_repo.dart +++ b/lib/features/water_monitor/water_monitor_repo.dart @@ -127,9 +127,17 @@ class WaterMonitorRepoImp implements WaterMonitorRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - // Try a few likely nested keys and fall back to the full response + // Extract progress data and history data dynamic extracted; + dynamic historyData; + if (response is Map) { + // Extract history data (available for all progress types) + if (response.containsKey('UserProgressHistoryData')) { + historyData = response['UserProgressHistoryData']; + } + + // Extract progress data based on type switch (progressType) { case ProgressType.today: if (response.containsKey('UserProgressForTodayData')) { @@ -162,11 +170,17 @@ class WaterMonitorRepoImp implements WaterMonitorRepo { extracted = response; } + // Package both progress data and history data + final combinedData = { + 'progressData': extracted, + 'historyData': historyData, + }; + apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, errorMessage: errorMessage, - data: extracted, + data: combinedData, ); } catch (e) { failure = DataParsingFailure(e.toString()); diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index c079b5e..2e3dbc3 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -152,6 +152,20 @@ class WaterMonitorViewModel extends ChangeNotifier { } } + /// Map selected duration to ProgressType enum + int _getProgressIdFromDuration() { + switch (_selectedDuration) { + case 'Daily': + return 1; + case 'Weekly': + return 2; + case 'Monthly': + return 3; + default: + return 1; + } + } + /// Fetch user progress data based on selected duration Future fetchUserProgressForMonitoring() async { try { @@ -182,11 +196,34 @@ class WaterMonitorViewModel extends ChangeNotifier { // Parse the response based on progress type try { - if (apiModel.data != null && apiModel.data is List) { + // Extract progressData and historyData from combined response + dynamic progressData; + dynamic historyData; + + if (apiModel.data is Map && apiModel.data.containsKey('progressData')) { + progressData = apiModel.data['progressData']; + historyData = apiModel.data['historyData']; + } else { + // Fallback to old structure + progressData = apiModel.data; + } + + // Parse history data (available for all progress types, especially for daily) + if (historyData != null && historyData is List) { + _historyList.clear(); + for (var item in historyData) { + if (item is Map) { + _historyList.add(UserProgressHistoryModel.fromJson(item as Map)); + } + } + log('History data parsed: ${_historyList.length} items'); + } + + if (progressData != null && progressData is List) { switch (progressType) { case ProgressType.today: _todayProgressList.clear(); - for (var item in apiModel.data) { + for (var item in progressData) { if (item is Map) { _todayProgressList.add(UserProgressForTodayModel.fromJson(item as Map)); } @@ -209,7 +246,7 @@ class WaterMonitorViewModel extends ChangeNotifier { case ProgressType.week: _weekProgressList.clear(); - for (var item in apiModel.data) { + for (var item in progressData) { if (item is Map) { _weekProgressList.add(UserProgressForWeekModel.fromJson(item as Map)); } @@ -219,7 +256,7 @@ class WaterMonitorViewModel extends ChangeNotifier { case ProgressType.month: _monthProgressList.clear(); - for (var item in apiModel.data) { + for (var item in progressData) { if (item is Map) { _monthProgressList.add(UserProgressForMonthModel.fromJson(item as Map)); } @@ -1036,7 +1073,7 @@ class WaterMonitorViewModel extends ChangeNotifier { // Create request model final requestModel = UndoUserActivityRequestModel( - progress: 1, + progress: _getProgressIdFromDuration(), mobileNumber: mobile, identificationNo: identification, ); @@ -1076,7 +1113,7 @@ class WaterMonitorViewModel extends ChangeNotifier { } } } - + fetchUserProgressForMonitoring(); _isLoading = false; notifyListeners(); return true; diff --git a/lib/presentation/water_monitor/water_consumption_screen.dart b/lib/presentation/water_monitor/water_consumption_screen.dart index a7a5e60..e91abf6 100644 --- a/lib/presentation/water_monitor/water_consumption_screen.dart +++ b/lib/presentation/water_monitor/water_consumption_screen.dart @@ -133,7 +133,7 @@ class _WaterConsumptionScreenState extends State { ], ), SizedBox(height: 12.h), - if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else _buildHistoryFlowchart() + if (!viewModel.isGraphView) _buildHistoryListView(viewModel) else _buildHistoryGraph() ], ); }), @@ -162,6 +162,37 @@ class _WaterConsumptionScreenState extends State { subTitle: "${todayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%", ), ); + + // Add history data if available (show ALL entries) + if (viewModel.historyList.isNotEmpty) { + listItems.add(Divider(height: 1, color: AppColors.dividerColor)); + listItems.add( + Padding( + padding: EdgeInsets.symmetric(vertical: 8.h), + child: "Water Intake History".toText14( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + ), + ); + + // Show all history entries + for (var history in viewModel.historyList) { + final quantity = "${history.quantity?.toStringAsFixed(0) ?? '0'} ml"; + final time = _formatHistoryDate(history.createdDate ?? ''); + + listItems.add( + buildHistoryListTile( + title: quantity, + subTitle: time, + ), + ); + + if (history != viewModel.historyList.last) { + listItems.add(Divider(height: 1, color: AppColors.dividerColor)); + } + } + } } else { listItems.add( Center( @@ -174,10 +205,12 @@ class _WaterConsumptionScreenState extends State { } } else if (selectedDuration == 'Weekly') { if (viewModel.weekProgressList.isNotEmpty) { - // Show today + last 6 days (total 7 days) + // Show previous 6 days + today (total 7 days) + // API returns data in reverse order (today first), so we reverse it to show oldest to newest (top to bottom) + // This ensures today appears at the end (bottom) final totalDays = viewModel.weekProgressList.length; final startIndex = totalDays > 7 ? totalDays - 7 : 0; - final weekDataToShow = viewModel.weekProgressList.skip(startIndex).toList(); + final weekDataToShow = viewModel.weekProgressList.skip(startIndex).toList().reversed.toList(); for (var dayData in weekDataToShow) { listItems.add( @@ -202,11 +235,11 @@ class _WaterConsumptionScreenState extends State { } } else if (selectedDuration == 'Monthly') { if (viewModel.monthProgressList.isNotEmpty) { - // Show current month + last 6 months (total 7 months) - // Reverse order to show oldest to newest (top to bottom) + // Show last 6 months + current month (total 7 months) + // Show in chronological order: oldest to newest (top to bottom) final totalMonths = viewModel.monthProgressList.length; final startIndex = totalMonths > 7 ? totalMonths - 7 : 0; - final monthDataToShow = viewModel.monthProgressList.skip(startIndex).toList().reversed.toList(); + final monthDataToShow = viewModel.monthProgressList.skip(startIndex).toList(); for (var monthData in monthDataToShow) { listItems.add( @@ -250,7 +283,7 @@ class _WaterConsumptionScreenState extends State { ); } - Widget _buildHistoryFlowchart() { + Widget _buildHistoryGraph() { return Consumer( builder: (context, viewModel, _) { final selectedDuration = viewModel.selectedDurationFilter; @@ -259,8 +292,40 @@ class _WaterConsumptionScreenState extends State { List dataPoints = []; if (selectedDuration == 'Daily') { - // For daily, we show a single bar/point with today's percentage - if (viewModel.todayProgressList.isNotEmpty) { + // For daily, show last 7 history entries with at least 5 minutes difference + if (viewModel.historyList.isNotEmpty) { + // Filter entries with at least 5 minutes difference + List filteredPoints = []; + DateTime? lastTime; + + for (var historyItem in viewModel.historyList) { + final currentTime = _parseHistoryDate(historyItem.createdDate ?? ''); + + // Add if first entry OR if more than 5 minutes difference from last added entry + if (lastTime == null || currentTime.difference(lastTime).inMinutes.abs() >= 5) { + final quantity = historyItem.quantity?.toDouble() ?? 0.0; + final time = _formatHistoryDate(historyItem.createdDate ?? ''); + + filteredPoints.add( + DataPoint( + value: quantity, + actualValue: quantity.toStringAsFixed(0), + label: time, + displayTime: time, + unitOfMeasurement: 'ml', + time: currentTime, + ), + ); + lastTime = currentTime; + } + } + + // Take only last 7 filtered entries + final totalFiltered = filteredPoints.length; + final startIndex = totalFiltered > 7 ? totalFiltered - 7 : 0; + dataPoints = filteredPoints.skip(startIndex).toList(); + } else if (viewModel.todayProgressList.isNotEmpty) { + // Fallback: show today's percentage if no history final todayData = viewModel.todayProgressList.first; final percentage = todayData.percentageConsumed?.toDouble() ?? 0.0; dataPoints.add( @@ -275,11 +340,13 @@ class _WaterConsumptionScreenState extends State { ); } } else if (selectedDuration == 'Weekly') { - // For weekly, show today + last 6 days (total 7 days) + // For weekly, show previous 6 days + today (total 7 days) + // API returns data in reverse order (today first), so we reverse it to show oldest to newest (left to right) + // This ensures today appears at the end (right side) if (viewModel.weekProgressList.isNotEmpty) { final totalDays = viewModel.weekProgressList.length; final startIndex = totalDays > 7 ? totalDays - 7 : 0; - final weekDataToShow = viewModel.weekProgressList.skip(startIndex).toList(); + final weekDataToShow = viewModel.weekProgressList.skip(startIndex).toList().reversed.toList(); for (var dayData in weekDataToShow) { final percentage = dayData.percentageConsumed?.toDouble() ?? 0.0; @@ -297,12 +364,12 @@ class _WaterConsumptionScreenState extends State { } } } else if (selectedDuration == 'Monthly') { - // For monthly, show current month + last 6 months (total 7 months) - // Reverse order to show oldest to newest (left to right) + // For monthly, show last 6 months + current month (total 7 months) + // Show in chronological order: oldest to newest (left to right) if (viewModel.monthProgressList.isNotEmpty) { final totalMonths = viewModel.monthProgressList.length; final startIndex = totalMonths > 7 ? totalMonths - 7 : 0; - final monthDataToShow = viewModel.monthProgressList.skip(startIndex).toList().reversed.toList(); + final monthDataToShow = viewModel.monthProgressList.skip(startIndex).toList(); for (var monthData in monthDataToShow) { final percentage = monthData.percentageConsumed?.toDouble() ?? 0.0; @@ -329,9 +396,14 @@ class _WaterConsumptionScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.bar_chart, size: 48.w, color: AppColors.greyTextColor.withValues(alpha: 0.5)), + Utils.buildSvgWithAssets( + icon: AppAssets.graphIcon, + iconColor: AppColors.greyTextColor.withValues(alpha: 0.5), + height: 32.w, + width: 32.w, + ), SizedBox(height: 12.h), - "No chart data available".toText14(color: AppColors.greyTextColor), + "No graph data available".toText14(color: AppColors.greyTextColor), ], ), ), @@ -346,43 +418,85 @@ class _WaterConsumptionScreenState extends State { ); } - // Define ranges for percentage (0-100%) - const double low = 25.0; // Below 25% is low - const double medium = 50.0; // 25-50% is medium - const double good = 75.0; // 50-75% is good - const double maxY = 100.0; // Max is 100% + // Configure graph based on selected duration + double maxY; + double minY; + double horizontalInterval; + double leftLabelInterval; + + if (selectedDuration == 'Daily') { + // For daily (quantity in ml), use max available cup size + // Get the biggest cup from available cups + final maxCupSize = viewModel.cups.isEmpty ? 500.0 : viewModel.cups.map((cup) => cup.capacityMl.toDouble()).reduce((a, b) => a > b ? a : b); + + maxY = maxCupSize; + minY = 0; + // Divide into 4 intervals (5 labels: 0, 1/4, 1/2, 3/4, max) + horizontalInterval = maxY / 4; + leftLabelInterval = maxY / 4; + } else { + // For weekly/monthly (percentage), use 0-100% + maxY = 100.0; + minY = 0; + horizontalInterval = 25; + leftLabelInterval = 25; + } return CustomGraph( + bottomLabelReservedSize: 30, dataPoints: dataPoints, makeGraphBasedOnActualValue: true, - leftLabelReservedSize: 50.w, - leftLabelInterval: 25, + leftLabelReservedSize: 50.h, showGridLines: true, maxY: maxY, - minY: 0, + minY: minY, maxX: dataPoints.length > 1 ? dataPoints.length.toDouble() - 0.75 : 1.0, - minX: -0.2, - horizontalInterval: 25, - // Grid lines every 25% + horizontalInterval: horizontalInterval, + leftLabelInterval: leftLabelInterval, showShadow: true, getDrawingHorizontalLine: (value) { - // Draw dashed lines at 25%, 50%, 75% - if (value == low || value == medium || value == good) { - return FlLine( - color: AppColors.greyTextColor.withValues(alpha: 0.3), - strokeWidth: 1.5, - dashArray: [8, 4], - ); + // Draw dashed lines at intervals + if (selectedDuration == 'Daily') { + // For daily, draw lines every 50 or 100 ml + if (value % horizontalInterval == 0 && value > 0) { + return FlLine( + color: AppColors.greyTextColor.withValues(alpha: 0.3), + strokeWidth: 1.5, + dashArray: [8, 4], + ); + } + } else { + // For weekly/monthly, draw lines at 25%, 50%, 75% + if (value == 25 || value == 50 || value == 75) { + return FlLine( + color: AppColors.successColor.withValues(alpha: 0.3), + strokeWidth: 1.5, + dashArray: [8, 4], + ); + } } return FlLine(color: AppColors.transparent, strokeWidth: 0); }, leftLabelFormatter: (value) { - // Show percentage labels at key points - if (value == 0) return '0%'.toText10(weight: FontWeight.w600); - if (value == 25) return '25%'.toText10(weight: FontWeight.w600); - if (value == 50) return '50%'.toText10(weight: FontWeight.w600); - if (value == 75) return '75%'.toText10(weight: FontWeight.w600); - if (value == 100) return '100%'.toText10(weight: FontWeight.w600); + if (selectedDuration == 'Daily') { + // Show exactly 5 labels: 0, 1/4, 1/2, 3/4, max + // Check if value matches one of the 5 positions + final interval = maxY / 4; + final positions = [0.0, interval, interval * 2, interval * 3, maxY]; + + for (var position in positions) { + if ((value - position).abs() < 1) { + return '${value.toInt()}ml'.toText10(weight: FontWeight.w600); + } + } + } else { + // Show percentage labels + if (value == 0) return '0%'.toText10(weight: FontWeight.w600); + if (value == 25) return '25%'.toText10(weight: FontWeight.w600); + if (value == 50) return '50%'.toText10(weight: FontWeight.w600); + if (value == 75) return '75%'.toText10(weight: FontWeight.w600); + if (value == 100) return '100%'.toText10(weight: FontWeight.w600); + } return SizedBox.shrink(); }, graphColor: AppColors.successColor, @@ -398,12 +512,12 @@ class _WaterConsumptionScreenState extends State { int index = value.round(); if (index < 0 || index >= data.length) return SizedBox.shrink(); - // For daily, show only index 0 - if (selectedDuration == 'Daily' && index == 0) { + // For daily, show all 7 time labels (last 7 entries) + if (selectedDuration == 'Daily' && index < 7) { return Padding( - padding: EdgeInsets.only(top: 8.h), - child: data[index].label.toText10( - weight: FontWeight.w600, + padding: EdgeInsets.only(top: 5.h), + child: data[index].label.toText8( + fontWeight: FontWeight.w600, color: AppColors.labelTextColor, ), ); @@ -412,7 +526,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: 8.h), + padding: EdgeInsets.only(top: 5.h), child: data[index].label.toText10( weight: FontWeight.w600, color: AppColors.labelTextColor, @@ -423,7 +537,7 @@ 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: 8.h), + padding: EdgeInsets.only(top: 5.h), child: data[index].label.toText10( weight: FontWeight.w600, color: AppColors.labelTextColor, @@ -626,6 +740,42 @@ class _WaterConsumptionScreenState extends State { return '${hour12.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')} $period'; } + /// Format history date from /Date(milliseconds+0300)/ format + String _formatHistoryDate(String dateString) { + try { + // Parse the /Date(milliseconds+0300)/ format + final regex = RegExp(r'\/Date\((\d+)'); + final match = regex.firstMatch(dateString); + if (match != null) { + final milliseconds = int.tryParse(match.group(1)!); + if (milliseconds != null) { + final dateTime = DateTime.fromMillisecondsSinceEpoch(milliseconds); + return _formatTime(dateTime); + } + } + } catch (e) { + return dateString; + } + return dateString; + } + + /// Parse history date from /Date(milliseconds+0300)/ format to DateTime + DateTime _parseHistoryDate(String dateString) { + try { + final regex = RegExp(r'\/Date\((\d+)'); + final match = regex.firstMatch(dateString); + if (match != null) { + final milliseconds = int.tryParse(match.group(1)!); + if (milliseconds != null) { + return DateTime.fromMillisecondsSinceEpoch(milliseconds); + } + } + } catch (e) { + // Return current time as fallback + } + return DateTime.now(); + } + @override Widget build(BuildContext context) { return Scaffold( 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 510926c..31f8fdb 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -30,8 +30,8 @@ class WaterActionButtonsWidget extends StatelessWidget { }, child: Utils.buildSvgWithAssets( icon: AppAssets.minimizeIcon, - height: 20.w, - width: 20.w, + height: 20.h, + width: 20.h, iconColor: AppColors.textColor, ), ), @@ -56,6 +56,8 @@ class WaterActionButtonsWidget extends StatelessWidget { }, child: Utils.buildSvgWithAssets( icon: AppAssets.addIconDark, + height: 20.h, + width: 20.h, ), ), ], @@ -96,12 +98,8 @@ class WaterActionButtonsWidget extends StatelessWidget { ); } }, - title: "Test Alert".needTranslation, - icon: Icon( - Icons.notifications_outlined, - color: AppColors.blueColor, - size: 24.w, - ), + title: "Plain Water".needTranslation, + icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w), ), _buildActionButton( context: context,