Initial commit

This commit is contained in:
brausjonas
2024-09-15 17:03:37 +02:00
commit 8740525457
42 changed files with 1515 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
import 'dart:io';
class FileHandler {
static Future<String> get localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
static Future<File> get localFile async {
final path = await localPath;
return File("$path/todos.json");
}
static Future<dynamic> get fileContent async {
await existCheck();
File f = await localFile;
String content = await f.readAsString();
final js = await json.decode(content);
return js;
}
static Future<void> saveFile(js) async {
File f = await localFile;
String content = json.encode(js);
f.writeAsString(content);
}
static Future<void> saveList(js, String listName) async {
final content = await fileContent;
content[listName] = js;
await saveFile(content);
}
static Future<void> renameList(String oldListName, String newListName) async {
var newJs = {};
final content = await fileContent;
for (String key in content.keys) {
if (key != oldListName) {
newJs[key] = content[key];
} else {
newJs[newListName] = content[key];
}
}
await saveFile(newJs);
}
static Future<void> addList(String listName) async {
var js = await fileContent;
js[listName] = {};
await saveFile(js);
}
static Future<void> removeList(String listName) async {
final js = await fileContent;
var newJs = {};
for (String key in js.keys) {
if (key != listName) {
newJs[key] = js[key];
}
}
await saveFile(newJs);
}
static Future<void> existCheck() async {
File f = await localFile;
bool exists = await f.exists();
if (!exists) {
await saveFile({"Personal": {}});
}
}
}
+9
View File
@@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
class Todo {
Todo({required this.title, required this.widget, required this.checked});
String title;
Widget widget;
bool checked;
}
+10
View File
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
import 'package:todoapp/elems/todo.dart';
class TodoList {
TodoList({required this.title, required this.widget, required this.todos});
String title;
Widget widget;
List<Todo> todos;
}
+29
View File
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:todoapp/pages/Home.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/providers/todoprovider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TodoProvider())
],
child: MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue, brightness: Brightness.dark),
useMaterial3: true,
),
home: const Home(),
),
);
}
}
+57
View File
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/providers/todoprovider.dart';
class AddListModal {
static String tempValue = "";
static void add(BuildContext context) {
showModalBottomSheet(
context: context,
showDragHandle: true,
useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => 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>().addList(value);
Navigator.pop(context);
},
),
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>().addList(tempValue);
}
Navigator.pop(context);
},
)
],
),
),
);
}
}
+57
View File
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/providers/todoprovider.dart';
class AddTodoModal {
static String tempValue = "";
static void add(BuildContext context) {
showModalBottomSheet(
context: context,
showDragHandle: true,
useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => 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);
Navigator.pop(context);
},
),
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>().addTodo(tempValue);
}
Navigator.pop(context);
},
)
],
),
),
);
}
}
+65
View File
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/providers/todoprovider.dart';
class EditListModal {
static String tempValue = "";
static void add(BuildContext context, String title) {
showModalBottomSheet(
context: context,
showDragHandle: true,
useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
autofocus: true,
initialValue: title,
decoration: const InputDecoration(labelText: "Title"),
onChanged: (value) => tempValue = value,
onFieldSubmitted: (value) {
context.read<TodoProvider>().renameList(title, value);
Navigator.pop(context);
},
),
const SizedBox(height: 20,),
SegmentedButton(
segments: const [
ButtonSegment(
value: "Delete",
label: Text("Delete"),
icon: Icon(Icons.delete)
),
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>().renameList(title, tempValue);
} else if (p0.last == "Delete") {
context.read<TodoProvider>().removeList(title);
}
Navigator.pop(context);
},
)
],
),
),
);
}
}
+65
View File
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/providers/todoprovider.dart';
class EditTodoModal {
static String tempValue = "";
static void add(BuildContext context, String title) {
showModalBottomSheet(
context: context,
showDragHandle: true,
useSafeArea: true,
scrollControlDisabledMaxHeightRatio: 1,
builder: (context) => Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
autofocus: true,
initialValue: title,
decoration: const InputDecoration(labelText: "Title"),
onChanged: (value) => tempValue = value,
onFieldSubmitted: (value) {
context.read<TodoProvider>().updateTodo(title, value);
Navigator.pop(context);
},
),
const SizedBox(height: 20,),
SegmentedButton(
segments: const [
ButtonSegment(
value: "Delete",
label: Text("Delete"),
icon: Icon(Icons.delete)
),
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(title, tempValue);
} else if (p0.last == "Delete") {
context.read<TodoProvider>().removeTodo(title);
}
Navigator.pop(context);
},
)
],
),
),
);
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/modals/addtodomodal.dart';
import 'package:todoapp/providers/todoprovider.dart';
class TodoActionButton extends StatelessWidget {
const TodoActionButton({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton.extended(
onPressed: () => {
AddTodoModal.add(context)
}, //TODO: implement
label: const Text("Add ToDo"),
icon: const Icon(Icons.add),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/providers/todoprovider.dart';
class TodoAppBar extends StatefulWidget implements PreferredSizeWidget {
const TodoAppBar({super.key});
@override
State<TodoAppBar> createState() => _TodoAppBarState();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _TodoAppBarState extends State<TodoAppBar> {
@override
Widget build(BuildContext context) {
return Consumer<TodoProvider>(
builder: (context, todoProvider, child) => AppBar(
elevation: 5,
title: Text(todoProvider.selectedList),
),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/providers/todoprovider.dart';
import '../elems/todo.dart';
class Body extends StatefulWidget {
const Body({super.key});
@override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
@override
Widget build(BuildContext context) {
return Consumer<TodoProvider>(
builder: (context, todoProvider, child) => Column(
children: [
for (Todo t in context.read<TodoProvider>().todos) t.widget,
],
),
);
}
}
+53
View File
@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/modals/addlistmodal.dart';
import 'package:todoapp/providers/todoprovider.dart';
class TodoDrawer extends StatefulWidget {
const TodoDrawer({super.key});
@override
State<TodoDrawer> createState() => _TodoDrawerState();
}
class _TodoDrawerState extends State<TodoDrawer> {
@override
Widget build(BuildContext context) {
return Drawer(
child: Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: Column(
children: [
const SizedBox(height: 60),
const Text("ToDo", style: TextStyle(fontSize: 20)),
const SizedBox(height: 20),
const Divider(height: 1),
const SizedBox(height: 20),
const Text("Lists", style: TextStyle(fontSize: 25)),
const SizedBox(height: 10),
SingleChildScrollView(
child: Consumer<TodoProvider>(
builder: (context, todoProvider, child) =>
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (String key in todoProvider.todoLists.keys)
todoProvider.todoLists[key]!.widget,
const SizedBox(height: 30),
FilledButton.icon(
onPressed: () {
AddListModal.add(context);
},
icon: const Icon(Icons.add),
label: const Text("Add List"),
)
],
),
)
)
],
),
),
);
}
}
+42
View File
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/modals/editlistmodal.dart';
import 'package:todoapp/providers/todoprovider.dart';
class TodoListWidget extends StatefulWidget {
const TodoListWidget({super.key, required this.title});
final String title;
@override
State<TodoListWidget> createState() => _TodoListWidgetState();
}
class _TodoListWidgetState extends State<TodoListWidget> {
@override
Widget build(BuildContext context) {
ButtonStyle buttonStyle = context
.read<TodoProvider>()
.selectedList == widget.title ? const ButtonStyle() : const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Color(0x00000000)));
return FilledButton.tonal(
onPressed: () {
context.read<TodoProvider>().changeList(widget.title);
Navigator.pop(context);
},
onLongPress: () {
EditListModal.add(context, widget.title);
},
style: buttonStyle,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Icons.list),
const SizedBox(width: 10),
Text(widget.title)
],
),
);
}
}
+34
View File
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/modals/edittodomodal.dart';
import 'package:todoapp/providers/todoprovider.dart';
class TodoWidget extends StatefulWidget {
const TodoWidget({super.key, required this.checked, required this.title});
final bool checked;
final String title;
@override
State<TodoWidget> createState() => _TodoWidgetState();
}
class _TodoWidgetState extends State<TodoWidget> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () {
EditTodoModal.add(context, widget.title);
},
child: CheckboxListTile(
value: widget.checked,
title: Text(widget.title,
style: widget.checked
? const TextStyle(decoration: TextDecoration.lineThrough)
: const TextStyle()),
onChanged: (value) =>
{context.read<TodoProvider>().setTodoState(widget.title, value!)},
),
);
}
}
+29
View File
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:todoapp/modules/actionbutton.dart';
import 'package:todoapp/modules/appbar.dart';
import 'package:todoapp/modules/body.dart';
import 'package:todoapp/modules/drawer.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/providers/todoprovider.dart';
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<TodoProvider>().readListsFromDisk(),
builder: (context, snapshot) => const Scaffold(
appBar: TodoAppBar(),
body: Body(),
drawer: TodoDrawer(),
floatingActionButton: TodoActionButton(),
),
);
}
}
+141
View File
@@ -0,0 +1,141 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoapp/elems/filehandler.dart';
import 'package:todoapp/elems/todo.dart';
import 'package:todoapp/elems/todolist.dart';
import 'package:todoapp/modules/todolistwidget.dart';
import 'package:todoapp/modules/todowidget.dart';
class TodoProvider extends ChangeNotifier {
TodoProvider() {}
Future<void> readListsFromDisk() async {
Map js = await FileHandler.fileContent;
for (String key in js.keys) {
todoLists[key] = TodoList(title: key, widget: buildTodoListWidget(key), todos: []);
for (String key2 in js[key].keys) {
todoLists[key]!.todos.add(Todo(title: key2, widget: buildTodoWidget(key2, js[key][key2]), checked: js[key][key2]));
}
}
todos = todoLists[selectedList]!.todos;
notifyListeners();
}
Map<String, TodoList> todoLists = {};
List<Todo> todos = [];
String selectedList = "Personal";
void renameList(String oldTitle, String newTitle) async {
TodoList tempList = todoLists[oldTitle]!;
tempList.title = newTitle;
tempList.widget = buildTodoListWidget(newTitle);
todoLists.remove(oldTitle);
todoLists[newTitle] = tempList;
selectedList = newTitle;
todos = todoLists[newTitle]!.todos;
await FileHandler.renameList(oldTitle, newTitle);
notifyListeners();
}
void removeList(String title) async {
todoLists.remove(title);
selectedList = "Personal";
todos = todoLists["Personal"]!.todos;
await FileHandler.removeList(title);
notifyListeners();
}
void addList(String title) async {
TodoList list =
TodoList(title: title, widget: buildTodoListWidget(title), todos: []);
todoLists[title] = list;
await FileHandler.addList(title);
notifyListeners();
}
void changeList(String title) {
if (!todoLists.containsKey(title)) {
return;
}
selectedList = title;
todos = todoLists[title]!.todos;
notifyListeners();
}
Future<void> writeTodoChanges() async {
Map js = {};
for (Todo t in todos) {
js[t.title] = t.checked;
}
print(js);
await FileHandler.saveList(js, selectedList);
}
void addTodo(String title) async {
todos.add(Todo(
title: title, checked: false, widget: buildTodoWidget(title, false)));
await writeTodoChanges();
notifyListeners();
}
void removeTodo(String title) async {
Todo todo = todoByTitle(title);
todos.remove(todo);
await writeTodoChanges();
notifyListeners();
}
void updateTodo(String oldTitle, String newTitle) async {
Todo todo = todoByTitle(oldTitle);
todo.title = newTitle;
todo.widget = buildTodoWidget(newTitle, todo.checked);
await writeTodoChanges();
notifyListeners();
}
void setTodoState(String title, bool checked) async {
Todo todo = todoByTitle(title);
todo.checked = checked;
todo.widget = buildTodoWidget(title, checked);
await writeTodoChanges();
notifyListeners();
}
TodoListWidget buildTodoListWidget(String title) {
return TodoListWidget(title: title);
}
TodoWidget buildTodoWidget(String title, bool checked) {
return TodoWidget(checked: checked, title: title);
}
Todo todoByTitle(String title) {
for (Todo t in todos) {
if (t.title == title) {
return t;
}
}
return Todo(title: "None", widget: const SizedBox(), checked: false);
}
}