Initial (redacted) commit.

This commit is contained in:
mustard 2024-08-26 00:34:20 +02:00
commit 655f8a036a
368 changed files with 20949 additions and 0 deletions

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:habitrack_app/pages/hydration_graph_widget.dart';
class SubpageHydrationButton extends StatelessWidget {
const SubpageHydrationButton({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 300,
margin: const EdgeInsets.only(top: 20, bottom: 7.5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
minimumSize: const Size(10, 70),
),
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (context) => const DashboardHydrationSubpage(),
),
),
},
child: Row(
children: [
Icon(
Icons.local_drink,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
' Hydration Widgets',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
);
}
}
class DashboardHydrationSubpage extends StatefulWidget {
const DashboardHydrationSubpage({super.key});
@override
State<StatefulWidget> createState() => _DashboardHydrationSubpageState();
}
class _DashboardHydrationSubpageState extends State<DashboardHydrationSubpage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.primary,
title: Text(
'Statistics: Hydration Widgets',
textScaler: const TextScaler.linear(1.2),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
body: const HydrationGraphWidget(),
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/pages/dashboard_hydration_subpage.dart';
import 'package:habitrack_app/pages/dashboard_task_subpage.dart';
import 'package:habitrack_app/pages/dashboard_timer_subpage.dart';
import 'package:habitrack_app/pages/reset_subpage.dart';
class DashboardPage extends ConsumerStatefulWidget {
const DashboardPage({super.key});
@override
ConsumerState<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends ConsumerState<DashboardPage> {
@override
Widget build(BuildContext context) {
//final items = ref.watch(itemsProvider);
//final len = items.value!.length;
return Scaffold(
appBar: AppBar(
title: Text(
'Dashboard',
textScaler: const TextScaler.linear(1.4),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
),
),
backgroundColor: Theme.of(context).colorScheme.secondary,
),
body: ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: const Column(
children: [
SubpageHydrationButton(),
SubpageTaskButton(),
SubpageTimerButton(),
ResetSubpageButton(),
],
),
),
),
);
}
}

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:habitrack_app/pages/tasks_graph_widget.dart';
class SubpageTaskButton extends StatelessWidget {
const SubpageTaskButton({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 300,
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
minimumSize: const Size(10, 70),
),
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (context) => const DashboardTaskSubpage(),
),
),
},
child: Row(
children: [
Icon(
Icons.task,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
' Task Widgets',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
);
}
}
class DashboardTaskSubpage extends StatefulWidget {
const DashboardTaskSubpage({super.key});
@override
State<StatefulWidget> createState() => _DashboardTaskSubpageState();
}
class _DashboardTaskSubpageState extends State<DashboardTaskSubpage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.primary,
title: Text(
'Statistics: Tasks Widgets',
textScaler: const TextScaler.linear(1.2),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
body: TasksGraphWidget(),
);
}
}

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:habitrack_app/pages/timer_graph_widget.dart';
class SubpageTimerButton extends StatelessWidget {
const SubpageTimerButton({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 300,
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
minimumSize: const Size(10, 70),
),
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (context) => const DashboardTimerSubpage(),
),
),
},
child: Row(
children: [
Icon(
Icons.timer,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
' Timer Widgets',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
);
}
}
class DashboardTimerSubpage extends StatefulWidget {
const DashboardTimerSubpage({super.key});
@override
State<StatefulWidget> createState() => _DashboardTimerSubpageState();
}
class _DashboardTimerSubpageState extends State<DashboardTimerSubpage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.primary,
title: Text(
'Statistics: Timer Widgets',
textScaler: const TextScaler.linear(1.2),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
body: TimerGraphWidget(),
);
}
}

View 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();
}
}

View file

@ -0,0 +1,123 @@
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/sembast/hydration.dart';
import 'package:habitrack_app/sembast/tasks_list.dart';
import 'package:habitrack_app/sembast/timer.dart';
class ResetSubpageButton extends ConsumerStatefulWidget {
const ResetSubpageButton({super.key});
@override
ConsumerState<ResetSubpageButton> createState() => _ResetSubpageButtonState();
}
class _ResetSubpageButtonState extends ConsumerState<ResetSubpageButton> {
Future<void> _confirmPopup() async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.onPrimary,
title: Text(
'Are you sure?',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
scrollable: true,
actions: <Widget>[
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
final controller = ref.watch(homeControllerProvider);
final items = ref.watch(itemsProvider);
switch (items) {
case AsyncData(:final value):
final items = value;
for (var i = 0; i < items.length; i++) {
final item = items.elementAt(i);
if (item is Hydration) {
controller.delete(item.id);
} else if (item is TasksItem) {
controller.delete(item.id);
} else if (item is TimerItem) {
controller.delete(item.id);
}
}
Navigator.of(context).pop();
}
//DateTime due = DateTime.parse(formattedDate);
// ignore: cascade_invocations
},
child: Text(
'Yes',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Container(
width: 300,
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
minimumSize: const Size(10, 70),
),
onPressed: _confirmPopup,
child: Row(
children: [
Icon(
Icons.delete,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
'Clear Database',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
);
}
}

View file

@ -0,0 +1,687 @@
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();
}
}

View file

@ -0,0 +1,487 @@
// 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();
}
}

View file

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
import 'package:habitrack_app/infrastructure/widget_wall/widget_wall.dart';
class WidgetPage extends StatelessWidget {
const WidgetPage({super.key});
@override
Widget build(BuildContext context) {
return const WidgetWall();
}
}