defined & added repeat-patterns.
This commit is contained in:
parent
9f8e0c875c
commit
db39eeea55
12
README.md
12
README.md
@ -4,12 +4,12 @@ A Reminder based on todo.txt synced via nextcloud
|
|||||||
|
|
||||||
## Current todos:
|
## Current todos:
|
||||||
|
|
||||||
- [ ] make repeat-datatype (like: daily, weekly on mo/th/fr, bi-monthly, etc.)
|
- [x] make repeat-datatype (like: daily, weekly on mo/th/fr, bi-monthly, etc.)
|
||||||
- [ ] define isomorphism for 'repeat:'-meta-tag
|
- [x] define isomorphism for 'repeat:'-meta-tag
|
||||||
- [ ] add interface for repeat-datatype in addReminder.dart
|
- [ ] add interface for repeat-datatype in addReminder.dart
|
||||||
- [x] save/load data to/from disk
|
- [x] save/load data to/from disk
|
||||||
- [x] adding/removing tasks
|
- [x] adding/removing tasks
|
||||||
- [ ] respect ordering that was used when starting the app when saving.
|
- [x] respect ordering that was used when starting the app when saving.
|
||||||
- [ ] add Nextcloud-login for getting a Token
|
- [ ] add Nextcloud-login for getting a Token
|
||||||
- [ ] use webdav for synchronizing with Nextcloud using that token
|
- [ ] use webdav for synchronizing with Nextcloud using that token
|
||||||
- [ ] sorting by "next up", "priority"
|
- [ ] sorting by "next up", "priority"
|
||||||
@ -28,6 +28,10 @@ A Reminder based on todo.txt synced via nextcloud
|
|||||||
|
|
||||||
### Adding Tasks
|
### Adding Tasks
|
||||||
![](img/2023-01-08_addTask.png)
|
![](img/2023-01-08_addTask.png)
|
||||||
|
(still missing repeat-options, currently defaults to daily.)
|
||||||
|
|
||||||
### Details/Removing tasks
|
### Details/Removing tasks
|
||||||
![](img/2023-01-10_Task_details.png)
|
![](img/2023-01-10_Task_details.png)
|
||||||
|
|
||||||
|
### Complex repeat patterns
|
||||||
|
![](img/2023-01-10_repeat_patterns.png)
|
BIN
img/2023-01-10_repeat_patterns.png
Normal file
BIN
img/2023-01-10_repeat_patterns.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:nextcloud_reminder/repeating_task.dart';
|
import 'package:nextcloud_reminder/repeating_task.dart';
|
||||||
|
import 'package:nextcloud_reminder/types/repeat.dart';
|
||||||
import 'package:nextcloud_reminder/types/tasks.dart';
|
import 'package:nextcloud_reminder/types/tasks.dart';
|
||||||
|
|
||||||
class AddTaskWidget extends StatefulWidget {
|
class AddTaskWidget extends StatefulWidget {
|
||||||
@ -124,7 +125,7 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
|
|||||||
const SnackBar(content: Text('Task added.')),
|
const SnackBar(content: Text('Task added.')),
|
||||||
);
|
);
|
||||||
widget.onSave(RepeatingTask(
|
widget.onSave(RepeatingTask(
|
||||||
task: Task(title: _titleController.text, begin: _beginDate.value), repeat: _repeat,));
|
task: TaskExtra(title: _titleController.text, begin: _beginDate.value, repeat: [RepeatInterval(interval: DateInterval.daily)],)));
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8,6 +8,7 @@ import 'package:nextcloud_reminder/repeating_task.dart';
|
|||||||
import 'package:nextcloud_reminder/table.dart';
|
import 'package:nextcloud_reminder/table.dart';
|
||||||
import 'package:nextcloud_reminder/types/tasks.dart';
|
import 'package:nextcloud_reminder/types/tasks.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class HomeWidget extends StatefulWidget {
|
class HomeWidget extends StatefulWidget {
|
||||||
const HomeWidget({super.key, required this.title});
|
const HomeWidget({super.key, required this.title});
|
||||||
@ -30,10 +31,9 @@ class HomeWidget extends StatefulWidget {
|
|||||||
class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
|
class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
|
||||||
|
|
||||||
late final ScrollTable _checkboxes;
|
late final ScrollTable _checkboxes;
|
||||||
int _counter = 1;
|
|
||||||
late final Directory appDocDirectory;
|
late final Directory appDocDirectory;
|
||||||
late final File todotxt = File('${appDocDirectory.path}/todo.txt');
|
late final File todotxt = File('${appDocDirectory.path}/todo.txt');
|
||||||
final List<Task> tasks = [];
|
final List<TaskExtra> tasks = [];
|
||||||
|
|
||||||
void _loadTodos() {
|
void _loadTodos() {
|
||||||
for (var element in tasks) {
|
for (var element in tasks) {
|
||||||
@ -81,7 +81,9 @@ class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
void saveData() async {
|
void saveData() async {
|
||||||
//TODO: better update lines instead of blindly overwriting.
|
//TODO: better update lines instead of blindly overwriting.
|
||||||
String data = tasks.map((t) => t.toString()).join("\n");
|
List<TaskExtra> tmp = tasks;
|
||||||
|
tmp.sort((a, b) => a.lineNumber != null && b.lineNumber != null ? a.lineNumber! - b.lineNumber! : -1,);
|
||||||
|
String data = tmp.map((t) => t.formatAsTask()).join("\n");
|
||||||
debugPrint("Saving:\n$data");
|
debugPrint("Saving:\n$data");
|
||||||
await todotxt.writeAsString(data);
|
await todotxt.writeAsString(data);
|
||||||
}
|
}
|
||||||
@ -90,7 +92,7 @@ class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
|
|||||||
if (t != null) {
|
if (t != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_checkboxes.addTask(t!);
|
_checkboxes.addTask(t!);
|
||||||
tasks.add(t.task);
|
tasks.add(TaskExtra.fromTask(t.task));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,18 @@ abstract class TodoParser {
|
|||||||
|
|
||||||
static final _todoParser = _definition.build();
|
static final _todoParser = _definition.build();
|
||||||
|
|
||||||
static List<Task> parse(List<String> input) {
|
static List<TaskExtra> parse(List<String> input) {
|
||||||
final List<Task> ret = [];
|
final List<TaskExtra> ret = [];
|
||||||
|
var line=1;
|
||||||
for (var element in input) {
|
for (var element in input) {
|
||||||
var parsed = _todoParser.parse(element);
|
var parsed = _todoParser.parse(element);
|
||||||
if (parsed.isSuccess) {
|
if (parsed.isSuccess) {
|
||||||
ret.add(parsed.value);
|
ret.add(TaskExtra.fromTask(parsed.value, lineNumber: line));
|
||||||
} else {
|
} else {
|
||||||
debugPrint(parsed.message);
|
debugPrint(parsed.message);
|
||||||
debugPrint(element);
|
debugPrint(element);
|
||||||
}
|
}
|
||||||
|
line++;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,9 @@ import 'package:nextcloud_reminder/task_item.dart';
|
|||||||
import 'package:nextcloud_reminder/types/tasks.dart';
|
import 'package:nextcloud_reminder/types/tasks.dart';
|
||||||
|
|
||||||
class RepeatingTask extends StatefulWidget {
|
class RepeatingTask extends StatefulWidget {
|
||||||
const RepeatingTask({super.key, required this.task, this.repeat=1});
|
const RepeatingTask({super.key, required this.task});
|
||||||
|
|
||||||
final Task task;
|
final TaskExtra task;
|
||||||
final int repeat;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _RepeatingTaskState();
|
State<StatefulWidget> createState() => _RepeatingTaskState();
|
||||||
@ -18,7 +17,17 @@ class _RepeatingTaskState extends State<RepeatingTask> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_occurrences = List<TaskItem>.generate(10, (index) => TaskItem(done: index % widget.repeat == 0 ? false : null));
|
_occurrences = List<TaskItem>.generate(10, (index) {
|
||||||
|
var start = widget.task.begin ?? DateTime.now();
|
||||||
|
var comparator = DateTime.now().add(Duration(days: index));
|
||||||
|
for (var r in widget.task.repeat) {
|
||||||
|
if (r.repeatHit(start, comparator)) return TaskItem(done: widget.task.done,);
|
||||||
|
}
|
||||||
|
if (widget.task.repeat.isEmpty && start.day == comparator.day && start.month == comparator.month && start.year == comparator.year) {
|
||||||
|
return TaskItem(done: widget.task.done,);
|
||||||
|
}
|
||||||
|
return const TaskItem(done: null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
97
lib/types/repeat.dart
Normal file
97
lib/types/repeat.dart
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
class RepeatInterval {
|
||||||
|
int every;
|
||||||
|
DateInterval interval;
|
||||||
|
|
||||||
|
RepeatInterval({required this.interval, this.every=1});
|
||||||
|
|
||||||
|
static RepeatInterval? fromString(String input) {
|
||||||
|
RegExpMatch? m = RegExp(r'(\d*)(\D+)').firstMatch(input);
|
||||||
|
if (m != null) {
|
||||||
|
DateInterval? di = _stringToInterval(m!.group(2)!);
|
||||||
|
if (di != null) {
|
||||||
|
return RepeatInterval(interval: di, every: m?.group(1) == "" ? 1 : int.parse(m!.group(1)!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does the RepeatInterval referencing from a hit b?
|
||||||
|
bool repeatHit(DateTime a, DateTime b) {
|
||||||
|
var daydiff = a.difference(b).inDays;
|
||||||
|
var weeks = (daydiff / 7).floor();
|
||||||
|
switch (interval) {
|
||||||
|
case DateInterval.daily: return daydiff % every == 0;
|
||||||
|
case DateInterval.weekly: return daydiff % (every*7) == 0;
|
||||||
|
case DateInterval.monthly: return a.day == b.day;
|
||||||
|
case DateInterval.monday: return weeks % every == 0 && b.weekday == DateTime.monday;
|
||||||
|
case DateInterval.tuesday: return weeks % every == 0 && b.weekday == DateTime.tuesday;
|
||||||
|
case DateInterval.wednesday: return weeks % every == 0 && b.weekday == DateTime.wednesday;
|
||||||
|
case DateInterval.thursday: return weeks % every == 0 && b.weekday == DateTime.thursday;
|
||||||
|
case DateInterval.friday: return weeks % every == 0 && b.weekday == DateTime.friday;
|
||||||
|
case DateInterval.saturday: return weeks % every == 0 && b.weekday == DateTime.saturday;
|
||||||
|
case DateInterval.sunday: return weeks % every == 0 && b.weekday == DateTime.sunday;
|
||||||
|
case DateInterval.dayOfMonth: return b.day == every;
|
||||||
|
case DateInterval.dayOfYear: return DateTime.now().difference(b).inDays == every;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() {
|
||||||
|
return "$every${_intervalToString(interval)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateInterval? _stringToInterval(String input) {
|
||||||
|
switch (input) {
|
||||||
|
case "daily":
|
||||||
|
case "day": return DateInterval.daily;
|
||||||
|
case "weekly":
|
||||||
|
case "week": return DateInterval.weekly;
|
||||||
|
case "monthly":
|
||||||
|
case "month": return DateInterval.monthly;
|
||||||
|
case "mon": return DateInterval.monday;
|
||||||
|
case "tue": return DateInterval.tuesday;
|
||||||
|
case "wed": return DateInterval.wednesday;
|
||||||
|
case "thu": return DateInterval.thursday;
|
||||||
|
case "fri": return DateInterval.friday;
|
||||||
|
case "sat": return DateInterval.saturday;
|
||||||
|
case "sun": return DateInterval.sunday;
|
||||||
|
case "ofMonth": return DateInterval.dayOfMonth;
|
||||||
|
case "ofYear": return DateInterval.dayOfYear;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _intervalToString(DateInterval di) {
|
||||||
|
switch (di) {
|
||||||
|
case DateInterval.daily: return "day";
|
||||||
|
case DateInterval.weekly: return "week";
|
||||||
|
case DateInterval.monthly: return "month";
|
||||||
|
case DateInterval.monday: return "mon";
|
||||||
|
case DateInterval.tuesday: return "tue";
|
||||||
|
case DateInterval.wednesday: return "wed";
|
||||||
|
case DateInterval.thursday: return "thu";
|
||||||
|
case DateInterval.friday: return "fri";
|
||||||
|
case DateInterval.saturday: return "sat";
|
||||||
|
case DateInterval.sunday: return "sun";
|
||||||
|
case DateInterval.dayOfMonth: return "ofMonth";
|
||||||
|
case DateInterval.dayOfYear: return "ofYear";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DateInterval {
|
||||||
|
daily,
|
||||||
|
weekly,
|
||||||
|
monthly,
|
||||||
|
monday,
|
||||||
|
tuesday,
|
||||||
|
wednesday,
|
||||||
|
thursday,
|
||||||
|
friday,
|
||||||
|
saturday,
|
||||||
|
sunday,
|
||||||
|
dayOfMonth,
|
||||||
|
dayOfYear,
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,45 @@
|
|||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:nextcloud_reminder/types/repeat.dart';
|
||||||
|
|
||||||
|
class TaskExtra extends Task {
|
||||||
|
TaskExtra({required super.title,
|
||||||
|
super.done = false,
|
||||||
|
super.contexts = const [],
|
||||||
|
super.projects = const [],
|
||||||
|
super.meta = const {},
|
||||||
|
super.priority,
|
||||||
|
super.begin,
|
||||||
|
super.end,
|
||||||
|
this.lineNumber,
|
||||||
|
this.repeat = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskExtra.fromTask(Task t, {this.lineNumber})
|
||||||
|
: repeat = t.meta["repeat"]?.split("/").map(RepeatInterval.fromString).whereNotNull().toList() ?? []
|
||||||
|
, super(title: t.title,
|
||||||
|
done: t.done,
|
||||||
|
contexts: t.contexts,
|
||||||
|
projects: t.projects,
|
||||||
|
meta: t.meta,
|
||||||
|
priority: t.priority,
|
||||||
|
begin: t.begin,
|
||||||
|
end: t.end,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int? lineNumber;
|
||||||
|
final List<RepeatInterval> repeat;
|
||||||
|
|
||||||
|
formatAsTask() {
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "$lineNumber: ${super.toString()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Task {
|
class Task {
|
||||||
final bool done;
|
final bool done;
|
||||||
final DateTime? begin;
|
final DateTime? begin;
|
||||||
|
@ -30,7 +30,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -40,6 +40,7 @@ dependencies:
|
|||||||
petitparser: ^5.1.0
|
petitparser: ^5.1.0
|
||||||
tuple: ^2.0.1
|
tuple: ^2.0.1
|
||||||
flutter_window_close: ^0.2.2
|
flutter_window_close: ^0.2.2
|
||||||
|
collection: ^1.16.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user