feat(storage): notes are vault sidecar notebooks
All checks were successful
CI / Windows build (push) Successful in 12m55s
All checks were successful
CI / Windows build (push) Successful in 12m55s
Phase 4. Standalone notes move off SQLite into the vault, like the PDF annotations. - "Create notebook" makes a vault folder with a notebook.badnote.json (BadnoteSidecar docType 'notebook' + a title field), opened via SidecarRepository. - PenNoteScreen loads/saves its strokes (page 0) + title to that sidecar instead of the SQLite Note model. - note_provider lists notes from a vault scan (VaultService.scanNotes = folders with notebook.badnote.json and no source file); the doc scan still excludes them. Delete removes the folder. PDF/slide editors unchanged; pre-existing SQLite notes migrate in Phase 5. analyze clean, tests green.
This commit is contained in:
@@ -119,7 +119,7 @@ class HomeScreen extends ConsumerWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No ink notes yet — tap + to create one',
|
||||
l.noNotesYetHint,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(
|
||||
color: Theme.of(
|
||||
@@ -180,8 +180,18 @@ class HomeScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// "Create notebook": prompt a title (defaulting to Untitled), create the
|
||||
/// standalone notebook FOLDER + `notebook.badnote.json` via
|
||||
/// `VaultService.createEmptyNotebook`, then open the editor on the new note.
|
||||
Future<void> _createAndOpenNote(BuildContext context, WidgetRef ref) async {
|
||||
final note = await ref.read(noteListProvider.notifier).createNote();
|
||||
final title = await _promptNotebookTitle(context);
|
||||
if (title == null) return; // cancelled
|
||||
final l = context.mounted ? AppLocalizations.of(context) : null;
|
||||
final resolved = title.trim().isEmpty
|
||||
? (l?.untitledNote ?? 'Untitled')
|
||||
: title.trim();
|
||||
final note =
|
||||
await ref.read(noteListProvider.notifier).createNote(title: resolved);
|
||||
if (context.mounted) {
|
||||
Navigator.of(
|
||||
context,
|
||||
@@ -189,6 +199,35 @@ class HomeScreen extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask for a notebook title. Returns the entered string (possibly empty →
|
||||
/// caller defaults it), or null if the user cancelled.
|
||||
Future<String?> _promptNotebookTitle(BuildContext context) {
|
||||
final l = AppLocalizations.of(context);
|
||||
final controller = TextEditingController(text: l.untitledNote);
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(l.newNotebookTitle),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(hintText: l.notebookTitleHint),
|
||||
onSubmitted: (v) => Navigator.of(ctx).pop(v),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: Text(l.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(controller.text),
|
||||
child: Text(l.create),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Single top-level "Import file" action (sibling of "Create notebook"):
|
||||
/// pick a pdf/docx/pptx/ppt, copy it into a new vault notebook folder, then
|
||||
/// open the IN-VAULT copy in the right editor (routed by extension).
|
||||
@@ -386,8 +425,10 @@ class _NoteTileState extends ConsumerState<_NoteTile> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Stroke count is no longer cached in the vault scan (strokes
|
||||
// load lazily in the editor), so the tile shows only the date.
|
||||
Text(
|
||||
'${note.strokes.length} stroke${note.strokes.length == 1 ? '' : 's'} · $dateStr',
|
||||
dateStr,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
if (note.tags.isNotEmpty)
|
||||
|
||||
Reference in New Issue
Block a user