Completed Graph

pull/135/head
faizatflutter 4 months ago
parent d4dd475e0b
commit 0e2de5a55d

@ -127,9 +127,17 @@ class WaterMonitorRepoImp implements WaterMonitorRepo {
}, },
onSuccess: (response, statusCode, {messageStatus, errorMessage}) { onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try { try {
// Try a few likely nested keys and fall back to the full response // Extract progress data and history data
dynamic extracted; dynamic extracted;
dynamic historyData;
if (response is Map) { 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) { switch (progressType) {
case ProgressType.today: case ProgressType.today:
if (response.containsKey('UserProgressForTodayData')) { if (response.containsKey('UserProgressForTodayData')) {
@ -162,11 +170,17 @@ class WaterMonitorRepoImp implements WaterMonitorRepo {
extracted = response; extracted = response;
} }
// Package both progress data and history data
final combinedData = {
'progressData': extracted,
'historyData': historyData,
};
apiResponse = GenericApiModel<dynamic>( apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus, messageStatus: messageStatus,
statusCode: statusCode, statusCode: statusCode,
errorMessage: errorMessage, errorMessage: errorMessage,
data: extracted, data: combinedData,
); );
} catch (e) { } catch (e) {
failure = DataParsingFailure(e.toString()); failure = DataParsingFailure(e.toString());

@ -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 /// Fetch user progress data based on selected duration
Future<void> fetchUserProgressForMonitoring() async { Future<void> fetchUserProgressForMonitoring() async {
try { try {
@ -182,11 +196,34 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Parse the response based on progress type // Parse the response based on progress type
try { 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<String, dynamic>));
}
}
log('History data parsed: ${_historyList.length} items');
}
if (progressData != null && progressData is List) {
switch (progressType) { switch (progressType) {
case ProgressType.today: case ProgressType.today:
_todayProgressList.clear(); _todayProgressList.clear();
for (var item in apiModel.data) { for (var item in progressData) {
if (item is Map) { if (item is Map) {
_todayProgressList.add(UserProgressForTodayModel.fromJson(item as Map<String, dynamic>)); _todayProgressList.add(UserProgressForTodayModel.fromJson(item as Map<String, dynamic>));
} }
@ -209,7 +246,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
case ProgressType.week: case ProgressType.week:
_weekProgressList.clear(); _weekProgressList.clear();
for (var item in apiModel.data) { for (var item in progressData) {
if (item is Map) { if (item is Map) {
_weekProgressList.add(UserProgressForWeekModel.fromJson(item as Map<String, dynamic>)); _weekProgressList.add(UserProgressForWeekModel.fromJson(item as Map<String, dynamic>));
} }
@ -219,7 +256,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
case ProgressType.month: case ProgressType.month:
_monthProgressList.clear(); _monthProgressList.clear();
for (var item in apiModel.data) { for (var item in progressData) {
if (item is Map) { if (item is Map) {
_monthProgressList.add(UserProgressForMonthModel.fromJson(item as Map<String, dynamic>)); _monthProgressList.add(UserProgressForMonthModel.fromJson(item as Map<String, dynamic>));
} }
@ -1036,7 +1073,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Create request model // Create request model
final requestModel = UndoUserActivityRequestModel( final requestModel = UndoUserActivityRequestModel(
progress: 1, progress: _getProgressIdFromDuration(),
mobileNumber: mobile, mobileNumber: mobile,
identificationNo: identification, identificationNo: identification,
); );
@ -1076,7 +1113,7 @@ class WaterMonitorViewModel extends ChangeNotifier {
} }
} }
} }
fetchUserProgressForMonitoring();
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
return true; return true;

@ -133,7 +133,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
], ],
), ),
SizedBox(height: 12.h), 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<WaterConsumptionScreen> {
subTitle: "${todayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%", 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 { } else {
listItems.add( listItems.add(
Center( Center(
@ -174,10 +205,12 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
} }
} else if (selectedDuration == 'Weekly') { } else if (selectedDuration == 'Weekly') {
if (viewModel.weekProgressList.isNotEmpty) { 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 totalDays = viewModel.weekProgressList.length;
final startIndex = totalDays > 7 ? totalDays - 7 : 0; 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) { for (var dayData in weekDataToShow) {
listItems.add( listItems.add(
@ -202,11 +235,11 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
} }
} else if (selectedDuration == 'Monthly') { } else if (selectedDuration == 'Monthly') {
if (viewModel.monthProgressList.isNotEmpty) { if (viewModel.monthProgressList.isNotEmpty) {
// Show current month + last 6 months (total 7 months) // Show last 6 months + current month (total 7 months)
// Reverse order to show oldest to newest (top to bottom) // Show in chronological order: oldest to newest (top to bottom)
final totalMonths = viewModel.monthProgressList.length; final totalMonths = viewModel.monthProgressList.length;
final startIndex = totalMonths > 7 ? totalMonths - 7 : 0; 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) { for (var monthData in monthDataToShow) {
listItems.add( listItems.add(
@ -250,7 +283,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
); );
} }
Widget _buildHistoryFlowchart() { Widget _buildHistoryGraph() {
return Consumer<WaterMonitorViewModel>( return Consumer<WaterMonitorViewModel>(
builder: (context, viewModel, _) { builder: (context, viewModel, _) {
final selectedDuration = viewModel.selectedDurationFilter; final selectedDuration = viewModel.selectedDurationFilter;
@ -259,8 +292,40 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
List<DataPoint> dataPoints = []; List<DataPoint> dataPoints = [];
if (selectedDuration == 'Daily') { if (selectedDuration == 'Daily') {
// For daily, we show a single bar/point with today's percentage // For daily, show last 7 history entries with at least 5 minutes difference
if (viewModel.todayProgressList.isNotEmpty) { if (viewModel.historyList.isNotEmpty) {
// Filter entries with at least 5 minutes difference
List<DataPoint> 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 todayData = viewModel.todayProgressList.first;
final percentage = todayData.percentageConsumed?.toDouble() ?? 0.0; final percentage = todayData.percentageConsumed?.toDouble() ?? 0.0;
dataPoints.add( dataPoints.add(
@ -275,11 +340,13 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
); );
} }
} else if (selectedDuration == 'Weekly') { } 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) { if (viewModel.weekProgressList.isNotEmpty) {
final totalDays = viewModel.weekProgressList.length; final totalDays = viewModel.weekProgressList.length;
final startIndex = totalDays > 7 ? totalDays - 7 : 0; 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) { for (var dayData in weekDataToShow) {
final percentage = dayData.percentageConsumed?.toDouble() ?? 0.0; final percentage = dayData.percentageConsumed?.toDouble() ?? 0.0;
@ -297,12 +364,12 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
} }
} }
} else if (selectedDuration == 'Monthly') { } else if (selectedDuration == 'Monthly') {
// For monthly, show current month + last 6 months (total 7 months) // For monthly, show last 6 months + current month (total 7 months)
// Reverse order to show oldest to newest (left to right) // Show in chronological order: oldest to newest (left to right)
if (viewModel.monthProgressList.isNotEmpty) { if (viewModel.monthProgressList.isNotEmpty) {
final totalMonths = viewModel.monthProgressList.length; final totalMonths = viewModel.monthProgressList.length;
final startIndex = totalMonths > 7 ? totalMonths - 7 : 0; 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) { for (var monthData in monthDataToShow) {
final percentage = monthData.percentageConsumed?.toDouble() ?? 0.0; final percentage = monthData.percentageConsumed?.toDouble() ?? 0.0;
@ -329,9 +396,14 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ 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), 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<WaterConsumptionScreen> {
); );
} }
// Define ranges for percentage (0-100%) // Configure graph based on selected duration
const double low = 25.0; // Below 25% is low double maxY;
const double medium = 50.0; // 25-50% is medium double minY;
const double good = 75.0; // 50-75% is good double horizontalInterval;
const double maxY = 100.0; // Max is 100% 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( return CustomGraph(
bottomLabelReservedSize: 30,
dataPoints: dataPoints, dataPoints: dataPoints,
makeGraphBasedOnActualValue: true, makeGraphBasedOnActualValue: true,
leftLabelReservedSize: 50.w, leftLabelReservedSize: 50.h,
leftLabelInterval: 25,
showGridLines: true, showGridLines: true,
maxY: maxY, maxY: maxY,
minY: 0, minY: minY,
maxX: dataPoints.length > 1 ? dataPoints.length.toDouble() - 0.75 : 1.0, maxX: dataPoints.length > 1 ? dataPoints.length.toDouble() - 0.75 : 1.0,
minX: -0.2, horizontalInterval: horizontalInterval,
horizontalInterval: 25, leftLabelInterval: leftLabelInterval,
// Grid lines every 25%
showShadow: true, showShadow: true,
getDrawingHorizontalLine: (value) { getDrawingHorizontalLine: (value) {
// Draw dashed lines at 25%, 50%, 75% // Draw dashed lines at intervals
if (value == low || value == medium || value == good) { if (selectedDuration == 'Daily') {
return FlLine( // For daily, draw lines every 50 or 100 ml
color: AppColors.greyTextColor.withValues(alpha: 0.3), if (value % horizontalInterval == 0 && value > 0) {
strokeWidth: 1.5, return FlLine(
dashArray: [8, 4], 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); return FlLine(color: AppColors.transparent, strokeWidth: 0);
}, },
leftLabelFormatter: (value) { leftLabelFormatter: (value) {
// Show percentage labels at key points if (selectedDuration == 'Daily') {
if (value == 0) return '0%'.toText10(weight: FontWeight.w600); // Show exactly 5 labels: 0, 1/4, 1/2, 3/4, max
if (value == 25) return '25%'.toText10(weight: FontWeight.w600); // Check if value matches one of the 5 positions
if (value == 50) return '50%'.toText10(weight: FontWeight.w600); final interval = maxY / 4;
if (value == 75) return '75%'.toText10(weight: FontWeight.w600); final positions = [0.0, interval, interval * 2, interval * 3, maxY];
if (value == 100) return '100%'.toText10(weight: FontWeight.w600);
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(); return SizedBox.shrink();
}, },
graphColor: AppColors.successColor, graphColor: AppColors.successColor,
@ -398,12 +512,12 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
int index = value.round(); int index = value.round();
if (index < 0 || index >= data.length) return SizedBox.shrink(); if (index < 0 || index >= data.length) return SizedBox.shrink();
// For daily, show only index 0 // For daily, show all 7 time labels (last 7 entries)
if (selectedDuration == 'Daily' && index == 0) { if (selectedDuration == 'Daily' && index < 7) {
return Padding( return Padding(
padding: EdgeInsets.only(top: 8.h), padding: EdgeInsets.only(top: 5.h),
child: data[index].label.toText10( child: data[index].label.toText8(
weight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.labelTextColor, color: AppColors.labelTextColor,
), ),
); );
@ -412,7 +526,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
// For weekly, show all 7 days (today + last 6 days) // For weekly, show all 7 days (today + last 6 days)
if (selectedDuration == 'Weekly' && index < 7) { if (selectedDuration == 'Weekly' && index < 7) {
return Padding( return Padding(
padding: EdgeInsets.only(top: 8.h), padding: EdgeInsets.only(top: 5.h),
child: data[index].label.toText10( child: data[index].label.toText10(
weight: FontWeight.w600, weight: FontWeight.w600,
color: AppColors.labelTextColor, color: AppColors.labelTextColor,
@ -423,7 +537,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
// For monthly, show all 7 months (current month + last 6 months) // For monthly, show all 7 months (current month + last 6 months)
if (selectedDuration == 'Monthly' && index < 7) { if (selectedDuration == 'Monthly' && index < 7) {
return Padding( return Padding(
padding: EdgeInsets.only(top: 8.h), padding: EdgeInsets.only(top: 5.h),
child: data[index].label.toText10( child: data[index].label.toText10(
weight: FontWeight.w600, weight: FontWeight.w600,
color: AppColors.labelTextColor, color: AppColors.labelTextColor,
@ -626,6 +740,42 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
return '${hour12.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')} $period'; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(

@ -30,8 +30,8 @@ class WaterActionButtonsWidget extends StatelessWidget {
}, },
child: Utils.buildSvgWithAssets( child: Utils.buildSvgWithAssets(
icon: AppAssets.minimizeIcon, icon: AppAssets.minimizeIcon,
height: 20.w, height: 20.h,
width: 20.w, width: 20.h,
iconColor: AppColors.textColor, iconColor: AppColors.textColor,
), ),
), ),
@ -56,6 +56,8 @@ class WaterActionButtonsWidget extends StatelessWidget {
}, },
child: Utils.buildSvgWithAssets( child: Utils.buildSvgWithAssets(
icon: AppAssets.addIconDark, icon: AppAssets.addIconDark,
height: 20.h,
width: 20.h,
), ),
), ),
], ],
@ -96,12 +98,8 @@ class WaterActionButtonsWidget extends StatelessWidget {
); );
} }
}, },
title: "Test Alert".needTranslation, title: "Plain Water".needTranslation,
icon: Icon( icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w),
Icons.notifications_outlined,
color: AppColors.blueColor,
size: 24.w,
),
), ),
_buildActionButton( _buildActionButton(
context: context, context: context,

Loading…
Cancel
Save