Completed Graph

pull/135/head
faizatflutter 2 weeks ago
parent d4dd475e0b
commit 0e2de5a55d

@ -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<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: extracted,
data: combinedData,
);
} catch (e) {
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
Future<void> 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<String, dynamic>));
}
}
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<String, dynamic>));
}
@ -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<String, dynamic>));
}
@ -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<String, dynamic>));
}
@ -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;

@ -133,7 +133,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
],
),
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'}%",
),
);
// 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<WaterConsumptionScreen> {
}
} 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<WaterConsumptionScreen> {
}
} 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<WaterConsumptionScreen> {
);
}
Widget _buildHistoryFlowchart() {
Widget _buildHistoryGraph() {
return Consumer<WaterMonitorViewModel>(
builder: (context, viewModel, _) {
final selectedDuration = viewModel.selectedDurationFilter;
@ -259,8 +292,40 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
List<DataPoint> 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<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 percentage = todayData.percentageConsumed?.toDouble() ?? 0.0;
dataPoints.add(
@ -275,11 +340,13 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
);
}
} 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<WaterConsumptionScreen> {
}
}
} 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<WaterConsumptionScreen> {
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<WaterConsumptionScreen> {
);
}
// 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<WaterConsumptionScreen> {
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<WaterConsumptionScreen> {
// 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<WaterConsumptionScreen> {
// 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<WaterConsumptionScreen> {
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(

@ -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,

Loading…
Cancel
Save