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,465 @@
//import 'dart:ffi';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.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:logger/logger.dart';
// ignore: must_be_immutable
class CompoundWidgetTimer extends ConsumerStatefulWidget {
CompoundWidgetTimer({required this.item, super.key});
TimerItem item;
double getProgress() {
return (item.current / item.goal) / 60;
}
@override
ConsumerState<CompoundWidgetTimer> createState() =>
_CompoundWidgetTimerState();
}
class _CompoundWidgetTimerState extends ConsumerState<CompoundWidgetTimer>
with WidgetsBindingObserver {
_CompoundWidgetTimerState();
String button1text = '';
Timer _timer = Timer(Duration.zero, () => ());
DateTime calcDue() {
final now = DateTime.now();
const secondsToAdd = 10;
const duration = Duration(seconds: secondsToAdd);
final futureTime = now.add(duration);
Logger().i(futureTime);
return DateTime.now();
}
void handleButton1() {
//start timer from 0
const oneSec = Duration(seconds: 1);
if (widget.item.state == 'initial') {
widget.item = widget.item.copyWith(state: 'running');
ref.watch(homeControllerProvider).edit(widget.item);
_timer = Timer.periodic(
oneSec,
(Timer funcTimer) {
if (widget.item.current == (widget.item.goal * 60)) {
setState(() {
final now = DateTime.now();
widget.item = widget.item.copyWith(
state: 'completed',
completedOn: now.toString(),
);
ref.watch(homeControllerProvider).edit(widget.item);
funcTimer.cancel();
});
} else {
setState(() {
widget.item =
widget.item.copyWith(current: widget.item.current + 1);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
},
);
}
// continue timer
else if (widget.item.state == 'paused') {
widget.item = widget.item.copyWith(state: 'running');
ref.watch(homeControllerProvider).edit(widget.item);
_timer = Timer.periodic(
const Duration(seconds: 1),
(Timer funcTimer) {
if (widget.item.current == (widget.item.goal * 60)) {
setState(() {
final now = DateTime.now();
widget.item = widget.item.copyWith(
state: 'completed',
completedOn: now.toString(),
);
ref.watch(homeControllerProvider).edit(widget.item);
funcTimer.cancel();
});
} else {
setState(() {
widget.item =
widget.item.copyWith(current: widget.item.current + 1);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
},
);
} else if (widget.item.state == 'running') {
widget.item = widget.item.copyWith(state: 'paused');
ref.watch(homeControllerProvider).edit(widget.item);
_timer.cancel();
} else if (widget.item.state == 'completed') {}
}
void stopTimer() {
setState(() {
if (_timer.isActive) {
_timer.cancel();
}
widget.item = widget.item.copyWith(
current: 0,
state: 'initial',
);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
String _formatCurrent() {
final hours = (widget.item.current / 3600).floor();
final minutes = ((widget.item.current - (hours * 3600)) / 60).floor();
return '$hours hours : $minutes minutes';
}
String _formattedTime() {
final minutesTotal = widget.item.goal;
final hours = (minutesTotal / 60).floor();
final minutes = minutesTotal - (hours * 60);
return '$hours hours : $minutes minutes';
}
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
String _getButton1Text(BuildContext context) {
if (widget.item.state == 'initial') {
return AppLocalizations.of(context)!.timerWidget_buttonStart;
} else if (widget.item.state == 'running') {
return AppLocalizations.of(context)!.timerWidget_buttonPause;
} else if (widget.item.state == 'paused') {
return AppLocalizations.of(context)!.timerWidget_buttonContinue;
}
return 'Done';
}
@override
Widget build(BuildContext context) {
ref
..watch(homeControllerProvider)
..watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.timer,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
height: 30,
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
// alignment: TextAlign.left,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
// alignment: TextAlign.left,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref),
),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Stack(
children: <Widget>[
Container(
alignment: Alignment.center,
// color: Colors.blueAccent,
width: MediaQuery.of(context).size.width,
height: 250,
child: SizedBox(
height: 150,
// color: Colors.greenAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// ignore: lines_longer_than_80_chars
'${AppLocalizations.of(context)!.timerWidget_current}: ${_formatCurrent()} \n ${AppLocalizations.of(context)!.timerWidget_goal}: ${_formattedTime()}',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
],
),
),
),
Positioned(
top: 10,
left: MediaQuery.of(context).size.width * 0.15,
width: MediaQuery.of(context).size.width * 0.60,
height: 220,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.onPrimary,
strokeWidth: 7,
value: widget.getProgress(),
semanticsLabel: 'Circular progress indicator',
),
),
],
),
Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: handleButton1,
child: Text(
_getButton1Text(context),
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
TextButton(
onPressed: stopTimer,
child: Text(
AppLocalizations.of(context)!
.timerWidget_buttonReset,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
],
),
],
),
),
],
),
),
// Add more widgets here as needed
],
],
),
);
}
Future<void> _showSettingsMenu(WidgetRef ref) async {
logger.i('Opening settings');
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
'duration': SettingEntryDuration(
name: AppLocalizations.of(context)!.timerWidgetSettings_duration,
defaultValue: widget.item.goal,
),
},
);
if (widget.item.state == 'running') {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.timerWidget_pausedForEdit,
),
),
);
} else {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () async {
ref.watch(itemsProvider);
final items = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(isVisible: false);
setState(() {
items.edit(widget.item);
});
logger.i('Attempting delete');
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
backgroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {
_timer.cancel();
ref.watch(homeControllerProvider);
logger.i('Attempting edit of name and/or duration');
final name = settingEntries.getValue('name') as String;
logger.i('New name: $name');
final duration = settingEntries.getValue('duration') as int;
widget.item =
widget.item.copyWith(goal: duration, name: name);
setState(() {
ref.watch(homeControllerProvider).edit(widget.item);
});
logger.i('NAME AND DURATION SUCCESSFULLY UPDATED');
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
}
}

View file

@ -0,0 +1,968 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.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';
const seperator = '_SEPARATOR_';
// ignore: must_be_immutable
class IndividualTodoWidget extends ConsumerStatefulWidget {
IndividualTodoWidget({
required this.todo,
required this.due,
required this.completed,
required this.item,
required this.isVisible,
required this.createdOn,
required this.completedOn,
super.key,
});
late String todo;
late bool completed;
late DateTime due;
late DateTime createdOn;
late DateTime completedOn;
late TasksItem item;
late bool isVisible;
@override
ConsumerState<IndividualTodoWidget> createState() =>
_IndividualTodoWidgetState();
String stringRepr() {
return [
todo,
due.toString().substring(0, 10),
createdOn,
completedOn,
completed,
isVisible,
].join(seperator);
}
void deleteItem(WidgetRef ref) {
final searchTerm = stringRepr();
if (completed == true) {
final completedTaskList = <String>[...item.completedTaskList];
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (searchTerm == completedTaskList.elementAt(i)) {
var newItem = searchTerm;
final toConvert = newItem.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed =
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(due);
newItem = [
todo,
formattedDate.substring(0, 10),
createdOn,
completedOn,
completed,
false,
].join(seperator);
completedTaskList[i] = newItem;
final controller = ref.watch(homeControllerProvider);
item = item.copyWith(completedTaskList: completedTaskList);
controller.edit(item);
}
}
} else {
final taskList = <String>[...item.taskList];
for (var i = 0; i < taskList.length; i++) {
if (searchTerm == taskList.elementAt(i)) {
var newItem = searchTerm;
final toConvert = newItem.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed =
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(due);
newItem = [
todo,
formattedDate,
createdOn,
completedOn,
completed,
false,
].join(seperator);
taskList[i] = newItem;
final controller = ref.watch(homeControllerProvider);
item = item.copyWith(taskList: taskList);
controller.edit(item);
} else {}
}
}
}
bool overdue() {
final now = DateTime.now();
if (DateTime(now.year, now.month, now.day).isAfter(due)) {
return true;
}
return false;
}
}
class _IndividualTodoWidgetState extends ConsumerState<IndividualTodoWidget> {
_IndividualTodoWidgetState();
void _toggleChecked(bool? param) {
if (widget.overdue() && !widget.completed) {
} else {
final searchTerm = widget.stringRepr();
final completedTaskList = <String>[...widget.item.completedTaskList];
final taskList = <String>[...widget.item.taskList];
if (widget.completed == true) {
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (searchTerm == completedTaskList.elementAt(i)) {
widget.completed = !widget.completed;
final replacementStr = widget.stringRepr();
completedTaskList.removeAt(i);
taskList.add(replacementStr);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(
taskList: taskList,
completedTaskList: completedTaskList,
);
setState(() {
controller.edit(widget.item);
});
}
}
} else {
for (var i = 0; i < taskList.length; i++) {
if (searchTerm == taskList.elementAt(i)) {
widget.completed = !widget.completed;
// ignore: cascade_invocations
widget.completedOn = DateTime.now();
final replacementStr = widget.stringRepr();
taskList.removeAt(i);
completedTaskList.add(replacementStr);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(
taskList: taskList,
completedTaskList: completedTaskList,
);
setState(
() {
controller.edit(widget.item);
},
);
} else {}
}
}
setState(() {});
}
}
void _editItem(String oldItem, String itemToAdd) {
final completedTaskList = <String>[...widget.item.completedTaskList];
final taskList = <String>[...widget.item.taskList];
if (widget.completed == true) {
//search in completed tasks list
for (var i = 0; i < completedTaskList.length; i++) {
if (oldItem == completedTaskList.elementAt(i)) {
completedTaskList.replaceRange(i, i + 1, [itemToAdd]);
final controller = ref.watch(homeControllerProvider);
widget.item =
widget.item.copyWith(completedTaskList: completedTaskList);
setState(() {
controller.edit(widget.item);
});
}
}
} else {
for (var i = 0; i < taskList.length; i++) {
if (oldItem == taskList.elementAt(i)) {
taskList.replaceRange(i, i + 1, [itemToAdd]);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(taskList: taskList);
setState(
() {
controller.edit(widget.item);
},
);
} else {}
}
}
}
Future<void> _editPopup(String oldItem) async {
final todoFieldEditController = TextEditingController(text: widget.todo);
DateTime? date = widget.due;
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.tasksWidget_editTask,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
scrollable: true,
content: Column(
children: <Widget>[
TextField(
controller: todoFieldEditController,
decoration: InputDecoration(hintText: widget.todo),
autofillHints: null,
),
TextButton(
child: Text(
AppLocalizations.of(context)!.taskSettings_duePicker,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: widget.due,
firstDate: DateTime(2024, 0, 0),
lastDate: DateTime(2101),
);
date = pickedDate;
},
),
],
),
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(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
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: () {
date ??= widget.due;
if (todoFieldEditController.text.isEmpty) {
todoFieldEditController.text = widget.todo;
}
Navigator.of(context).pop();
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(date!);
final newItem = [
todoFieldEditController.text,
formattedDate,
widget.createdOn,
widget.completedOn,
widget.completed,
widget.isVisible,
].join(seperator);
_editItem(oldItem, newItem);
todoFieldEditController.clear();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
final df = DateFormat('dd.MM.yyyy');
var dateText =
// ignore: lines_longer_than_80_chars
'${AppLocalizations.of(context)!.taskSettings_due}: ${df.format(widget.due)}';
var backgroundColor = Theme.of(context).colorScheme.secondary;
if (widget.overdue() && !widget.completed) {
dateText = AppLocalizations.of(context)!.tasksWidget_overdue;
backgroundColor = Theme.of(context).colorScheme.tertiary;
}
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
padding: const EdgeInsets.all(3),
width: 300,
decoration: BoxDecoration(
border: Border.all(
width: 1.5,
),
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 1,
blurRadius: 2,
),
],
),
child: Row(
children: [
Expanded(
flex: 0,
child: Checkbox(
activeColor: Colors.green,
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return null;
}
return Theme.of(context).colorScheme.onSecondary;
},
),
value: widget.completed,
onChanged: _toggleChecked,
),
),
Expanded(
flex: 2,
child: Column(
children: [
Text(
widget.todo,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
),
),
Text(
dateText,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
),
),
],
),
),
Expanded(
flex: 0,
child: IconButton(
onPressed: () => _editPopup(widget.stringRepr()),
icon: Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
onPressed: () => setState(() {
widget.deleteItem(ref);
}),
icon: Icon(
Icons.delete,
color: Theme.of(context).colorScheme.onSecondary,
),
),
),
],
),
);
}
}
// ignore: must_be_immutable
class CompoundWidgetTasks extends ConsumerStatefulWidget {
CompoundWidgetTasks({required this.item, super.key});
TasksItem item;
double getProgress() {
if (item.completedTaskList.isNotEmpty) {
return item.completedTaskList.length /
(item.taskList.length + item.completedTaskList.length);
}
return 0;
}
@override
ConsumerState<CompoundWidgetTasks> createState() =>
_CompoundWidgetTasksState();
}
class _CompoundWidgetTasksState extends ConsumerState<CompoundWidgetTasks> {
final TextEditingController _todoFieldController = TextEditingController();
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
IndividualTodoWidget _todoFromString(String input) {
final toConvert = input.split(seperator);
final todo = toConvert.elementAtOrNull(0)!;
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
final completed = toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
final newItem = IndividualTodoWidget(
todo: todo,
due: due,
completed: completed,
completedOn: completedOn,
item: widget.item,
isVisible: isVisible,
createdOn: createdOn,
);
return newItem;
}
List<Widget> _buildTodoList() {
final items = <IndividualTodoWidget>[];
final taskList = <String>[...widget.item.taskList];
final completedTaskList = <String>[...widget.item.completedTaskList];
taskList.sort(
(a, b) =>
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
),
);
completedTaskList.sort(
(a, b) =>
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
),
);
for (var i = 0; i < taskList.length; i++) {
final todoAsString = taskList.elementAt(i);
final newItem = _todoFromString(todoAsString);
if (newItem.isVisible) {
items.add(newItem);
}
}
for (var i = 0; i < completedTaskList.length; i++) {
final todoAsString = completedTaskList.elementAt(i);
final newItem = _todoFromString(todoAsString);
if (newItem.isVisible) {
items.add(newItem);
}
}
return items;
}
void _addItem(String toAdd) {
logger.i('ITEM TO ADD: $toAdd');
final controller = ref.watch(homeControllerProvider);
final toCopy = <String>[...widget.item.taskList];
// ignore: cascade_invocations
toCopy.add(toAdd);
widget.item = widget.item.copyWith(taskList: toCopy);
setState(() {
controller.edit(widget.item);
});
}
@override
Widget build(BuildContext context) {
ref.watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
//height: 100,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.task,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
// Width of the progress bar
height: 30, // Height of the progress bar
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: TextStyle(
// fontSize: 12,
// fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref, context),
),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Column(
children: [
OutlinedButton(
onPressed: _addPopup,
child: Row(
children: [
Icon(
Icons.add_task_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
// ignore: lines_longer_than_80_chars
' ${AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel}',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
ListView(
padding: const EdgeInsets.symmetric(vertical: 5),
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
children: _buildTodoList(),
),
if (anyTasksCompleted()) ...[
OutlinedButton(
onPressed: _deleteCompletedTasks,
child: Text(
AppLocalizations.of(context)!
.tasksWidget_deleteDone,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
),
],
/* if (anyTasksOverdue()) ...[
OutlinedButton(
onPressed: _rescheduleOverdueTasks,
child: Text(
AppLocalizations.of(context)!
.tasksWidget_rescheduleOverdue,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
),
],*/
],
),
],
),
),
],
],
),
);
}
bool anyTasksCompleted() {
for (final item in widget.item.completedTaskList) {
final toConvert = item.split(seperator);
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
if (isVisible) {
return true;
}
}
return false;
}
void _deleteCompletedTasks() {
final controller = ref.watch(homeControllerProvider);
final newCompletedTaskList = <String>[]; // widget.item.completedTaskList;
for (final itemString in widget.item.completedTaskList) {
final oldItem = _todoFromString(itemString);
final dateFormat = DateFormat('yyyy-MM-dd');
final newItem = [
oldItem.todo,
dateFormat.format(oldItem.due),
oldItem.createdOn,
oldItem.completedOn,
oldItem.completed,
false,
].join(seperator);
newCompletedTaskList.add(newItem);
}
widget.item = widget.item.copyWith(completedTaskList: newCompletedTaskList);
controller.edit(widget.item);
}
bool anyTasksOverdue() {
final taskList = <String>[...widget.item.taskList];
for (final taskString in taskList) {
if (_todoFromString(taskString).overdue()) {
return true;
}
}
return false;
}
// Used for the Reschedule Task Button
// ignore: unused_element
void _rescheduleOverdueTasks() {
final taskList = <String>[...widget.item.taskList];
for (var i = 0; i < taskList.length; i++) {
final taskString = taskList[i];
if (_todoFromString(taskString).overdue()) {
final oldItem = _todoFromString(taskString);
final now = DateTime.now();
final newItem = IndividualTodoWidget(
todo: oldItem.todo,
due: DateTime(now.year, now.month, now.day),
createdOn: DateTime.now(),
completedOn: DateTime.now(),
completed: oldItem.completed,
item: widget.item,
isVisible: true,
);
taskList.replaceRange(i, i + 1, [newItem.stringRepr()]);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(taskList: taskList);
setState(
() {
controller.edit(widget.item);
},
);
}
}
}
Future<void> _addPopup() async {
DateTime? date = DateTime.now();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
scrollable: true,
content: Column(
children: <Widget>[
TextField(
controller: _todoFieldController,
autofillHints: null,
decoration: InputDecoration(
hintText: AppLocalizations.of(context)!.taskSettings_name,
),
),
TextButton(
child: Text(
AppLocalizations.of(context)!.taskSettings_duePicker,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(), //get today's date
firstDate: DateTime.now(),
//DateTime.now() - not to allow to choose before today
lastDate: DateTime(2101),
);
date = pickedDate;
},
),
],
),
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(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
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: () {
//DateTime due = DateTime.parse(formattedDate);
// ignore: cascade_invocations
if (_todoFieldController.text.isEmpty) {
_todoFieldController.text = 'todo';
}
if (date != null && _todoFieldController.text.isNotEmpty) {
final dateFormat = DateFormat('yyyy-MM-dd');
final formattedDate = dateFormat.format(date!);
Navigator.of(context).pop();
final itemToAdd = [
_todoFieldController.text,
formattedDate,
DateTime.now(),
DateTime.now(),
false,
true,
].join(seperator);
_addItem(itemToAdd);
_todoFieldController.clear();
}
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
Future<void> _showSettingsMenu(WidgetRef ref, BuildContext context) async {
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
},
);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
ref
.watch(homeControllerProvider)
.edit(widget.item.copyWith(isVisible: false));
// await controller.delete(widget.item.id);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
final controller = ref.watch(homeControllerProvider);
final name = settingEntries.getValue('name') as String;
widget.item = widget.item.copyWith(name: name);
setState(() {
widget.item = widget.item.copyWith(name: name);
controller.edit(widget.item);
});
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
}

