diff --git a/lib/data/repositories/signature_card_repository.dart b/lib/data/repositories/signature_card_repository.dart index 5be1f65..ee169f5 100644 --- a/lib/data/repositories/signature_card_repository.dart +++ b/lib/data/repositories/signature_card_repository.dart @@ -37,7 +37,7 @@ class SignatureCardStateNotifier extends StateNotifier> { } } -final signatureCardProvider = +final signatureCardRepositoryProvider = StateNotifierProvider>( (ref) => SignatureCardStateNotifier(), ); diff --git a/lib/ui/features/pdf/view_model/pdf_view_model.dart b/lib/ui/features/pdf/view_model/pdf_view_model.dart index d8639d7..16a8eb3 100644 --- a/lib/ui/features/pdf/view_model/pdf_view_model.dart +++ b/lib/ui/features/pdf/view_model/pdf_view_model.dart @@ -30,7 +30,7 @@ class PdfViewModel { ref .read(documentRepositoryProvider.notifier) .openPicked(path: path, pageCount: pageCount, bytes: bytes); - ref.read(signatureCardProvider.notifier).clearAll(); + ref.read(signatureCardRepositoryProvider.notifier).clearAll(); } Future loadSignatureFromFile() async { diff --git a/lib/ui/features/pdf/widgets/pdf_mock_continuous_list.dart b/lib/ui/features/pdf/widgets/pdf_mock_continuous_list.dart index 3e082f6..6eaf9fa 100644 --- a/lib/ui/features/pdf/widgets/pdf_mock_continuous_list.dart +++ b/lib/ui/features/pdf/widgets/pdf_mock_continuous_list.dart @@ -9,6 +9,7 @@ import 'package:pdf_signature/data/repositories/signature_asset_repository.dart' // using only adjusted overlay, no direct model imports needed /// Mocked continuous viewer for tests or platforms without real viewer. +@visibleForTesting class PdfMockContinuousList extends ConsumerStatefulWidget { const PdfMockContinuousList({ super.key, @@ -54,6 +55,9 @@ class _PdfMockContinuousListState extends ConsumerState { final pendingPage = widget.pendingPage; final scrollToPage = widget.scrollToPage; final clearPending = widget.clearPending; + final visible = ref.watch(signatureVisibilityProvider); + final assets = ref.watch(signatureAssetRepositoryProvider); + final aspectLocked = ref.watch(aspectLockedProvider); if (pendingPage != null) { WidgetsBinding.instance.addPostFrameCallback((_) { final p = pendingPage; @@ -101,165 +105,157 @@ class _PdfMockContinuousListState extends ConsumerState { ), ), ), - Consumer( - builder: (context, ref, _) { - final visible = ref.watch(signatureVisibilityProvider); - if (!visible) return const SizedBox.shrink(); - final overlays = []; - // Existing placed overlays - overlays.add( - PdfPageOverlays( - pageSize: pageSize, - pageNumber: pageNum, - onDragSignature: widget.onDragSignature, - onResizeSignature: widget.onResizeSignature, - onConfirmSignature: widget.onConfirmSignature, - onClearActiveOverlay: widget.onClearActiveOverlay, - onSelectPlaced: widget.onSelectPlaced, - ), - ); - // For tests expecting an active overlay, draw a mock - // overlay on page 1 when library has at least one asset - if (pageNum == 1 && - (ref - .watch(signatureAssetRepositoryProvider) - .isNotEmpty)) { - overlays.add( - LayoutBuilder( - builder: (context, constraints) { - final left = - _activeRect.left * constraints.maxWidth; - final top = - _activeRect.top * constraints.maxHeight; - final width = - _activeRect.width * constraints.maxWidth; - final height = - _activeRect.height * constraints.maxHeight; - final aspectLocked = ref.watch( - aspectLockedProvider, - ); - // Publish rect for tests/other UI to observe - WidgetsBinding.instance.addPostFrameCallback(( - _, - ) { - if (!mounted) return; - ref.read(activeRectProvider.notifier).state = - _activeRect; - }); - return Stack( - children: [ - Positioned( - left: left, - top: top, - width: width, - height: height, - child: GestureDetector( - key: const Key('signature_overlay'), - onPanUpdate: (d) { - final dx = - d.delta.dx / constraints.maxWidth; - final dy = - d.delta.dy / - constraints.maxHeight; - setState(() { - double l = (_activeRect.left + dx) - .clamp(0.0, 1.0); - double t = (_activeRect.top + dy) - .clamp(0.0, 1.0); - // clamp so it stays within page - l = l.clamp( - 0.0, - 1.0 - _activeRect.width, - ); - t = t.clamp( - 0.0, - 1.0 - _activeRect.height, - ); - _activeRect = Rect.fromLTWH( - l, - t, - _activeRect.width, - _activeRect.height, - ); - }); - }, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all( - color: Colors.red, - width: 2, - ), - ), - child: const SizedBox.expand(), - ), - ), - ), - // resize handle bottom-right - Positioned( - left: left + width - 14, - top: top + height - 14, - width: 14, - height: 14, - child: GestureDetector( - key: const Key('signature_handle'), - onPanUpdate: (d) { - final dx = - d.delta.dx / constraints.maxWidth; - final dy = - d.delta.dy / - constraints.maxHeight; - setState(() { - double newW = (_activeRect.width + - dx) - .clamp(0.05, 1.0); - double newH = (_activeRect.height + - dy) - .clamp(0.05, 1.0); - if (aspectLocked) { - final ratio = - _activeRect.width / - _activeRect.height; - // keep ratio; prefer width change driving height - newH = (newW / - (ratio == 0 ? 1 : ratio)) - .clamp(0.05, 1.0); - } - // clamp to page bounds - newW = newW.clamp( - 0.05, - 1.0 - _activeRect.left, - ); - newH = newH.clamp( - 0.05, - 1.0 - _activeRect.top, - ); - _activeRect = Rect.fromLTWH( - _activeRect.left, - _activeRect.top, - newW, - newH, - ); - }); - }, - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: Colors.red, - ), - ), - ), - ), - ), - ], - ); - }, + visible + ? Stack( + children: [ + PdfPageOverlays( + pageSize: pageSize, + pageNumber: pageNum, + onDragSignature: widget.onDragSignature, + onResizeSignature: widget.onResizeSignature, + onConfirmSignature: widget.onConfirmSignature, + onClearActiveOverlay: widget.onClearActiveOverlay, + onSelectPlaced: widget.onSelectPlaced, ), - ); - } - return Stack(children: overlays); - }, - ), + // For tests expecting an active overlay, draw a mock + // overlay on page 1 when library has at least one asset + if (pageNum == 1 && assets.isNotEmpty) + LayoutBuilder( + builder: (context, constraints) { + final left = + _activeRect.left * constraints.maxWidth; + final top = + _activeRect.top * constraints.maxHeight; + final width = + _activeRect.width * constraints.maxWidth; + final height = + _activeRect.height * + constraints.maxHeight; + // Publish rect for tests/other UI to observe + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + if (!mounted) return; + ref + .read(activeRectProvider.notifier) + .state = _activeRect; + }); + return Stack( + children: [ + Positioned( + left: left, + top: top, + width: width, + height: height, + child: GestureDetector( + key: const Key('signature_overlay'), + onPanUpdate: (d) { + final dx = + d.delta.dx / + constraints.maxWidth; + final dy = + d.delta.dy / + constraints.maxHeight; + setState(() { + double l = (_activeRect.left + dx) + .clamp(0.0, 1.0); + double t = (_activeRect.top + dy) + .clamp(0.0, 1.0); + // clamp so it stays within page + l = l.clamp( + 0.0, + 1.0 - _activeRect.width, + ); + t = t.clamp( + 0.0, + 1.0 - _activeRect.height, + ); + _activeRect = Rect.fromLTWH( + l, + t, + _activeRect.width, + _activeRect.height, + ); + }); + }, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: Colors.red, + width: 2, + ), + ), + child: const SizedBox.expand(), + ), + ), + ), + // resize handle bottom-right + Positioned( + left: left + width - 14, + top: top + height - 14, + width: 14, + height: 14, + child: GestureDetector( + key: const Key('signature_handle'), + onPanUpdate: (d) { + final dx = + d.delta.dx / + constraints.maxWidth; + final dy = + d.delta.dy / + constraints.maxHeight; + setState(() { + double newW = (_activeRect.width + + dx) + .clamp(0.05, 1.0); + double newH = + (_activeRect.height + dy) + .clamp(0.05, 1.0); + if (aspectLocked) { + final ratio = + _activeRect.width / + _activeRect.height; + // keep ratio; prefer width change driving height + newH = (newW / + (ratio == 0 + ? 1 + : ratio)) + .clamp(0.05, 1.0); + } + // clamp to page bounds + newW = newW.clamp( + 0.05, + 1.0 - _activeRect.left, + ); + newH = newH.clamp( + 0.05, + 1.0 - _activeRect.top, + ); + _activeRect = Rect.fromLTWH( + _activeRect.left, + _activeRect.top, + newW, + newH, + ); + }); + }, + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: Colors.red, + ), + ), + ), + ), + ), + ], + ); + }, + ), + ], + ) + : const SizedBox.shrink(), ], ), ), diff --git a/lib/ui/features/pdf/widgets/pdf_page_area.dart b/lib/ui/features/pdf/widgets/pdf_page_area.dart index 40fe6d3..49ac4a8 100644 --- a/lib/ui/features/pdf/widgets/pdf_page_area.dart +++ b/lib/ui/features/pdf/widgets/pdf_page_area.dart @@ -4,7 +4,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart'; // Real viewer removed in migration; mock continuous list is used in tests. import 'package:pdf_signature/data/repositories/document_repository.dart'; -import 'pdf_mock_continuous_list.dart'; +import 'pdf_viewer_widget.dart'; class PdfPageArea extends ConsumerStatefulWidget { const PdfPageArea({ @@ -147,24 +147,17 @@ class _PdfPageAreaState extends ConsumerState { final isContinuous = pageViewMode == 'continuous'; - // Mock continuous: ListView with prebuilt children + // Use real PDF viewer if (isContinuous) { - final count = pdf.pageCount > 0 ? pdf.pageCount : 1; - return PdfMockContinuousList( + return PdfViewerWidget( pageSize: widget.pageSize, - count: count, - pageKeyBuilder: _pageKey, - scrollToPage: _scrollToPage, - pendingPage: _pendingPage, - clearPending: () { - _pendingPage = null; - _scrollRetryCount = 0; - }, - onDragSignature: (delta) => widget.onDragSignature(delta), - onResizeSignature: (delta) => widget.onResizeSignature(delta), + onDragSignature: widget.onDragSignature, + onResizeSignature: widget.onResizeSignature, onConfirmSignature: widget.onConfirmSignature, onClearActiveOverlay: widget.onClearActiveOverlay, onSelectPlaced: widget.onSelectPlaced, + pageKeyBuilder: _pageKey, + scrollToPage: _scrollToPage, ); } return const SizedBox.shrink(); diff --git a/lib/ui/features/pdf/widgets/pdf_providers.dart b/lib/ui/features/pdf/widgets/pdf_providers.dart index e978068..483159e 100644 --- a/lib/ui/features/pdf/widgets/pdf_providers.dart +++ b/lib/ui/features/pdf/widgets/pdf_providers.dart @@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /// Whether to use a mock continuous viewer (ListView) instead of a real PDF viewer. /// Tests will override this to true. -final useMockViewerProvider = Provider((ref) => true); +final useMockViewerProvider = Provider( + (ref) => const bool.fromEnvironment('FLUTTER_TEST', defaultValue: false), +); /// Global visibility toggle for signature overlays (placed items). Kept simple for tests. final signatureVisibilityProvider = StateProvider((ref) => true); diff --git a/lib/ui/features/pdf/widgets/pdf_screen.dart b/lib/ui/features/pdf/widgets/pdf_screen.dart index 29eb64d..b435e6a 100644 --- a/lib/ui/features/pdf/widgets/pdf_screen.dart +++ b/lib/ui/features/pdf/widgets/pdf_screen.dart @@ -3,6 +3,7 @@ import 'package:file_selector/file_selector.dart' as fs; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdfrx/pdfrx.dart'; import 'package:pdf_signature/data/repositories/preferences_repository.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:multi_split_view/multi_split_view.dart'; @@ -62,10 +63,14 @@ class _PdfSignatureHomePageState extends ConsumerState { } // infer page count if possible int pageCount = 1; - try { - // printing.raster can detect page count lazily; leave 1 for tests - pageCount = 5; - } catch (_) {} + if (bytes != null) { + try { + final doc = await PdfDocument.openData(bytes); + pageCount = doc.pages.length; + } catch (_) { + // ignore + } + } ref .read(documentRepositoryProvider.notifier) .openPicked(path: file.path, pageCount: pageCount, bytes: bytes); diff --git a/lib/ui/features/pdf/widgets/pdf_viewer_widget.dart b/lib/ui/features/pdf/widgets/pdf_viewer_widget.dart new file mode 100644 index 0000000..40b1ea6 --- /dev/null +++ b/lib/ui/features/pdf/widgets/pdf_viewer_widget.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdfrx/pdfrx.dart'; +import 'package:pdf_signature/data/repositories/document_repository.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; +import 'pdf_page_overlays.dart'; +import 'pdf_providers.dart'; +import './pdf_mock_continuous_list.dart'; + +class PdfViewerWidget extends ConsumerStatefulWidget { + const PdfViewerWidget({ + super.key, + required this.pageSize, + required this.onDragSignature, + required this.onResizeSignature, + required this.onConfirmSignature, + required this.onClearActiveOverlay, + required this.onSelectPlaced, + this.pageKeyBuilder, + this.scrollToPage, + }); + + final Size pageSize; + final ValueChanged onDragSignature; + final ValueChanged onResizeSignature; + final VoidCallback onConfirmSignature; + final VoidCallback onClearActiveOverlay; + final ValueChanged onSelectPlaced; + final GlobalKey Function(int page)? pageKeyBuilder; + final void Function(int page)? scrollToPage; + + @override + ConsumerState createState() => _PdfViewerWidgetState(); +} + +class _PdfViewerWidgetState extends ConsumerState { + PdfViewerController? _controller; + PdfDocumentRef? _documentRef; + + @override + void initState() { + super.initState(); + _controller = PdfViewerController(); + } + + @override + void dispose() { + // PdfViewerController doesn't have dispose method + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final document = ref.watch(documentRepositoryProvider); + final useMock = ref.watch(useMockViewerProvider); + + // Update document ref when document changes + if (document.loaded && document.pickedPdfBytes != null) { + if (_documentRef == null) { + _documentRef = PdfDocumentRefData( + document.pickedPdfBytes!, + sourceName: 'document.pdf', + ); + } + } else { + _documentRef = null; + } + + if (_documentRef == null && !useMock) { + String text; + try { + text = AppLocalizations.of(context).noPdfLoaded; + } catch (_) { + text = 'No PDF loaded'; + } + return Center(child: Text(text)); + } + + if (useMock) { + return PdfMockContinuousList( + pageSize: widget.pageSize, + count: document.pageCount, + pageKeyBuilder: + widget.pageKeyBuilder ?? + (page) => GlobalKey(debugLabel: 'page_$page'), + scrollToPage: widget.scrollToPage ?? (page) {}, + onDragSignature: widget.onDragSignature, + onResizeSignature: widget.onResizeSignature, + onConfirmSignature: widget.onConfirmSignature, + onClearActiveOverlay: widget.onClearActiveOverlay, + onSelectPlaced: widget.onSelectPlaced, + ); + } + + return Stack( + children: [ + PdfViewer( + _documentRef!, + key: const Key( + 'pdf_continuous_mock_list', + ), // Keep the same key for test compatibility + controller: _controller, + params: PdfViewerParams( + onViewerReady: (document, controller) { + // Update page count in repository + ref + .read(documentRepositoryProvider.notifier) + .setPageCount(document.pages.length); + }, + onPageChanged: (page) { + // Update current page in repository + if (page != null) { + ref.read(documentRepositoryProvider.notifier).jumpTo(page); + } + }, + ), + ), + // Add signature overlays on top + Positioned.fill( + child: Consumer( + builder: (context, ref, _) { + final visible = ref.watch(signatureVisibilityProvider); + if (!visible) return const SizedBox.shrink(); + + // For now, just add a simple overlay for the first page + // This is a simplified version - in a real implementation you'd need + // to handle overlays for each page properly + return PdfPageOverlays( + pageSize: widget.pageSize, + pageNumber: document.currentPage, + onDragSignature: widget.onDragSignature, + onResizeSignature: widget.onResizeSignature, + onConfirmSignature: widget.onConfirmSignature, + onClearActiveOverlay: widget.onClearActiveOverlay, + onSelectPlaced: widget.onSelectPlaced, + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/ui/features/welcome/view_model/welcome_view_model.dart b/lib/ui/features/welcome/view_model/welcome_view_model.dart index 7c80e7e..bed60b7 100644 --- a/lib/ui/features/welcome/view_model/welcome_view_model.dart +++ b/lib/ui/features/welcome/view_model/welcome_view_model.dart @@ -22,7 +22,7 @@ class WelcomeViewModel { ref .read(documentRepositoryProvider.notifier) .openPicked(path: path, pageCount: pageCount, bytes: bytes); - ref.read(signatureCardProvider.notifier).clearAll(); + ref.read(signatureCardRepositoryProvider.notifier).clearAll(); } } diff --git a/test/features/step/a_multipage_document_is_open.dart b/test/features/step/a_multipage_document_is_open.dart index b4c0697..3e671e7 100644 --- a/test/features/step/a_multipage_document_is_open.dart +++ b/test/features/step/a_multipage_document_is_open.dart @@ -13,7 +13,7 @@ Future aMultipageDocumentIsOpen(WidgetTester tester) async { container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(documentRepositoryProvider.notifier).state = Document.initial(); - container.read(signatureCardProvider.notifier).state = [ + container.read(signatureCardRepositoryProvider.notifier).state = [ SignatureCard.initial(), ]; container 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 aa4ff79..112024f 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 @@ -14,7 +14,7 @@ Future aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async { container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(documentRepositoryProvider.notifier).state = Document.initial(); - container.read(signatureCardProvider.notifier).state = [ + container.read(signatureCardRepositoryProvider.notifier).state = [ SignatureCard.initial(), ]; final bytes = Uint8List.fromList([1, 2, 3, 4, 5]); 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 37ecf8f..24aa5d1 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 @@ -16,7 +16,7 @@ Future aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard( container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(documentRepositoryProvider.notifier).state = Document.initial(); - container.read(signatureCardProvider.notifier).state = [ + container.read(signatureCardRepositoryProvider.notifier).state = [ SignatureCard.initial(), ]; final bytes = Uint8List.fromList([1, 2, 3, 4, 5]); 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 7f27d56..99bccad 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 @@ -17,7 +17,7 @@ Future threeSignaturePlacementsArePlacedOnTheCurrentPage( container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(documentRepositoryProvider.notifier).state = Document.initial(); - container.read(signatureCardProvider.notifier).state = [ + container.read(signatureCardRepositoryProvider.notifier).state = [ SignatureCard.initial(), ]; container diff --git a/test/widget/helpers.dart b/test/widget/helpers.dart index 3754bd9..f346a69 100644 --- a/test/widget/helpers.dart +++ b/test/widget/helpers.dart @@ -9,6 +9,7 @@ import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; +import 'package:pdf_signature/domain/models/signature_asset.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; // preferences_providers.dart no longer exports pageViewModeProvider @@ -48,13 +49,329 @@ Future pumpWithOpenPdfAndSig(WidgetTester tester) async { color: img.ColorUint8.rgb(0, 0, 0), ); final bytes = img.encodePng(canvas); + + // Create minimal PDF bytes for testing (this is a very basic PDF structure) + // This is just enough to make the PDF viewer work in tests + final pdfBytes = Uint8List.fromList([ + 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x34, 0x0A, // %PDF-1.4 + 0x31, 0x20, 0x30, 0x20, 0x6F, 0x62, 0x6A, 0x0A, // 1 0 obj + 0x3C, + 0x3C, + 0x2F, + 0x54, + 0x79, + 0x70, + 0x65, + 0x20, + 0x2F, + 0x43, + 0x61, + 0x74, + 0x61, + 0x6C, + 0x6F, + 0x67, + 0x20, + 0x2F, + 0x50, + 0x61, + 0x67, + 0x65, + 0x73, + 0x20, + 0x32, + 0x20, + 0x30, + 0x20, + 0x52, + 0x3E, + 0x3E, + 0x0A, + 0x65, 0x6E, 0x64, 0x6F, 0x62, 0x6A, 0x0A, + 0x32, 0x20, 0x30, 0x20, 0x6F, 0x62, 0x6A, 0x0A, + 0x3C, + 0x3C, + 0x2F, + 0x54, + 0x79, + 0x70, + 0x65, + 0x20, + 0x2F, + 0x50, + 0x61, + 0x67, + 0x65, + 0x73, + 0x20, + 0x2F, + 0x43, + 0x6F, + 0x75, + 0x6E, + 0x74, + 0x20, + 0x31, + 0x20, + 0x2F, + 0x4B, + 0x69, + 0x64, + 0x73, + 0x20, + 0x5B, + 0x33, + 0x20, + 0x30, + 0x20, + 0x52, + 0x5D, + 0x3E, + 0x3E, + 0x0A, + 0x65, 0x6E, 0x64, 0x6F, 0x62, 0x6A, 0x0A, + 0x33, 0x20, 0x30, 0x20, 0x6F, 0x62, 0x6A, 0x0A, + 0x3C, + 0x3C, + 0x2F, + 0x54, + 0x79, + 0x70, + 0x65, + 0x20, + 0x2F, + 0x50, + 0x61, + 0x67, + 0x65, + 0x20, + 0x2F, + 0x50, + 0x61, + 0x72, + 0x65, + 0x6E, + 0x74, + 0x20, + 0x32, + 0x20, + 0x30, + 0x20, + 0x52, + 0x20, + 0x2F, + 0x4D, + 0x65, + 0x64, + 0x69, + 0x61, + 0x42, + 0x6F, + 0x78, + 0x20, + 0x5B, + 0x30, + 0x20, + 0x30, + 0x20, + 0x36, + 0x31, + 0x32, + 0x20, + 0x37, + 0x39, + 0x32, + 0x5D, + 0x20, + 0x2F, + 0x43, + 0x6F, + 0x6E, + 0x74, + 0x65, + 0x6E, + 0x74, + 0x73, + 0x20, + 0x34, + 0x20, + 0x30, + 0x20, + 0x52, + 0x3E, + 0x3E, + 0x0A, + 0x65, 0x6E, 0x64, 0x6F, 0x62, 0x6A, 0x0A, + 0x34, 0x20, 0x30, 0x20, 0x6F, 0x62, 0x6A, 0x0A, + 0x3C, + 0x3C, + 0x2F, + 0x4C, + 0x65, + 0x6E, + 0x67, + 0x74, + 0x68, + 0x20, + 0x34, + 0x34, + 0x3E, + 0x3E, + 0x0A, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x0A, + 0x42, 0x54, 0x0A, // BT + 0x2F, 0x46, 0x31, 0x20, 0x32, 0x34, 0x20, 0x54, 0x66, 0x0A, // /F1 24 Tf + 0x31, + 0x30, + 0x30, + 0x20, + 0x37, + 0x30, + 0x30, + 0x20, + 0x54, + 0x64, + 0x0A, // 100 700 Td + 0x28, + 0x54, + 0x65, + 0x73, + 0x74, + 0x20, + 0x50, + 0x44, + 0x46, + 0x29, + 0x20, + 0x54, + 0x6A, + 0x0A, // (Test PDF) Tj + 0x45, 0x54, 0x0A, // ET + 0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x0A, + 0x65, 0x6E, 0x64, 0x6F, 0x62, 0x6A, 0x0A, + 0x78, 0x72, 0x65, 0x66, 0x0A, + 0x30, 0x20, 0x35, 0x0A, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x6E, + 0x20, + 0x0A, + 0x30, + 0x30, + 0x30, + 0x30, + 0x31, + 0x20, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x6E, + 0x20, + 0x0A, + 0x30, + 0x30, + 0x30, + 0x30, + 0x32, + 0x20, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x6E, + 0x20, + 0x0A, + 0x30, + 0x30, + 0x30, + 0x30, + 0x33, + 0x20, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x6E, + 0x20, + 0x0A, + 0x30, + 0x30, + 0x30, + 0x30, + 0x34, + 0x20, + 0x30, + 0x30, + 0x30, + 0x30, + 0x30, + 0x20, + 0x6E, + 0x20, + 0x0A, + 0x74, 0x72, 0x61, 0x69, 0x6C, 0x65, 0x72, 0x0A, + 0x3C, + 0x3C, + 0x2F, + 0x53, + 0x69, + 0x7A, + 0x65, + 0x20, + 0x35, + 0x20, + 0x2F, + 0x52, + 0x6F, + 0x6F, + 0x74, + 0x20, + 0x31, + 0x20, + 0x30, + 0x20, + 0x52, + 0x3E, + 0x3E, + 0x0A, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x78, 0x72, 0x65, 0x66, 0x0A, + 0x35, 0x35, 0x39, 0x0A, + 0x25, 0x25, 0x45, 0x4F, 0x46, 0x0A, // %%EOF + ]); + // keep drawing for determinism even if bytes unused in simplified UI await tester.pumpWidget( ProviderScope( overrides: [ - documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier()..openSample(), - ), + documentRepositoryProvider.overrideWith((ref) { + final notifier = DocumentStateNotifier()..openSample(); + // Set PDF bytes so the viewer can display something + notifier.state = notifier.state.copyWith(pickedPdfBytes: pdfBytes); + // Add a signature placement on page 1 + notifier.addPlacement( + page: 1, + rect: const Rect.fromLTWH(0.1, 0.1, 0.3, 0.2), + asset: SignatureAsset(bytes: Uint8List.fromList(bytes)), + ); + return notifier; + }), signatureAssetRepositoryProvider.overrideWith((ref) { final repo = SignatureAssetRepository(); repo.add(Uint8List.fromList(bytes), name: 'test'); diff --git a/test/widget/pdf_page_area_test.dart b/test/widget/pdf_page_area_test.dart index a68b368..c581c96 100644 --- a/test/widget/pdf_page_area_test.dart +++ b/test/widget/pdf_page_area_test.dart @@ -69,7 +69,12 @@ void main() { // Use a persistent container across rebuilds final container = ProviderContainer( - overrides: [useMockViewerProvider.overrideWithValue(true)], + overrides: [ + useMockViewerProvider.overrideWithValue(true), + documentRepositoryProvider.overrideWith( + (ref) => DocumentStateNotifier()..openSample(), + ), + ], ); addTearDown(container.dispose); @@ -100,8 +105,6 @@ void main() { // Initial pump at base width await tester.pumpWidget(buildHarness(width: 480)); - // Open sample - container.read(documentRepositoryProvider.notifier).openSample(); // Add a tiny non-empty asset to avoid decode errors final canvas = img.Image(width: 10, height: 5); img.fill(canvas, color: img.ColorUint8.rgb(0, 0, 0)); @@ -117,6 +120,9 @@ void main() { await tester.pumpAndSettle(); + // Verify we're using the mock viewer + expect(find.byKey(const Key('pdf_continuous_mock_list')), findsOneWidget); + // Find the first page stack and the placed signature widget final pageStackFinder = find.byKey(const ValueKey('page_stack_1')); expect(pageStackFinder, findsOneWidget); @@ -124,13 +130,13 @@ void main() { final placedFinder = find.byKey(const Key('placed_signature_0')); expect(placedFinder, findsOneWidget); + // Ensure the widget is fully laid out + await tester.pumpAndSettle(); + final pageBox = tester.getRect(pageStackFinder); - // Measure the positioned overlay area via its DecoratedBox ancestor - final placedBox1 = tester.getRect( - find - .ancestor(of: placedFinder, matching: find.byType(DecoratedBox)) - .first, - ); + + // The placed signature widget itself is a DecoratedBox + final placedBox1 = tester.getRect(placedFinder); // Compute normalized position within the page container final relX1 = (placedBox1.left - pageBox.left) / pageBox.width; @@ -142,11 +148,7 @@ void main() { await tester.pumpAndSettle(); final pageBox2 = tester.getRect(pageStackFinder); - final placedBox2 = tester.getRect( - find - .ancestor(of: placedFinder, matching: find.byType(DecoratedBox)) - .first, - ); + final placedBox2 = tester.getRect(placedFinder); final relX2 = (placedBox2.left - pageBox2.left) / pageBox2.width; final relY2 = (placedBox2.top - pageBox2.top) / pageBox2.height;