feat(pdf): anchored scratch links replace board
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:
2026-06-24 20:02:12 +08:00
parent f757701391
commit 9bb5c483d6
13 changed files with 759 additions and 1076 deletions

View File

@@ -0,0 +1,88 @@
// lib/models/scratch_link.dart
//
// A PDF-anchored scratch link: a sticky-note "tab" placed at a normalized
// position (nx, ny in [0,1]) on a specific page of a document. Tapping the
// anchor opens a split view whose right pane is an infinite freehand scratchpad
// that BELONGS TO THIS ANCHOR (keyed by [id]).
//
// Plain immutable value class (no freezed codegen) so it compiles without a
// build_runner step. Equality is by value so anchors can be diffed in lists.
import 'package:flutter/foundation.dart';
@immutable
class ScratchLink {
const ScratchLink({
required this.id,
required this.documentId,
required this.pageIndex,
required this.nx,
required this.ny,
});
/// Stable anchor id (uuid). Doubles as the scratchpad storage key so each
/// anchor gets its own private infinite scratchpad.
final String id;
/// The owning document (the editor's stable document-id for the PDF path).
final String documentId;
/// 0-based page the anchor sits on.
final int pageIndex;
/// Normalized horizontal position on the page, in [0, 1].
final double nx;
/// Normalized vertical position on the page, in [0, 1].
final double ny;
ScratchLink copyWith({
String? id,
String? documentId,
int? pageIndex,
double? nx,
double? ny,
}) =>
ScratchLink(
id: id ?? this.id,
documentId: documentId ?? this.documentId,
pageIndex: pageIndex ?? this.pageIndex,
nx: nx ?? this.nx,
ny: ny ?? this.ny,
);
Map<String, dynamic> toJson() => {
'id': id,
'documentId': documentId,
'pageIndex': pageIndex,
'nx': nx,
'ny': ny,
};
factory ScratchLink.fromJson(Map<String, dynamic> json) => ScratchLink(
id: json['id'] as String,
documentId: json['documentId'] as String,
pageIndex: (json['pageIndex'] as num).toInt(),
nx: (json['nx'] as num).toDouble(),
ny: (json['ny'] as num).toDouble(),
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ScratchLink &&
runtimeType == other.runtimeType &&
id == other.id &&
documentId == other.documentId &&
pageIndex == other.pageIndex &&
nx == other.nx &&
ny == other.ny;
@override
int get hashCode => Object.hash(id, documentId, pageIndex, nx, ny);
@override
String toString() =>
'ScratchLink(id: $id, documentId: $documentId, pageIndex: $pageIndex, '
'nx: $nx, ny: $ny)';
}