From 43f95cb3218f841c03cf06f839ea47c8fa405c0e Mon Sep 17 00:00:00 2001 From: Stefan Dresselhaus Date: Wed, 11 Jan 2023 17:18:40 +0100 Subject: [PATCH] added selector for repeat-interval. Needs UI-polish though.. --- README.md | 2 +- lib/addReminder.dart | 130 ++++++++++++++++++++++--------------------- pubspec.lock | 14 +++++ pubspec.yaml | 1 + 4 files changed, 84 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 5a9b7ed..47008ce 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Reminder based on todo.txt synced via nextcloud - [x] make repeat-datatype (like: daily, weekly on mo/th/fr, bi-monthly, etc.) - [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] adding/removing tasks - [x] respect ordering that was used when starting the app when saving. diff --git a/lib/addReminder.dart b/lib/addReminder.dart index 4adee6a..3d8c8a7 100644 --- a/lib/addReminder.dart +++ b/lib/addReminder.dart @@ -1,7 +1,10 @@ +import 'package:date_field/date_field.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:nextcloud_reminder/repeating_task.dart'; import 'package:nextcloud_reminder/types/repeat.dart'; import 'package:nextcloud_reminder/types/tasks.dart'; +import 'package:tuple/tuple.dart'; class AddTaskWidget extends StatefulWidget { const AddTaskWidget({super.key, this.restorationId, required this.onSave}); @@ -15,11 +18,14 @@ class AddTaskWidget extends StatefulWidget { //TODO: make _repeat changeable. -class _AddTaskWidgetState extends State with RestorationMixin { +class _AddTaskWidgetState extends State { final _formKey = GlobalKey(); final _titleController = TextEditingController(); - final int _repeat = 1; - final RestorableDateTime _beginDate = RestorableDateTime(DateTime.now()); + static _emptyRepetition() { + return Tuple2(TextEditingController(text: "1"), ValueNotifier(DateInterval.daily)); + } + final List>> _repeatEveryController = [_emptyRepetition()]; + DateTime _beginDate = DateTime.now(); @override void dispose() { @@ -27,53 +33,45 @@ class _AddTaskWidgetState extends State with RestorationMixin { super.dispose(); } - @override - String? get restorationId => widget.restorationId; - - late final RestorableRouteFuture _restorableDatePickerRouteFuture = - RestorableRouteFuture( - onComplete: _selectDate, - onPresent: (NavigatorState navigator, Object? arguments) { - return navigator.restorablePush( - _datePickerRoute, - arguments: _beginDate.value.millisecondsSinceEpoch, - ); - }, - ); - - static Route _datePickerRoute( - BuildContext context, - Object? arguments, - ) { - return DialogRoute( - 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; - }); + String _prettyInterval(DateInterval d) { + switch (d) { + default: + return d.toString(); } } + Widget _repeatBuilder(BuildContext context, Tuple2> data) { + return Row( + children: [ + Text("Repeat every " ), + Expanded( + flex: 1, + child: TextFormField( + controller: data.item1, + decoration: const InputDecoration( + hintText: "1", + ), + keyboardType: const TextInputType.numberWithOptions(signed: false, decimal: false), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ), + ), + Expanded( + flex: 3, + child: DropdownButton( + 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, + ), + ), + IconButton(onPressed: () => setState(() { + _repeatEveryController.remove(data); + }), icon: Icon(Icons.remove, color: Theme.of(context).errorColor,)), + ], + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -98,21 +96,21 @@ class _AddTaskWidgetState extends State with RestorationMixin { labelText: "Taskname" ), ), - Container( - decoration: const BoxDecoration(), - child: Row( - children: [ - Text("Begin: ", style: Theme.of(context).textTheme.labelLarge), - Expanded( - child: Text(Task.formatDate(_beginDate.value)) - ), - IconButton( - onPressed: () => _restorableDatePickerRouteFuture.present(), - icon: Icon(Icons.date_range, - color: Theme.of(context).focusColor)) - ] - ) + + DateTimeField( + onDateSelected: (v) => setState(() { _beginDate = v; }), + selectedDate: _beginDate, + decoration: const InputDecoration( + suffixIcon: Icon(Icons.event_note), + labelText: "Begin" + ), + mode: DateTimeFieldPickerMode.date, ), + ] + _repeatEveryController.map((c) => _repeatBuilder(context,c)).toList() + + [ + ElevatedButton(onPressed: () => setState(() { + _repeatEveryController.add(_emptyRepetition()); + }), child: const Text("add repetition")), Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: ElevatedButton( @@ -124,8 +122,16 @@ class _AddTaskWidgetState extends State with RestorationMixin { 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.value, repeat: [RepeatInterval(interval: DateInterval.daily)],))); + task: TaskExtra( + title: _titleController.text, + begin: _beginDate, + meta: {"repeat": meta}, + repeat: repeats, + ) + )); Navigator.pop(context); } }, diff --git a/pubspec.lock b/pubspec.lock index 0a7bb28..c636f4b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + date_field: + dependency: "direct main" + description: + name: date_field + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" fake_async: dependency: transitive description: @@ -93,6 +100,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.2" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f0e3fed..169aae4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: tuple: ^2.0.1 flutter_window_close: ^0.2.2 collection: ^1.16.0 + date_field: ^3.0.2 dev_dependencies: flutter_test: