969 lines
32 KiB
Dart
969 lines
32 KiB
Dart
![]() |
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,
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
},
|
||
|
);
|
||
|
}
|
||
|
}
|