Time
This commit is contained in:
+2
-1
@@ -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;
|
||||
}
|
||||
+100
-38
@@ -1,56 +1,118 @@
|
||||
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<TodoProvider>().addTodo(value);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
builder: (context) => const ModalCreateTodo(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModalCreateTodo extends StatefulWidget {
|
||||
const ModalCreateTodo({super.key});
|
||||
|
||||
@override
|
||||
State<ModalCreateTodo> createState() => _ModalCreateTodoState();
|
||||
}
|
||||
|
||||
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(
|
||||
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<TodoProvider>().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<TodoProvider>().addTodo(tempValue, combineTime());
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,64 +1,141 @@
|
||||
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<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)
|
||||
),
|
||||
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<ModalEditTodo> createState() => _ModalEditTodoState();
|
||||
}
|
||||
|
||||
class _ModalEditTodoState extends State<ModalEditTodo> {
|
||||
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<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) {
|
||||
if (p0.last == "Save") {
|
||||
context.read<TodoProvider>().updateTodo(title, tempValue);
|
||||
} else if (p0.last == "Delete") {
|
||||
context.read<TodoProvider>().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<TodoProvider>().updateTodo(widget.oldValue, tempValue, combineTime());
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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<TodoWidget> createState() => _TodoWidgetState();
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -15,10 +16,19 @@ class TodoProvider extends ChangeNotifier {
|
||||
Map js = await FileHandler.fileContent;
|
||||
|
||||
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) {
|
||||
todoLists[key]!.todos.add(Todo(title: key2, widget: buildTodoWidget(key2, js[key][key2]), checked: js[key][key2]));
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user