View file

@ -0,0 +1,491 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.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/hydration.dart';
import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart';
// ignore: must_be_immutable
class CompoundWidgetWater extends ConsumerStatefulWidget {
CompoundWidgetWater({required this.item, super.key});
late Hydration item;
double getProgress() {
return item.current / item.goal;
}
@override
ConsumerState<CompoundWidgetWater> createState() =>
_CompoundWidgetWaterState();
}
class _CompoundWidgetWaterState extends ConsumerState<CompoundWidgetWater> {
void _toggleExpansion() {
setState(() {
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
ref.watch(homeControllerProvider).edit(widget.item);
});
}
void _addQuantity(int toAdd) {
setState(() {
final controller = ref.watch(homeControllerProvider);
final oldval = widget.item.current;
logger.i('Old item: ${widget.item}');
widget.item = widget.item.copyWith(current: oldval + toAdd);
logger.i('New item: ${widget.item}');
controller.edit(widget.item);
});
}
@override
Widget build(BuildContext context) {
ref.watch(itemsProvider);
return Container(
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
//height: 100,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(7)),
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 2,
blurRadius: 5,
// changes position of shadow
),
],
),
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.item.isExpanded) ...[
Icon(
Icons.local_drink,
size: 20,
color: Theme.of(context).colorScheme.onPrimary,
),
Expanded(
flex: 2,
child: Container(
margin: const EdgeInsets.only(left: 10, right: 10),
// Width of the progress bar
height: 30, // Height of the progress bar
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(5),
), // Rounded corners
child: LinearProgressIndicator(
value:
widget.getProgress(), // Progress value (0.0 - 1.0)
backgroundColor:
Colors.grey.withOpacity(0.5), // Background color
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xffA4E8FD),
), // Progress color
),
),
),
),
Expanded(
child: Text(
widget.item.name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
if (widget.item.isExpanded) ...[
Expanded(
child: Text(
widget.item.name,
textScaler: const TextScaler.linear(2),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => _showSettingsMenu(ref),
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
Expanded(
flex: 0,
child: IconButton(
icon: Icon(
widget.item.isExpanded
? Icons.arrow_drop_up_outlined
: Icons.arrow_drop_down_circle_outlined,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: _toggleExpansion,
),
//child: Icon(Icons.arrow_drop_down_circle_outlined),
),
],
),
if (widget.item.isExpanded) ...[
// Additional child elements when expanded
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
//INSERT WIDGET SPECIFIC STUFF HERE
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Expanded(
child: OvershootLiquidLinearProgressIndicator(
current: widget.item.current,
goal: widget.item.goal,
),
),
Expanded(
child: Column(
children: [
Text(
'${widget.item.current / 1000} / ${widget.item.goal / 1000} L',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: () => {
_addQuantity(
widget.item.button1Amount,
),
},
child: Text(
'${widget.item.button1Amount} mL',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onSecondary,
),
),
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: () => {
_addQuantity(
widget.item.button2Amount,
),
},
child: Text(
'${widget.item.button2Amount} mL',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
),
Expanded(
child: OutlinedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
onPressed: _showPopupCustomAmount,
child: Text(
AppLocalizations.of(context)!
.waterWidget_customAmountButton,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
),
],
),
),
],
),
),
],
),
),
// Add more widgets here as needed
],
],
),
);
}
Future<void> _showSettingsMenu(WidgetRef ref) async {
final settingEntries = WidgetSettingsData(
entries: {
'name': SettingEntryText(
name: AppLocalizations.of(context)!.widgetSettings_name,
defaultValue: widget.item.name,
),
'button1Amount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_button1,
defaultValue: widget.item.button1Amount,
),
'button2Amount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_button2,
defaultValue: widget.item.button2Amount,
),
'targetGoal': SettingEntrySlider(
name: AppLocalizations.of(context)!.waterWidgetSettings_goal,
defaultValue: double.parse(widget.item.goal.toString()),
divisions: 80,
topValue: 4000,
),
'currentAmount': SettingEntryNumeric(
name: AppLocalizations.of(context)!.waterWidgetSettings_current,
defaultValue: widget.item.current,
),
},
);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
content: WidgetSettings(
entries: settingEntries,
),
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
//widget.parent.delete(ref);
ref.watch(itemsProvider);
final controller = ref.watch(homeControllerProvider);
widget.item = widget.item.copyWith(isVisible: false);
controller.edit(widget.item);
logger.i('Attempting delete');
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_deleteButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.onPrimary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
final controller = ref.watch(homeControllerProvider);
logger.i('Attempting edit of water widget stuff');
// widget.settingEntries.notify();
final name = settingEntries.getValue('name') as String;
final currentAmount =
settingEntries.getValue('currentAmount') as int;
final targetGoal =
(settingEntries.getValue('targetGoal') as double).round();
final button1Amount =
settingEntries.getValue('button1Amount') as int;
final button2Amount =
settingEntries.getValue('button2Amount') as int;
widget.item = widget.item.copyWith(
name: name,
current: currentAmount,
goal: targetGoal,
button1Amount: button1Amount,
button2Amount: button2Amount,
);
controller.edit(widget.item);
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
);
},
);
}
Future<void> _showPopupCustomAmount() async {
final customFieldController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.waterWidget_customAmountMessage,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
content: Column(
children: <Widget>[
TextField(
controller: customFieldController,
keyboardType: TextInputType.number,
autofocus: true,
),
],
),
actions: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_cancelButton,
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: () {
Navigator.of(context).pop();
final result = int.tryParse(customFieldController.text) ?? 0;
_addQuantity(result);
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_saveButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
},
);
}
}
class OvershootLiquidLinearProgressIndicator extends StatelessWidget {
const OvershootLiquidLinearProgressIndicator({
required this.current,
required this.goal,
super.key,
});
final int current;
final int goal;
@override
Widget build(BuildContext context) {
var mainColor = Colors.blue;
var backColor = Colors.white;
var value = 0.0;
if (current < goal) {
mainColor = Colors.blue;
backColor = Colors.white;
value = (current - goal * 0) / goal;
} else if (current < goal * 2) {
mainColor = Colors.orange;
backColor = Colors.blue;
value = (current - goal * 1) / goal;
} else {
mainColor = Colors.red;
backColor = Colors.orange;
value = (current - goal * 2) / goal;
}
return LiquidLinearProgressIndicator(
value: value,
valueColor: AlwaysStoppedAnimation(
mainColor,
),
backgroundColor: backColor,
borderColor: Colors.black,
borderWidth: 3,
borderRadius: 12,
direction: Axis.vertical,
);
}
}

