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 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 = [...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 = [...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 { _IndividualTodoWidgetState(); void _toggleChecked(bool? param) { if (widget.overdue() && !widget.completed) { } else { final searchTerm = widget.stringRepr(); final completedTaskList = [...widget.item.completedTaskList]; final taskList = [...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 = [...widget.item.completedTaskList]; final taskList = [...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 _editPopup(String oldItem) async { final todoFieldEditController = TextEditingController(text: widget.todo); DateTime? date = widget.due; return showDialog( 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: [ 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: [ 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 createState() => _CompoundWidgetTasksState(); } class _CompoundWidgetTasksState extends ConsumerState { 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 _buildTodoList() { final items = []; final taskList = [...widget.item.taskList]; final completedTaskList = [...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 = [...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(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 = []; // 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 = [...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 = [...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 _addPopup() async { DateTime? date = DateTime.now(); return showDialog( 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: [ 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: [ 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 _showSettingsMenu(WidgetRef ref, BuildContext context) async { final settingEntries = WidgetSettingsData( entries: { 'name': SettingEntryText( name: AppLocalizations.of(context)!.widgetSettings_name, defaultValue: widget.item.name, ), }, ); return showDialog( 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, ), ), ), ], ); }, ); } }