refactor: unify signature placement handling with new model structure

This commit is contained in:
insleker 2025-09-08 17:07:09 +08:00
parent f74b724712
commit 4f149656bd
17 changed files with 179 additions and 167 deletions

View File

@ -1,12 +1,12 @@
# AGENTS
Always read `README.md` and `docs/meta-arch.md` when new chat created.
Always read [`README.md`](README.md) and [`meta-arch.md`](docs/meta-arch.md) when new chat created.
Additionally read relevant files depends on task.
* If want to modify use cases (files at `test/features/*.feature`)
* read `docs/FRs.md`
* read [`FRs.md`](docs/FRs.md)
* If want to modify code (implement or test) of `View` of MVVM (UI widget) (files at `lib/ui/features/*/widgets/*`)
* read `docs/wireframe.md`, `docs/NFRs.md`, `test/features/*.feature`
* read [`wireframe.md`](docs/wireframe.md), [`NFRs.md`](docs/NFRs.md), `test/features/*.feature`
* If want to modify code (implement or test) of non-View e.g. `Model`, `View Model`, services...
* read `test/features/*.feature`, `docs/NFRs.md`
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)

View File

@ -126,7 +126,7 @@ void main() {
final pdf = container.read(pdfProvider);
container
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: r, image: imageId);
.addPlacement(page: pdf.currentPage, rect: r, imageId: imageId);
container.read(signatureProvider.notifier).clearActiveOverlay();
await tester.pumpAndSettle();

View File

