This commit is contained in:
brausjonas
2024-09-15 19:51:29 +02:00
parent 8740525457
commit 77a411e4b7
8 changed files with 391 additions and 110 deletions
+2 -1
View File
@@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class Todo { class Todo {
Todo({required this.title, required this.widget, required this.checked}); Todo({required this.title, required this.widget, required this.checked, required this.dueTime});
String title; String title;
Widget widget; Widget widget;
DateTime dueTime;
bool checked; bool checked;
} }
+100 -38
View File
@@ -1,56 +1,118 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart'; import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/modals/datepickermodal.dart';
import 'package:todoapp/modals/timepickermodal.dart';
import 'package:todoapp/providers/todoprovider.dart'; import 'package:todoapp/providers/todoprovider.dart';
class AddTodoModal { class AddTodoModal {
static String tempValue = "";
static void add(BuildContext context) { static void add(BuildContext context) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
showDragHandle: true, showDragHandle: true,
useSafeArea: true, useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1, scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => Padding( builder: (context) => const ModalCreateTodo(),
padding: const EdgeInsets.only(left: 30, right: 30), );
child: Column( }
crossAxisAlignment: CrossAxisAlignment.stretch, }
children: [
TextFormField( class ModalCreateTodo extends StatefulWidget {
autofocus: true, const ModalCreateTodo({super.key});
decoration: const InputDecoration(labelText: "Title"),
onChanged: (value) => tempValue = value, @override
onFieldSubmitted: (value) { State<ModalCreateTodo> createState() => _ModalCreateTodoState();
context.read<TodoProvider>().addTodo(value); }
Navigator.pop(context);
}, class _ModalCreateTodoState extends State<ModalCreateTodo> {
String tempValue = "";
DateTime tempDateSelect = DateTime.now();
TimeOfDay tempTimeSelect = TimeOfDay.now();
String formatNumber(int number) {
if (number < 10) {
return "0$number";
}
return number.toString();
}
DateTime combineTime() {
return DateTime(tempDateSelect.year, tempDateSelect.month,
tempDateSelect.day, tempTimeSelect.hour, tempTimeSelect.minute);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
autofocus: true,
decoration: const InputDecoration(labelText: "Title"),
onChanged: (value) => tempValue = value,
onFieldSubmitted: (value) {
context.read<TodoProvider>().addTodo(value, combineTime());
Navigator.pop(context);
},
),
const SizedBox(height: 20),
FilledButton.tonal(
onPressed: () async {
tempDateSelect =
(await DatePickerModal.add(context, tempDateSelect))!;
setState(() {});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Icons.date_range),
const SizedBox(width: 20),
Text(
"${tempDateSelect.year}-${formatNumber(tempDateSelect.month)}-${formatNumber(tempDateSelect.day)}")
],
), ),
const SizedBox(height: 20,), ),
SegmentedButton( const SizedBox(height: 10),
segments: const [ FilledButton.tonal(
ButtonSegment( onPressed: () async {
tempTimeSelect =
(await TimePickerModal.add(context, tempTimeSelect))!;
setState(() {});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Icons.timer),
const SizedBox(width: 20),
Text(
"${formatNumber(tempTimeSelect.hour)}:${formatNumber(tempTimeSelect.minute)}")
],
),
),
const SizedBox(height: 20),
SegmentedButton(
segments: const [
ButtonSegment(
value: "Cancel", value: "Cancel",
label: Text("Cancel"), label: Text("Cancel"),
icon: Icon(Icons.cancel) icon: Icon(Icons.cancel)),
), ButtonSegment(
ButtonSegment( value: "Save", label: Text("Save"), icon: Icon(Icons.save)),
value: "Save", ],
label: Text("Save"), selected: const {"None"},
icon: Icon(Icons.save) style: ButtonStyle(
), iconColor: WidgetStatePropertyAll(
], Theme.of(context).colorScheme.primary)),
selected: const {"None"}, onSelectionChanged: (p0) {
style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), if (p0.last == "Save") {
onSelectionChanged: (p0) { context.read<TodoProvider>().addTodo(tempValue, combineTime());
if (p0.last == "Save") { }
context.read<TodoProvider>().addTodo(tempValue); Navigator.pop(context);
} },
Navigator.pop(context); )
}, ],
)
],
),
), ),
); );
} }
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class DatePickerModal {
static Future<DateTime?> add(BuildContext context, DateTime initial) async {
DateTime? selected = await showDatePicker(
context: context,
initialDate: initial,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year+10, 12, 31),
);
return selected;
}
}
+126 -49
View File
@@ -1,64 +1,141 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart'; import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/modals/datepickermodal.dart';
import 'package:todoapp/modals/timepickermodal.dart';
import 'package:todoapp/providers/todoprovider.dart'; import 'package:todoapp/providers/todoprovider.dart';
class EditTodoModal { class EditTodoModal {
static String tempValue = ""; static void add(BuildContext context, String title, DateTime dueTime) {
static void add(BuildContext context, String title) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
showDragHandle: true, showDragHandle: true,
useSafeArea: true, useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1, scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => Padding( builder: (context) => ModalEditTodo(
padding: const EdgeInsets.only(left: 30, right: 30), oldValue: title,
child: Column( initDate: DateTime(dueTime.year, dueTime.month, dueTime.day),
crossAxisAlignment: CrossAxisAlignment.stretch, initTime: TimeOfDay(hour: dueTime.hour, minute: dueTime.minute)),
children: [ );
TextFormField( }
autofocus: true, }
initialValue: title,
decoration: const InputDecoration(labelText: "Title"), class ModalEditTodo extends StatefulWidget {
onChanged: (value) => tempValue = value, const ModalEditTodo(
onFieldSubmitted: (value) { {super.key,
context.read<TodoProvider>().updateTodo(title, value); required this.oldValue,
Navigator.pop(context); required this.initDate,
}, required this.initTime});
),
const SizedBox(height: 20,), final String oldValue;
SegmentedButton( final DateTime initDate;
segments: const [ final TimeOfDay initTime;
ButtonSegment(
value: "Delete", @override
label: Text("Delete"), State<ModalEditTodo> createState() => _ModalEditTodoState();
icon: Icon(Icons.delete) }
),
ButtonSegment( class _ModalEditTodoState extends State<ModalEditTodo> {
value: "Cancel", String tempValue = "";
label: Text("Cancel"), TimeOfDay tempTimeSelect = TimeOfDay.now();
icon: Icon(Icons.cancel) DateTime tempDateSelect = DateTime.now();
),
ButtonSegment( String formatNumber(int number) {
value: "Save", if (number < 10) {
label: Text("Save"), return "0$number";
icon: Icon(Icons.save) }
), return number.toString();
}
DateTime combineTime() {
return DateTime(tempDateSelect.year, tempDateSelect.month,
tempDateSelect.day, tempTimeSelect.hour, tempTimeSelect.minute);
}
bool firstBuild = true;
@override
Widget build(BuildContext context) {
if (firstBuild) {
tempValue = widget.oldValue;
tempDateSelect = widget.initDate;
tempTimeSelect = widget.initTime;
firstBuild = false;
}
return Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
autofocus: true,
initialValue: widget.oldValue,
decoration: const InputDecoration(labelText: "Title"),
onChanged: (value) => tempValue = value,
onFieldSubmitted: (value) {
context.read<TodoProvider>().updateTodo(widget.oldValue, value, combineTime());
Navigator.pop(context);
},
),
const SizedBox(height: 20),
FilledButton.tonal(
onPressed: () async {
tempDateSelect =
(await DatePickerModal.add(context, tempDateSelect))!;
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
print(tempDateSelect);
setState(() {});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Icons.date_range),
const SizedBox(width: 20),
Text(
"${tempDateSelect.year}-${formatNumber(tempDateSelect.month)}-${formatNumber(tempDateSelect.day)}")
], ],
selected: const {"None"}, ),
style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), ),
onSelectionChanged: (p0) { const SizedBox(height: 10),
if (p0.last == "Save") { FilledButton.tonal(
context.read<TodoProvider>().updateTodo(title, tempValue); onPressed: () async {
} else if (p0.last == "Delete") { tempTimeSelect =
context.read<TodoProvider>().removeTodo(title); (await TimePickerModal.add(context, tempTimeSelect))!;
} setState(() {});
Navigator.pop(context); },
}, child: Row(
) mainAxisAlignment: MainAxisAlignment.start,
], children: [
), const Icon(Icons.timer),
const SizedBox(width: 20),
Text(
"${formatNumber(tempTimeSelect.hour)}:${formatNumber(tempTimeSelect.minute)}")
],
),
),
const SizedBox(height: 20),
SegmentedButton(
segments: const [
ButtonSegment(
value: "Cancel",
label: Text("Cancel"),
icon: Icon(Icons.cancel)),
ButtonSegment(
value: "Save", label: Text("Save"), icon: Icon(Icons.save)),
],
selected: const {"None"},
style: ButtonStyle(
iconColor: WidgetStatePropertyAll(
Theme.of(context).colorScheme.primary)),
onSelectionChanged: (p0) {
if (p0.last == "Save") {
context.read<TodoProvider>().updateTodo(widget.oldValue, tempValue, combineTime());
}
Navigator.pop(context);
},
)
],
), ),
); );
} }
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TimePickerModal {
static Future<TimeOfDay?> add(BuildContext context, TimeOfDay initial) async {
TimeOfDay? timeSelect = await showTimePicker(
context: context,
initialTime: initial,
);
return timeSelect;
}
}
+7
View File
@@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/modals/datepickermodal.dart';
import 'package:todoapp/modals/timepickermodal.dart';
import 'package:todoapp/providers/todoprovider.dart';
+94 -2
View File
@@ -4,24 +4,116 @@ import 'package:todoapp/modals/edittodomodal.dart';
import 'package:todoapp/providers/todoprovider.dart'; import 'package:todoapp/providers/todoprovider.dart';
class TodoWidget extends StatefulWidget { class TodoWidget extends StatefulWidget {
const TodoWidget({super.key, required this.checked, required this.title}); const TodoWidget(
{super.key,
required this.checked,
required this.title,
required this.dueTime});
final bool checked; final bool checked;
final String title; final String title;
final DateTime dueTime;
@override @override
State<TodoWidget> createState() => _TodoWidgetState(); State<TodoWidget> createState() => _TodoWidgetState();
} }
class _TodoWidgetState extends State<TodoWidget> { class _TodoWidgetState extends State<TodoWidget> {
String formatNumber(int number) {
if (number < 10) {
return "0$number";
}
return number.toString();
}
List<dynamic> buildDisplayString() {
DateTime today = DateTime.now();
DateTime due = widget.dueTime;
DateTime nowDay = DateTime(2000, 1, today.day);
DateTime dueDay = DateTime(2000, 1, due.day);
int diff = dueDay.difference(nowDay).inDays;
Map<int, String> monthList = {
1: "Jan",
2: "Feb",
3: "Mar",
4: "Apr",
5: "Mai",
6: "Jun",
7: "Jul",
8: "Aug",
9: "Sep",
10: "Okt",
11: "Nov",
12: "Dez"
};
if (diff < 0) {
return [
"Over",
Colors.red,
Colors.white
];
}
else if (diff < 1) {
return [
"Today ${formatNumber(due.hour)}:${formatNumber(due.minute)}",
Theme.of(context).colorScheme.primary,
Colors.black
];
} else if (diff < 2) {
return [
"Tomorrow ${formatNumber(due.hour)}:${formatNumber(due.minute)}",
Theme.of(context).colorScheme.inversePrimary,
Colors.white
];
} else if (diff < 7) {
return [
"In $diff days",
Theme.of(context).colorScheme.secondary.withOpacity(0.35),
Colors.white
];
}
else {
return [
"${monthList[due.month]} ${formatNumber(due.day)}, ${due.year}",
Theme.of(context).colorScheme.secondary.withOpacity(0.35),
Colors.white
];
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onLongPress: () { onLongPress: () {
EditTodoModal.add(context, widget.title); EditTodoModal.add(context, widget.title, widget.dueTime);
}, },
child: CheckboxListTile( child: CheckboxListTile(
value: widget.checked, value: widget.checked,
subtitle: Row(
children: [
Container(
padding:
widget.checked ? EdgeInsets.zero : const EdgeInsets.all(4),
decoration: BoxDecoration(
color: widget.checked
? const Color(0x00000000)
: buildDisplayString()[1],
borderRadius: BorderRadius.circular(10)),
child: Text(
buildDisplayString()[0],
style: TextStyle(
color:
widget.checked ? Colors.white : buildDisplayString()[2],
decoration: widget.checked
? TextDecoration.lineThrough
: TextDecoration.none),
),
)
],
),
title: Text(widget.title, title: Text(widget.title,
style: widget.checked style: widget.checked
? const TextStyle(decoration: TextDecoration.lineThrough) ? const TextStyle(decoration: TextDecoration.lineThrough)
+31 -15
View File
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -15,10 +16,19 @@ class TodoProvider extends ChangeNotifier {
Map js = await FileHandler.fileContent; Map js = await FileHandler.fileContent;
for (String key in js.keys) { for (String key in js.keys) {
todoLists[key] = TodoList(title: key, widget: buildTodoListWidget(key), todos: []); todoLists[key] =
TodoList(title: key, widget: buildTodoListWidget(key), todos: []);
for (String key2 in js[key].keys) { Map decode = js[key];
todoLists[key]!.todos.add(Todo(title: key2, widget: buildTodoWidget(key2, js[key][key2]), checked: js[key][key2])); for (var key2 in decode.keys) {
String title = key2;
bool checked = decode[key2]["checked"];
DateTime dueTime = DateTime.parse(decode[key2]["dueTime"]);
todoLists[key]!.todos.add(Todo(
title: title,
widget: buildTodoWidget(title, checked, dueTime),
checked: checked,
dueTime: dueTime));
} }
} }
@@ -79,17 +89,17 @@ class TodoProvider extends ChangeNotifier {
Map js = {}; Map js = {};
for (Todo t in todos) { for (Todo t in todos) {
js[t.title] = t.checked; js["${t.title}"] = {
"checked": t.checked,
"dueTime": t.dueTime.toString()
};
} }
print(js);
await FileHandler.saveList(js, selectedList); await FileHandler.saveList(js, selectedList);
} }
void addTodo(String title) async { void addTodo(String title, DateTime dueTime) async {
todos.add(Todo( todos.add(Todo(
title: title, checked: false, widget: buildTodoWidget(title, false))); title: title, checked: false, widget: buildTodoWidget(title, false, dueTime), dueTime: dueTime));
await writeTodoChanges(); await writeTodoChanges();
notifyListeners(); notifyListeners();
@@ -103,10 +113,12 @@ class TodoProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateTodo(String oldTitle, String newTitle) async { void updateTodo(String oldTitle, String newTitle, DateTime dueTime) async {
Todo todo = todoByTitle(oldTitle); Todo todo = todoByTitle(oldTitle);
todo.title = newTitle; todo.title = newTitle;
todo.widget = buildTodoWidget(newTitle, todo.checked); todo.dueTime = dueTime;
todo.widget = buildTodoWidget(newTitle, todo.checked, dueTime);
await writeTodoChanges(); await writeTodoChanges();
notifyListeners(); notifyListeners();
@@ -116,7 +128,7 @@ class TodoProvider extends ChangeNotifier {
Todo todo = todoByTitle(title); Todo todo = todoByTitle(title);
todo.checked = checked; todo.checked = checked;
todo.widget = buildTodoWidget(title, checked); todo.widget = buildTodoWidget(title, checked, todo.dueTime);
await writeTodoChanges(); await writeTodoChanges();
notifyListeners(); notifyListeners();
@@ -126,8 +138,8 @@ class TodoProvider extends ChangeNotifier {
return TodoListWidget(title: title); return TodoListWidget(title: title);
} }
TodoWidget buildTodoWidget(String title, bool checked) { TodoWidget buildTodoWidget(String title, bool checked, DateTime dueTime) {
return TodoWidget(checked: checked, title: title); return TodoWidget(checked: checked, title: title, dueTime: dueTime);
} }
Todo todoByTitle(String title) { Todo todoByTitle(String title) {
@@ -136,6 +148,10 @@ class TodoProvider extends ChangeNotifier {
return t; return t;
} }
} }
return Todo(title: "None", widget: const SizedBox(), checked: false); return Todo(
title: "None",
widget: const SizedBox(),
checked: false,
dueTime: DateTime.now());
} }
} }