habitrack/app/lib/pages/timer_graph_widget.dart
2024-08-26 00:34:20 +02:00

487 lines
16 KiB
Dart

// ignore_for_file: cascade_invocations
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_controller.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/timer.dart';
import 'package:intl/intl.dart';
class DataPoint {
DataPoint({required this.date, required this.progress});
DateTime date;
double progress;
}
class TimerGraphWidget extends ConsumerStatefulWidget {
TimerGraphWidget({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_TimerGraphWidgetState();
final entries = <DataPoint>[
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
];
}
class _TimerGraphWidgetState extends ConsumerState<TimerGraphWidget> {
final gradientColors = [
Colors.cyan,
Colors.blueAccent,
];
final gradient2Colors = [
Colors.amber,
Colors.amberAccent,
];
String _formatTime() {
final minutesTotal =
(_selectedValue == 'weekly') ? _timePlanned : _todayGoal;
final hours = (minutesTotal / 60).floor();
final minutes = (minutesTotal - (hours * 60)).toInt();
return '$hours hours : $minutes minutes';
}
String _formatCurrent() {
final secondsTotal =
(_selectedValue == 'weekly') ? _timeCompleted : _todayCurrent;
final hours = (secondsTotal / 3600).floor();
final minutes = ((secondsTotal - (hours * 3600)) / 60).floor();
return '$hours hours : $minutes minutes';
}
void _buildList(List<dynamic> value) {
final items = value;
_timeCompleted = 0;
_timePlanned = 0;
setState(() {
_todayCurrent = 0;
_todayGoal = 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);
}
});
for (final item in items) {
if (_selectedValue == 'weekly' && item is TimerItem) {
final parsedDate = DateTime.parse(item.createdOn);
for (var i = 0; i <= 6; i++) {
if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) &&
parsedDate.isAfter(_latestDate.subtract(Duration(days: i + 1)))) {
logger.i('LOOPING');
_timeCompleted += item.current;
_timePlanned += item.goal;
// _maxTasks += 1;
final hours = (item.goal / 60).ceil().toDouble();
if (hours > _maxTime) {
_maxTime = hours;
}
// Update maxAmount
setState(() {
_weeklyPlannedEntries.elementAt(6).progress = item.goal / 60;
_weeklyWorkedEntries.elementAt(6).progress = item.current / 3600;
// _weeklyWorkedEntries.elementAt(6 - i).progress =
// _tasksCompleted.toDouble();
});
}
}
} else if (_selectedValue == 'daily' && item is TimerItem) {
final parsedDate = DateTime.parse(item.createdOn);
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');
_todayCurrent += item.current;
_todayGoal += item.goal;
}
}
}
}
void _showPreviousWeek() {
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() {
logger.i('CURRENT: ${_todayCurrent / 60}');
logger.i('MINUTES TOTAL: $_todayGoal');
logger.i('PERCENTAGE: ${_todayCurrent / (_todayGoal * 60)}');
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);
ref.watch(homeControllerProvider);
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';
int _timeCompleted = 0;
int _timePlanned = 0;
double _maxTime = 0;
DateTime _latestDate = DateTime.now();
double _todayCurrent = 0;
double _todayGoal = 0;
List<DataPoint> _weeklyPlannedEntries = <DataPoint>[];
List<DataPoint> _weeklyWorkedEntries = <DataPoint>[];
@override
Widget build(BuildContext context) {
final items = ref.watch(itemsProvider);
// ignore: unused_element
double getProgress() {
logger.i('CURRENT: $_todayCurrent');
logger.i('GOAL: $_todayGoal');
if (_todayGoal == 0) {
return 0;
}
return _todayCurrent / _todayGoal;
}
ref.watch(homeControllerProvider);
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[0].date;
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 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: _maxTime,
lineBarsData: [
LineChartBarData(
isCurved: true,
preventCurveOverShooting: true,
spots: flSpots,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradientColors
.map((color) => color.withOpacity(0.3))
.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.3))
.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: 50,
getTitlesWidget: (value, meta) => Text(
'$value h',
style: const TextStyle(
fontSize: 10,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
interval: 0.5,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
reservedSize: 30,
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: _todayCurrent / 60, // Progress
color: Theme.of(context).colorScheme.primaryFixedDim,
radius: 60,
title: (_todayGoal > 0)
// ignore: lines_longer_than_80_chars
? '${((_todayCurrent * 100 / 60) / _todayGoal).round()} %'
: '0 %',
titleStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
),
),
PieChartSectionData(
value: (_todayCurrent > 0)
? (_todayGoal - (_todayCurrent / 60))
: 1, // Total - progress
color: Theme.of(context).colorScheme.secondary,
radius: 60,
showTitle: _todayCurrent <= 0,
titleStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primaryFixedDim,
),
title: '0%',
),
],
),
),
),
],
SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: Column(
children: [
Text(
'You worked for ${_formatCurrent()}${_getText()}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
Text(
'Out of a total goal of ${_formatTime()}${_getText()}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
);
}
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();
}
}