687 lines
23 KiB
Dart
687 lines
23 KiB
Dart
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/tasks_list.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
class DataPoint {
|
|
DataPoint({required this.date, required this.progress});
|
|
DateTime date;
|
|
double progress;
|
|
}
|
|
|
|
class TasksGraphWidget extends ConsumerStatefulWidget {
|
|
TasksGraphWidget({super.key});
|
|
|
|
@override
|
|
ConsumerState<ConsumerStatefulWidget> createState() =>
|
|
_TasksGraphWidgetState();
|
|
|
|
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 _TasksGraphWidgetState extends ConsumerState<TasksGraphWidget> {
|
|
void _buildList(List<dynamic> value) {
|
|
final items = value;
|
|
_tasksCompleted = 0;
|
|
_tasksPlanned = 0;
|
|
_weeklyOverdue = 0;
|
|
_totalTasksCompleted = 0;
|
|
_totalTasksPlanned = 0;
|
|
_maxTasks = 0;
|
|
_weeklyPlannedEntries = [];
|
|
_weeklyOverdueEntries = [];
|
|
_todayOverdue = 0;
|
|
|
|
_totalOverdue = 0;
|
|
const seperator = '_SEPARATOR_';
|
|
|
|
setState(() {
|
|
_todayCompleted = 0;
|
|
_todayPlanned = 0;
|
|
});
|
|
for (var i = 0; i <= 6; i++) {
|
|
final itemToInsert = DataPoint(
|
|
date: _latestDate.subtract(Duration(days: 6 - i)),
|
|
progress: 0,
|
|
);
|
|
|
|
_weeklyPlannedEntries.add(itemToInsert);
|
|
}
|
|
|
|
_weeklyWorkedEntries = [];
|
|
|
|
for (var i = 0; i <= 6; i++) {
|
|
final itemToInsert = DataPoint(
|
|
date: _latestDate.subtract(Duration(days: 6 - i)),
|
|
progress: 0,
|
|
);
|
|
_weeklyWorkedEntries.add(itemToInsert);
|
|
}
|
|
|
|
for (var i = 0; i <= 6; i++) {
|
|
final itemToInsert = DataPoint(
|
|
date: _latestDate.subtract(Duration(days: 6 - i)),
|
|
progress: 0,
|
|
);
|
|
_weeklyOverdueEntries.add(itemToInsert);
|
|
}
|
|
|
|
for (final item in items) {
|
|
if (_selectedValue == 'weekly' && item is TasksItem) {
|
|
logger.i('HMM');
|
|
|
|
_tasksCompleted = 0;
|
|
|
|
for (final individualToDo in item.completedTaskList) {
|
|
final toConvert = individualToDo.split(seperator);
|
|
final parsedDate = DateTime.parse(toConvert.elementAtOrNull(2)!);
|
|
|
|
var alreadyAdded = false;
|
|
|
|
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');
|
|
if (!alreadyAdded) {
|
|
alreadyAdded = true;
|
|
_tasksCompleted += 1;
|
|
_totalTasksCompleted += 1;
|
|
}
|
|
|
|
// Update maxAmount
|
|
setState(() {
|
|
logger.i('COMPLETED: $_tasksCompleted');
|
|
_weeklyWorkedEntries.elementAt(6 - i).progress =
|
|
_tasksCompleted.toDouble();
|
|
_weeklyPlannedEntries.elementAt(6 - i).progress =
|
|
_tasksPlanned.toDouble() +
|
|
_tasksCompleted.toDouble() +
|
|
_weeklyOverdue;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
_tasksPlanned = 0;
|
|
_weeklyOverdue = 0;
|
|
|
|
for (final individualToDo in item.taskList) {
|
|
final toConvert = individualToDo.split(seperator);
|
|
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
|
|
|
|
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
|
|
|
|
final parsedDate = completedOn;
|
|
|
|
var alreadyAdded = false;
|
|
logger.i('NANI');
|
|
|
|
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');
|
|
|
|
// Update maxAmount
|
|
setState(() {
|
|
if (!alreadyAdded) {
|
|
alreadyAdded = true;
|
|
final now = DateTime.now();
|
|
// _maxTasks += 1;
|
|
if (DateTime(now.year, now.month, now.day).isAfter(due)) {
|
|
logger
|
|
..i('OVERDUE TASK')
|
|
..i('NOW: $now')
|
|
..i('DUE: $due');
|
|
_totalOverdue += 1;
|
|
_weeklyOverdue += 1;
|
|
_weeklyOverdueEntries.elementAt(6 - i).progress =
|
|
_weeklyOverdue;
|
|
} else {
|
|
logger.i('Task is NOT overdue');
|
|
_tasksPlanned += 1;
|
|
_totalTasksPlanned += 1;
|
|
}
|
|
}
|
|
_weeklyPlannedEntries.elementAt(6 - i).progress =
|
|
_tasksPlanned.toDouble() +
|
|
_tasksCompleted.toDouble() +
|
|
_weeklyOverdue;
|
|
});
|
|
}
|
|
}
|
|
logger
|
|
..i('TASKS PLANNED FOR THIS DAY: $_tasksPlanned')
|
|
..i('TASKS COMPLETED FOR THIS DAY: $_tasksCompleted')
|
|
..i('TOTAL TASKS: ${_tasksPlanned + _tasksCompleted}');
|
|
|
|
if (_tasksPlanned + _tasksCompleted + _weeklyOverdue > _maxTasks) {
|
|
_maxTasks = (_tasksPlanned + _tasksCompleted + _weeklyOverdue)
|
|
.ceilToDouble();
|
|
logger.i('MAX TASKS: $_maxTasks');
|
|
}
|
|
}
|
|
} else if (_selectedValue == 'daily' && item is TasksItem) {
|
|
for (final individualToDo in item.completedTaskList) {
|
|
final toConvert = individualToDo.split(seperator);
|
|
|
|
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
|
|
|
|
final completed =
|
|
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
|
|
|
|
final oneDayAgo = _latestDate.subtract(const Duration(days: 1));
|
|
final parsedDate = completedOn;
|
|
if (parsedDate.isBefore(oneDayAgo)) {
|
|
logger.i('More than a day old');
|
|
} else if (parsedDate.isAfter(oneDayAgo) &&
|
|
parsedDate.isBefore(_latestDate)) {
|
|
logger.i('TOday');
|
|
if (completed) {
|
|
_todayCompleted += 1;
|
|
_todayPlanned += 1;
|
|
} else {
|
|
_todayPlanned += 1;
|
|
}
|
|
}
|
|
}
|
|
for (final individualToDo in item.taskList) {
|
|
final toConvert = individualToDo.split(seperator);
|
|
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
|
|
|
|
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
|
|
|
|
final completed =
|
|
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
|
|
|
|
final oneDayAgo = _latestDate.subtract(const Duration(days: 1));
|
|
final parsedDate = createdOn;
|
|
if (parsedDate.isBefore(oneDayAgo)) {
|
|
logger.i('More than a day old');
|
|
} else if (parsedDate.isAfter(oneDayAgo) &&
|
|
parsedDate.isBefore(_latestDate)) {
|
|
logger.i('TOday');
|
|
if (completed) {
|
|
_todayCompleted += 1;
|
|
_todayPlanned += 1;
|
|
} else {
|
|
_todayPlanned += 1;
|
|
final now = DateTime.now();
|
|
if (DateTime(now.year, now.month, now.day).isAfter(due)) {
|
|
logger
|
|
..i('OVERDUE TASK')
|
|
..i('NOW: $now')
|
|
..i('DUE: $due');
|
|
_totalOverdue += 1;
|
|
_todayOverdue += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _showPreviousWeek() {
|
|
if (_selectedValue == 'weekly') {
|
|
_latestDate = _latestDate.subtract(const Duration(days: 7));
|
|
} else if (_selectedValue == 'daily') {
|
|
_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') {
|
|
_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';
|
|
}
|
|
|
|
final gradientColors = [
|
|
Colors.cyan,
|
|
Colors.blueAccent,
|
|
];
|
|
final gradient2Colors = [
|
|
Colors.amber,
|
|
Colors.amberAccent,
|
|
];
|
|
final gradient3Colors = [
|
|
Colors.deepPurple,
|
|
Colors.deepPurpleAccent,
|
|
];
|
|
List<dynamic> thisWeekItems = [];
|
|
List<dynamic> todayItems = [];
|
|
String? _selectedValue = 'weekly';
|
|
|
|
int _tasksCompleted = 0;
|
|
int _tasksPlanned = 0;
|
|
int _totalTasksCompleted = 0;
|
|
int _totalTasksPlanned = 0;
|
|
double _maxTasks = 0;
|
|
DateTime _latestDate = DateTime.now();
|
|
double _todayCompleted = 0;
|
|
double _todayPlanned = 0;
|
|
double _todayOverdue = 0;
|
|
double _weeklyOverdue = 0;
|
|
double _totalOverdue = 0;
|
|
|
|
List<DataPoint> _weeklyPlannedEntries = <DataPoint>[];
|
|
List<DataPoint> _weeklyWorkedEntries = <DataPoint>[];
|
|
List<DataPoint> _weeklyOverdueEntries = <DataPoint>[];
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final items = ref.watch(itemsProvider);
|
|
|
|
// ignore: unused_element
|
|
double getProgress() {
|
|
if (_todayPlanned == 0) {
|
|
return 0;
|
|
}
|
|
logger.i(
|
|
'GLORIOUS PROGRESS${_todayCompleted / _todayPlanned}',
|
|
);
|
|
return _todayCompleted; // - this._todayCompleted;
|
|
}
|
|
|
|
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;
|
|
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.elementAt(0).date, dataPoint.date)
|
|
.toDouble();
|
|
final lcbd = FlSpot(xValue, dataPoint.progress);
|
|
|
|
weeklyWorkedSpots.add(lcbd);
|
|
}
|
|
|
|
final weeklyOverdueSpots = <FlSpot>[];
|
|
for (final dataPoint in _weeklyOverdueEntries) {
|
|
final xValue = _daysBetween(_weeklyOverdueEntries[0].date, dataPoint.date)
|
|
.toDouble();
|
|
final lcbd = FlSpot(xValue, dataPoint.progress);
|
|
weeklyOverdueSpots.add(lcbd);
|
|
// 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(
|
|
// color: Theme.of(context).colorScheme.primaryContainer,
|
|
padding:
|
|
const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5),
|
|
child: SizedBox(
|
|
width: MediaQuery.of(context).size.width * 0.9,
|
|
height: MediaQuery.of(context).size.height * 0.5,
|
|
child: LineChart(
|
|
LineChartData(
|
|
minY: 0,
|
|
maxY: _maxTasks,
|
|
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(),
|
|
),
|
|
),
|
|
),
|
|
LineChartBarData(
|
|
color: Colors.purple,
|
|
isCurved: true,
|
|
isStrokeCapRound: true,
|
|
preventCurveOverShooting: true,
|
|
spots: weeklyOverdueSpots,
|
|
belowBarData: BarAreaData(
|
|
show: true,
|
|
gradient: LinearGradient(
|
|
colors: gradient3Colors
|
|
.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: 50,
|
|
getTitlesWidget: (value, meta) => Text(
|
|
'${value.toInt()} task${value == 1 ? '' : 's'}',
|
|
style: const TextStyle(
|
|
fontSize: 10,
|
|
fontStyle: FontStyle.italic,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
interval: 1,
|
|
),
|
|
),
|
|
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: _todayCompleted, // Progress
|
|
color: Theme.of(context).colorScheme.primaryFixedDim,
|
|
|
|
// Cyan
|
|
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
|
|
_getCompletedDescription() + _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') ? (_totalTasksPlanned + _totalTasksCompleted + _totalOverdue).toInt() : _todayPlanned.toInt()}${_getText()}',
|
|
),
|
|
Text(
|
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
_getMissedDescription(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
String _getCompletedDescription() {
|
|
if (_selectedValue == 'weekly') {
|
|
if (_totalTasksCompleted == 0) {
|
|
return '0 tasks completed';
|
|
} else if (_totalTasksCompleted == 1) {
|
|
return '1 task completed';
|
|
} else {
|
|
return '$_totalTasksCompleted tasks completed';
|
|
}
|
|
} else if (_selectedValue == 'daily') {
|
|
if (_todayCompleted == 0) {
|
|
return '0 tasks completed';
|
|
} else if (_todayCompleted == 1) {
|
|
return '1 task completed';
|
|
} else {
|
|
return '${_todayCompleted.toInt()} tasks completed';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
String _getMissedDescription() {
|
|
if (_selectedValue == 'weekly') {
|
|
if (_totalOverdue == 0) {
|
|
return 'No tasks were missed';
|
|
} else if (_totalOverdue == 1) {
|
|
return '1 task was missed';
|
|
} else {
|
|
return '${_totalOverdue.toInt()} tasks were missed';
|
|
}
|
|
} else if (_selectedValue == 'daily') {
|
|
if (_todayOverdue == 0) {
|
|
return 'No tasks were missed';
|
|
} else if (_todayOverdue == 1) {
|
|
return '1 task was missed';
|
|
} else {
|
|
return '${_todayOverdue.toInt()} tasks were missed';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|