View file

@ -0,0 +1,375 @@
import 'package:duration_picker/duration_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:habitrack_app/main.dart';
import 'package:intl/intl.dart';
abstract class SettingEntry extends StatefulWidget {
const SettingEntry({required this.name, super.key});
final String name;
@override
State<StatefulWidget> createState() => _SettingEntryState();
dynamic getValue() {
throw UnimplementedError();
}
void setValue(dynamic newValue) {
throw UnimplementedError();
}
}
class _SettingEntryState extends State<SettingEntry> {
@override
Widget build(BuildContext context) {
return const Text('Abstract, not implemented');
}
}
// ###############################TEXT#########################################
class SettingEntryText extends SettingEntry {
SettingEntryText({
required super.name,
this.defaultValue = 'Some Text',
super.key,
}) {
setValue(defaultValue);
}
final TextEditingController valueController = TextEditingController();
final String defaultValue;
@override
State<SettingEntryText> createState() => _SettingEntryTextState();
@override
String getValue() {
logger.i('GETTING VALUE ${valueController.text}');
return valueController.text;
}
@override
void setValue(dynamic newValue) {
if (newValue is! String) {
throw Exception('Value of SettingEntryText can only be a String!');
}
valueController.text = newValue;
}
}
class _SettingEntryTextState extends State<SettingEntryText> {
void monitorTextChange() {
final text = widget.valueController.text;
logger.i('TEXT: $text');
}
@override
void initState() {
super.initState();
// Start listening to changes.
widget.valueController.addListener(monitorTextChange);
}
@override
void dispose() {
// Clean up the controller when the widget is removed from the widget tree.
// This also removes the _printLatestValue listener.
widget.valueController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
alignment: AlignmentDirectional.bottomStart,
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
// color: Colors.black,
child: Text(
widget.name,
textAlign: TextAlign.left,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
),
TextField(
keyboardType: TextInputType.text,
controller: widget.valueController,
),
],
);
}
}
// ###############################NUMERIC#######################################
class SettingEntryNumeric extends SettingEntry {
SettingEntryNumeric({required super.name, int defaultValue = 0, super.key}) {
setValue(defaultValue);
}
final TextEditingController valueController = TextEditingController();
@override
State<SettingEntryNumeric> createState() => _SettingEntryNumericState();
@override
int getValue() {
return int.parse(valueController.text);
}
@override
void setValue(dynamic newValue) {
if (newValue is! int) {
throw Exception('Value of SettingEntryNumeric can only be an integer!');
}
valueController.text = newValue.toString();
}
}
class _SettingEntryNumericState extends State<SettingEntryNumeric> {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(widget.name),
TextField(
keyboardType: TextInputType.number,
controller: widget.valueController,
),
],
);
}
}
// ###############################SLIDER########################################
class SettingEntrySlider extends SettingEntry {
SettingEntrySlider({
required this.topValue,
required this.divisions,
required super.name,
double defaultValue = 0.0,
super.key,
}) {
setValue(defaultValue);
}
final double topValue;
final int divisions;
final value = DoubleSaver();
@override
State<SettingEntrySlider> createState() => _SettingEntrySliderState();
@override
double getValue() {
return value.v;
}
@override
void setValue(dynamic newValue) {
if (newValue is! double) {
throw Exception('Value of SettingEntrySlider can only be a Double!');
}
value.v = newValue;
}
}
class DoubleSaver {
double v = 0;
}
class _SettingEntrySliderState extends State<SettingEntrySlider> {
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
//Expanded(flex: 2, child: Text(widget.name)),
Text(widget.name),
Text(' ${widget.getValue()}'),
],
),
Row(
children: [
const Text('0 '),
Expanded(
child: Slider(
value: widget.getValue(),
divisions: widget.divisions,
max: widget.topValue,
onChanged: sliderChange,
),
),
Text(' ${widget.topValue}'),
],
),
],
);
}
void sliderChange(double newValue) {
setState(() {
widget.setValue(newValue);
});
}
}
// #############################DURATION#######################################
class IntSaver {
IntSaver({this.v = 30});
int v;
}
class SettingEntryDuration extends SettingEntry {
SettingEntryDuration({
required super.name,
required this.defaultValue,
super.key,
}) {
val = IntSaver(v: defaultValue);
}
final int defaultValue;
late final IntSaver val;
@override
State<SettingEntryDuration> createState() => _SettingEntryDurationState();
@override
int getValue() {
return val.v;
}
@override
void setValue(dynamic newValue) {
if (newValue is! int) {
throw Exception('Value of SettingEntryDuration can only be a Double!');
}
val.v = newValue;
}
}
class _SettingEntryDurationState extends State<SettingEntryDuration> {
@override
Widget build(BuildContext context) {
final textCurrent =
AppLocalizations.of(context)!.widgetSettings_durationPickerCurrent;
return Column(
children: [
Container(
alignment: AlignmentDirectional.bottomStart,
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
child: Text(
'$textCurrent ${widget.getValue()}m',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () async {
var resultingDuration = await showDurationPicker(
context: context,
initialTime: Duration(minutes: widget.val.v),
);
resultingDuration ??= Duration(minutes: widget.defaultValue);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Chose duration: ${resultingDuration.inMinutes}'),
),
);
widget.setValue(resultingDuration.inMinutes);
setState(() {});
},
child: Text(
AppLocalizations.of(context)!.widgetSettings_durationPickerButton,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
}
}
// ###############################DATE#########################################
class DateSaver {
DateTime v = DateTime.now();
}
class SettingEntryDate extends SettingEntry {
SettingEntryDate({required super.name, DateTime? defaultValue, super.key}) {
this.defaultValue = defaultValue ?? DateTime.now();
setValue(defaultValue);
}
final date = DateSaver();
late final DateTime defaultValue;
@override
State<SettingEntryDate> createState() => _SettingEntryDateState();
@override
DateTime getValue() {
return date.v;
}
@override
void setValue(dynamic newValue) {
if (newValue is! DateTime) {
throw Exception('Value of SettingEntryNumeric can only be a DateTime!');
}
date.v = newValue;
}
}
class _SettingEntryDateState extends State<SettingEntryDate> {
@override
Widget build(BuildContext context) {
final dateFormat = DateFormat('dd. MMMM');
return Column(
children: [
Text(
'${widget.name}: ${dateFormat.format(widget.date.v)} ',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primaryContainer,
),
),
TextButton(
onPressed: datePicker,
child: const Text('Choose a date'),
),
],
);
}
Future<void> datePicker() async {
final pickedDate = await showDatePicker(
context: context,
initialDate: widget.date.v, //get today's date
firstDate: DateTime.now(),
//DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101),
);
final cleanDate = pickedDate ?? widget.date.v;
widget.setValue(cleanDate);
setState(() {});
}
}

View file

@ -0,0 +1,79 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
class WidgetSettingsData {
WidgetSettingsData({required this.entries});
final Map<State, void Function(void Function())> listeners = {};
final Map<String, SettingEntry> entries;
void addListener(State toAdd, void Function(void Function()) updateCall) {
listeners[toAdd] = updateCall;
}
void removeListener(State toRemove) {
listeners.remove(toRemove);
}
void notify() {
for (final listener in listeners.keys) {
listeners[listener]!(() => ());
}
}
dynamic getValue(String key) {
return entries[key]?.getValue();
}
void setValue(String key, dynamic value) {
entries[key]?.setValue(value);
notify();
}
List<SettingEntry> asList() {
return entries.values.toList();
}
}
class WidgetSettings extends StatefulWidget {
const WidgetSettings({
required this.entries,
super.key,
});
final WidgetSettingsData entries;
@override
State<WidgetSettings> createState() => _WidgetSettingsState();
}
class _WidgetSettingsState extends State<WidgetSettings> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimaryContainer,),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
AppLocalizations.of(context)!.settingsHeader,
textScaler: const TextScaler.linear(1.5),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
),
body: ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,
child: ListView(
children: widget.entries.asList(),
),
),
);
}
}

View file

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ScaffoldWithBottomNavigationBar extends StatelessWidget {
const ScaffoldWithBottomNavigationBar({
required this.navigationShell,
Key? key,
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child: navigationShell),
bottomNavigationBar: BottomNavigationBar(
fixedColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.secondary,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Widget Wall',
),
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
label: 'Dashboard',
),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index),
),
);
}
void _onTap(BuildContext context, int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}
}

View file

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:habitrack_app/infrastructure/bottom_navigation.dart';
import 'package:habitrack_app/pages/dashboard_page.dart';
import 'package:habitrack_app/pages/widget_page.dart';
// navigators, root and each destination of bottom navigation bar
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorWidgetWallKey =
GlobalKey<NavigatorState>(debugLabel: 'widgetWall');
final _shellNavigatorDashboardKey =
GlobalKey<NavigatorState>(debugLabel: 'dashboard');
const String _widgetWallPath = '/widgetWall';
const String _dashboardPath = '/dashboard';
final goRouter = GoRouter(
initialLocation: _widgetWallPath,
navigatorKey: _rootNavigatorKey,
debugLogDiagnostics: true,
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithBottomNavigationBar(
navigationShell: navigationShell,
);
},
branches: [
StatefulShellBranch(
navigatorKey: _shellNavigatorWidgetWallKey,
routes: [
GoRoute(
path: _widgetWallPath,
pageBuilder: (context, state) => const NoTransitionPage(
child: WidgetPage(),
),
),
],
),
StatefulShellBranch(
navigatorKey: _shellNavigatorDashboardKey,
routes: [
GoRoute(
path: _dashboardPath,
pageBuilder: (context, state) => const NoTransitionPage(
child: DashboardPage(),
),
),
],
),
],
),
],
);

View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:habitrack_app/infrastructure/widget_wall/add_widget_menu.dart';
class AddWidgetButton extends StatelessWidget {
const AddWidgetButton({super.key});
@override
Widget build(BuildContext context) {
return OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute<dynamic>(
builder: (context) => const AddWidgetMenu(),
),
),
},
child: Icon(
Icons.add,
color: Theme.of(context).colorScheme.onPrimary,
),
);
}
}

View file

@ -0,0 +1,150 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/hydration.dart';
import 'package:habitrack_app/sembast/tasks_list.dart';
import 'package:habitrack_app/sembast/timer.dart';
//Add Widget to List- Button Class ######################################
class _AddWidgetToList extends ConsumerWidget {
const _AddWidgetToList({
required this.toAdd,
required this.buttonText,
required this.iconData,
});
final dynamic toAdd;
final String buttonText;
final IconData iconData;
void _buttonFunc(BuildContext context, WidgetRef ref) {
//ref.read(widgetListNotifierProvider.notifier).addWidget(toAdd());
//ref.read(homeControllerProvider).add()
ref.watch(homeControllerProvider).add(toAdd);
Navigator.pop(context);
logger.i('Button Func Called');
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
boxShadow: [
BoxShadow(
color: const Color(0x00000000).withOpacity(0.25),
spreadRadius: 1,
blurRadius: 2,
),
],
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
width: 300,
child: TextButton(
onPressed: () => {_buttonFunc(context, ref)},
child: Row(
children: [
Icon(
iconData,
color: Theme.of(context).colorScheme.onPrimary,
),
Text(
buttonText,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
],
),
),
);
}
}
// AddWidgetMenu ##########################################################
class AddWidgetMenu extends StatelessWidget {
const AddWidgetMenu({super.key});
@override
Widget build(BuildContext context) {
//AddWidgetToList - Button-Instances #######################################
final addWaterWidget = _AddWidgetToList(
toAdd: Hydration(
createdOn: DateTime.now().toString(),
completedOn: '',
isVisible: true,
widgetType: 'Hydration',
name: AppLocalizations.of(context)!.waterWidget_defaultName,
button1Amount: 100,
button2Amount: 250,
goal: 2500,
current: 0,
isExpanded: false,
),
buttonText: AppLocalizations.of(context)!.addWidget_water,
iconData: Icons.local_drink,
);
final addCompoundTimerWidget = _AddWidgetToList(
toAdd: TimerItem(
widgetType: 'Timer',
name: AppLocalizations.of(context)!.timerWidget_defaultName,
current: 0,
goal: 90,
isExpanded: false,
createdOn: DateTime.now().toString(),
completedOn: '',
isVisible: true,
state: 'initial',
),
buttonText: AppLocalizations.of(context)!.addWidget_timer,
iconData: Icons.timer,
);
final addTaskWidget = _AddWidgetToList(
toAdd: TasksItem(
isVisible: true,
widgetType: 'TODO',
name: AppLocalizations.of(context)!.tasksWidget_defaultName,
isExpanded: false,
taskList: [],
completedTaskList: [],
),
buttonText: AppLocalizations.of(context)!.addWidget_tasks,
iconData: Icons.task,
);
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimary,
),
title: Text(
AppLocalizations.of(context)!.addWidgetHeader,
textScaler: const TextScaler.linear(1.5),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.primary,
),
body: Container(
color: Theme.of(context).colorScheme.primaryContainer,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
addWaterWidget,
addTaskWidget,
addCompoundTimerWidget,
],
),
),
);
}
}

View file

@ -0,0 +1,109 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class DataPoint {
DataPoint({required this.date, required this.progress});
DateTime date;
double progress;
}
class GraphWidget extends ConsumerStatefulWidget {
GraphWidget({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _GraphWidgetState();
final entries = <DataPoint>[
DataPoint(date: DateTime(2024, 7, 4), progress: 1),
DataPoint(date: DateTime(2024, 7, 5), progress: 0.5),
DataPoint(date: DateTime(2024, 7, 6), progress: 1.25),
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
];
}
class _GraphWidgetState extends ConsumerState<GraphWidget> {
@override
Widget build(BuildContext context) {
final firstDate = widget.entries[0].date;
final flSpots = <FlSpot>[];
for (final dataPoint in widget.entries) {
final xValue = _daysBetween(firstDate, dataPoint.date).toDouble();
final lcbd = FlSpot(xValue, dataPoint.progress);
flSpots.add(lcbd);
}
final df = DateFormat('dd. MMMM');
return Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
height: MediaQuery.of(context).size.height * 0.5,
child: LineChart(
LineChartData(
minY: 0,
maxY: 1.5,
lineBarsData: [
LineChartBarData(spots: flSpots, isCurved: true),
],
gridData: const FlGridData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(
y: 1,
color: Colors.red,
),
],
),
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.toString()),
interval: 0.25,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
getTitlesWidget: (value, meta) => Text(
df.format(
DateTime(
firstDate.year,
firstDate.month,
firstDate.day,
).add(Duration(days: value.round())),
),
),
showTitles: true,
interval: 1,
),
),
),
),
),
),
const Text('You worked for X hours this past week'),
const Text('Out of a total of Y hours planned'),
],
);
}
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,27 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/sembast/item_repository.dart';
import 'package:habitrack_app/sembast/sembast_item_repository.dart';
final homeControllerProvider = Provider(
(ref) => HomeController(
itemRepository: ref.watch(itemRepositoryProvider),
),
);
class HomeController {
HomeController({required this.itemRepository});
final ItemRepository itemRepository;
Future<void> delete(int id) async {
await itemRepository.deleteItem(id);
}
Future<void> edit(dynamic item) async {
await itemRepository.updateItem(item);
}
Future<void> add(dynamic newItem) async {
await itemRepository.insertItem(newItem);
}
}

View file

@ -0,0 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/sembast/sembast_item_repository.dart';
final itemsProvider = StreamProvider(
(ref) => ref.watch(itemRepositoryProvider).getAllItemsStream(),
);