@ -1,6 +1,35 @@
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
/// Represents a single signature placement on a page combining both the
/// geometric rectangle (UI coordinate space) and the identifier of the
/// image/signature asset assigned to that placement.
class SignaturePlacement {
final Rect rect;
/// Rotation in degrees to apply when rendering/exporting this placement.
final double rotationDeg;
/// Identifier of the image (e.g., filename / asset id) assigned to this placement.
/// Nullable to allow a placement reserved before an image is chosen.
final String? imageId;
const SignaturePlacement({
required this.rect,
this.imageId,
this.rotationDeg = 0.0,
});
SignaturePlacement copyWith({
Rect? rect,
String? imageId,
double? rotationDeg,
}) => SignaturePlacement(
rect: rect ?? this.rect,
imageId: imageId ?? this.imageId,
rotationDeg: rotationDeg ?? this.rotationDeg,
);
}
class PdfState {
final bool loaded;
final int pageCount;
@ -8,10 +37,8 @@ class PdfState {
final String? pickedPdfPath;
final Uint8List? pickedPdfBytes;
final int? signedPage;
// Multiple signature placements per page, stored as UI-space rects (e.g., 400x560)
final Map<int, List<Rect>> placementsByPage;
// For each placement, store the assigned image identifier (e.g., filename) in the same index order.
final Map<int, List<String>> placementImageByPage;
// Multiple signature placements per page, each combines geometry and optional image id.
final Map<int, List<SignaturePlacement>> placementsByPage;
// UI state: selected placement index on the current page (if any)
final int? selectedPlacementIndex;
const PdfState({
@ -22,7 +49,6 @@ class PdfState {
this.pickedPdfBytes,
this.signedPage,
this.placementsByPage = const {},
this.placementImageByPage = const {},
this.selectedPlacementIndex,
});
factory PdfState.initial() => const PdfState(
@ -32,7 +58,6 @@ class PdfState {
pickedPdfBytes: null,
signedPage: null,
placementsByPage: {},
placementImageByPage: {},
selectedPlacementIndex: null,
);
PdfState copyWith({
@ -42,8 +67,7 @@ class PdfState {
String? pickedPdfPath,
Uint8List? pickedPdfBytes,
int? signedPage,
Map<int, List<Rect>>? placementsByPage,
Map<int, List<String>>? placementImageByPage,
Map<int, List<SignaturePlacement>>? placementsByPage,
int? selectedPlacementIndex,
}) => PdfState(
loaded: loaded ?? this.loaded,
@ -53,7 +77,6 @@ class PdfState {
pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes,
signedPage: signedPage ?? this.signedPage,
placementsByPage: placementsByPage ?? this.placementsByPage,
placementImageByPage: placementImageByPage ?? this.placementImageByPage,
selectedPlacementIndex:
selectedPlacementIndex ?? this.selectedPlacementIndex,
);

View File

@ -6,6 +6,7 @@ import 'package:pdf/widgets.dart' as pw;
import 'package:pdf/pdf.dart' as pdf;
import 'package:printing/printing.dart' as printing;
import 'package:image/image.dart' as img;
import '../model/model.dart';
// NOTE:
// - This exporter uses a raster snapshot of the UI (RepaintBoundary) and embeds it into a new PDF.
@ -32,8 +33,7 @@ class ExportService {
required Rect? signatureRectUi,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
Map<int, List<Rect>>? placementsByPage,
Map<int, List<String>>? placementImageByPage,
Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0,
}) async {
@ -55,7 +55,6 @@ class ExportService {
uiPageSize: uiPageSize,
signatureImageBytes: signatureImageBytes,
placementsByPage: placementsByPage,
placementImageByPage: placementImageByPage,
libraryBytes: libraryBytes,
targetDpi: targetDpi,
);
@ -76,8 +75,7 @@ class ExportService {
required Rect? signatureRectUi,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
Map<int, List<Rect>>? placementsByPage,
Map<int, List<String>>? placementImageByPage,
Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0,
}) async {
@ -104,12 +102,8 @@ class ExportService {
(placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements =
hasMulti
? (placementsByPage[pageIndex] ?? const <Rect>[])
: const <Rect>[];
final pageImageIds =
hasMulti
? (placementImageByPage?[pageIndex] ?? const <String>[])
: const <String>[];
? (placementsByPage[pageIndex] ?? const <SignaturePlacement>[])
: const <SignaturePlacement>[];
final shouldStampSingle =
!hasMulti &&
signedPage != null &&
@ -147,18 +141,18 @@ class ExportService {
// Multi-placement stamping: per-placement image from libraryBytes
if (hasMulti && pagePlacements.isNotEmpty) {
for (var i = 0; i < pagePlacements.length; i++) {
final r = pagePlacements[i];
final placement = pagePlacements[i];
final r = placement.rect;
final left = r.left / uiPageSize.width * widthPts;
final top = r.top / uiPageSize.height * heightPts;
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
Uint8List? bytes;
if (i < pageImageIds.length) {
final id = pageImageIds[i];
final id = placement.imageId;
if (id != null) {
bytes = libraryBytes?[id];
}
bytes ??=
signatureImageBytes; // fallback to single image if provided
bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) {
pw.MemoryImage? imgObj;
try {
@ -176,7 +170,13 @@ class ExportService {
height: h,
child: pw.FittedBox(
fit: pw.BoxFit.contain,
child: pw.Image(imgObj),
child: pw.Transform.rotate(
angle:
placement.rotationDeg *
3.1415926535 /
180.0,
child: pw.Image(imgObj),
),
),
),
),
@ -222,11 +222,9 @@ class ExportService {
final hasMulti =
(placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements =
hasMulti ? (placementsByPage[1] ?? const <Rect>[]) : const <Rect>[];
final pageImageIds =
hasMulti
? (placementImageByPage?[1] ?? const <String>[])
: const <String>[];
? (placementsByPage[1] ?? const <SignaturePlacement>[])
: const <SignaturePlacement>[];
final shouldStampSingle =
!hasMulti &&
signedPage != null &&
@ -270,18 +268,18 @@ class ExportService {
// Multi-placement stamping on fallback page
if (hasMulti && pagePlacements.isNotEmpty) {
for (var i = 0; i < pagePlacements.length; i++) {
final r = pagePlacements[i];
final placement = pagePlacements[i];
final r = placement.rect;
final left = r.left / uiPageSize.width * widthPts;
final top = r.top / uiPageSize.height * heightPts;
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
Uint8List? bytes;
if (i < pageImageIds.length) {
final id = pageImageIds[i];
final id = placement.imageId;
if (id != null) {
bytes = libraryBytes?[id];
}
bytes ??=
signatureImageBytes; // fallback to single image if provided
bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) {
pw.MemoryImage? imgObj;
try {
@ -313,7 +311,11 @@ class ExportService {
height: h,
child: pw.FittedBox(
fit: pw.BoxFit.contain,
child: pw.Image(imgObj),
child: pw.Transform.rotate(
angle:
placement.rotationDeg * 3.1415926535 / 180.0,
child: pw.Image(imgObj),
),
),
),
),

View File

@ -17,7 +17,6 @@ class PdfController extends StateNotifier<PdfState> {
pickedPdfPath: null,
signedPage: null,
placementsByPage: {},
placementImageByPage: {},
selectedPlacementIndex: null,
);
}
@ -35,7 +34,6 @@ class PdfController extends StateNotifier<PdfState> {
pickedPdfBytes: bytes,
signedPage: null,
placementsByPage: {},
placementImageByPage: {},
selectedPlacementIndex: null,
);
}
@ -67,49 +65,54 @@ class PdfController extends StateNotifier<PdfState> {
void addPlacement({
required int page,
required Rect rect,
String image = 'default.png',
String? imageId = 'default.png',
double rotationDeg = 0.0,
}) {
if (!state.loaded) return;
final p = page.clamp(1, state.pageCount);
final map = Map<int, List<Rect>>.from(state.placementsByPage);
final list = List<Rect>.from(map[p] ?? const []);
list.add(rect);
map[p] = list;
// Sync image mapping list
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
final imgList = List<String>.from(imgMap[p] ?? const []);
imgList.add(image);
imgMap[p] = imgList;
state = state.copyWith(
placementsByPage: map,
placementImageByPage: imgMap,
selectedPlacementIndex: null,
final map = Map<int, List<SignaturePlacement>>.from(state.placementsByPage);
final list = List<SignaturePlacement>.from(map[p] ?? const []);
list.add(
SignaturePlacement(
rect: rect,
imageId: imageId,
rotationDeg: rotationDeg,
),
);
map[p] = list;
state = state.copyWith(placementsByPage: map, selectedPlacementIndex: null);
}
void updatePlacementRotation({
required int page,
required int index,
required double rotationDeg,
}) {
if (!state.loaded) return;
final p = page.clamp(1, state.pageCount);
final map = Map<int, List<SignaturePlacement>>.from(state.placementsByPage);
final list = List<SignaturePlacement>.from(map[p] ?? const []);
if (index >= 0 && index < list.length) {
list[index] = list[index].copyWith(rotationDeg: rotationDeg);
map[p] = list;
state = state.copyWith(placementsByPage: map);
}
}
void removePlacement({required int page, required int index}) {
if (!state.loaded) return;
final p = page.clamp(1, state.pageCount);
final map = Map<int, List<Rect>>.from(state.placementsByPage);
final list = List<Rect>.from(map[p] ?? const []);
final map = Map<int, List<SignaturePlacement>>.from(state.placementsByPage);
final list = List<SignaturePlacement>.from(map[p] ?? const []);
if (index >= 0 && index < list.length) {
list.removeAt(index);
// Sync image mapping
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
final imgList = List<String>.from(imgMap[p] ?? const []);
if (index >= 0 && index < imgList.length) {
imgList.removeAt(index);
}
if (list.isEmpty) {
map.remove(p);
imgMap.remove(p);
} else {
map[p] = list;
imgMap[p] = imgList;
}
state = state.copyWith(
placementsByPage: map,
placementImageByPage: imgMap,
selectedPlacementIndex: null,
);
}
@ -123,17 +126,20 @@ class PdfController extends StateNotifier<PdfState> {
}) {
if (!state.loaded) return;
final p = page.clamp(1, state.pageCount);
final map = Map<int, List<Rect>>.from(state.placementsByPage);
final list = List<Rect>.from(map[p] ?? const []);
final map = Map<int, List<SignaturePlacement>>.from(state.placementsByPage);
final list = List<SignaturePlacement>.from(map[p] ?? const []);
if (index >= 0 && index < list.length) {
list[index] = rect;
final existing = list[index];
list[index] = existing.copyWith(rect: rect);
map[p] = list;
state = state.copyWith(placementsByPage: map);
}
}
List<Rect> placementsOn(int page) {
return List<Rect>.from(state.placementsByPage[page] ?? const []);
List<SignaturePlacement> placementsOn(int page) {
return List<SignaturePlacement>.from(
state.placementsByPage[page] ?? const [],
);
}
void selectPlacement(int? index) {
@ -161,9 +167,9 @@ class PdfController extends StateNotifier<PdfState> {
// Convenience to get image name for a placement
String? imageOfPlacement({required int page, required int index}) {
final list = state.placementImageByPage[page] ?? const [];
final list = state.placementsByPage[page] ?? const [];
if (index < 0 || index >= list.length) return null;
return list[index];
return list[index].imageId;
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../signature/view_model/signature_controller.dart';
import '../../../../data/model/model.dart';
import '../view_model/pdf_controller.dart';
import 'signature_overlay.dart';
@ -30,12 +31,13 @@ class PdfPageOverlays extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final pdf = ref.watch(pdfProvider);
final sig = ref.watch(signatureProvider);
final placed = pdf.placementsByPage[pageNumber] ?? const <Rect>[];
final placed =
pdf.placementsByPage[pageNumber] ?? const <SignaturePlacement>[];
final widgets = <Widget>[];
for (int i = 0; i < placed.length; i++) {
// Stored as UI-space rects (SignatureController.pageSize).
final uiRect = placed[i];
final uiRect = placed[i].rect;
widgets.add(
SignatureOverlay(
pageSize: pageSize,

View File

@ -174,7 +174,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
uiPageSize: SignatureController.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
placementImageByPage: pdf.placementImageByPage,
libraryBytes: {
for (final a in ref.read(signatureLibraryProvider)) a.id: a.bytes,
},
@ -211,7 +210,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
uiPageSize: SignatureController.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
placementImageByPage: pdf.placementImageByPage,
libraryBytes: {
for (final a in ref.read(signatureLibraryProvider)) a.id: a.bytes,
},
@ -242,7 +240,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
uiPageSize: SignatureController.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
placementImageByPage: pdf.placementImageByPage,
libraryBytes: {
for (final a in ref.read(signatureLibraryProvider))
a.id: a.bytes,

View File

@ -253,11 +253,12 @@ class _SignatureImage extends ConsumerWidget {
final processed = ref.watch(processedSignatureImageProvider);
bytes = processed ?? sig.imageBytes;
} else if (placedIndex != null) {
// Use the image assigned to this placement
final imgId = ref
.read(pdfProvider)
.placementImageByPage[pageNumber]
?.elementAt(placedIndex!);
final placementList = ref.read(pdfProvider).placementsByPage[pageNumber];
final placement =
(placementList != null && placedIndex! < placementList.length)
? placementList[placedIndex!]
: null;
final imgId = placement?.imageId;
if (imgId != null) {
final lib = ref.watch(signatureLibraryProvider);
for (final a in lib) {
@ -267,7 +268,6 @@ class _SignatureImage extends ConsumerWidget {
}
}
}
// Fallback to current processed
bytes ??= ref.read(processedSignatureImageProvider) ?? sig.imageBytes;
}
@ -281,9 +281,19 @@ class _SignatureImage extends ConsumerWidget {
return Center(child: Text(label));
}
// Use live rotation for interactive overlay; stored rotation for placed
double rotationDeg = 0.0;
if (interactive) {
rotationDeg = sig.rotation;
} else if (placedIndex != null) {
final placementList = ref.read(pdfProvider).placementsByPage[pageNumber];
if (placementList != null && placedIndex! < placementList.length) {
rotationDeg = placementList[placedIndex!].rotationDeg;
}
}
return RotatedSignatureImage(
bytes: bytes,
rotationDeg: interactive ? sig.rotation : 0.0,
rotationDeg: rotationDeg,
enableAngleAwareScale: interactive,
fit: BoxFit.contain,
wrapInRepaintBoundary: true,

View File

@ -21,17 +21,13 @@ class SignatureController extends StateNotifier<SignatureState> {
@visibleForTesting
void placeDefaultRect() {
final w = 120.0, h = 60.0;
state = state.copyWith(
rect: Rect.fromCenter(
center: Offset(
(pageSize.width / 2) * (Random().nextDouble() * 1.5 + 1),
(pageSize.height / 2) * (Random().nextDouble() * 1.5 + 1),
),
width: w,
height: h,
),
editingEnabled: true,
);
final rand = Random();
// Generate a center within 10%..90% of each axis to reduce off-screen risk
final cx = pageSize.width * (0.1 + rand.nextDouble() * 0.8);
final cy = pageSize.height * (0.1 + rand.nextDouble() * 0.8);
Rect r = Rect.fromCenter(center: Offset(cx, cy), width: w, height: h);
r = _clampRectToPage(r);
state = state.copyWith(rect: r, editingEnabled: true);
}
void loadSample() {
@ -181,37 +177,20 @@ class SignatureController extends StateNotifier<SignatureState> {
if (!pdf.loaded) return null;
// Bind the processed image at placement time (so placed preview matches adjustments).
// If processed bytes exist, always create a new asset for this placement.
String id = '';
// Compose final bytes for placement: apply adjustments (processed) then rotation.
Uint8List? srcBytes = ref.read(processedSignatureImageProvider);
srcBytes ??= state.imageBytes;
// If still null, fall back to asset reference only.
if (srcBytes != null && srcBytes.isNotEmpty) {
final rot = state.rotation % 360;
Uint8List finalBytes = srcBytes;
if (rot != 0) {
try {
final decoded = img.decodeImage(srcBytes);
if (decoded != null) {
var out = img.copyRotate(
decoded,
angle: rot,
interpolation: img.Interpolation.linear,
);
finalBytes = Uint8List.fromList(img.encodePng(out, level: 6));
}
} catch (_) {}
}
id = ref
.read(signatureLibraryProvider.notifier)
.add(finalBytes, name: 'image');
} else {
id = state.assetId ?? 'default.png';
}
// Prefer reusing an existing library asset id when the active overlay is
// based on a library item. If there is no library asset, do NOT create
// a new library card here keep the placement's image id empty so the
// UI and exporter will fall back to using the processed/current bytes.
String id = state.assetId ?? '';
// Store as UI-space rect (consistent with export and rendering paths)
ref
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: r, image: id);
.addPlacement(
page: pdf.currentPage,
rect: r,
imageId: id,
rotationDeg: state.rotation,
);
// Newly placed index is the last one on the page
final idx =
(ref.read(pdfProvider).placementsByPage[pdf.currentPage]?.length ?? 1) -
@ -227,39 +206,23 @@ class SignatureController extends StateNotifier<SignatureState> {
// Test/helper variant: confirm using a ProviderContainer instead of WidgetRef.
// Useful in widget tests where obtaining a WidgetRef is not straightforward.
@visibleForTesting
Rect? confirmCurrentSignatureWithContainer(ProviderContainer container) {
final r = state.rect;
if (r == null) return null;
final pdf = container.read(pdfProvider);
if (!pdf.loaded) return null;
String id = '';
Uint8List? srcBytes = container.read(processedSignatureImageProvider);
srcBytes ??= state.imageBytes;
if (srcBytes != null && srcBytes.isNotEmpty) {
final rot = state.rotation % 360;
Uint8List finalBytes = srcBytes;
if (rot != 0) {
try {
final decoded = img.decodeImage(srcBytes);
if (decoded != null) {
var out = img.copyRotate(
decoded,
angle: rot,
interpolation: img.Interpolation.linear,
);
finalBytes = Uint8List.fromList(img.encodePng(out, level: 6));
}
} catch (_) {}
}
id = container
.read(signatureLibraryProvider.notifier)
.add(finalBytes, name: 'image');
} else {
id = state.assetId ?? 'default.png';
}
// Reuse existing library id if present; otherwise leave empty so the
// placement will reference the current bytes via fallback paths.
String id = state.assetId ?? '';
container
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: r, image: id);
.addPlacement(
page: pdf.currentPage,
rect: r,
imageId: id,
rotationDeg: state.rotation,
);
final idx =
(container
.read(pdfProvider)

View File

@ -21,5 +21,5 @@ Future<void> aSignatureIsPlacedOnPage(WidgetTester tester, num page) async {
final Rect r = container.read(signatureProvider).rect!;
container
.read(pdfProvider.notifier)
.addPlacement(page: page.toInt(), rect: r, image: 'default.png');
.addPlacement(page: page.toInt(), rect: r, imageId: 'default.png');
}

View File

@ -10,9 +10,11 @@ Future<void> adjustingOneInstanceDoesNotAffectTheOthers(
final container = TestWorld.container ?? ProviderContainer();
final before = container.read(pdfProvider.notifier).placementsOn(2);
expect(before.length, greaterThanOrEqualTo(2));
final modified = before[0].translate(5, 0).inflate(3);
final modified = before[0].rect.translate(5, 0).inflate(3);
container.read(pdfProvider.notifier).removePlacement(page: 2, index: 0);
container.read(pdfProvider.notifier).addPlacement(page: 2, rect: modified);
container
.read(pdfProvider.notifier)
.addPlacement(page: 2, rect: modified, imageId: before[0].imageId);
final after = container.read(pdfProvider.notifier).placementsOn(2);
expect(after.any((r) => r == before[1]), isTrue);
expect(after.any((p) => p.rect == before[1].rect), isTrue);
}

View File

@ -11,11 +11,18 @@ Future<void> draggingOrResizingOneDoesNotChangeTheOther(
final container = TestWorld.container ?? ProviderContainer();
final list = container.read(pdfProvider.notifier).placementsOn(1);
expect(list.length, greaterThanOrEqualTo(2));
final before = List<Rect>.from(list.take(2));
final before = List<Rect>.from(list.take(2).map((p) => p.rect));
// Simulate changing the first only
final changed = before[0].inflate(5);
container.read(pdfProvider.notifier).removePlacement(page: 1, index: 0);
container.read(pdfProvider.notifier).addPlacement(page: 1, rect: changed);
container
.read(pdfProvider.notifier)
.addPlacement(
page: 1,
rect: changed,
imageId: list[1].imageId,
rotationDeg: list[1].rotationDeg,
);
final after = container.read(pdfProvider.notifier).placementsOn(1);
expect(after.any((r) => r == before[1]), isTrue);
expect(after.any((p) => p.rect == before[1]), isTrue);
}

View File

@ -11,9 +11,9 @@ Future<void> eachSignatureCanBeDraggedAndResizedIndependently(
final list = container.read(pdfProvider.notifier).placementsOn(1);
expect(list.length, greaterThanOrEqualTo(2));
// Independence is modeled by distinct rects; ensure not equal and both within page
expect(list[0], isNot(equals(list[1])));
for (final r in list.take(2)) {
expect(r.left, greaterThanOrEqualTo(0));
expect(r.top, greaterThanOrEqualTo(0));
expect(list[0].rect, isNot(equals(list[1].rect)));
for (final p in list.take(2)) {
expect(p.rect.left, greaterThanOrEqualTo(0));
expect(p.rect.top, greaterThanOrEqualTo(0));
}
}

View File

@ -31,5 +31,5 @@ Future<void> theUserNavigatesToPageAndPlacesAnotherSignature(
final Rect r = container.read(signatureProvider).rect!;
container
.read(pdfProvider.notifier)
.addPlacement(page: page.toInt(), rect: r, image: 'default.png');
.addPlacement(page: page.toInt(), rect: r, imageId: 'default.png');
}

View File

@ -54,5 +54,5 @@ Future<void> theUserPlacesASignatureFromPictureOnPage(
((TestWorld.placeFromPictureCallCount <= 1) ? 1 : 3);
container
.read(pdfProvider.notifier)
.addPlacement(page: page, rect: r, image: name);
.addPlacement(page: page, rect: r, imageId: name);
}

View File

@ -30,5 +30,5 @@ Future<void> theUserPlacesASignatureOnPage(
final Rect r = container.read(signatureProvider).rect!;
container
.read(pdfProvider.notifier)
.addPlacement(page: page.toInt(), rect: r, image: 'default.png');
.addPlacement(page: page.toInt(), rect: r, imageId: 'default.png');
}

View File

@ -119,7 +119,7 @@ void main() {
final processed = container3.read(processedSignatureImageProvider);
expect(processed, isNotNull);
final pdf = container3.read(pdfProvider);
final imgId = pdf.placementImageByPage[pdf.currentPage]?.first;
final imgId = pdf.placementsByPage[pdf.currentPage]?.first.imageId;
expect(imgId, isNotNull);
final lib = container3.read(signatureLibraryProvider);
final match = lib.firstWhere((a) => a.id == imgId);