import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/data/services/export_service.dart'; import '../../domain/models/model.dart'; class DocumentStateNotifier extends StateNotifier { DocumentStateNotifier() : super(Document.initial()); final ExportService _service = ExportService(); @visibleForTesting void openSample() { state = state.copyWith( loaded: true, pageCount: 5, pickedPdfBytes: null, placementsByPage: >{}, ); } void openPicked({required int pageCount, Uint8List? bytes}) { state = state.copyWith( loaded: true, pageCount: pageCount, pickedPdfBytes: bytes, placementsByPage: >{}, ); } void close() { state = Document.initial(); } void setPageCount(int count) { if (!state.loaded) return; state = state.copyWith(pageCount: count.clamp(1, 9999)); } void jumpTo(int page) { // currentPage is now in view model, so jumpTo does nothing here } // Multiple-signature helpers (rects are stored in normalized fractions 0..1 // relative to the page size: left/top/width/height are all 0..1) void addPlacement({ required int page, required Rect rect, SignatureAsset? asset, double rotationDeg = 0.0, GraphicAdjust? graphicAdjust, }) { if (!state.loaded) return; final p = page.clamp(1, state.pageCount); final map = Map>.from(state.placementsByPage); final list = List.from(map[p] ?? const []); list.add( SignaturePlacement( rect: rect, asset: asset ?? SignatureAsset(bytes: _singleTransparentPng), rotationDeg: rotationDeg, graphicAdjust: graphicAdjust ?? const GraphicAdjust(), ), ); map[p] = list; state = state.copyWith(placementsByPage: map); } // Tiny 1x1 transparent PNG to avoid decode crashes in tests when no real // signature bytes were provided. static final Uint8List _singleTransparentPng = Uint8List.fromList([ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xE5, 0x27, 0xD4, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]); 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>.from(state.placementsByPage); final list = List.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>.from(state.placementsByPage); final list = List.from(map[p] ?? const []); if (index >= 0 && index < list.length) { list.removeAt(index); if (list.isEmpty) { map.remove(p); } else { map[p] = list; } state = state.copyWith(placementsByPage: map); } } // Update the rect of an existing placement on a page. void updatePlacementRect({ required int page, required int index, required Rect rect, }) { if (!state.loaded) return; final p = page.clamp(1, state.pageCount); final map = Map>.from(state.placementsByPage); final list = List.from(map[p] ?? const []); if (index >= 0 && index < list.length) { final existing = list[index]; list[index] = existing.copyWith(rect: rect); map[p] = list; state = state.copyWith(placementsByPage: map); } } List placementsOn(int page) { return List.from( state.placementsByPage[page] ?? const [], ); } // Convenience to get asset for a placement SignatureAsset? assetOfPlacement({required int page, required int index}) { final list = state.placementsByPage[page] ?? const []; if (index < 0 || index >= list.length) return null; return list[index].asset; } Future exportDocument({ required String outputPath, required Size uiPageSize, required Uint8List? signatureImageBytes, }) async { if (!state.loaded || state.pickedPdfBytes == null) return; final bytes = await _service.exportSignedPdfFromBytes( srcBytes: state.pickedPdfBytes!, uiPageSize: uiPageSize, signatureImageBytes: signatureImageBytes, placementsByPage: state.placementsByPage, ); if (bytes == null) return; _service.saveBytesToFile(bytes: bytes, outputPath: outputPath); // await } } final documentRepositoryProvider = StateNotifierProvider( (ref) => DocumentStateNotifier(), );