View file

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/function_widgets/compound_widgets/compound_timer_widget.dart';
import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_tasks.dart';
import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_water.dart';
import 'package:habitrack_app/infrastructure/widget_wall/add_widget_button.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/hydration.dart';
import 'package:habitrack_app/sembast/tasks_list.dart';
import 'package:habitrack_app/sembast/timer.dart';
/// Displays detailed information about a SampleItem.
class WidgetWall extends ConsumerStatefulWidget {
const WidgetWall({super.key});
@override
ConsumerState<WidgetWall> createState() => _WidgetWallState();
}
class _WidgetWallState extends ConsumerState<WidgetWall> {
Future<List<Widget>> buildList() async {
ref.watch(homeControllerProvider);
//var itemCount = 0;
final items = ref.watch(itemsProvider);
// ignore: unused_local_variable
final val = items.value;
return <Widget>[];
}
@override
Widget build(BuildContext context) {
// this.buildList();
final controller = ref.watch(itemsProvider);
switch (controller) {
case AsyncError(:final error):
return Text('Error: $error');
case AsyncData(:final value):
final allItems = value;
final items = <dynamic>[];
for (var i = 0; i < allItems.length; i++) {
// ignore: avoid_dynamic_calls
if (allItems.elementAt(i).isVisible == true) {
logger.i('INSERTING VISIBLE ITEM');
items.add(allItems.elementAt(i));
}
}
final itemWidgets = <Widget>[];
final itemCount = items.length;
for (var i = 0; i < itemCount; i++) {
final item = items.elementAt(i);
if (item is Hydration && item.isVisible) {
final itemwidget = CompoundWidgetWater(item: item);
itemWidgets.insert(i, itemwidget);
} else if (item is TimerItem && item.isVisible) {
if (item.isVisible == true) {
logger.i('VISIBLE');
final itemwidget = CompoundWidgetTimer(item: item);
itemWidgets.insert(i, itemwidget);
} else {
logger.i('IS NOT VISIBLE');
}
} else if (item is TasksItem && item.isVisible) {
final itemwidget = CompoundWidgetTasks(item: item);
itemWidgets.insert(i, itemwidget);
}
}
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor: Theme.of(context).colorScheme.secondary,
foregroundColor: Theme.of(context).colorScheme.primary,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
textScaler: const TextScaler.linear(2),
'Habitrack ',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
// fontStyle: FontStyle.italic,
),
),
const Align(
alignment: Alignment.topRight,
child: AddWidgetButton(),
),
],
),
),
body: ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,
child: ListView(
padding: const EdgeInsets.all(8),
children: itemWidgets,
),
),
);
default:
return const CircularProgressIndicator();
}
}
}

59
app/lib/l10n/app_de.arb Normal file
View file

@ -0,0 +1,59 @@
{
"settingsHeader": "Einstellungen",
"widgetSettings_durationPickerCurrent": "Derzeitige Laufzeit:",
"widgetSettings_durationPickerButton": "Laufzeit auswählen",
"addWidgetHeader": "Widget Hinzufügen",
"addWidget_water": "Wasser-Buddy hinzufügen",
"addWidget_timer": "Fokus-Timer hinzufügen",
"addWidget_tasks": "Tasks Widget hinzufügen",
"widgetSettings_name": "Name des Widgets",
"widgetSettings_saveButton": "Speichern",
"widgetSettings_deleteButton": "Löschen",
"widgetSettings_cancelButton": "Abbrechen",
"waterWidget_defaultName": "Trink-Buddy",
"waterWidgetSettings_button1": "Button 1 in ml",
"waterWidgetSettings_button2": "Button 2 in ml",
"waterWidgetSettings_goal": "Tagesziel",
"waterWidgetSettings_current": "Derzeit getrunken",
"waterWidget_customAmountButton": "+ Beliebig",
"waterWidget_customAmountMessage": "Geben sie die Geünschte Wassermenge ein:",
"tasksWidget_defaultName": "Tasks Widget",
"tasksWidget_addTaskButtonLabel": "Aufgabe Hinzufügen",
"tasksWidget_editTask": "Aufgabe bearbeiten",
"taskDefaultName": "Neue Aufgabe",
"taskSettings_name": "Aufgabenname",
"taskSettings_due": "Fällig am",
"taskSettings_duePicker": "Fälligkeitsdatum auswählen",
"tasksWidget_overdue": "Überfällig!",
"tasksWidget_deleteDone": "Erledigte Aufgaben löschen",
"tasksWidget_rescheduleOverdue": "Überfällige Aufgaben neu planen",
"timerWidget_defaultName": "Fokus-Timer",
"timerWidget_buttonStart": "Starten",
"timerWidget_buttonPause": "Anhalten",
"timerWidget_buttonContinue": "Weiter",
"timerWidget_buttonReset": "Zurücksetzen",
"timerWidgetSettings_duration": "Tägliches Ziel",
"timerWidget_current": "Jetzt",
"timerWidget_goal": "Ziel",
"timerWidget_pausedForEdit": "Halte den Timer an, um ihn bearbeiten zu können!"
}

64
app/lib/l10n/app_en.arb Normal file
View file

@ -0,0 +1,64 @@
{
"settingsHeader": "Settings",
"widgetSettings_durationPickerCurrent": "Current Duration:",
"widgetSettings_durationPickerButton": "Choose Duration",
"addWidgetHeader": "Widget Picker",
"addWidget_water": "Add Hydration Tracker",
"addWidget_timer": "Add Focus Timer",
"addWidget_tasks": "Add Tasks Widget",
"widgetSettings_name": "Widget Name",
"widgetSettings_saveButton": "Confirm",
"widgetSettings_deleteButton": "Delete",
"widgetSettings_cancelButton": "Cancel",
"waterWidget_defaultName": "Hydration Tracker",
"waterWidgetSettings_button1": "Button 1 in ml",
"waterWidgetSettings_button2": "Button 2 in ml",
"waterWidgetSettings_goal": "Daily Goal",
"waterWidgetSettings_current": "Current Amount",
"waterWidget_customAmountButton": "+ custom",
"waterWidget_customAmountMessage": "Enter an amount as desired:",
"tasksWidget_defaultName": "Tasks Widget",
"tasksWidget_addTaskButtonLabel": "Add Task",
"tasksWidget_editTask": "Edit Task",
"taskDefaultName": "Examplar Task",
"taskSettings_name": "Task Name",
"taskSettings_due": "Due Date",
"taskSettings_duePicker": "Choose a due date",
"tasksWidget_overdue": "Overdue!",
"tasksWidget_deleteDone": "Delete completed tasks",
"tasksWidget_rescheduleOverdue": "Reschedule overdue tasks",
"timerWidget_defaultName": "Focus Timer",
"timerWidget_buttonStart": "Start",
"timerWidget_buttonPause": "Stop",
"timerWidget_buttonContinue": "Continue",
"timerWidget_buttonReset": "Reset",
"timerWidgetSettings_duration": "Daily Goal",
"timerWidget_current": "Current",
"timerWidget_goal": "Goal",
"timerWidget_pausedForEdit": "Timer must be paused before editing!"
}

86
app/lib/main.dart Normal file
View file

@ -0,0 +1,86 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/infrastructure/routing.dart';
import 'package:habitrack_app/sembast/global_providers.dart';
import 'package:logger/logger.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sembast/sembast_io.dart';
import 'package:sembast_web/sembast_web.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kIsWeb) {
final factory = databaseFactoryWeb;
final db = await factory.openDatabase('test');
runApp(
ProviderScope(
overrides: [databaseProvider.overrideWithValue(db)],
child: const MainApp(),
),
);
// running on the web!
} else {
final appPath = await getApplicationDocumentsDirectory();
appPath.createSync(recursive: true);
final dbPath = join(appPath.path, 'widgets.db');
final database = await databaseFactoryIo.openDatabase(dbPath);
const androidConfig = FlutterBackgroundAndroidConfig(
notificationTitle: 'flutter_background example app',
notificationText:
// ignore: lines_longer_than_80_chars
'Background notification for keeping the example app running in the background',
notificationIcon: AndroidResource(
name: 'background_icon',
// ignore: avoid_redundant_argument_values
defType: 'drawable',
), // Default is ic_launcher from folder mipmap
);
await FlutterBackground.initialize(androidConfig: androidConfig);
await FlutterBackground.enableBackgroundExecution();
runApp(
ProviderScope(
overrides: [
databaseProvider.overrideWithValue(database),
],
child: const MainApp(),
),
);
}
}
Logger logger = Logger();
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Habitrack',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.amber,
),
),
),
routerConfig: goRouter,
);
}
}

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

View file

@ -0,0 +1,5 @@
import 'package:riverpod/riverpod.dart';
import 'package:sembast/sembast.dart';
final databaseProvider =
Provider<Database>((_) => throw Exception('Database not initialized'));

View file

@ -0,0 +1,31 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'hydration.freezed.dart';
part 'hydration.g.dart';
@freezed
class Hydration with _$Hydration {
const factory Hydration({
required String widgetType,
required String name,
required int button1Amount,
required int button2Amount,
required int goal,
required int current,
required bool isExpanded,
required bool isVisible,
required String createdOn,
required String completedOn,
@Default(-1) int id,
}) = _Hydration;
factory Hydration.fromJson(Map<String, Object?> json) =>
_$HydrationFromJson(json);
}
extension JsonWithoutId on Hydration {
Map<String, dynamic> toJsonWithoutId() {
final map = toJson()..remove('id');
return map;
}
}

View file

