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 createState() => _TasksGraphWidgetState(); } class _TasksGraphWidgetState extends ConsumerState { void _buildList(List 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 thisWeekItems = []; List todayItems = []; String? _selectedValue = 'weekly'; double _thisWeekCompleted = 0; double _thisWeekPlanned = 0; double _maxAmount = 0; DateTime _latestDate = DateTime.now(); double _todayCompleted = 0; double _todayPlanned = 0; List _weeklyPlannedEntries = []; List _weeklyWorkedEntries = []; @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 = []; 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 = []; 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( 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(); } }