added selector for repeat-interval. Needs UI-polish though..
This commit is contained in:
parent
db39eeea55
commit
43f95cb321
@ -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] make repeat-datatype (like: daily, weekly on mo/th/fr, bi-monthly, etc.)
|
||||||
- [x] 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
|
||||||
- [x] adding/removing tasks
|
- [x] adding/removing tasks
|
||||||
- [x] respect ordering that was used when starting the app when saving.
|
- [x] respect ordering that was used when starting the app when saving.
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
import 'package:date_field/date_field.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.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/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});
|
||||||
@ -15,11 +18,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() {
|
||||||
@ -27,53 +33,45 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
String _prettyInterval(DateInterval d) {
|
||||||
String? get restorationId => widget.restorationId;
|
switch (d) {
|
||||||
|
default:
|
||||||
late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture =
|
return d.toString();
|
||||||
RestorableRouteFuture<DateTime?>(
|
|
||||||
onComplete: _selectDate,
|
|
||||||
onPresent: (NavigatorState navigator, Object? arguments) {
|
|
||||||
return navigator.restorablePush(
|
|
||||||
_datePickerRoute,
|
|
||||||
arguments: _beginDate.value.millisecondsSinceEpoch,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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 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<DateInterval>(
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -98,21 +96,21 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
|
|||||||
labelText: "Taskname"
|
labelText: "Taskname"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
decoration: const BoxDecoration(),
|
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(Task.formatDate(_beginDate.value))
|
labelText: "Begin"
|
||||||
),
|
),
|
||||||
IconButton(
|
mode: DateTimeFieldPickerMode.date,
|
||||||
onPressed: () => _restorableDatePickerRouteFuture.present(),
|
|
||||||
icon: Icon(Icons.date_range,
|
|
||||||
color: Theme.of(context).focusColor))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
] + _repeatEveryController.map((c) => _repeatBuilder(context,c)).toList()
|
||||||
|
+ [
|
||||||
|
ElevatedButton(onPressed: () => setState(() {
|
||||||
|
_repeatEveryController.add(_emptyRepetition());
|
||||||
|
}), child: const Text("add repetition")),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
@ -124,8 +122,16 @@ class _AddTaskWidgetState extends State<AddTaskWidget> with RestorationMixin {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Task added.')),
|
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(
|
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);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -43,6 +43,13 @@ 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"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -93,6 +100,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2"
|
version: "0.2.2"
|
||||||
|
intl:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,6 +41,7 @@ dependencies:
|
|||||||
tuple: ^2.0.1
|
tuple: ^2.0.1
|
||||||
flutter_window_close: ^0.2.2
|
flutter_window_close: ^0.2.2
|
||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
|
date_field: ^3.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user