feat(pdf): anchored scratch links replace board
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Replace the rejected standalone sticky-card board with the real feature: place a link anchor anywhere on a PDF page, tap it to open split view whose right pane is THAT anchor's own infinite scratchpad (keyed by anchor id) — like a paper sticky-note tab. - ScratchLink model + scratch_links table (id, doc, page, nx, ny). - PDF editor: "place link" tool drops/loads/shows tappable markers; tap opens SplitViewScreen for that anchor; long-press deletes. - SplitViewScreen rebuilt on pdfrx (was syncfusion), right scratchpad keyed by scratchLinkId, new brush palette (was AnnotationToolbar). - Remove board_screen + its test + the home board entry. analyze clean, tests green.
This commit is contained in:
@@ -16,6 +16,7 @@ import '../models/ink_stroke.dart';
|
||||
import '../models/note.dart';
|
||||
import '../models/pen_tool.dart';
|
||||
import '../models/pointer_device_kind.dart';
|
||||
import '../models/scratch_link.dart';
|
||||
|
||||
class DatabaseService {
|
||||
static DatabaseService? _instance;
|
||||
@@ -56,7 +57,7 @@ class DatabaseService {
|
||||
|
||||
_database = await openDatabase(
|
||||
dbPath,
|
||||
version: 7,
|
||||
version: 8,
|
||||
onCreate: _onCreate,
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
@@ -202,6 +203,28 @@ class DatabaseService {
|
||||
|
||||
// Sticky-note board cards (v7): F7 双链 + 无限便利贴.
|
||||
await _createBoardCardsTable(db);
|
||||
|
||||
// PDF-anchored scratch links (v8).
|
||||
await _createScratchLinksTable(db);
|
||||
}
|
||||
|
||||
/// PDF-anchored scratch links table (v8). One row per [ScratchLink] anchor.
|
||||
/// The anchor [id] doubles as the storage key for its private scratchpad
|
||||
/// (reused from the [scratchpads] table — see [saveScratchpad]).
|
||||
Future<void> _createScratchLinksTable(DatabaseExecutor db) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE scratch_links (
|
||||
id TEXT PRIMARY KEY,
|
||||
document_id TEXT NOT NULL,
|
||||
page_index INTEGER NOT NULL,
|
||||
nx REAL NOT NULL,
|
||||
ny REAL NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
await db.execute(
|
||||
'CREATE INDEX idx_scratch_links_doc ON scratch_links(document_id)',
|
||||
);
|
||||
}
|
||||
|
||||
/// Sticky-note board cards table (F7). One row per [BoardCard]; a board is the
|
||||
@@ -232,6 +255,13 @@ class DatabaseService {
|
||||
if (oldVersion < 5) await _migrateV4toV5(db);
|
||||
if (oldVersion < 6) await _migrateV5toV6(db);
|
||||
if (oldVersion < 7) await _migrateV6toV7(db);
|
||||
if (oldVersion < 8) await _migrateV7toV8(db);
|
||||
}
|
||||
|
||||
Future<void> _migrateV7toV8(Database db) async {
|
||||
await db.transaction((txn) async {
|
||||
await _createScratchLinksTable(txn);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _migrateV6toV7(Database db) async {
|
||||
@@ -1003,4 +1033,54 @@ class DatabaseService {
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// ── Scratch links CRUD (PDF-anchored scratchpad tabs) ──────────────────
|
||||
|
||||
/// Insert or replace a [ScratchLink] anchor. The anchor's private scratchpad
|
||||
/// lives in the [scratchpads] table keyed by [ScratchLink.id] — saved/loaded
|
||||
/// via [saveScratchpad] / [loadScratchpad].
|
||||
Future<void> saveScratchLink(ScratchLink link) async {
|
||||
await _database.insert(
|
||||
'scratch_links',
|
||||
{
|
||||
'id': link.id,
|
||||
'document_id': link.documentId,
|
||||
'page_index': link.pageIndex,
|
||||
'nx': link.nx,
|
||||
'ny': link.ny,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
/// Load all anchors for [documentId], oldest first.
|
||||
Future<List<ScratchLink>> loadScratchLinks(String documentId) async {
|
||||
final rows = await _database.query(
|
||||
'scratch_links',
|
||||
where: 'document_id = ?',
|
||||
whereArgs: [documentId],
|
||||
orderBy: 'created_at ASC',
|
||||
);
|
||||
return rows
|
||||
.map(
|
||||
(row) => ScratchLink(
|
||||
id: row['id'] as String,
|
||||
documentId: row['document_id'] as String,
|
||||
pageIndex: row['page_index'] as int,
|
||||
nx: (row['nx'] as num).toDouble(),
|
||||
ny: (row['ny'] as num).toDouble(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Delete an anchor and its private scratchpad (the scratchpad row keyed by
|
||||
/// the anchor id), so a deleted anchor leaves no orphaned ink behind.
|
||||
Future<void> deleteScratchLink(String id) async {
|
||||
await _database.transaction((txn) async {
|
||||
await txn.delete('scratch_links', where: 'id = ?', whereArgs: [id]);
|
||||
await txn.delete('scratchpads', where: 'document_id = ?', whereArgs: [id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user