habitrack/app/lib/pages/hydration_graph_widget.dart

479 lines
16 KiB
Dart
Raw Normal View History

2024-08-26 00:34:20 +02:00
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/hydration.dart';
import 'package:intl/intl.dart';
class DataPoint {
DataPoint({required this.date, required this.progress});
DateTime date;
double progress;
}
class HydrationGraphWidget extends ConsumerStatefulWidget {
const HydrationGraphWidget({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_TasksGraphWidgetState();
}
class _TasksGraphWidgetState extends ConsumerState<HydrationGraphWidget> {
void _buildList(List<dynamic> value) {
final items = value;
_thisWeekCompleted = 0;
_thisWeekPlanned = 0;
_maxAmount = 0;
setState(() {
_todayCompleted = 0;
_todayPlanned = 0;
_weeklyPlannedEntries = [];
_weeklyWorkedEntries = [];
for (var i = 0; i <= 6; i++) {
final itemToInsert = DataPoint(
date: _latestDate.subtract(Duration(days: 6 - i)),
progress: 0,
);
_weeklyPlannedEntries.add(itemToInsert);
}
for (var i = 0; i <= 6; i++) {
final itemToInsert = DataPoint(
date: _latestDate.subtract(Duration(days: 6 - i)),
progress: 0,
);
_weeklyWorkedEntries.add(itemToInsert);
}
});
_thisWeekPlanned = 0;
for (final item in items) {
if (_selectedValue == 'weekly') {
var alreadyAdded = false;
for (var i = 0; i <= 6; i++) {
DateTime parsedDate;
if (item is Hydration) {
if (item.current / item.goal >= 1 && item.completedOn != '') {
//item is completed
parsedDate = DateTime.parse(item.completedOn);
} else {
parsedDate = DateTime.parse(item.createdOn);
logger.i('GOAL: $item.goal');
}
logger
.i('BEFORE $i days ago and AFTER ${i + 1} days ago, element at '
'index ${6 - i} will be updated.');
if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) &&
parsedDate
.isAfter(_latestDate.subtract(Duration(days: i + 1)))) {
logger.i('LOOPING');
if (!alreadyAdded) {
alreadyAdded = true;
_thisWeekCompleted += item.current / 1000;
_thisWeekPlanned += item.goal / 1000;
}
if (item.goal > _maxAmount) {
_maxAmount = (item.goal.toDouble() / 1000).ceilToDouble();
}
if (item.current > _maxAmount && item.current > item.goal) {
_maxAmount = (item.current.toDouble() / 1000).ceilToDouble();
}
// Update maxAmount
setState(() {
_weeklyPlannedEntries.elementAt(6 - i).progress =
item.goal.toDouble() / 1000;
_weeklyWorkedEntries.elementAt(6 - i).progress =
item.current.toDouble() / 1000;
});
}
}
}
} else if (_selectedValue == 'daily' && item is Hydration) {
logger.i('DATES');
final parsedDate = DateTime.parse(item.createdOn);
logger.i('LATEST DATE: $_latestDate');
final oneDayAgo = _latestDate.subtract(const Duration(days: 1));
if (parsedDate.isBefore(oneDayAgo)) {
logger.i('More than a day old');
} else if (parsedDate.isAfter(oneDayAgo) &&
parsedDate.isBefore(_latestDate)) {
logger.i('TOday');
_todayCompleted = item.current / 1000;
_todayPlanned = item.goal / 1000;
}
}
}
}
void _showPreviousWeek() {
setState(() {
_todayPlanned = 0;
_todayCompleted = 0;
_thisWeekPlanned = 0;
_thisWeekCompleted = 0;
});
if (_selectedValue == 'weekly') {
_latestDate = _latestDate.subtract(const Duration(days: 7));
} else if (_selectedValue == 'daily') {
logger.i('HMMM');
_latestDate = _latestDate.subtract(const Duration(days: 1));
}
final items = ref.watch(itemsProvider);
switch (items) {
case AsyncError(:final error):
logger.i('Error: $error');
case AsyncData(:final value):
final allItems = value;
_buildList(allItems);
default:
logger.i('Hmmm, how can we help?');
} // get current date
// show past 7 days starting 14 days ago
}
void _showNextWeek() {
if (_selectedValue == 'weekly') {
_latestDate = _latestDate.add(const Duration(days: 7));
} else if (_selectedValue == 'daily') {
logger.i('HMMM');
_latestDate = _latestDate.add(const Duration(days: 1));
}
if (!_latestDate.isAfter(DateTime.now())) {
final items = ref.watch(itemsProvider);
switch (items) {
case AsyncError(:final error):
logger.i('Error: $error');
case AsyncData(:final value):
final allItems = value;
_buildList(allItems);
default:
logger.i('Hmmm, how can we help?');
} // get current date
}
// show past 7 days starting 14 days ago
}
String _getText() {
final dateFormat = DateFormat('dd. MMM yyyy');
if (_selectedValue == 'weekly' &&
_latestDate.isAfter(DateTime.now().subtract(const Duration(days: 6)))) {
return ' this past week';
} else if (_selectedValue == 'weekly' &&
!_latestDate
.isAfter(DateTime.now().subtract(const Duration(days: 6)))) {
return ' '
'from'
' ${dateFormat.format(_latestDate.subtract(const Duration(days: 6)))}'
' to ${dateFormat.format(_latestDate)}';
}
final formattedDate = dateFormat.format(_latestDate);
// return ' on ${_latestDate.toString().substring(6, 10)}';
return ' on $formattedDate';
}
List<dynamic> thisWeekItems = [];
List<dynamic> todayItems = [];
String? _selectedValue = 'weekly';
double _thisWeekCompleted = 0;
double _thisWeekPlanned = 0;
double _maxAmount = 0;
DateTime _latestDate = DateTime.now();
double _todayCompleted = 0;
double _todayPlanned = 0;
List<DataPoint> _weeklyPlannedEntries = <DataPoint>[];
List<DataPoint> _weeklyWorkedEntries = <DataPoint>[];
@override
Widget build(BuildContext context) {
final items = ref.watch(itemsProvider);
switch (items) {
case AsyncError(:final error):
logger.i('Error: $error');
case AsyncData(:final value):
final allItems = value;
_buildList(allItems);
default:
logger.i('Hmmm, how can we help?');
}
final firstDate = _weeklyPlannedEntries.elementAtOrNull(0)!.date;
logger.i('HMMM $firstDate');
final flSpots = <FlSpot>[];
for (final dataPoint in _weeklyPlannedEntries) {
final xValue = _daysBetween(_weeklyPlannedEntries[0].date, dataPoint.date)
.toDouble();
final lcbd = FlSpot(
xValue,
dataPoint.progress,
);
flSpots.add(lcbd);
}
final weeklyWorkedSpots = <FlSpot>[];
for (final dataPoint in _weeklyWorkedEntries) {
final xValue =
_daysBetween(_weeklyWorkedEntries[0].date, dataPoint.date).toDouble();
final lcbd = FlSpot(xValue, dataPoint.progress);
weeklyWorkedSpots.add(lcbd);
}
final gradientColors = [
Colors.cyan,
Colors.blueAccent,
];
final gradient2Colors = [
Colors.amber,
Colors.amberAccent,
];
final df = DateFormat('dd. MMMM');
return Column(
children: [
Container(
height: 75,
color: Theme.of(context).colorScheme.primaryContainer,
margin: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _showPreviousWeek,
style: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
child: const Text('Previous'),
),
DropdownButton<String>(
value: _selectedValue,
borderRadius: BorderRadius.circular(10),
onChanged: (value) {
setState(() {
_latestDate = DateTime.now();
_selectedValue = value;
});
},
items: const [
DropdownMenuItem(
value: 'daily',
child: Text('Daily'),
),
DropdownMenuItem(
value: 'weekly',
child: Text('Weekly'),
),
],
),
ElevatedButton(
onPressed: _showNextWeek,
style: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
child: const Text('Next'),
),
],
),
),
if (_selectedValue == 'weekly') ...[
Container(
padding:
const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
height: MediaQuery.of(context).size.height * 0.5,
child: LineChart(
LineChartData(
minY: 0,
maxY: _maxAmount,
lineBarsData: [
LineChartBarData(
isCurved: true,
preventCurveOverShooting: true,
spots: flSpots,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradientColors
.map((color) => color.withOpacity(0.6))
.toList(),
),
),
),
LineChartBarData(
color: Colors.amber,
isCurved: true,
isStrokeCapRound: true,
preventCurveOverShooting: true,
spots: weeklyWorkedSpots,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradient2Colors
.map((color) => color.withOpacity(0.6))
.toList(),
),
),
),
],
titlesData: FlTitlesData(
rightTitles: const AxisTitles(
sideTitles: SideTitles(
reservedSize: 30,
interval: 20,
),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(
interval: 20,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) => Text(
'$value l',
style: const TextStyle(
fontSize: 10,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
// interval: 0.25,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
getTitlesWidget: (value, meta) => Text(
style: const TextStyle(
fontSize: 10,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
df.format(
DateTime(
firstDate.year,
firstDate.month,
firstDate.day,
).add(Duration(days: value.round())),
),
),
showTitles: true,
interval: 1,
),
),
),
),
),
),
),
],
if (_selectedValue == 'daily') ...[
SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
height: MediaQuery.of(context).size.height * 0.5,
child: PieChart(
PieChartData(
sectionsSpace: 0,
sections: [
PieChartSectionData(
value: _todayCompleted, // Progress
color: Theme.of(context).colorScheme.primaryFixedDim,
radius: 60,
title: (_todayPlanned > 0)
// ignore: lines_longer_than_80_chars
? '${((_todayCompleted / _todayPlanned) * 100).floorToDouble()} %'
: '0 %',
titleStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
),
),
PieChartSectionData(
titleStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primaryFixedDim,
),
value: (_todayPlanned > 0 && _todayCompleted > 0)
? (_todayPlanned - _todayCompleted)
: 1, // Total - progress
color: Theme.of(context).colorScheme.secondary,
radius: 60,
showTitle: _todayPlanned == 0 ||
(_todayPlanned > 0 && _todayCompleted == 0),
title: '0 %',
),
],
),
),
),
],
SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: Column(
children: [
Text(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 15,
fontWeight: FontWeight.bold,
),
// ignore: lines_longer_than_80_chars
'${(_selectedValue == 'weekly') ? _thisWeekCompleted : _todayCompleted} liters drunk${_getText()}',
),
Text(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 15,
fontWeight: FontWeight.bold,
),
// ignore: lines_longer_than_80_chars
'Out of a total goal of ${(_selectedValue == 'weekly') ? _thisWeekPlanned : _todayPlanned} liters',
),
],
),
),
],
);
}
int _daysBetween(DateTime from, DateTime to) {
final d1 = DateTime(from.year, from.month, from.day);
final d2 = DateTime(to.year, to.month, to.day);
return (d2.difference(d1).inHours / 24).round();
}
}