Initial (redacted) commit.
This commit is contained in:
commit
655f8a036a
368 changed files with 20949 additions and 0 deletions
478
app/lib/pages/hydration_graph_widget.dart
Normal file
478
app/lib/pages/hydration_graph_widget.dart
Normal file
|
@ -0,0 +1,478 @@
|
|||
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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue