mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-07-04 18:43:07 +02:00
Notes list & filtering: - Text-search filter plus independent date-range filters for both created and last-updated (AND-combined), a priority filter, and a new status filter. Default view hides Done/Abandoned and renders as "unfiltered" (no badge for the default state); fixed badge clipping. - NoteSort options wired into the list UI; watchCount() for the "N saved". Status & priority: - New Status enum (toDo/inProgress/Done/Abandoned) as a settable + filterable attribute on every note, with capture-screen dropdown. - Removed "None" priority: every note is Low/Medium/High, default Medium. Schema migration v2->v3 rewrites legacy priority 0 -> Medium. Export / import: - NotesMarkdown round-trippable single-file format with HTML-comment markers. - Settings "Export notes" (mobile share sheet / desktop writes ~/todo/BACKLOG.md) and "Import notes" (file picker + safe newer-wins merge by id). Structured template: - Every new note pre-fills the richer what/where/must/nice/out/done/depends/ estimate/refs scaffold. Tests: - New fast (~5s), deterministic suite via FakeNoteRepository (no DB timers) and injected http/file-selector/url-launcher fakes. 86 tests, 96.2% line coverage (note.dart & sync_service.dart at 100%, settings 98.7%). Mobile-only share branch excluded via coverage:ignore (unreachable on the Linux test host). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
129 lines
3.7 KiB
Dart
129 lines
3.7 KiB
Dart
/// Domain model for a single idea/note.
|
|
///
|
|
/// Notes are stored locally in a CRDT-backed SQLite table (see
|
|
/// [NoteRepository]). The CRDT layer manages its own metadata columns
|
|
/// (`hlc`, `node_id`, `modified`, `is_deleted`); the fields here are the
|
|
/// user-facing data only.
|
|
library;
|
|
|
|
/// Priority tier for a note, used for sorting and visual grouping.
|
|
///
|
|
/// Every note always has a priority (there is no "none"); [medium] is the
|
|
/// default. Stored as the integer [value] so ordering is trivial in SQL.
|
|
enum Priority {
|
|
low(1, 'Low'),
|
|
medium(2, 'Medium'),
|
|
high(3, 'High');
|
|
|
|
const Priority(this.value, this.label);
|
|
|
|
/// The default applied to new notes and to any legacy/unknown value.
|
|
static const Priority defaultValue = Priority.medium;
|
|
|
|
/// Integer persisted in the database; higher means more important.
|
|
final int value;
|
|
|
|
/// Human-readable label for UI controls (pickers, filters, list rows).
|
|
final String label;
|
|
|
|
/// Rebuilds a [Priority] from its stored [value], defaulting to
|
|
/// [defaultValue] for any unknown/legacy value (e.g. the old `0` = none)
|
|
/// so reads never throw and pre-existing notes show as Medium.
|
|
static Priority fromValue(int? value) {
|
|
return Priority.values.firstWhere(
|
|
(p) => p.value == value,
|
|
orElse: () => defaultValue,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Workflow state of a note, independent of its [Priority].
|
|
///
|
|
/// Stored as the integer [value]. [todo] is the default (0) so existing
|
|
/// notes created before this field existed read back as "to do".
|
|
enum Status {
|
|
todo(0, 'To do'),
|
|
inProgress(1, 'In progress'),
|
|
done(2, 'Done'),
|
|
abandoned(3, 'Abandoned');
|
|
|
|
const Status(this.value, this.label);
|
|
|
|
/// Integer persisted in the database.
|
|
final int value;
|
|
|
|
/// Human-readable label for UI controls.
|
|
final String label;
|
|
|
|
/// Rebuilds a [Status] from its stored [value], defaulting to [todo]
|
|
/// for any unknown/legacy value so reads never throw.
|
|
static Status fromValue(int? value) {
|
|
return Status.values.firstWhere(
|
|
(s) => s.value == value,
|
|
orElse: () => Status.todo,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// An immutable idea/note record.
|
|
class Note {
|
|
const Note({
|
|
required this.id,
|
|
required this.text,
|
|
required this.priority,
|
|
required this.status,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
});
|
|
|
|
/// Stable unique id (UUID v4). Also the CRDT primary key.
|
|
final String id;
|
|
|
|
/// The markdown body of the idea.
|
|
final String text;
|
|
|
|
/// Priority tier for sorting/filtering.
|
|
final Priority priority;
|
|
|
|
/// Workflow state (to do / in progress / done / abandoned).
|
|
final Status status;
|
|
|
|
/// When the note was first created (set once, never changed).
|
|
final DateTime createdAt;
|
|
|
|
/// When the note's content was last modified locally.
|
|
final DateTime updatedAt;
|
|
|
|
/// Builds a [Note] from a raw CRDT query row.
|
|
///
|
|
/// Timestamps are stored as ISO-8601 strings for human-readable,
|
|
/// lexicographically sortable values.
|
|
factory Note.fromRow(Map<String, Object?> row) {
|
|
return Note(
|
|
id: row['id'] as String,
|
|
text: (row['text'] as String?) ?? '',
|
|
priority: Priority.fromValue(row['priority'] as int?),
|
|
status: Status.fromValue(row['status'] as int?),
|
|
createdAt: DateTime.parse(row['created_at'] as String),
|
|
updatedAt: DateTime.parse(row['updated_at'] as String),
|
|
);
|
|
}
|
|
|
|
/// Returns a copy with selected fields replaced.
|
|
Note copyWith({
|
|
String? text,
|
|
Priority? priority,
|
|
Status? status,
|
|
DateTime? updatedAt,
|
|
}) {
|
|
return Note(
|
|
id: id,
|
|
text: text ?? this.text,
|
|
priority: priority ?? this.priority,
|
|
status: status ?? this.status,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt ?? this.updatedAt,
|
|
);
|
|
}
|
|
}
|