Compare commits

..

8 Commits
v1.0 ... main

39 changed files with 694 additions and 210 deletions

View File

@ -1,42 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 135454af32477f815a7525073027a3ff9eff1bfd
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: android
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: ios
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: linux
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: macos
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
- platform: windows
create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -1,19 +1,29 @@
# nextcloud_reminder # todotxt_reminder
A Reminder based on todo.txt synced via nextcloud 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 - [x] add interface for repeat-datatype in addReminder.dart
- [x] save/load data to/from disk - [x] save/load data to/from disk
- [ ] respect ordering that was used when starting the app when saving. - [x] adding/removing tasks
- [x] respect ordering that was used when starting the app when saving.
- [ ] add annoying pop-up message so you actually do your tasks ^^
- [ ] 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"
- [ ] respect 'color:'-meta-tag (usual formats like "#aabbcc", html-colors like "red") - [ ] respect 'color:'-meta-tag (usual formats like "#aabbcc", html-colors like "red")
- [ ] use color in rendering todos - [ ] use color in rendering todos
- [ ] make application-settings
- [ ] store/load settings
- [ ] setting for number of days into the future
- [ ] theme
- [x] light/dark mode
- [ ] color theme by system colors
- [ ] own primary/secondary color theme
- [ ] fancy pop-animation & sound for the checkbox
## Current looks: ## Current looks:
@ -21,4 +31,14 @@ A Reminder based on todo.txt synced via nextcloud
![](img/2023-01-08_application.png) ![](img/2023-01-08_application.png)
### Adding Tasks ### Adding Tasks
![](img/2023-01-08_addTask.png) ![](img/2023-01-13_addTask.png)
### Details/Removing tasks
![](img/2023-01-10_Task_details.png)
### Complex repeat patterns
![](img/2023-01-10_repeat_patterns.png)
### Light/Dark theme
![](img/2023-01-13_theme_light.png)
![](img/2023-01-13_theme_dark.png)

View File

@ -26,10 +26,13 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion //compileSdkVersion flutter.compileSdkVersion
compileSdkVersion 33 //default: 31, flutter_local_notification needs min 33
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
//coreLibraryDesugaringEnabled true //flutter_local_notification
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@ -43,14 +46,18 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "cloud.dresselhaus.todotxt_reminder"
applicationId "cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion //minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion minSdkVersion 20
//targetSdkVersion flutter.targetSdkVersion
targetSdkVersion 33 //flutter.targetSdkVersion //33 = Android 13 = needed for request of notification-permissions.
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
//flutter_local_notification
multiDexEnabled true
} }
buildTypes { buildTypes {
@ -67,5 +74,8 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.0' //1.1.5
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.window:window:1.0.0' //flutter_local_notification for Android 12L
implementation 'androidx.window:window-java:1.0.0' //flutter_local_notification for Android 12L
} }

28
android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,28 @@
# FROM: https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/proguard-rules.pro
## Gson rules
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder"> package="cloud.dresselhaus.todotxt_reminder">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder"> package="cloud.dresselhaus.todotxt_reminder">
<application <application
android:label="nextcloud_reminder" android:label="Todo.txt Reminder"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View File

@ -1,4 +1,4 @@
package cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder package cloud.dresselhaus.todotxt_reminder
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- example from https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/res/raw/keep.xml
See: https://pub.dev/packages/flutter_local_notifications#release-build-configuration
-->
<!--resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/*,@raw/slow_spring_board" /-->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/*" />

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder"> package="cloud.dresselhaus.todotxt_reminder">
<!-- The INTERNET permission is required for development. Specifically, <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.6.10' ext.kotlin_version = '1.7.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.1.2' classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
img/2023-01-13_addTask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -294,7 +294,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.nextcloudreminder.nextcloudReminder; PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.todotxt_reminder;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -422,7 +422,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.nextcloudreminder.nextcloudReminder; PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.todotxt_reminder;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -444,7 +444,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.nextcloudreminder.nextcloudReminder; PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.todotxt_reminder;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Nextcloud Reminder</string> <string>Todo.txt Reminder</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>nextcloud_reminder</string> <string>Todo.txt Reminder</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@ -1,6 +1,12 @@
import 'package:date_field/date_field.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.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';
import 'package:tuple/tuple.dart';
class AddTaskWidget extends StatefulWidget { class AddTaskWidget extends StatefulWidget {
const AddTaskWidget({super.key, this.restorationId, required this.onSave}); const AddTaskWidget({super.key, this.restorationId, required this.onSave});
@ -14,11 +20,14 @@ class AddTaskWidget extends StatefulWidget {
//TODO: make _repeat changeable. //TODO: make _repeat changeable.
class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin { class _AddTaskWidgetState extends State<AddTaskWidget> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController(); final _titleController = TextEditingController();
final int _repeat = 1; static _emptyRepetition() {
final RestorableDateTime _beginDate = RestorableDateTime(DateTime.now()); return Tuple2(TextEditingController(text: "1"), ValueNotifier(DateInterval.daily));
}
final List<Tuple2<TextEditingController,ValueNotifier<DateInterval>>> _repeatEveryController = [_emptyRepetition()];
DateTime _beginDate = DateTime.now();
@override @override
void dispose() { void dispose() {
@ -26,53 +35,86 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
super.dispose(); super.dispose();
} }
@override String _prettyInterval(DateInterval d) {
String? get restorationId => widget.restorationId; switch (d) {
case DateInterval.daily: return "day";
late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture = case DateInterval.weekly: return "week";
RestorableRouteFuture<DateTime?>( case DateInterval.monthly: return "month";
onComplete: _selectDate, case DateInterval.monday: return "monday";
onPresent: (NavigatorState navigator, Object? arguments) { case DateInterval.tuesday: return "tuesday";
return navigator.restorablePush( case DateInterval.wednesday: return "wednesday";
_datePickerRoute, case DateInterval.thursday: return "thursday";
arguments: _beginDate.value.millisecondsSinceEpoch, case DateInterval.friday: return "friday";
); case DateInterval.saturday: return "saturday";
}, case DateInterval.sunday: return "sunday";
); case DateInterval.dayOfMonth: return "day of the month";
case DateInterval.dayOfYear: return "day of the year";
static Route<DateTime> _datePickerRoute(
BuildContext context,
Object? arguments,
) {
return DialogRoute<DateTime>(
context: context,
builder: (BuildContext context) {
return DatePickerDialog(
restorationId: 'date_picker_dialog',
initialEntryMode: DatePickerEntryMode.calendarOnly,
initialDate: DateTime.fromMillisecondsSinceEpoch(arguments! as int),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
},
);
}
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_beginDate, 'selected_date');
registerForRestoration(
_restorableDatePickerRouteFuture, 'date_picker_route_future');
}
void _selectDate(DateTime? newSelectedDate) {
if (newSelectedDate != null) {
setState(() {
_beginDate.value = newSelectedDate;
});
} }
} }
Widget _repeatBuilder(BuildContext context, Tuple2<TextEditingController,ValueNotifier<DateInterval>> data) {
return IntrinsicHeight(child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: TextFormField(
controller: data.item1,
decoration: InputDecoration(
prefix: const Text("Repeat every "),
suffix: () {
if (data.item1.value.text.isEmpty) return const Text("");
switch (data.item1.value.text.substring(data.item1.value.text.length - 1)) {
case "1":
return const Text("st");
case "2":
return const Text("nd");
case "3":
return const Text("rd");
default:
return const Text("th");
}}(),
border: const OutlineInputBorder()
),
textAlign: TextAlign.end,
keyboardType: const TextInputType.numberWithOptions(signed: false, decimal: false),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (_) {setState(() {
//nothing just rerender.
});},
),
),
const Gap(4),
Expanded(
//padding: const EdgeInsets.only(left: 30, right: 20),
child: DropdownButtonFormField<DateInterval>(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
items: DateInterval.values.map((v) => DropdownMenuItem(value: v, child: Text(_prettyInterval(v)))).toList(),
onChanged: (v) => setState(() {
data.item2.value = v ?? data.item2.value;
}),
value: data.item2.value,
),
),
PreferredSize(
preferredSize: const Size.square(40),
child: IconButton(
alignment: Alignment.topCenter,
onPressed: () => setState(() {
_repeatEveryController.remove(data);
}),
icon: Icon(Icons.remove, color: Theme.of(context).errorColor,)
)),
],
));
}
Widget _addPadding(Widget w) {
return Padding(padding: EdgeInsets.all(4), child: w);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -84,7 +126,7 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TextFormField( _addPadding(TextFormField(
// The validator receives the text that the user has entered. // The validator receives the text that the user has entered.
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@ -92,45 +134,56 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
} }
return null; return null;
}, },
autofocus: true,
controller: _titleController, controller: _titleController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Taskname" labelText: "Taskname"
), ),
), )),
Container(
decoration: const BoxDecoration(), _addPadding(DateTimeField(
child: Row( onDateSelected: (v) => setState(() { _beginDate = v; }),
children: [ selectedDate: _beginDate,
Text("Begin: ", style: Theme.of(context).textTheme.labelLarge), decoration: const InputDecoration(
Expanded( suffixIcon: Icon(Icons.event_note),
child: Text("${_beginDate.value.year}-${_beginDate.value.month.toString().padLeft(2,'0')}-${_beginDate.value.day.toString().padLeft(2,'0')}") labelText: "Begin"
),
IconButton(
onPressed: () => _restorableDatePickerRouteFuture.present(),
icon: Icon(Icons.date_range,
color: Theme.of(context).focusColor))
]
)
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Task added.')),
);
widget.onSave(RepeatingTask(
task: Task(title: _titleController.text, begin: _beginDate.value), repeat: _repeat,));
Navigator.pop(context);
}
},
child: const Text('Submit'),
), ),
), mode: DateTimeFieldPickerMode.date,
)),
] + _repeatEveryController.map((c) => _addPadding(_repeatBuilder(context,c))).toList()
+ [
_addPadding(_addPadding(Row(
children: [
ElevatedButton(onPressed: () => setState(() {
_repeatEveryController.add(_emptyRepetition());
}), child: const Text("add repetition")),
Expanded(child: Container()),
ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Task added.')),
);
var repeats = _repeatEveryController.map((e) => RepeatInterval(interval: e.item2.value, every: int.parse(e.item1.text))).toList();
var meta = repeats.map((e) => e.toString()).join("/");
widget.onSave(RepeatingTask(
task: TaskExtra(
title: _titleController.text,
begin: _beginDate,
meta: {"repeat": meta},
repeat: repeats,
)
));
Navigator.pop(context);
}
},
child: const Text('Submit'),
),
]
))),
], ],
), ),
) )

View File

@ -1,12 +1,14 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_window_close/flutter_window_close.dart';
import 'package:nextcloud_reminder/addReminder.dart'; import 'package:nextcloud_reminder/addReminder.dart';
import 'package:nextcloud_reminder/parser/todotxt.dart'; import 'package:nextcloud_reminder/parser/todotxt.dart';
import 'package:nextcloud_reminder/repeating_task.dart'; 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});
@ -28,11 +30,10 @@ class HomeWidget extends StatefulWidget {
class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver { 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 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) {
@ -45,7 +46,6 @@ class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
appDocDirectory = (await getApplicationDocumentsDirectory()); appDocDirectory = (await getApplicationDocumentsDirectory());
if (await todotxt.exists()) { if (await todotxt.exists()) {
var data = await todotxt.readAsLines(); var data = await todotxt.readAsLines();
debugPrint(data.toString());
tasks.addAll(TodoParser.parse(data)); tasks.addAll(TodoParser.parse(data));
_loadTodos(); _loadTodos();
} else { } else {
@ -57,7 +57,12 @@ class _HomeWidgetState extends State<HomeWidget> with WidgetsBindingObserver {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_checkboxes = ScrollTable(title: "TODOs", deleteCallback: _removeTask);
initLazy(); initLazy();
FlutterWindowClose.setWindowShouldCloseHandler(() async {
saveData();
return true;
});
} }
@override @override
@ -76,29 +81,26 @@ 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);
} }
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) { void _addTask(RepeatingTask? t) {
if (t != null) { if (t != null) {
setState(() { setState(() {
_checkboxes.addTask(t!); _checkboxes.addTask(t!);
tasks.add(t.task); tasks.add(TaskExtra.fromTask(t.task));
}); });
} }
} }
void _removeTask(RepeatingTask t) {
setState(() {
tasks.remove(t.task);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,24 +1,66 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl_standalone.dart';
import 'package:nextcloud_reminder/homescreen.dart'; import 'package:nextcloud_reminder/homescreen.dart';
void main() { void main() {
runApp(const TodoTxtReminderApp()); findSystemLocale().then((value) =>
initializeDateFormatting(value).then((_) =>
runApp(const TodoTxtReminderApp())
));
} }
class TodoTxtReminderApp extends StatelessWidget { class TodoTxtReminderApp extends StatelessWidget {
const TodoTxtReminderApp({super.key}); const TodoTxtReminderApp({super.key});
static const primaryColor = Colors.pink;
static const secondaryColor = Colors.amber;
getInteractColor(Color a, Color b) {
return (Set<MaterialState> states) {
const Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
};
if (states.any(interactiveStates.contains)) {
return b;
}
return a;
};
}
Color getColor(Set<MaterialState> states) {
return getInteractColor(primaryColor, secondaryColor)(states);
}
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const inputDecorationTheme = InputDecorationTheme(border: OutlineInputBorder(), focusColor: secondaryColor);
return MaterialApp( return MaterialApp(
title: 'Nextcloud Reminder', title: 'Todo.txt Reminder',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application. brightness: Brightness.light,
primarySwatch: Colors.blue, colorScheme: ThemeData.light().colorScheme.copyWith(primary: primaryColor, secondary: secondaryColor, background: primaryColor.shade50),
inputDecorationTheme: inputDecorationTheme,
checkboxTheme: ThemeData.light().checkboxTheme.copyWith(
fillColor: MaterialStateProperty.resolveWith(getColor),
checkColor: MaterialStateProperty.resolveWith(getInteractColor(Colors.white,Colors.black))
),
), ),
home: const HomeWidget(title: 'todo.txt reminder'), darkTheme: ThemeData(
brightness: Brightness.dark,
colorScheme: ThemeData.dark().colorScheme.copyWith(primary: primaryColor, secondary: secondaryColor, background: primaryColor.shade500.withAlpha(32)),
inputDecorationTheme: inputDecorationTheme,
checkboxTheme: ThemeData.dark().checkboxTheme.copyWith(
fillColor: MaterialStateProperty.resolveWith(getColor),
checkColor: MaterialStateProperty.resolveWith(getInteractColor(Colors.white,Colors.black))
),
),
themeMode: ThemeMode.system,
home: const HomeWidget(title: 'Todo.txt Reminder'),
); );
} }
} }

View File

@ -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;
} }

View File

@ -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,14 +17,30 @@ 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
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Container(
decoration: BoxDecoration(border: Border(
bottom: BorderSide(color: Theme.of(context).colorScheme.background, width: 1),
),),
margin: const EdgeInsets.all(0.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _occurrences, children: _occurrences,
); )
);
} }
} }

View File

@ -1,8 +1,12 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:nextcloud_reminder/repeating_task.dart'; import 'package:nextcloud_reminder/repeating_task.dart';
import 'package:nextcloud_reminder/types/tasks.dart';
class ScrollTable extends StatefulWidget { 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 // 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 // case the title) provided by the parent (in this case the App widget) and
@ -11,6 +15,7 @@ class ScrollTable extends StatefulWidget {
final String title; final String title;
final _ScrollTableState _internalState = _ScrollTableState(); final _ScrollTableState _internalState = _ScrollTableState();
final ValueChanged<RepeatingTask> deleteCallback;
@override @override
State<ScrollTable> createState() => _internalState; State<ScrollTable> createState() => _internalState;
@ -24,6 +29,7 @@ class ScrollTable extends StatefulWidget {
class _ScrollTableState extends State<ScrollTable> { class _ScrollTableState extends State<ScrollTable> {
final List<RepeatingTask> _content = []; final List<RepeatingTask> _content = [];
final _contentStart = DateTime.now();
addTask(RepeatingTask task) { addTask(RepeatingTask task) {
setState(() { setState(() {
@ -31,37 +37,115 @@ 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: const 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")),
]
);
});
}
Widget _todoHeader(BuildContext context) {
return Container(
width: 200,
height: 60,
alignment: Alignment.bottomLeft,
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12),
decoration: BoxDecoration(color: Theme.of(context).colorScheme.background),
child: Text("TODO", style: Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).colorScheme.primary),),
);
}
Widget _datesHeader(BuildContext context) {
return Row(
children: List.generate(10, (i) => Container(
width: 60,
height: 60,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 16),
decoration: BoxDecoration(color: Theme.of(context).colorScheme.background),
child: Transform.rotate(angle: -pi/3,
child: Text(DateFormat.Md().format(_contentStart.add(Duration(days: i))),
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).colorScheme.primary),),
)
))
);
}
List<Widget> _buildTitles(BuildContext context) {
return List<Widget>.from(_content.map((RepeatingTask t) => return List<Widget>.from(_content.map((RepeatingTask t) =>
Container( Container(
alignment: Alignment.center, alignment: Alignment.centerLeft,
width: 120.0, width: 200.0,
height: 60.0, height: 60.0,
color: Colors.white, margin: const EdgeInsets.only(top: 1),
margin: const EdgeInsets.all(4.0), decoration: BoxDecoration(color: Theme.of(context).canvasColor, border: Border(bottom: BorderSide(color: Theme.of(context).colorScheme.background, width: 1),)),
child: Text(t.task.title, style: Theme child: SizedBox(width: 200, child: TextButton(
.of(context) onPressed: () => _showDetailsAndRemoveTask(context,t),
.textTheme style: const ButtonStyle(alignment: AlignmentDirectional.centerStart),
.labelMedium), child: Text(t.task.title,
))); softWrap: true,
textAlign: TextAlign.left,
style: Theme.of(context).textTheme.titleSmall,
)
)),
)
)
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return SingleChildScrollView(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildTitles(), //TODO: Text in container wie bei _buildTitles oben und width/height/margin/etc. festnageln.
children: List<Widget>.from([_todoHeader(context)]) + _buildTitles(context),
), ),
Flexible( Flexible(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _content, children: [_datesHeader(context)] + List<Widget>.from(_content),
), ),
), ),
) )

View File

@ -26,8 +26,10 @@ class _TaskItemState extends State<TaskItem>{
alignment: Alignment.center, alignment: Alignment.center,
width: 60.0, width: 60.0,
height: 60.0, height: 60.0,
color: Colors.white, decoration: BoxDecoration(border: Border(
margin: const EdgeInsets.all(4.0), left: BorderSide(color: Theme.of(context).colorScheme.background, width: 1)
),),
margin: const EdgeInsets.all(0.0),
child: _done == null ? null : Checkbox(value: _done, onChanged: (newState) => setState(() { child: _done == null ? null : Checkbox(value: _done, onChanged: (newState) => setState(() {
_done = newState!; _done = newState!;
})), })),

97
lib/types/repeat.dart Normal file
View 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,
}

View File

@ -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;
@ -19,12 +61,16 @@ class Task {
this.end, this.end,
}); });
static String formatDate(DateTime dt) {
return "${dt!.year}-${dt!.month.toString().padLeft(2,'0')}-${dt!.day.toString().padLeft(2,'0')}";
}
@override @override
String toString() { String toString() {
return (done ? "x " : "") return (done ? "x " : "")
+ (priority == null ? "" : "(${priority!}) ") + (priority == null ? "" : "(${priority!}) ")
+ (begin == null ? "" : "${begin!.year}-${begin!.month}-${begin!.day} ") + (begin == null ? "" : "${Task.formatDate(begin!)} ")
+ (end == null ? "" : "${end!.year}-${end!.month}-${end!.day} ") + (end == null ? "" : "${Task.formatDate(end!)} ")
+ ("$title ") + ("$title ")
+ meta.entries.map((entry) => "${entry.key}:${entry.value}").join(" ") + meta.entries.map((entry) => "${entry.key}:${entry.value}").join(" ")
; ;

View File

@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "nextcloud_reminder") set(BINARY_NAME "Todo.txt Reminder")
# The unique GTK application identifier for this application. See: # The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID # https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "cloud.dresselhaus.nextcloud_reminder.nextcloud_reminder") set(APPLICATION_ID "cloud.dresselhaus.todotxt_reminder")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_window_close/flutter_window_close_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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);
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_window_close
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

8
local.properties Normal file
View File

@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Sun Jan 15 22:45:27 CET 2023
sdk.dir=C\:\\Users\\Drezi\\AppData\\Local\\Android\\Sdk

View File

@ -5,8 +5,12 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import flutter_local_notifications
import flutter_window_close
import path_provider_macos import path_provider_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterWindowClosePlugin.register(with: registry.registrar(forPlugin: "FlutterWindowClosePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@ -5,10 +5,10 @@
// 'flutter create' template. // 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window. // The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = nextcloud_reminder PRODUCT_NAME = Todo.txt Reminder;
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.nextcloudreminder.nextcloudReminder PRODUCT_BUNDLE_IDENTIFIER = cloud.dresselhaus.todotxt_reminder;
// The copyright displayed in application information // The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2023 cloud.dresselhaus.nextcloud_reminder. All rights reserved. PRODUCT_COPYRIGHT = Copyright © 2023 cloud.dresselhaus.nextcloud_reminder. All rights reserved.

View File

@ -1,6 +1,13 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -30,7 +37,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"
@ -43,6 +50,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
date_field:
dependency: "direct main"
description:
name: date_field
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -76,11 +97,65 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "13.0.0"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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"
gap:
dependency: "direct main"
description:
name: gap
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
intl:
dependency: "direct main"
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -150,7 +225,7 @@ packages:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.6" version: "2.0.7"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -240,6 +315,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.12" version: "0.4.12"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.1"
tuple: tuple:
dependency: "direct main" dependency: "direct main"
description: description:
@ -267,7 +349,14 @@ packages:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+2" version: "0.2.0+3"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0"
sdks: sdks:
dart: ">=2.18.6 <3.0.0" dart: ">=2.18.6 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View File

@ -3,7 +3,7 @@ description: A Reminder based on todo.txt synced via nextcloud
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'https://gitea.dresselhaus.cloud/api/packages/drezil/pub' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43 # A version number is three numbers separated by dots, like 1.2.43
@ -39,6 +39,12 @@ dependencies:
path_provider: ^2.0.11 path_provider: ^2.0.11
petitparser: ^5.1.0 petitparser: ^5.1.0
tuple: ^2.0.1 tuple: ^2.0.1
flutter_window_close: ^0.2.2
collection: ^1.16.0
date_field: ^3.0.2
gap: ^2.0.1
intl: ^0.17.0
flutter_local_notifications: ^13.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_window_close/flutter_window_close_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterWindowClosePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWindowClosePlugin"));
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_window_close
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "cloud.dresselhaus.nextcloud_reminder" "\0" VALUE "CompanyName", "cloud.dresselhaus.nextcloud_reminder" "\0"
VALUE "FileDescription", "nextcloud_reminder" "\0" VALUE "FileDescription", "Todo.txt Reminder" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "nextcloud_reminder" "\0" VALUE "InternalName", "Todo.txt Reminder" "\0"
VALUE "LegalCopyright", "Copyright (C) 2023 cloud.dresselhaus.nextcloud_reminder. All rights reserved." "\0" VALUE "LegalCopyright", "Copyright (C) 2023 cloud.dresselhaus.nextcloud_reminder. All rights reserved." "\0"
VALUE "OriginalFilename", "nextcloud_reminder.exe" "\0" VALUE "OriginalFilename", "nextcloud_reminder.exe" "\0"
VALUE "ProductName", "nextcloud_reminder" "\0" VALUE "ProductName", "Todo.txt Reminder" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"
END END
END END

View File

@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"nextcloud_reminder", origin, size)) { if (!window.CreateAndShow(L"Todo.txt Reminder", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);