feat(todo): reminders on the device, Upcoming's week strip, the task pane and search (ADR 0033, D2, D5, D8)
A task with a time takes reminders, minutes before it, in its sealed payload; each device plans the coming ones whenever the tasks change and schedules them with the system through flutter_local_notifications (Android, iOS, macOS, Windows; on Linux the running app shows them), at the minute where Android allows exact alarms. Notifying is asked for when a task first gets a reminder; signing out or switching person cancels them. Upcoming gets its week strip: the month opening Material's calendar, the weeks before and after, a dot under each day with tasks; a day picked scrolls the agenda to it, and scrolling moves the strip along. From expanded windows on, a task opens in a pane beside the list rather than in a dialog. Search, from every tab's app bar, finds projects by name and tasks by every word typed, in their title, description or labels, whatever the case and accents, on the device alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
aa013f5a00
commit
666e8dbcbb
2 changed files with 2140 additions and 57 deletions
|
|
@ -42,6 +42,7 @@ void main() {
|
|||
deadline: const Day(2026, 11, 8),
|
||||
priority: Priority.p2,
|
||||
labelIds: const ['l1', 'l2'],
|
||||
reminders: const [30, 0],
|
||||
completedAt: DateTime(2026, 11, 2, 7),
|
||||
completions: [DateTime(2026, 10, 19, 7, 5), DateTime(2026, 10, 22, 6)],
|
||||
);
|
||||
|
|
@ -77,6 +78,7 @@ void main() {
|
|||
'deadline': '2026-11-08',
|
||||
'priority': 2,
|
||||
'labelIds': ['l1', 'l2'],
|
||||
'reminders': [30, 0],
|
||||
'completedAt': DateTime(2026, 11, 2, 7).toUtc().toIso8601String(),
|
||||
'completions': [
|
||||
DateTime(2026, 10, 19, 7, 5).toUtc().toIso8601String(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task_search.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
||||
|
||||
/// Search (ADR 0033, D2): the tasks every word typed is found in, open
|
||||
/// ones first and the nearest date first, on the device alone, as the
|
||||
/// instance reads no task (D5).
|
||||
void main() {
|
||||
OpenTask task(
|
||||
String id,
|
||||
String title, {
|
||||
String description = '',
|
||||
List<String> labels = const [],
|
||||
Day? day,
|
||||
bool done = false,
|
||||
}) => (
|
||||
id: id,
|
||||
content: TaskContent(
|
||||
title: title,
|
||||
projectId: 'p',
|
||||
description: description,
|
||||
labelIds: labels,
|
||||
date: day == null ? null : TaskDate(day),
|
||||
completedAt: done ? DateTime(2026, 11, 2) : null,
|
||||
),
|
||||
);
|
||||
|
||||
final tasks = [
|
||||
task('milk', 'Buy oat milk', labels: ['errand']),
|
||||
task('call', 'Call Anna', description: 'About the Café opening'),
|
||||
task('done', 'Buy bread', done: true),
|
||||
task('later', 'Buy stamps', day: const Day(2026, 11, 20)),
|
||||
task('soon', 'Buy flowers', day: const Day(2026, 11, 3)),
|
||||
];
|
||||
const labels = {'errand': 'Errands'};
|
||||
|
||||
List<String> found(String query) => [
|
||||
for (final task in searchTasks(tasks, query, labelNames: labels)) task.id,
|
||||
];
|
||||
|
||||
test('finds every word in the title, the description or a label, in any '
|
||||
'case and with or without accents', () {
|
||||
expect(found('anna cafe'), ['call']);
|
||||
expect(found('CAFÉ'), ['call']);
|
||||
expect(found('errands'), ['milk']);
|
||||
expect(found('oat errand'), ['milk']);
|
||||
expect(found('oat anna'), isEmpty);
|
||||
});
|
||||
|
||||
test('puts the open ones first, the nearest date first, the undated after '
|
||||
'them, the done ones last', () {
|
||||
expect(found('buy'), ['soon', 'later', 'milk', 'done']);
|
||||
});
|
||||
|
||||
test('finds nothing for nothing typed', () {
|
||||
expect(found(''), isEmpty);
|
||||
expect(found(' '), isEmpty);
|
||||
});
|
||||
|
||||
test('folds what is typed as it folds what is searched', () {
|
||||
expect(folded('Ärger über Öl, Straße'), 'arger uber ol, strasse');
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue