diff --git a/README.md b/README.md index c0a1aa8..ed45bca 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,18 @@ A Reminder based on todo.txt synced via nextcloud - [ ] define isomorphism for 'repeat:'-meta-tag - [ ] add interface for repeat-datatype in addReminder.dart - [x] save/load data to/from disk +- [x] adding/removing tasks - [ ] respect ordering that was used when starting the app when saving. - [ ] add Nextcloud-login for getting a Token - [ ] use webdav for synchronizing with Nextcloud using that token - [ ] sorting by "next up", "priority" - [ ] respect 'color:'-meta-tag (usual formats like "#aabbcc", html-colors like "red") - [ ] use color in rendering todos +- [ ] make application-settings + - [ ] store/load settings + - [ ] setting for number of days into the future + - [ ] theme (light/dark mode, system theme) +- [ ] fancy pop-animation & sound for the checkbox ## Current looks: @@ -21,4 +27,7 @@ A Reminder based on todo.txt synced via nextcloud ![](img/2023-01-08_application.png) ### Adding Tasks -![](img/2023-01-08_addTask.png) \ No newline at end of file +![](img/2023-01-08_addTask.png) + +### Details/Removing tasks +![](img/2023-01-10_Task_details.png) \ No newline at end of file diff --git a/img/2023-01-10_Task_details.png b/img/2023-01-10_Task_details.png new file mode 100644 index 0000000..92bda9e Binary files /dev/null and b/img/2023-01-10_Task_details.png differ diff --git a/lib/addReminder.dart b/lib/addReminder.dart index 01a0570..e873043 100644 --- a/lib/addReminder.dart +++ b/lib/addReminder.dart @@ -103,7 +103,7 @@ class _AddTaskWidgetState extends State with RestorationMixin { children: [ Text("Begin: ", style: Theme.of(context).textTheme.labelLarge), Expanded( - child: Text("${_beginDate.value.year}-${_beginDate.value.month.toString().padLeft(2,'0')}-${_beginDate.value.day.toString().padLeft(2,'0')}") + child: Text(Task.formatDate(_beginDate.value)) ), IconButton( onPressed: () => _restorableDatePickerRouteFuture.present(), diff --git a/lib/homescreen.dart b/lib/homescreen.dart index fa98a16..e4d81af 100644 --- a/lib/homescreen.dart +++ b/lib/homescreen.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_window_close/flutter_window_close.dart'; import 'package:nextcloud_reminder/addReminder.dart'; import 'package:nextcloud_reminder/parser/todotxt.dart'; import 'package:nextcloud_reminder/repeating_task.dart'; @@ -28,7 +29,7 @@ class HomeWidget extends StatefulWidget { class _HomeWidgetState extends State with WidgetsBindingObserver { - final ScrollTable _checkboxes = ScrollTable(title: "TODOs"); + late final ScrollTable _checkboxes; int _counter = 1; late final Directory appDocDirectory; late final File todotxt = File('${appDocDirectory.path}/todo.txt'); @@ -45,7 +46,6 @@ class _HomeWidgetState extends State with WidgetsBindingObserver { appDocDirectory = (await getApplicationDocumentsDirectory()); if (await todotxt.exists()) { var data = await todotxt.readAsLines(); - debugPrint(data.toString()); tasks.addAll(TodoParser.parse(data)); _loadTodos(); } else { @@ -57,7 +57,12 @@ class _HomeWidgetState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); + _checkboxes = ScrollTable(title: "TODOs", deleteCallback: _removeTask); initLazy(); + FlutterWindowClose.setWindowShouldCloseHandler(() async { + saveData(); + return true; + }); } @override @@ -81,16 +86,6 @@ class _HomeWidgetState extends State with WidgetsBindingObserver { await todotxt.writeAsString(data); } - void _addDummyTask() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // stuff without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _checkboxes.addTask(RepeatingTask(task: Task(title: "Dummy Task #$_counter", begin: DateTime.now()), repeat: _counter++)); - }); - } void _addTask(RepeatingTask? t) { if (t != null) { setState(() { @@ -99,6 +94,11 @@ class _HomeWidgetState extends State with WidgetsBindingObserver { }); } } + void _removeTask(RepeatingTask t) { + setState(() { + tasks.remove(t.task); + }); + } @override Widget build(BuildContext context) { diff --git a/lib/repeating_task.dart b/lib/repeating_task.dart index 34161c6..ddb2070 100644 --- a/lib/repeating_task.dart +++ b/lib/repeating_task.dart @@ -23,9 +23,13 @@ class _RepeatingTaskState extends State { @override Widget build(BuildContext context) { - return Row( + return Container( + decoration: const BoxDecoration(border: Border(bottom: BorderSide(),)), + margin: const EdgeInsets.all(0.0), + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: _occurrences, - ); + ) + ); } } \ No newline at end of file diff --git a/lib/table.dart b/lib/table.dart index 52bbf4f..0e58428 100644 --- a/lib/table.dart +++ b/lib/table.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:nextcloud_reminder/repeating_task.dart'; +import 'package:nextcloud_reminder/types/tasks.dart'; class ScrollTable extends StatefulWidget { - ScrollTable({super.key, required this.title}); + ScrollTable({super.key, required this.title, required this.deleteCallback}); // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and @@ -11,6 +12,7 @@ class ScrollTable extends StatefulWidget { final String title; final _ScrollTableState _internalState = _ScrollTableState(); + final ValueChanged deleteCallback; @override State createState() => _internalState; @@ -31,19 +33,64 @@ class _ScrollTableState extends State { }); } - List _buildTitles() { + removeTask(RepeatingTask task) { + setState(() { + _content.remove(task); + }); + widget.deleteCallback(task); + } + + Future _showDetailsAndRemoveTask(BuildContext context, RepeatingTask t) { + return showDialog(context: context, + barrierDismissible: true, + builder: (BuildContext context) + { + return AlertDialog( + title: const Text("Task details"), + content: SingleChildScrollView( + child: ListBody( + children: [ Text(t.task.title), + Text("Projects: ${t.task.projects.isEmpty ? "none" : t.task.projects.join(", ")}"), + Text("Contexts: ${t.task.contexts.isEmpty ? "none" : t.task.contexts.join(", ")}"), + Padding(padding: EdgeInsets.only(top: 16), + child: Text(t.task.meta.isEmpty ? "" : "Meta:", style: Theme.of(context).textTheme.bodyLarge,) + ), + ] + List.of(t.task.meta.entries.map((e) => Text("${e.key}: ${e.value}"))), + ), + ), + actions: [ + TextButton(onPressed: () { + removeTask(t); + Navigator.of(context).pop(); + }, + style: TextButton.styleFrom(foregroundColor: Theme + .of(context) + .errorColor), + child: const Text("remove"),), + TextButton(onPressed: () => Navigator.of(context).pop(), + child: const Text("close")), + ] + + ); + }); + } + + List _buildTitles(BuildContext context) { return List.from(_content.map((RepeatingTask t) => Container( - alignment: Alignment.center, - width: 120.0, + alignment: Alignment.centerLeft, + width: 150.0, height: 60.0, - color: Colors.white, - margin: const EdgeInsets.all(4.0), - child: Text(t.task.title, style: Theme - .of(context) - .textTheme - .labelMedium), - ))); + margin: const EdgeInsets.only(left: 4, top: 1), + decoration: const BoxDecoration(color: Colors.white, border: Border(bottom: BorderSide(),)), + child: TextButton( + onPressed: () => _showDetailsAndRemoveTask(context,t), + child: Text(t.task.title, + style: Theme.of(context).textTheme.labelMedium), + ) + ) + ) + ); } @override @@ -54,14 +101,15 @@ class _ScrollTableState extends State { children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, - children: _buildTitles(), + //TODO: Text in container wie bei _buildTitles oben und width/height/margin/etc. festnageln. + children: List.from([Text("Todo", style: Theme.of(context).dataTableTheme.headingTextStyle,)]) + _buildTitles(context), ), Flexible( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: _content, + children: List.from([Text("header ... date, date, date .. fancy turned 60 degrees", style: Theme.of(context).dataTableTheme.headingTextStyle)]) + List.from(_content), ), ), ) diff --git a/lib/task_item.dart b/lib/task_item.dart index e77d3f6..8b1d7ed 100644 --- a/lib/task_item.dart +++ b/lib/task_item.dart @@ -27,7 +27,7 @@ class _TaskItemState extends State{ width: 60.0, height: 60.0, color: Colors.white, - margin: const EdgeInsets.all(4.0), + margin: const EdgeInsets.all(0.0), child: _done == null ? null : Checkbox(value: _done, onChanged: (newState) => setState(() { _done = newState!; })), diff --git a/lib/types/tasks.dart b/lib/types/tasks.dart index b52bc8a..f4fa9d6 100644 --- a/lib/types/tasks.dart +++ b/lib/types/tasks.dart @@ -19,12 +19,16 @@ class Task { this.end, }); + static String formatDate(DateTime dt) { + return "${dt!.year}-${dt!.month.toString().padLeft(2,'0')}-${dt!.day.toString().padLeft(2,'0')}"; + } + @override String toString() { return (done ? "x " : "") + (priority == null ? "" : "(${priority!}) ") - + (begin == null ? "" : "${begin!.year}-${begin!.month}-${begin!.day} ") - + (end == null ? "" : "${end!.year}-${end!.month}-${end!.day} ") + + (begin == null ? "" : "${Task.formatDate(begin!)} ") + + (end == null ? "" : "${Task.formatDate(end!)} ") + ("$title ") + meta.entries.map((entry) => "${entry.key}:${entry.value}").join(" ") ; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..9d48e63 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_window_close_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWindowClosePlugin"); + flutter_window_close_plugin_register_with_registrar(flutter_window_close_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..8b616e4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_window_close ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0d56f51..08b50cd 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import flutter_window_close import path_provider_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterWindowClosePlugin.register(with: registry.registrar(forPlugin: "FlutterWindowClosePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 3724a95..82d7417 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,25 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_window_close: + dependency: "direct main" + description: + name: flutter_window_close + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f3c5d20..f97f81f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: path_provider: ^2.0.11 petitparser: ^5.1.0 tuple: ^2.0.1 + flutter_window_close: ^0.2.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..f6af760 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterWindowClosePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterWindowClosePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..5ffb577 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_window_close ) list(APPEND FLUTTER_FFI_PLUGIN_LIST