task removeable, added header, more todos 🤷♀️ ..
This commit is contained in:
		@@ -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:
 | 
			
		||||
 | 
			
		||||
@@ -22,3 +28,6 @@ A Reminder based on todo.txt synced via nextcloud
 | 
			
		||||
 | 
			
		||||
### Adding Tasks
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Details/Removing tasks
 | 
			
		||||

 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								img/2023-01-10_Task_details.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/2023-01-10_Task_details.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 27 KiB  | 
@@ -103,7 +103,7 @@ class _AddTaskWidgetState extends State<AddTaskWidget> 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(),
 | 
			
		||||
 
 | 
			
		||||
@@ -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<HomeWidget> 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<HomeWidget> 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<HomeWidget> 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<HomeWidget> 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<HomeWidget> with WidgetsBindingObserver {
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  void _removeTask(RepeatingTask t) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      tasks.remove(t.task);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,13 @@ class _RepeatingTaskState extends State<RepeatingTask> {
 | 
			
		||||
 | 
			
		||||
  @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,
 | 
			
		||||
        )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<RepeatingTask> deleteCallback;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<ScrollTable> createState() => _internalState;
 | 
			
		||||
@@ -31,19 +33,64 @@ class _ScrollTableState extends State<ScrollTable> {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<Widget> _buildTitles() {
 | 
			
		||||
  removeTask(RepeatingTask task) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _content.remove(task);
 | 
			
		||||
    });
 | 
			
		||||
    widget.deleteCallback(task);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _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: <Widget>[
 | 
			
		||||
            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<Widget> _buildTitles(BuildContext context) {
 | 
			
		||||
    return List<Widget>.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<ScrollTable> {
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            Column(
 | 
			
		||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
              children: _buildTitles(),
 | 
			
		||||
              //TODO: Text in container wie bei _buildTitles oben und width/height/margin/etc. festnageln.
 | 
			
		||||
              children: List<Widget>.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<Widget>.from([Text("header ... date, date, date .. fancy turned 60 degrees", style: Theme.of(context).dataTableTheme.headingTextStyle)]) + List<Widget>.from(_content),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ class _TaskItemState extends State<TaskItem>{
 | 
			
		||||
      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!;
 | 
			
		||||
      })),
 | 
			
		||||
 
 | 
			
		||||
@@ -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(" ")
 | 
			
		||||
    ;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,10 @@
 | 
			
		||||
 | 
			
		||||
#include "generated_plugin_registrant.h"
 | 
			
		||||
 | 
			
		||||
#include <flutter_window_close/flutter_window_close_plugin.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
			
		||||
  flutter_window_close
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
 | 
			
		||||
 
 | 
			
		||||
@@ -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"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@
 | 
			
		||||
 | 
			
		||||
#include "generated_plugin_registrant.h"
 | 
			
		||||
 | 
			
		||||
#include <flutter_window_close/flutter_window_close_plugin.h>
 | 
			
		||||
 | 
			
		||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
 | 
			
		||||
  FlutterWindowClosePluginRegisterWithRegistrar(
 | 
			
		||||
      registry->GetRegistrarForPlugin("FlutterWindowClosePlugin"));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
			
		||||
  flutter_window_close
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user