diff --git a/lib/data/repositories/signature_card_repository.dart b/lib/data/repositories/signature_card_repository.dart index 833b4a0..8a1a40d 100644 --- a/lib/data/repositories/signature_card_repository.dart +++ b/lib/data/repositories/signature_card_repository.dart @@ -1,16 +1,73 @@ +import 'dart:typed_data'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/models/model.dart'; +import '../../data/services/signature_image_processing_service.dart'; -class SignatureCardStateNotifier extends StateNotifier> { - SignatureCardStateNotifier() : super(const []); +/// CachedSignatureCard extends SignatureCard with an internal processed cache +class CachedSignatureCard extends SignatureCard { + Uint8List? _cachedProcessed; + + CachedSignatureCard({ + required super.asset, + required super.rotationDeg, + super.graphicAdjust, + Uint8List? initialProcessed, + }); + + /// Returns cached processed bytes for the current [graphicAdjust], computing + /// via [service] if not cached yet. + Uint8List getOrComputeProcessed(SignatureImageProcessingService service) { + final existing = _cachedProcessed; + if (existing != null) return existing; + final computed = service.processImage(asset.bytes, graphicAdjust); + _cachedProcessed = computed; + return computed; + } + + /// Invalidate the cached processed bytes, forcing recompute next time. + void invalidateCache() { + _cachedProcessed = null; + } + + /// Sets/updates the processed bytes explicitly (used after adjustments update) + void setProcessed(Uint8List bytes) { + _cachedProcessed = bytes; + } + + factory CachedSignatureCard.initial() => CachedSignatureCard( + asset: SignatureCard.initial().asset, + rotationDeg: SignatureCard.initial().rotationDeg, + graphicAdjust: SignatureCard.initial().graphicAdjust, + ); +} + +class SignatureCardStateNotifier + extends StateNotifier> { + SignatureCardStateNotifier() : super(const []) { + state = const []; + } + + // Stateless image processing service used by this repository + final SignatureImageProcessingService _processingService = + SignatureImageProcessingService(); void add(SignatureCard card) { - state = List.of(state)..add(card); + final wrapped = + card is CachedSignatureCard + ? card + : CachedSignatureCard( + asset: card.asset, + rotationDeg: card.rotationDeg, + graphicAdjust: card.graphicAdjust, + ); + final next = List.of(state)..add(wrapped); + state = List.unmodifiable(next); } void addWithAsset(SignatureAsset asset, double rotationDeg) { - state = List.of(state) - ..add(SignatureCard(asset: asset, rotationDeg: rotationDeg)); + final next = List.of(state) + ..add(CachedSignatureCard(asset: asset, rotationDeg: rotationDeg)); + state = List.unmodifiable(next); } void update( @@ -18,30 +75,78 @@ class SignatureCardStateNotifier extends StateNotifier> { double? rotationDeg, GraphicAdjust? graphicAdjust, ) { - final list = List.of(state); + final list = List.of(state); for (var i = 0; i < list.length; i++) { final c = list[i]; if (c == card) { - list[i] = c.copyWith( + final updated = c.copyWith( rotationDeg: rotationDeg ?? c.rotationDeg, graphicAdjust: graphicAdjust ?? c.graphicAdjust, ); - state = list; + // Compute and set the single processed bytes for the updated adjust + final processed = _processingService.processImage( + updated.asset.bytes, + updated.graphicAdjust, + ); + final next = CachedSignatureCard( + asset: updated.asset, + rotationDeg: updated.rotationDeg, + graphicAdjust: updated.graphicAdjust, + ); + next.setProcessed(processed); + list[i] = next; + state = List.unmodifiable(list); return; } } } void remove(SignatureCard card) { - state = state.where((c) => c != card).toList(growable: false); + state = List.unmodifiable( + state.where((c) => c != card).toList(growable: false), + ); } void clearAll() { - state = const []; + state = const []; + } + + /// Returns processed image bytes for the given asset + adjustments. + /// Uses an internal cache to avoid re-processing. + Uint8List getProcessedBytes(SignatureAsset asset, GraphicAdjust adjust) { + // Try to find a matching card by asset + for (final c in state) { + if (c.asset == asset) { + // If requested adjust equals the card's current adjust, use per-card cache + if (c.graphicAdjust == adjust) { + return c.getOrComputeProcessed(_processingService); + } + // Previewing unsaved adjustments: compute without caching + return _processingService.processImage(asset.bytes, adjust); + } + } + // Asset not found among cards (e.g., preview in dialog): compute on-the-fly + return _processingService.processImage(asset.bytes, adjust); + } + + /// Clears all cached processed images. + void clearProcessedCache() { + for (final c in state) { + c.invalidateCache(); + } + } + + /// Clears cached processed images for a specific asset only. + void clearCacheForAsset(SignatureAsset asset) { + for (final c in state) { + if (c.asset == asset) { + c.invalidateCache(); + } + } } } -final signatureCardRepositoryProvider = - StateNotifierProvider>( - (ref) => SignatureCardStateNotifier(), - ); +final signatureCardRepositoryProvider = StateNotifierProvider< + SignatureCardStateNotifier, + List +>((ref) => SignatureCardStateNotifier()); diff --git a/lib/data/services/signature_image_processing_service.dart b/lib/data/services/signature_image_processing_service.dart new file mode 100644 index 0000000..357ee01 --- /dev/null +++ b/lib/data/services/signature_image_processing_service.dart @@ -0,0 +1,126 @@ +import 'dart:typed_data'; +import 'package:image/image.dart' as img; +import '../../domain/models/model.dart' as domain; + +/// Service for processing signature images with graphic adjustments +class SignatureImageProcessingService { + /// Decode image bytes once and reuse the decoded image for preview processing. + img.Image? decode(Uint8List bytes) { + try { + return img.decodeImage(bytes); + } catch (_) { + return null; + } + } + + /// Process image bytes with the given graphic adjustments + Uint8List processImage(Uint8List bytes, domain.GraphicAdjust adjust) { + if (adjust.contrast == 1.0 && + adjust.brightness == 0.0 && + !adjust.bgRemoval) { + return bytes; // No processing needed + } + try { + final decoded = img.decodeImage(bytes); + if (decoded != null) { + img.Image processed = decoded; + + // Apply contrast and brightness first + if (adjust.contrast != 1.0 || adjust.brightness != 0.0) { + processed = img.adjustColor( + processed, + contrast: adjust.contrast, + brightness: adjust.brightness, + ); + } + + // Apply background removal after color adjustments + if (adjust.bgRemoval) { + processed = _removeBackground(processed); + } + + // Encode back to PNG to preserve transparency + return Uint8List.fromList(img.encodePng(processed)); + } else { + return bytes; + } + } catch (e) { + // If processing fails, return original bytes + return bytes; + } + } + + /// Fast preview processing: + /// - Reuses a decoded image + /// - Downscales to a small size for UI preview + /// - Uses low-compression PNG to reduce CPU cost + Uint8List processPreviewFromDecoded( + img.Image decoded, + domain.GraphicAdjust adjust, { + int maxDimension = 256, + }) { + try { + // Create a small working copy for quick adjustments + final int w = decoded.width; + final int h = decoded.height; + final double scale = (w > h ? maxDimension / w : maxDimension / h).clamp( + 0.0, + 1.0, + ); + img.Image work = + (scale < 1.0) + ? img.copyResize(decoded, width: (w * scale).round()) + : img.Image.from(decoded); + + // Apply contrast and brightness + if (adjust.contrast != 1.0 || adjust.brightness != 0.0) { + work = img.adjustColor( + work, + contrast: adjust.contrast, + brightness: adjust.brightness, + ); + } + + // Background removal on downscaled image for speed + if (adjust.bgRemoval) { + work = _removeBackground(work); + } + + // Encode with low compression (level 0) for speed + return Uint8List.fromList(img.encodePng(work, level: 0)); + } catch (_) { + // Fall back to original size path if something goes wrong + return processImage( + Uint8List.fromList(img.encodePng(decoded, level: 0)), + adjust, + ); + } + } + + /// Remove near-white background using simple threshold approach for maximum speed + img.Image _removeBackground(img.Image image) { + final result = + image.hasAlpha ? img.Image.from(image) : image.convert(numChannels: 4); + + // Simple and fast: single pass through all pixels + for (int y = 0; y < result.height; y++) { + for (int x = 0; x < result.width; x++) { + final pixel = result.getPixel(x, y); + final r = pixel.r; + final g = pixel.g; + final b = pixel.b; + + // Simple threshold: if pixel is close to white, make it transparent + const int threshold = 240; // Very close to white + if (r >= threshold && g >= threshold && b >= threshold) { + result.setPixel( + x, + y, + img.ColorRgba8(r.toInt(), g.toInt(), b.toInt(), 0), + ); + } + } + } + return result; + } +} diff --git a/lib/domain/models/graphic_adjust.dart b/lib/domain/models/graphic_adjust.dart index ff5800b..f05bb12 100644 --- a/lib/domain/models/graphic_adjust.dart +++ b/lib/domain/models/graphic_adjust.dart @@ -18,4 +18,17 @@ class GraphicAdjust { brightness: brightness ?? this.brightness, bgRemoval: bgRemoval ?? this.bgRemoval, ); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is GraphicAdjust && + runtimeType == other.runtimeType && + contrast == other.contrast && + brightness == other.brightness && + bgRemoval == other.bgRemoval; + + @override + int get hashCode => + contrast.hashCode ^ brightness.hashCode ^ bgRemoval.hashCode; } diff --git a/lib/domain/models/signature_asset.dart b/lib/domain/models/signature_asset.dart index 6ff564f..edca0b9 100644 --- a/lib/domain/models/signature_asset.dart +++ b/lib/domain/models/signature_asset.dart @@ -6,4 +6,22 @@ class SignatureAsset { // List>? strokes; final String? name; // optional display name (e.g., filename) const SignatureAsset({required this.bytes, this.name}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SignatureAsset && + name == other.name && + _bytesEqual(bytes, other.bytes); + + @override + int get hashCode => name.hashCode ^ bytes.length.hashCode; + + static bool _bytesEqual(Uint8List a, Uint8List b) { + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; + } } diff --git a/lib/ui/features/pdf/widgets/draw_canvas.dart b/lib/ui/features/pdf/widgets/draw_canvas.dart index 74fe2d4..f5e791b 100644 --- a/lib/ui/features/pdf/widgets/draw_canvas.dart +++ b/lib/ui/features/pdf/widgets/draw_canvas.dart @@ -10,6 +10,7 @@ class DrawCanvas extends StatefulWidget { this.control, this.onConfirm, this.debugBytesSink, + this.closeOnConfirmImmediately = false, }); final hand.HandSignatureControl? control; @@ -17,6 +18,9 @@ class DrawCanvas extends StatefulWidget { // For tests: allows observing exported bytes without relying on Navigator @visibleForTesting final ValueNotifier? debugBytesSink; + // When true (used by bottom sheet), the sheet will be closed immediately + // on confirm without waiting for export to finish. + final bool closeOnConfirmImmediately; @override State createState() => _DrawCanvasState(); @@ -48,6 +52,12 @@ class _DrawCanvasState extends State { ElevatedButton( key: const Key('btn_canvas_confirm'), onPressed: () async { + // If requested, close the sheet immediately without waiting + // for the potentially heavy export. + if (widget.closeOnConfirmImmediately && + Navigator.canPop(context)) { + Navigator.of(context).pop(); + } // Export signature to PNG bytes final byteData = await _control.toImage( width: 1024, @@ -60,7 +70,7 @@ class _DrawCanvasState extends State { widget.debugBytesSink?.value = bytes; if (widget.onConfirm != null) { widget.onConfirm!(bytes); - } else { + } else if (!widget.closeOnConfirmImmediately) { if (context.mounted) { Navigator.of(context).pop(bytes); } diff --git a/lib/ui/features/pdf/widgets/pdf_screen.dart b/lib/ui/features/pdf/widgets/pdf_screen.dart index d758ae3..c5cc7b5 100644 --- a/lib/ui/features/pdf/widgets/pdf_screen.dart +++ b/lib/ui/features/pdf/widgets/pdf_screen.dart @@ -124,10 +124,7 @@ class _PdfSignatureHomePageState extends ConsumerState { context: context, isScrollControlled: true, enableDrag: false, - builder: - (_) => DrawCanvas( - onConfirm: (bytes) => Navigator.of(context).pop(bytes), - ), + builder: (_) => const DrawCanvas(closeOnConfirmImmediately: true), ); if (result != null && result.isNotEmpty) { // In simplified UI, adding to library isn't implemented diff --git a/lib/ui/features/pdf/widgets/signature_overlay.dart b/lib/ui/features/pdf/widgets/signature_overlay.dart index 3aa38f0..9235aa7 100644 --- a/lib/ui/features/pdf/widgets/signature_overlay.dart +++ b/lib/ui/features/pdf/widgets/signature_overlay.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../domain/models/model.dart'; import '../../signature/widgets/rotated_signature_image.dart'; +import '../../signature/view_model/signature_view_model.dart'; /// Minimal overlay widget for rendering a placed signature. -class SignatureOverlay extends StatelessWidget { +class SignatureOverlay extends ConsumerWidget { const SignatureOverlay({ super.key, required this.pageSize, @@ -18,7 +20,10 @@ class SignatureOverlay extends StatelessWidget { final int placedIndex; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final processedBytes = ref + .watch(signatureViewModelProvider) + .getProcessedBytes(placement.asset, placement.graphicAdjust); return LayoutBuilder( builder: (context, constraints) { final left = rect.left * constraints.maxWidth; @@ -40,7 +45,7 @@ class SignatureOverlay extends StatelessWidget { child: FittedBox( fit: BoxFit.contain, child: RotatedSignatureImage( - bytes: placement.asset.bytes, + bytes: processedBytes, rotationDeg: placement.rotationDeg, ), ), diff --git a/lib/ui/features/signature/view_model/signature_view_model.dart b/lib/ui/features/signature/view_model/signature_view_model.dart index 8ea97a5..562fd80 100644 --- a/lib/ui/features/signature/view_model/signature_view_model.dart +++ b/lib/ui/features/signature/view_model/signature_view_model.dart @@ -1,11 +1,26 @@ +import 'dart:typed_data'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdf_signature/domain/models/model.dart' as domain; +import 'package:pdf_signature/data/repositories/signature_card_repository.dart' + as repo; class SignatureViewModel { final Ref ref; SignatureViewModel(this.ref); - // Add methods as needed + Uint8List getProcessedBytes( + domain.SignatureAsset asset, + domain.GraphicAdjust adjust, + ) { + final notifier = ref.read(repo.signatureCardRepositoryProvider.notifier); + return notifier.getProcessedBytes(asset, adjust); + } + + void clearCache() { + final notifier = ref.read(repo.signatureCardRepositoryProvider.notifier); + notifier.clearProcessedCache(); + } } final signatureViewModelProvider = Provider((ref) { diff --git a/lib/ui/features/signature/widgets/image_editor_dialog.dart b/lib/ui/features/signature/widgets/image_editor_dialog.dart index 5c14083..2a01ac1 100644 --- a/lib/ui/features/signature/widgets/image_editor_dialog.dart +++ b/lib/ui/features/signature/widgets/image_editor_dialog.dart @@ -1,10 +1,13 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:image/image.dart' as img; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; import '../../pdf/widgets/adjustments_panel.dart'; import '../../../../domain/models/model.dart' as domain; +import '../view_model/signature_view_model.dart'; import 'rotated_signature_image.dart'; +import '../../../../data/services/signature_image_processing_service.dart'; +import 'package:image/image.dart' as img; class ImageEditorResult { final double rotation; @@ -16,7 +19,7 @@ class ImageEditorResult { }); } -class ImageEditorDialog extends StatefulWidget { +class ImageEditorDialog extends ConsumerStatefulWidget { const ImageEditorDialog({ super.key, required this.asset, @@ -29,16 +32,20 @@ class ImageEditorDialog extends StatefulWidget { final domain.GraphicAdjust initialGraphicAdjust; @override - State createState() => _ImageEditorDialogState(); + ConsumerState createState() => _ImageEditorDialogState(); } -class _ImageEditorDialogState extends State { +class _ImageEditorDialogState extends ConsumerState { late bool _aspectLocked; late bool _bgRemoval; late double _contrast; late double _brightness; late double _rotation; late Uint8List _processedBytes; + img.Image? _decodedSource; // Reused decoded source for fast previews + bool _previewScheduled = false; + bool _previewDirty = false; + late final SignatureImageProcessingService _svc; @override void initState() { @@ -48,62 +55,47 @@ class _ImageEditorDialogState extends State { _contrast = widget.initialGraphicAdjust.contrast; _brightness = 1.0; // Changed from 0.0 to 1.0 _rotation = widget.initialRotation; - _processedBytes = widget.asset.bytes; // Initialize with original bytes + _processedBytes = widget.asset.bytes; // initial preview + _svc = SignatureImageProcessingService(); + // Decode once for preview reuse + // Note: package:image lives in service; expose decode via service + _decodedSource = _svc.decode(widget.asset.bytes); } - /// Update processed image bytes when processing parameters change + @override + void dispose() { + // Frame callbacks are tied to mounting; nothing to cancel explicitly + super.dispose(); + } + + /// Update processed image bytes when processing parameters change. + /// Coalesce rapid changes once per frame to keep UI responsive and tests stable. void _updateProcessedBytes() { - try { - final decoded = img.decodeImage(widget.asset.bytes); + _previewDirty = true; + if (_previewScheduled) return; + _previewScheduled = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + _previewScheduled = false; + if (!mounted || !_previewDirty) return; + _previewDirty = false; + final adjust = domain.GraphicAdjust( + contrast: _contrast, + brightness: _brightness, + bgRemoval: _bgRemoval, + ); + // Fast preview path: reuse decoded, downscale, low-compression encode + final decoded = _decodedSource; if (decoded != null) { - img.Image processed = decoded; - - // Apply contrast and brightness first - if (_contrast != 1.0 || _brightness != 1.0) { - processed = img.adjustColor( - processed, - contrast: _contrast, - brightness: _brightness, - ); - } - - // Apply background removal after color adjustments - if (_bgRemoval) { - processed = _removeBackground(processed); - } - - // Encode back to PNG to preserve transparency - _processedBytes = Uint8List.fromList(img.encodePng(processed)); + final preview = _svc.processPreviewFromDecoded(decoded, adjust); + if (mounted) setState(() => _processedBytes = preview); + } else { + // Fallback to repository path if decode failed + final bytes = ref + .read(signatureViewModelProvider) + .getProcessedBytes(widget.asset, adjust); + if (mounted) setState(() => _processedBytes = bytes); } - } catch (e) { - // If processing fails, keep original bytes - _processedBytes = widget.asset.bytes; - } - } - - /// Remove near-white background using simple threshold approach for maximum speed - /// TODO: remove double loops with SIMD matrix - img.Image _removeBackground(img.Image image) { - final result = - image.hasAlpha ? img.Image.from(image) : image.convert(numChannels: 4); - - // Simple and fast: single pass through all pixels - for (int y = 0; y < result.height; y++) { - for (int x = 0; x < result.width; x++) { - final pixel = result.getPixel(x, y); - final r = pixel.r; - final g = pixel.g; - final b = pixel.b; - - // Simple threshold: if pixel is close to white, make it transparent - const int threshold = 240; // Very close to white - if (r >= threshold && g >= threshold && b >= threshold) { - result.setPixelRgba(x, y, r, g, b, 0); - } - } - } - - return result; + }); } @override diff --git a/lib/ui/features/signature/widgets/signature_card.dart b/lib/ui/features/signature/widgets/signature_card.dart index 70c3df9..4e337f2 100644 --- a/lib/ui/features/signature/widgets/signature_card.dart +++ b/lib/ui/features/signature/widgets/signature_card.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/domain/models/model.dart' as domain; import 'signature_drag_data.dart'; import 'rotated_signature_image.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; +import '../view_model/signature_view_model.dart'; -class SignatureCard extends StatelessWidget { +class SignatureCard extends ConsumerWidget { const SignatureCard({ super.key, required this.asset, @@ -26,11 +28,14 @@ class SignatureCard extends StatelessWidget { final domain.GraphicAdjust graphicAdjust; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final processedBytes = ref + .watch(signatureViewModelProvider) + .getProcessedBytes(asset, graphicAdjust); // Fit inside 96x64 with 6px padding using the shared rotated image widget const boxW = 96.0, boxH = 64.0, pad = 6.0; Widget img = RotatedSignatureImage( - bytes: asset.bytes, + bytes: processedBytes, rotationDeg: rotationDeg, ); Widget base = SizedBox( @@ -166,7 +171,7 @@ class SignatureCard extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(6.0), child: RotatedSignatureImage( - bytes: asset.bytes, + bytes: processedBytes, rotationDeg: rotationDeg, ), ), diff --git a/lib/ui/features/signature/widgets/signature_drawer.dart b/lib/ui/features/signature/widgets/signature_drawer.dart index 1b5584e..d7efd59 100644 --- a/lib/ui/features/signature/widgets/signature_drawer.dart +++ b/lib/ui/features/signature/widgets/signature_drawer.dart @@ -62,6 +62,7 @@ class _SignatureDrawerState extends ConsumerState { if (!mounted) return; final result = await showDialog( context: context, + barrierDismissible: false, builder: (_) => ImageEditorDialog( asset: card.asset, diff --git a/test/features/step/a_multipage_document_is_open.dart b/test/features/step/a_multipage_document_is_open.dart index fceb6ec..1424f53 100644 --- a/test/features/step/a_multipage_document_is_open.dart +++ b/test/features/step/a_multipage_document_is_open.dart @@ -16,7 +16,7 @@ Future aMultipageDocumentIsOpen(WidgetTester tester) async { container.read(documentRepositoryProvider.notifier).state = Document.initial(); container.read(signatureCardRepositoryProvider.notifier).state = [ - SignatureCard.initial(), + CachedSignatureCard.initial(), ]; container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5); // Reset page state providers diff --git a/test/features/step/a_signature_asset_is_loaded_or_drawn.dart b/test/features/step/a_signature_asset_is_loaded_or_drawn.dart index c185b02..6a056fa 100644 --- a/test/features/step/a_signature_asset_is_loaded_or_drawn.dart +++ b/test/features/step/a_signature_asset_is_loaded_or_drawn.dart @@ -15,7 +15,7 @@ Future aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async { container.read(documentRepositoryProvider.notifier).state = Document.initial(); container.read(signatureCardRepositoryProvider.notifier).state = [ - SignatureCard.initial(), + CachedSignatureCard.initial(), ]; // Use a tiny valid PNG so any later image decoding succeeds. final bytes = Uint8List.fromList([ diff --git a/test/features/step/a_signature_asset_loaded_or_drawn_is_wrapped_in_a_signature_card.dart b/test/features/step/a_signature_asset_loaded_or_drawn_is_wrapped_in_a_signature_card.dart index 8767c18..2df1a9c 100644 --- a/test/features/step/a_signature_asset_loaded_or_drawn_is_wrapped_in_a_signature_card.dart +++ b/test/features/step/a_signature_asset_loaded_or_drawn_is_wrapped_in_a_signature_card.dart @@ -17,7 +17,7 @@ Future aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard( container.read(documentRepositoryProvider.notifier).state = Document.initial(); container.read(signatureCardRepositoryProvider.notifier).state = [ - SignatureCard.initial(), + CachedSignatureCard.initial(), ]; final bytes = Uint8List.fromList([1, 2, 3, 4, 5]); container diff --git a/test/features/step/three_signature_placements_are_placed_on_the_current_page.dart b/test/features/step/three_signature_placements_are_placed_on_the_current_page.dart index 08fb6b8..c834f69 100644 --- a/test/features/step/three_signature_placements_are_placed_on_the_current_page.dart +++ b/test/features/step/three_signature_placements_are_placed_on_the_current_page.dart @@ -20,7 +20,7 @@ Future threeSignaturePlacementsArePlacedOnTheCurrentPage( container.read(documentRepositoryProvider.notifier).state = Document.initial(); container.read(signatureCardRepositoryProvider.notifier).state = [ - SignatureCard.initial(), + CachedSignatureCard.initial(), ]; container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5); final pdfN = container.read(documentRepositoryProvider.notifier);