diff --git a/lib/elems/todo.dart b/lib/elems/todo.dart index ad4f441..b2dc9a5 100644 --- a/lib/elems/todo.dart +++ b/lib/elems/todo.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; 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; Widget widget; + DateTime dueTime; bool checked; } \ No newline at end of file diff --git a/lib/modals/addtodomodal.dart b/lib/modals/addtodomodal.dart index 6839d91..67a3819 100644 --- a/lib/modals/addtodomodal.dart +++ b/lib/modals/addtodomodal.dart @@ -1,57 +1,119 @@ 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'; 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().addTodo(value); - Navigator.pop(context); - }, + builder: (context) => const ModalCreateTodo(), + ); + } +} + +class ModalCreateTodo extends StatefulWidget { + const ModalCreateTodo({super.key}); + + @override + State createState() => _ModalCreateTodoState(); +} + +class _ModalCreateTodoState extends State { + 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().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( - segments: const [ - ButtonSegment( + ), + const SizedBox(height: 10), + FilledButton.tonal( + 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", 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().addTodo(tempValue); - } - Navigator.pop(context); - }, - ) - ], - ), + 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().addTodo(tempValue, combineTime()); + } + Navigator.pop(context); + }, + ) + ], ), ); } -} \ No newline at end of file +} diff --git a/lib/modals/datepickermodal.dart b/lib/modals/datepickermodal.dart new file mode 100644 index 0000000..ee14715 --- /dev/null +++ b/lib/modals/datepickermodal.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class DatePickerModal { + static Future 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; + } +} \ No newline at end of file diff --git a/lib/modals/edittodomodal.dart b/lib/modals/edittodomodal.dart index 66edbb3..2d7f393 100644 --- a/lib/modals/edittodomodal.dart +++ b/lib/modals/edittodomodal.dart @@ -1,65 +1,142 @@ 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'; class EditTodoModal { - static String tempValue = ""; - - static void add(BuildContext context, String title) { + static void add(BuildContext context, String title, DateTime dueTime) { 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().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) - ), + builder: (context) => ModalEditTodo( + oldValue: title, + initDate: DateTime(dueTime.year, dueTime.month, dueTime.day), + initTime: TimeOfDay(hour: dueTime.hour, minute: dueTime.minute)), + ); + } +} + +class ModalEditTodo extends StatefulWidget { + const ModalEditTodo( + {super.key, + required this.oldValue, + required this.initDate, + required this.initTime}); + + final String oldValue; + final DateTime initDate; + final TimeOfDay initTime; + + @override + State createState() => _ModalEditTodoState(); +} + +class _ModalEditTodoState extends State { + String tempValue = ""; + TimeOfDay tempTimeSelect = TimeOfDay.now(); + DateTime tempDateSelect = DateTime.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); + } + + 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().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) { - if (p0.last == "Save") { - context.read().updateTodo(title, tempValue); - } else if (p0.last == "Delete") { - context.read().removeTodo(title); - } - Navigator.pop(context); - }, - ) - ], - ), + ), + ), + const SizedBox(height: 10), + FilledButton.tonal( + 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", + 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().updateTodo(widget.oldValue, tempValue, combineTime()); + } + Navigator.pop(context); + }, + ) + ], ), ); } -} \ No newline at end of file +} diff --git a/lib/modals/timepickermodal.dart b/lib/modals/timepickermodal.dart new file mode 100644 index 0000000..257b005 --- /dev/null +++ b/lib/modals/timepickermodal.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class TimePickerModal { + static Future add(BuildContext context, TimeOfDay initial) async { + TimeOfDay? timeSelect = await showTimePicker( + context: context, + initialTime: initial, + ); + + return timeSelect; + } +} \ No newline at end of file diff --git a/lib/modules/modalcreatetodo.dart b/lib/modules/modalcreatetodo.dart new file mode 100644 index 0000000..be5992e --- /dev/null +++ b/lib/modules/modalcreatetodo.dart @@ -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'; + diff --git a/lib/modules/todowidget.dart b/lib/modules/todowidget.dart index a6b2a85..e3147a2 100644 --- a/lib/modules/todowidget.dart +++ b/lib/modules/todowidget.dart @@ -4,24 +4,116 @@ 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}); + const TodoWidget( + {super.key, + required this.checked, + required this.title, + required this.dueTime}); final bool checked; final String title; + final DateTime dueTime; @override State createState() => _TodoWidgetState(); } class _TodoWidgetState extends State { + String formatNumber(int number) { + if (number < 10) { + return "0$number"; + } + return number.toString(); + } + + List 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 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 Widget build(BuildContext context) { return GestureDetector( onLongPress: () { - EditTodoModal.add(context, widget.title); + EditTodoModal.add(context, widget.title, widget.dueTime); }, child: CheckboxListTile( 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, style: widget.checked ? const TextStyle(decoration: TextDecoration.lineThrough) diff --git a/lib/providers/todoprovider.dart b/lib/providers/todoprovider.dart index 454650c..39154c0 100644 --- a/lib/providers/todoprovider.dart +++ b/lib/providers/todoprovider.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -10,15 +11,24 @@ import 'package:todoapp/modules/todowidget.dart'; class TodoProvider extends ChangeNotifier { TodoProvider() {} - + Future 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])); + for (String key in js.keys) { + todoLists[key] = + TodoList(title: key, widget: buildTodoListWidget(key), todos: []); + + Map decode = js[key]; + 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 = {}; 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); } - void addTodo(String title) async { + void addTodo(String title, DateTime dueTime) async { todos.add(Todo( - title: title, checked: false, widget: buildTodoWidget(title, false))); + title: title, checked: false, widget: buildTodoWidget(title, false, dueTime), dueTime: dueTime)); await writeTodoChanges(); notifyListeners(); @@ -103,10 +113,12 @@ class TodoProvider extends ChangeNotifier { notifyListeners(); } - void updateTodo(String oldTitle, String newTitle) async { + void updateTodo(String oldTitle, String newTitle, DateTime dueTime) async { Todo todo = todoByTitle(oldTitle); todo.title = newTitle; - todo.widget = buildTodoWidget(newTitle, todo.checked); + todo.dueTime = dueTime; + + todo.widget = buildTodoWidget(newTitle, todo.checked, dueTime); await writeTodoChanges(); notifyListeners(); @@ -116,7 +128,7 @@ class TodoProvider extends ChangeNotifier { Todo todo = todoByTitle(title); todo.checked = checked; - todo.widget = buildTodoWidget(title, checked); + todo.widget = buildTodoWidget(title, checked, todo.dueTime); await writeTodoChanges(); notifyListeners(); @@ -126,8 +138,8 @@ class TodoProvider extends ChangeNotifier { return TodoListWidget(title: title); } - TodoWidget buildTodoWidget(String title, bool checked) { - return TodoWidget(checked: checked, title: title); + TodoWidget buildTodoWidget(String title, bool checked, DateTime dueTime) { + return TodoWidget(checked: checked, title: title, dueTime: dueTime); } Todo todoByTitle(String title) { @@ -136,6 +148,10 @@ class TodoProvider extends ChangeNotifier { return t; } } - return Todo(title: "None", widget: const SizedBox(), checked: false); + return Todo( + title: "None", + widget: const SizedBox(), + checked: false, + dueTime: DateTime.now()); } }