@ -0,0 +1,392 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'hydration.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
Hydration _$HydrationFromJson(Map<String, dynamic> json) {
return _Hydration.fromJson(json);
}
/// @nodoc
mixin _$Hydration {
String get widgetType => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
int get button1Amount => throw _privateConstructorUsedError;
int get button2Amount => throw _privateConstructorUsedError;
int get goal => throw _privateConstructorUsedError;
int get current => throw _privateConstructorUsedError;
bool get isExpanded => throw _privateConstructorUsedError;
bool get isVisible => throw _privateConstructorUsedError;
String get createdOn => throw _privateConstructorUsedError;
String get completedOn => throw _privateConstructorUsedError;
int get id => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HydrationCopyWith<Hydration> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HydrationCopyWith<$Res> {
factory $HydrationCopyWith(Hydration value, $Res Function(Hydration) then) =
_$HydrationCopyWithImpl<$Res, Hydration>;
@useResult
$Res call(
{String widgetType,
String name,
int button1Amount,
int button2Amount,
int goal,
int current,
bool isExpanded,
bool isVisible,
String createdOn,
String completedOn,
int id});
}
/// @nodoc
class _$HydrationCopyWithImpl<$Res, $Val extends Hydration>
implements $HydrationCopyWith<$Res> {
_$HydrationCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? button1Amount = null,
Object? button2Amount = null,
Object? goal = null,
Object? current = null,
Object? isExpanded = null,
Object? isVisible = null,
Object? createdOn = null,
Object? completedOn = null,
Object? id = null,
}) {
return _then(_value.copyWith(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
button1Amount: null == button1Amount
? _value.button1Amount
: button1Amount // ignore: cast_nullable_to_non_nullable
as int,
button2Amount: null == button2Amount
? _value.button2Amount
: button2Amount // ignore: cast_nullable_to_non_nullable
as int,
goal: null == goal
? _value.goal
: goal // ignore: cast_nullable_to_non_nullable
as int,
current: null == current
? _value.current
: current // ignore: cast_nullable_to_non_nullable
as int,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
createdOn: null == createdOn
? _value.createdOn
: createdOn // ignore: cast_nullable_to_non_nullable
as String,
completedOn: null == completedOn
? _value.completedOn
: completedOn // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$HydrationImplCopyWith<$Res>
implements $HydrationCopyWith<$Res> {
factory _$$HydrationImplCopyWith(
_$HydrationImpl value, $Res Function(_$HydrationImpl) then) =
__$$HydrationImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String widgetType,
String name,
int button1Amount,
int button2Amount,
int goal,
int current,
bool isExpanded,
bool isVisible,
String createdOn,
String completedOn,
int id});
}
/// @nodoc
class __$$HydrationImplCopyWithImpl<$Res>
extends _$HydrationCopyWithImpl<$Res, _$HydrationImpl>
implements _$$HydrationImplCopyWith<$Res> {
__$$HydrationImplCopyWithImpl(
_$HydrationImpl _value, $Res Function(_$HydrationImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? button1Amount = null,
Object? button2Amount = null,
Object? goal = null,
Object? current = null,
Object? isExpanded = null,
Object? isVisible = null,
Object? createdOn = null,
Object? completedOn = null,
Object? id = null,
}) {
return _then(_$HydrationImpl(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
button1Amount: null == button1Amount
? _value.button1Amount
: button1Amount // ignore: cast_nullable_to_non_nullable
as int,
button2Amount: null == button2Amount
? _value.button2Amount
: button2Amount // ignore: cast_nullable_to_non_nullable
as int,
goal: null == goal
? _value.goal
: goal // ignore: cast_nullable_to_non_nullable
as int,
current: null == current
? _value.current
: current // ignore: cast_nullable_to_non_nullable
as int,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
createdOn: null == createdOn
? _value.createdOn
: createdOn // ignore: cast_nullable_to_non_nullable
as String,
completedOn: null == completedOn
? _value.completedOn
: completedOn // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HydrationImpl with DiagnosticableTreeMixin implements _Hydration {
const _$HydrationImpl(
{required this.widgetType,
required this.name,
required this.button1Amount,
required this.button2Amount,
required this.goal,
required this.current,
required this.isExpanded,
required this.isVisible,
required this.createdOn,
required this.completedOn,
this.id = -1});
factory _$HydrationImpl.fromJson(Map<String, dynamic> json) =>
_$$HydrationImplFromJson(json);
@override
final String widgetType;
@override
final String name;
@override
final int button1Amount;
@override
final int button2Amount;
@override
final int goal;
@override
final int current;
@override
final bool isExpanded;
@override
final bool isVisible;
@override
final String createdOn;
@override
final String completedOn;
@override
@JsonKey()
final int id;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Hydration(widgetType: $widgetType, name: $name, button1Amount: $button1Amount, button2Amount: $button2Amount, goal: $goal, current: $current, isExpanded: $isExpanded, isVisible: $isVisible, createdOn: $createdOn, completedOn: $completedOn, id: $id)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Hydration'))
..add(DiagnosticsProperty('widgetType', widgetType))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('button1Amount', button1Amount))
..add(DiagnosticsProperty('button2Amount', button2Amount))
..add(DiagnosticsProperty('goal', goal))
..add(DiagnosticsProperty('current', current))
..add(DiagnosticsProperty('isExpanded', isExpanded))
..add(DiagnosticsProperty('isVisible', isVisible))
..add(DiagnosticsProperty('createdOn', createdOn))
..add(DiagnosticsProperty('completedOn', completedOn))
..add(DiagnosticsProperty('id', id));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HydrationImpl &&
(identical(other.widgetType, widgetType) ||
other.widgetType == widgetType) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.button1Amount, button1Amount) ||
other.button1Amount == button1Amount) &&
(identical(other.button2Amount, button2Amount) ||
other.button2Amount == button2Amount) &&
(identical(other.goal, goal) || other.goal == goal) &&
(identical(other.current, current) || other.current == current) &&
(identical(other.isExpanded, isExpanded) ||
other.isExpanded == isExpanded) &&
(identical(other.isVisible, isVisible) ||
other.isVisible == isVisible) &&
(identical(other.createdOn, createdOn) ||
other.createdOn == createdOn) &&
(identical(other.completedOn, completedOn) ||
other.completedOn == completedOn) &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
widgetType,
name,
button1Amount,
button2Amount,
goal,
current,
isExpanded,
isVisible,
createdOn,
completedOn,
id);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HydrationImplCopyWith<_$HydrationImpl> get copyWith =>
__$$HydrationImplCopyWithImpl<_$HydrationImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HydrationImplToJson(
this,
);
}
}
abstract class _Hydration implements Hydration {
const factory _Hydration(
{required final String widgetType,
required final String name,
required final int button1Amount,
required final int button2Amount,
required final int goal,
required final int current,
required final bool isExpanded,
required final bool isVisible,
required final String createdOn,
required final String completedOn,
final int id}) = _$HydrationImpl;
factory _Hydration.fromJson(Map<String, dynamic> json) =
_$HydrationImpl.fromJson;
@override
String get widgetType;
@override
String get name;
@override
int get button1Amount;
@override
int get button2Amount;
@override
int get goal;
@override
int get current;
@override
bool get isExpanded;
@override
bool get isVisible;
@override
String get createdOn;
@override
String get completedOn;
@override
int get id;
@override
@JsonKey(ignore: true)
_$$HydrationImplCopyWith<_$HydrationImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'hydration.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HydrationImpl _$$HydrationImplFromJson(Map<String, dynamic> json) =>
_$HydrationImpl(
widgetType: json['widgetType'] as String,
name: json['name'] as String,
button1Amount: (json['button1Amount'] as num).toInt(),
button2Amount: (json['button2Amount'] as num).toInt(),
goal: (json['goal'] as num).toInt(),
current: (json['current'] as num).toInt(),
isExpanded: json['isExpanded'] as bool,
isVisible: json['isVisible'] as bool,
createdOn: json['createdOn'] as String,
completedOn: json['completedOn'] as String,
id: (json['id'] as num?)?.toInt() ?? -1,
);
Map<String, dynamic> _$$HydrationImplToJson(_$HydrationImpl instance) =>
<String, dynamic>{
'widgetType': instance.widgetType,
'name': instance.name,
'button1Amount': instance.button1Amount,
'button2Amount': instance.button2Amount,
'goal': instance.goal,
'current': instance.current,
'isExpanded': instance.isExpanded,
'isVisible': instance.isVisible,
'createdOn': instance.createdOn,
'completedOn': instance.completedOn,
'id': instance.id,
};

View file

@ -0,0 +1,9 @@
abstract class ItemRepository {
Future<int> insertItem(dynamic item);
Future<void> updateItem(dynamic item);
Future<void> deleteItem(int itemId);
Stream<List<dynamic>> getAllItemsStream();
}

View file

@ -0,0 +1,71 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:habitrack_app/main.dart';
import 'package:habitrack_app/sembast/global_providers.dart';
import 'package:habitrack_app/sembast/hydration.dart';
import 'package:habitrack_app/sembast/item_repository.dart';
import 'package:habitrack_app/sembast/tasks_list.dart';
import 'package:habitrack_app/sembast/timer.dart';
import 'package:sembast/sembast.dart';
final itemRepositoryProvider = Provider(
(ref) => SembastItemRepository(
database: ref.watch(databaseProvider),
),
);
class SembastItemRepository implements ItemRepository {
SembastItemRepository({required this.database}) {
_store = intMapStoreFactory.store('item_store');
}
final Database database;
late final StoreRef<int, Map<String, dynamic>> _store;
@override
Future<int> insertItem(dynamic item) {
if (item is Hydration) {
_store.add(database, item.toJson());
} else if (item is TimerItem) {
_store.add(database, item.toJson());
} else if (item is TasksItem) {
_store.add(database, item.toJson());
}
return Future.value(0);
}
@override
Future<void> updateItem(dynamic item) {
if (item is Hydration) {
_store.record(item.id).update(database, item.toJson());
} else if (item is TimerItem) {
_store.record(item.id).update(database, item.toJson());
} else if (item is TasksItem) {
logger.i('UPDATING TASKS ITEM');
_store.record(item.id).update(database, item.toJson());
}
logger.i('Item got past update: $item');
return Future.value();
// throw Error();
}
@override
Future<void> deleteItem(int itemId) => _store.record(itemId).delete(database);
@override
Stream<List<dynamic>> getAllItemsStream() =>
_store.query().onSnapshots(database).map(
(snapshot) => snapshot.map((item) {
logger.i('Querying the database! ${item.value}');
if (item.value.toString().contains('widgetType: Hydration')) {
logger.i('Le hydration has arrived');
return Hydration.fromJson(item.value).copyWith(id: item.key);
} else if (item.value.toString().contains('widgetType: Timer')) {
logger.i('Le timer has arrived');
return TimerItem.fromJson(item.value).copyWith(id: item.key);
} else if (item.value.toString().contains('widgetType: TODO')) {
logger.i('Le TODO has arrived');
return TasksItem.fromJson(item.value).copyWith(id: item.key);
}
}).toList(growable: false),
);
}

View file

@ -0,0 +1,53 @@
// ignore_for_file: flutter_style_todos
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'tasks_list.freezed.dart';
part 'tasks_list.g.dart';
@freezed
class IndividualTodo with _$IndividualTodo {
const factory IndividualTodo({
required String todo,
required bool isCompleted,
required DateTime dueDate,
}) = _IndividualTodo;
factory IndividualTodo.fromJson(Map<String, Object?> json) =>
_$IndividualTodoFromJson(json);
}
@freezed
class TasksItem with _$TasksItem {
const factory TasksItem({
required String widgetType,
required String name,
required bool isExpanded,
required List<String> taskList,
required List<String> completedTaskList,
required bool isVisible,
@Default(-1) int id,
}) = _TasksItem;
factory TasksItem.fromJson(Map<String, Object?> json) =>
_$TasksItemFromJson(json);
}
extension JsonWithoutId on TasksItem {
Map<String, dynamic> toJsonWithoutId() {
final map = toJson()..remove('id');
// map['taskList'] = taskList.map((todo) => todo.toJson()).toList();
//map['completedTaskList'] =
// completedTaskList.map((todo) => todo.toJson()).toList();
return map;
}
}
/*
extension JsonWithoutID on TODO {
Map<String, dynamic> toJsonWithoutId() {
final map = toJson()..remove('id');
map['taskList'] = taskList.map((todo) => todo.toJson()).toList();
map['completedTaskList'] =
completedTaskList.map((todo) => todo.toJson()).toList();
return map;
}
}*/

View file

@ -0,0 +1,502 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'tasks_list.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
IndividualTodo _$IndividualTodoFromJson(Map<String, dynamic> json) {
return _IndividualTodo.fromJson(json);
}
/// @nodoc
mixin _$IndividualTodo {
String get todo => throw _privateConstructorUsedError;
bool get isCompleted => throw _privateConstructorUsedError;
DateTime get dueDate => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IndividualTodoCopyWith<IndividualTodo> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IndividualTodoCopyWith<$Res> {
factory $IndividualTodoCopyWith(
IndividualTodo value, $Res Function(IndividualTodo) then) =
_$IndividualTodoCopyWithImpl<$Res, IndividualTodo>;
@useResult
$Res call({String todo, bool isCompleted, DateTime dueDate});
}
/// @nodoc
class _$IndividualTodoCopyWithImpl<$Res, $Val extends IndividualTodo>
implements $IndividualTodoCopyWith<$Res> {
_$IndividualTodoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? todo = null,
Object? isCompleted = null,
Object? dueDate = null,
}) {
return _then(_value.copyWith(
todo: null == todo
? _value.todo
: todo // ignore: cast_nullable_to_non_nullable
as String,
isCompleted: null == isCompleted
? _value.isCompleted
: isCompleted // ignore: cast_nullable_to_non_nullable
as bool,
dueDate: null == dueDate
? _value.dueDate
: dueDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$IndividualTodoImplCopyWith<$Res>
implements $IndividualTodoCopyWith<$Res> {
factory _$$IndividualTodoImplCopyWith(_$IndividualTodoImpl value,
$Res Function(_$IndividualTodoImpl) then) =
__$$IndividualTodoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String todo, bool isCompleted, DateTime dueDate});
}
/// @nodoc
class __$$IndividualTodoImplCopyWithImpl<$Res>
extends _$IndividualTodoCopyWithImpl<$Res, _$IndividualTodoImpl>
implements _$$IndividualTodoImplCopyWith<$Res> {
__$$IndividualTodoImplCopyWithImpl(
_$IndividualTodoImpl _value, $Res Function(_$IndividualTodoImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? todo = null,
Object? isCompleted = null,
Object? dueDate = null,
}) {
return _then(_$IndividualTodoImpl(
todo: null == todo
? _value.todo
: todo // ignore: cast_nullable_to_non_nullable
as String,
isCompleted: null == isCompleted
? _value.isCompleted
: isCompleted // ignore: cast_nullable_to_non_nullable
as bool,
dueDate: null == dueDate
? _value.dueDate
: dueDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IndividualTodoImpl
with DiagnosticableTreeMixin
implements _IndividualTodo {
const _$IndividualTodoImpl(
{required this.todo, required this.isCompleted, required this.dueDate});
factory _$IndividualTodoImpl.fromJson(Map<String, dynamic> json) =>
_$$IndividualTodoImplFromJson(json);
@override
final String todo;
@override
final bool isCompleted;
@override
final DateTime dueDate;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'IndividualTodo(todo: $todo, isCompleted: $isCompleted, dueDate: $dueDate)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'IndividualTodo'))
..add(DiagnosticsProperty('todo', todo))
..add(DiagnosticsProperty('isCompleted', isCompleted))
..add(DiagnosticsProperty('dueDate', dueDate));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IndividualTodoImpl &&
(identical(other.todo, todo) || other.todo == todo) &&
(identical(other.isCompleted, isCompleted) ||
other.isCompleted == isCompleted) &&
(identical(other.dueDate, dueDate) || other.dueDate == dueDate));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, todo, isCompleted, dueDate);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IndividualTodoImplCopyWith<_$IndividualTodoImpl> get copyWith =>
__$$IndividualTodoImplCopyWithImpl<_$IndividualTodoImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IndividualTodoImplToJson(
this,
);
}
}
abstract class _IndividualTodo implements IndividualTodo {
const factory _IndividualTodo(
{required final String todo,
required final bool isCompleted,
required final DateTime dueDate}) = _$IndividualTodoImpl;
factory _IndividualTodo.fromJson(Map<String, dynamic> json) =
_$IndividualTodoImpl.fromJson;
@override
String get todo;
@override
bool get isCompleted;
@override
DateTime get dueDate;
@override
@JsonKey(ignore: true)
_$$IndividualTodoImplCopyWith<_$IndividualTodoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
TasksItem _$TasksItemFromJson(Map<String, dynamic> json) {
return _TasksItem.fromJson(json);
}
/// @nodoc
mixin _$TasksItem {
String get widgetType => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
bool get isExpanded => throw _privateConstructorUsedError;
List<String> get taskList => throw _privateConstructorUsedError;
List<String> get completedTaskList => throw _privateConstructorUsedError;
bool get isVisible => throw _privateConstructorUsedError;
int get id => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TasksItemCopyWith<TasksItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TasksItemCopyWith<$Res> {
factory $TasksItemCopyWith(TasksItem value, $Res Function(TasksItem) then) =
_$TasksItemCopyWithImpl<$Res, TasksItem>;
@useResult
$Res call(
{String widgetType,
String name,
bool isExpanded,
List<String> taskList,
List<String> completedTaskList,
bool isVisible,
int id});
}
/// @nodoc
class _$TasksItemCopyWithImpl<$Res, $Val extends TasksItem>
implements $TasksItemCopyWith<$Res> {
_$TasksItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? isExpanded = null,
Object? taskList = null,
Object? completedTaskList = null,
Object? isVisible = null,
Object? id = null,
}) {
return _then(_value.copyWith(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
taskList: null == taskList
? _value.taskList
: taskList // ignore: cast_nullable_to_non_nullable
as List<String>,
completedTaskList: null == completedTaskList
? _value.completedTaskList
: completedTaskList // ignore: cast_nullable_to_non_nullable
as List<String>,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$TasksItemImplCopyWith<$Res>
implements $TasksItemCopyWith<$Res> {
factory _$$TasksItemImplCopyWith(
_$TasksItemImpl value, $Res Function(_$TasksItemImpl) then) =
__$$TasksItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String widgetType,
String name,
bool isExpanded,
List<String> taskList,
List<String> completedTaskList,
bool isVisible,
int id});
}
/// @nodoc
class __$$TasksItemImplCopyWithImpl<$Res>
extends _$TasksItemCopyWithImpl<$Res, _$TasksItemImpl>
implements _$$TasksItemImplCopyWith<$Res> {
__$$TasksItemImplCopyWithImpl(
_$TasksItemImpl _value, $Res Function(_$TasksItemImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? isExpanded = null,
Object? taskList = null,
Object? completedTaskList = null,
Object? isVisible = null,
Object? id = null,
}) {
return _then(_$TasksItemImpl(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
taskList: null == taskList
? _value._taskList
: taskList // ignore: cast_nullable_to_non_nullable
as List<String>,
completedTaskList: null == completedTaskList
? _value._completedTaskList
: completedTaskList // ignore: cast_nullable_to_non_nullable
as List<String>,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TasksItemImpl with DiagnosticableTreeMixin implements _TasksItem {
const _$TasksItemImpl(
{required this.widgetType,
required this.name,
required this.isExpanded,
required final List<String> taskList,
required final List<String> completedTaskList,
required this.isVisible,
this.id = -1})
: _taskList = taskList,
_completedTaskList = completedTaskList;
factory _$TasksItemImpl.fromJson(Map<String, dynamic> json) =>
_$$TasksItemImplFromJson(json);
@override
final String widgetType;
@override
final String name;
@override
final bool isExpanded;
final List<String> _taskList;
@override
List<String> get taskList {
if (_taskList is EqualUnmodifiableListView) return _taskList;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_taskList);
}
final List<String> _completedTaskList;
@override
List<String> get completedTaskList {
if (_completedTaskList is EqualUnmodifiableListView)
return _completedTaskList;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_completedTaskList);
}
@override
final bool isVisible;
@override
@JsonKey()
final int id;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'TasksItem(widgetType: $widgetType, name: $name, isExpanded: $isExpanded, taskList: $taskList, completedTaskList: $completedTaskList, isVisible: $isVisible, id: $id)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'TasksItem'))
..add(DiagnosticsProperty('widgetType', widgetType))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('isExpanded', isExpanded))
..add(DiagnosticsProperty('taskList', taskList))
..add(DiagnosticsProperty('completedTaskList', completedTaskList))
..add(DiagnosticsProperty('isVisible', isVisible))
..add(DiagnosticsProperty('id', id));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TasksItemImpl &&
(identical(other.widgetType, widgetType) ||
other.widgetType == widgetType) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.isExpanded, isExpanded) ||
other.isExpanded == isExpanded) &&
const DeepCollectionEquality().equals(other._taskList, _taskList) &&
const DeepCollectionEquality()
.equals(other._completedTaskList, _completedTaskList) &&
(identical(other.isVisible, isVisible) ||
other.isVisible == isVisible) &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
widgetType,
name,
isExpanded,
const DeepCollectionEquality().hash(_taskList),
const DeepCollectionEquality().hash(_completedTaskList),
isVisible,
id);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TasksItemImplCopyWith<_$TasksItemImpl> get copyWith =>
__$$TasksItemImplCopyWithImpl<_$TasksItemImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TasksItemImplToJson(
this,
);
}
}
abstract class _TasksItem implements TasksItem {
const factory _TasksItem(
{required final String widgetType,
required final String name,
required final bool isExpanded,
required final List<String> taskList,
required final List<String> completedTaskList,
required final bool isVisible,
final int id}) = _$TasksItemImpl;
factory _TasksItem.fromJson(Map<String, dynamic> json) =
_$TasksItemImpl.fromJson;
@override
String get widgetType;
@override
String get name;
@override
bool get isExpanded;
@override
List<String> get taskList;
@override
List<String> get completedTaskList;
@override
bool get isVisible;
@override
int get id;
@override
@JsonKey(ignore: true)
_$$TasksItemImplCopyWith<_$TasksItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tasks_list.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$IndividualTodoImpl _$$IndividualTodoImplFromJson(Map<String, dynamic> json) =>
_$IndividualTodoImpl(
todo: json['todo'] as String,
isCompleted: json['isCompleted'] as bool,
dueDate: DateTime.parse(json['dueDate'] as String),
);
Map<String, dynamic> _$$IndividualTodoImplToJson(
_$IndividualTodoImpl instance) =>
<String, dynamic>{
'todo': instance.todo,
'isCompleted': instance.isCompleted,
'dueDate': instance.dueDate.toIso8601String(),
};
_$TasksItemImpl _$$TasksItemImplFromJson(Map<String, dynamic> json) =>
_$TasksItemImpl(
widgetType: json['widgetType'] as String,
name: json['name'] as String,
isExpanded: json['isExpanded'] as bool,
taskList:
(json['taskList'] as List<dynamic>).map((e) => e as String).toList(),
completedTaskList: (json['completedTaskList'] as List<dynamic>)
.map((e) => e as String)
.toList(),
isVisible: json['isVisible'] as bool,
id: (json['id'] as num?)?.toInt() ?? -1,
);
Map<String, dynamic> _$$TasksItemImplToJson(_$TasksItemImpl instance) =>
<String, dynamic>{
'widgetType': instance.widgetType,
'name': instance.name,
'isExpanded': instance.isExpanded,
'taskList': instance.taskList,
'completedTaskList': instance.completedTaskList,
'isVisible': instance.isVisible,
'id': instance.id,
};

View file

@ -0,0 +1,50 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'timer.freezed.dart';
part 'timer.g.dart';
@freezed
class TimerItem with _$TimerItem {
const factory TimerItem({
required String widgetType,
required String name,
required int current,
required int goal,
required bool isExpanded,
required bool isVisible,
required String createdOn,
required String completedOn,
required String state,
@Default(-1) int id,
}) = _TimerItem;
factory TimerItem.fromJson(Map<String, Object?> json) =>
_$TimerItemFromJson(json);
}
extension JsonWithoutId on TimerItem {
Map<String, dynamic> toJsonWithoutId() {
final map = toJson()..remove('id');
return map;
}
}
/*
@freezed
class Student with _$Student {
const factory Student({
required String name,
int? id,
}) = _Student;
factory Student.fromJson(Map<String, Object?> json) =>
_$StudentFromJson(json);
}
extension JsonWithoutId on Student {
Map<String, dynamic> toJsonWithoutId() {
final map = toJson()..remove('id');
return map;
}
}
*/

View file

@ -0,0 +1,358 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'timer.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
TimerItem _$TimerItemFromJson(Map<String, dynamic> json) {
return _TimerItem.fromJson(json);
}
/// @nodoc
mixin _$TimerItem {
String get widgetType => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
int get current => throw _privateConstructorUsedError;
int get goal => throw _privateConstructorUsedError;
bool get isExpanded => throw _privateConstructorUsedError;
bool get isVisible => throw _privateConstructorUsedError;
String get createdOn => throw _privateConstructorUsedError;
String get completedOn => throw _privateConstructorUsedError;
String get state => throw _privateConstructorUsedError;
int get id => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TimerItemCopyWith<TimerItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TimerItemCopyWith<$Res> {
factory $TimerItemCopyWith(TimerItem value, $Res Function(TimerItem) then) =
_$TimerItemCopyWithImpl<$Res, TimerItem>;
@useResult
$Res call(
{String widgetType,
String name,
int current,
int goal,
bool isExpanded,
bool isVisible,
String createdOn,
String completedOn,
String state,
int id});
}
/// @nodoc
class _$TimerItemCopyWithImpl<$Res, $Val extends TimerItem>
implements $TimerItemCopyWith<$Res> {
_$TimerItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? current = null,
Object? goal = null,
Object? isExpanded = null,
Object? isVisible = null,
Object? createdOn = null,
Object? completedOn = null,
Object? state = null,
Object? id = null,
}) {
return _then(_value.copyWith(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
current: null == current
? _value.current
: current // ignore: cast_nullable_to_non_nullable
as int,
goal: null == goal
? _value.goal
: goal // ignore: cast_nullable_to_non_nullable
as int,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
createdOn: null == createdOn
? _value.createdOn
: createdOn // ignore: cast_nullable_to_non_nullable
as String,
completedOn: null == completedOn
? _value.completedOn
: completedOn // ignore: cast_nullable_to_non_nullable
as String,
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$TimerItemImplCopyWith<$Res>
implements $TimerItemCopyWith<$Res> {
factory _$$TimerItemImplCopyWith(
_$TimerItemImpl value, $Res Function(_$TimerItemImpl) then) =
__$$TimerItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String widgetType,
String name,
int current,
int goal,
bool isExpanded,
bool isVisible,
String createdOn,
String completedOn,
String state,
int id});
}
/// @nodoc
class __$$TimerItemImplCopyWithImpl<$Res>
extends _$TimerItemCopyWithImpl<$Res, _$TimerItemImpl>
implements _$$TimerItemImplCopyWith<$Res> {
__$$TimerItemImplCopyWithImpl(
_$TimerItemImpl _value, $Res Function(_$TimerItemImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? widgetType = null,
Object? name = null,
Object? current = null,
Object? goal = null,
Object? isExpanded = null,
Object? isVisible = null,
Object? createdOn = null,
Object? completedOn = null,
Object? state = null,
Object? id = null,
}) {
return _then(_$TimerItemImpl(
widgetType: null == widgetType
? _value.widgetType
: widgetType // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
current: null == current
? _value.current
: current // ignore: cast_nullable_to_non_nullable
as int,
goal: null == goal
? _value.goal
: goal // ignore: cast_nullable_to_non_nullable
as int,
isExpanded: null == isExpanded
? _value.isExpanded
: isExpanded // ignore: cast_nullable_to_non_nullable
as bool,
isVisible: null == isVisible
? _value.isVisible
: isVisible // ignore: cast_nullable_to_non_nullable
as bool,
createdOn: null == createdOn
? _value.createdOn
: createdOn // ignore: cast_nullable_to_non_nullable
as String,
completedOn: null == completedOn
? _value.completedOn
: completedOn // ignore: cast_nullable_to_non_nullable
as String,
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as String,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TimerItemImpl with DiagnosticableTreeMixin implements _TimerItem {
const _$TimerItemImpl(
{required this.widgetType,
required this.name,
required this.current,
required this.goal,
required this.isExpanded,
required this.isVisible,
required this.createdOn,
required this.completedOn,
required this.state,
this.id = -1});
factory _$TimerItemImpl.fromJson(Map<String, dynamic> json) =>
_$$TimerItemImplFromJson(json);
@override
final String widgetType;
@override
final String name;
@override
final int current;
@override
final int goal;
@override
final bool isExpanded;
@override
final bool isVisible;
@override
final String createdOn;
@override
final String completedOn;
@override
final String state;
@override
@JsonKey()
final int id;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'TimerItem(widgetType: $widgetType, name: $name, current: $current, goal: $goal, isExpanded: $isExpanded, isVisible: $isVisible, createdOn: $createdOn, completedOn: $completedOn, state: $state, id: $id)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'TimerItem'))
..add(DiagnosticsProperty('widgetType', widgetType))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('current', current))
..add(DiagnosticsProperty('goal', goal))
..add(DiagnosticsProperty('isExpanded', isExpanded))
..add(DiagnosticsProperty('isVisible', isVisible))
..add(DiagnosticsProperty('createdOn', createdOn))
..add(DiagnosticsProperty('completedOn', completedOn))
..add(DiagnosticsProperty('state', state))
..add(DiagnosticsProperty('id', id));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TimerItemImpl &&
(identical(other.widgetType, widgetType) ||
other.widgetType == widgetType) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.current, current) || other.current == current) &&
(identical(other.goal, goal) || other.goal == goal) &&
(identical(other.isExpanded, isExpanded) ||
other.isExpanded == isExpanded) &&
(identical(other.isVisible, isVisible) ||
other.isVisible == isVisible) &&
(identical(other.createdOn, createdOn) ||
other.createdOn == createdOn) &&
(identical(other.completedOn, completedOn) ||
other.completedOn == completedOn) &&
(identical(other.state, state) || other.state == state) &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, widgetType, name, current, goal,
isExpanded, isVisible, createdOn, completedOn, state, id);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TimerItemImplCopyWith<_$TimerItemImpl> get copyWith =>
__$$TimerItemImplCopyWithImpl<_$TimerItemImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TimerItemImplToJson(
this,
);
}
}
abstract class _TimerItem implements TimerItem {
const factory _TimerItem(
{required final String widgetType,
required final String name,
required final int current,
required final int goal,
required final bool isExpanded,
required final bool isVisible,
required final String createdOn,
required final String completedOn,
required final String state,
final int id}) = _$TimerItemImpl;
factory _TimerItem.fromJson(Map<String, dynamic> json) =
_$TimerItemImpl.fromJson;
@override
String get widgetType;
@override
String get name;
@override
int get current;
@override
int get goal;
@override
bool get isExpanded;
@override
bool get isVisible;
@override
String get createdOn;
@override
String get completedOn;
@override
String get state;
@override
int get id;
@override
@JsonKey(ignore: true)
_$$TimerItemImplCopyWith<_$TimerItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,35 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'timer.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TimerItemImpl _$$TimerItemImplFromJson(Map<String, dynamic> json) =>
_$TimerItemImpl(
widgetType: json['widgetType'] as String,
name: json['name'] as String,
current: (json['current'] as num).toInt(),
goal: (json['goal'] as num).toInt(),
isExpanded: json['isExpanded'] as bool,
isVisible: json['isVisible'] as bool,
createdOn: json['createdOn'] as String,
completedOn: json['completedOn'] as String,
state: json['state'] as String,
id: (json['id'] as num?)?.toInt() ?? -1,
);
Map<String, dynamic> _$$TimerItemImplToJson(_$TimerItemImpl instance) =>
<String, dynamic>{
'widgetType': instance.widgetType,
'name': instance.name,
'current': instance.current,
'goal': instance.goal,
'isExpanded': instance.isExpanded,
'isVisible': instance.isVisible,
'createdOn': instance.createdOn,
'completedOn': instance.completedOn,
'state': instance.state,
'id': instance.id,
};