feat: partially implement new view of UI

This commit is contained in:
insleker 2025-09-11 00:13:47 +08:00
parent 189bc7e6e6
commit 4d2cd09adf
14 changed files with 659 additions and 201 deletions

View File

@ -37,7 +37,7 @@ class SignatureCardStateNotifier extends StateNotifier<List<SignatureCard>> {
} }
} }
final signatureCardProvider = final signatureCardRepositoryProvider =
StateNotifierProvider<SignatureCardStateNotifier, List<SignatureCard>>( StateNotifierProvider<SignatureCardStateNotifier, List<SignatureCard>>(
(ref) => SignatureCardStateNotifier(), (ref) => SignatureCardStateNotifier(),
); );

View File

@ -30,7 +30,7 @@ class PdfViewModel {
ref ref
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: path, pageCount: pageCount, bytes: bytes); .openPicked(path: path, pageCount: pageCount, bytes: bytes);
ref.read(signatureCardProvider.notifier).clearAll(); ref.read(signatureCardRepositoryProvider.notifier).clearAll();
} }
Future<Uint8List?> loadSignatureFromFile() async { Future<Uint8List?> loadSignatureFromFile() async {

View File

@ -9,6 +9,7 @@ import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'
// using only adjusted overlay, no direct model imports needed // using only adjusted overlay, no direct model imports needed
/// Mocked continuous viewer for tests or platforms without real viewer. /// Mocked continuous viewer for tests or platforms without real viewer.
@visibleForTesting
class PdfMockContinuousList extends ConsumerStatefulWidget { class PdfMockContinuousList extends ConsumerStatefulWidget {
const PdfMockContinuousList({ const PdfMockContinuousList({
super.key, super.key,
@ -54,6 +55,9 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
final pendingPage = widget.pendingPage; final pendingPage = widget.pendingPage;
final scrollToPage = widget.scrollToPage; final scrollToPage = widget.scrollToPage;
final clearPending = widget.clearPending; final clearPending = widget.clearPending;
final visible = ref.watch(signatureVisibilityProvider);
final assets = ref.watch(signatureAssetRepositoryProvider);
final aspectLocked = ref.watch(aspectLockedProvider);
if (pendingPage != null) { if (pendingPage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final p = pendingPage; final p = pendingPage;
@ -101,13 +105,9 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
), ),
), ),
), ),
Consumer( visible
builder: (context, ref, _) { ? Stack(
final visible = ref.watch(signatureVisibilityProvider); children: [
if (!visible) return const SizedBox.shrink();
final overlays = <Widget>[];
// Existing placed overlays
overlays.add(
PdfPageOverlays( PdfPageOverlays(
pageSize: pageSize, pageSize: pageSize,
pageNumber: pageNum, pageNumber: pageNum,
@ -117,14 +117,9 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
onClearActiveOverlay: widget.onClearActiveOverlay, onClearActiveOverlay: widget.onClearActiveOverlay,
onSelectPlaced: widget.onSelectPlaced, onSelectPlaced: widget.onSelectPlaced,
), ),
);
// For tests expecting an active overlay, draw a mock // For tests expecting an active overlay, draw a mock
// overlay on page 1 when library has at least one asset // overlay on page 1 when library has at least one asset
if (pageNum == 1 && if (pageNum == 1 && assets.isNotEmpty)
(ref
.watch(signatureAssetRepositoryProvider)
.isNotEmpty)) {
overlays.add(
LayoutBuilder( LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final left = final left =
@ -134,17 +129,16 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
final width = final width =
_activeRect.width * constraints.maxWidth; _activeRect.width * constraints.maxWidth;
final height = final height =
_activeRect.height * constraints.maxHeight; _activeRect.height *
final aspectLocked = ref.watch( constraints.maxHeight;
aspectLockedProvider,
);
// Publish rect for tests/other UI to observe // Publish rect for tests/other UI to observe
WidgetsBinding.instance.addPostFrameCallback(( WidgetsBinding.instance.addPostFrameCallback((
_, _,
) { ) {
if (!mounted) return; if (!mounted) return;
ref.read(activeRectProvider.notifier).state = ref
_activeRect; .read(activeRectProvider.notifier)
.state = _activeRect;
}); });
return Stack( return Stack(
children: [ children: [
@ -157,7 +151,8 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
key: const Key('signature_overlay'), key: const Key('signature_overlay'),
onPanUpdate: (d) { onPanUpdate: (d) {
final dx = final dx =
d.delta.dx / constraints.maxWidth; d.delta.dx /
constraints.maxWidth;
final dy = final dy =
d.delta.dy / d.delta.dy /
constraints.maxHeight; constraints.maxHeight;
@ -204,7 +199,8 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
key: const Key('signature_handle'), key: const Key('signature_handle'),
onPanUpdate: (d) { onPanUpdate: (d) {
final dx = final dx =
d.delta.dx / constraints.maxWidth; d.delta.dx /
constraints.maxWidth;
final dy = final dy =
d.delta.dy / d.delta.dy /
constraints.maxHeight; constraints.maxHeight;
@ -212,8 +208,8 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
double newW = (_activeRect.width + double newW = (_activeRect.width +
dx) dx)
.clamp(0.05, 1.0); .clamp(0.05, 1.0);
double newH = (_activeRect.height + double newH =
dy) (_activeRect.height + dy)
.clamp(0.05, 1.0); .clamp(0.05, 1.0);
if (aspectLocked) { if (aspectLocked) {
final ratio = final ratio =
@ -221,7 +217,9 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
_activeRect.height; _activeRect.height;
// keep ratio; prefer width change driving height // keep ratio; prefer width change driving height
newH = (newW / newH = (newW /
(ratio == 0 ? 1 : ratio)) (ratio == 0
? 1
: ratio))
.clamp(0.05, 1.0); .clamp(0.05, 1.0);
} }
// clamp to page bounds // clamp to page bounds
@ -255,11 +253,9 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
); );
}, },
), ),
); ],
} )
return Stack(children: overlays); : const SizedBox.shrink(),
},
),
], ],
), ),
), ),

View File

@ -4,7 +4,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
// Real viewer removed in migration; mock continuous list is used in tests. // Real viewer removed in migration; mock continuous list is used in tests.
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'pdf_mock_continuous_list.dart'; import 'pdf_viewer_widget.dart';
class PdfPageArea extends ConsumerStatefulWidget { class PdfPageArea extends ConsumerStatefulWidget {
const PdfPageArea({ const PdfPageArea({
@ -147,24 +147,17 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
final isContinuous = pageViewMode == 'continuous'; final isContinuous = pageViewMode == 'continuous';
// Mock continuous: ListView with prebuilt children // Use real PDF viewer
if (isContinuous) { if (isContinuous) {
final count = pdf.pageCount > 0 ? pdf.pageCount : 1; return PdfViewerWidget(
return PdfMockContinuousList(
pageSize: widget.pageSize, pageSize: widget.pageSize,
count: count, onDragSignature: widget.onDragSignature,
pageKeyBuilder: _pageKey, onResizeSignature: widget.onResizeSignature,
scrollToPage: _scrollToPage,
pendingPage: _pendingPage,
clearPending: () {
_pendingPage = null;
_scrollRetryCount = 0;
},
onDragSignature: (delta) => widget.onDragSignature(delta),
onResizeSignature: (delta) => widget.onResizeSignature(delta),
onConfirmSignature: widget.onConfirmSignature, onConfirmSignature: widget.onConfirmSignature,
onClearActiveOverlay: widget.onClearActiveOverlay, onClearActiveOverlay: widget.onClearActiveOverlay,
onSelectPlaced: widget.onSelectPlaced, onSelectPlaced: widget.onSelectPlaced,
pageKeyBuilder: _pageKey,
scrollToPage: _scrollToPage,
); );
} }
return const SizedBox.shrink(); return const SizedBox.shrink();

View File

@ -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. /// Whether to use a mock continuous viewer (ListView) instead of a real PDF viewer.
/// Tests will override this to true. /// Tests will override this to true.
final useMockViewerProvider = Provider<bool>((ref) => true); final useMockViewerProvider = Provider<bool>(
(ref) => const bool.fromEnvironment('FLUTTER_TEST', defaultValue: false),
);
/// Global visibility toggle for signature overlays (placed items). Kept simple for tests. /// Global visibility toggle for signature overlays (placed items). Kept simple for tests.
final signatureVisibilityProvider = StateProvider<bool>((ref) => true); final signatureVisibilityProvider = StateProvider<bool>((ref) => true);

View File

@ -3,6 +3,7 @@ import 'package:file_selector/file_selector.dart' as fs;
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:multi_split_view/multi_split_view.dart'; import 'package:multi_split_view/multi_split_view.dart';
@ -62,10 +63,14 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
} }
// infer page count if possible // infer page count if possible
int pageCount = 1; int pageCount = 1;
if (bytes != null) {
try { try {
// printing.raster can detect page count lazily; leave 1 for tests final doc = await PdfDocument.openData(bytes);
pageCount = 5; pageCount = doc.pages.length;
} catch (_) {} } catch (_) {
// ignore
}
}
ref ref
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: file.path, pageCount: pageCount, bytes: bytes); .openPicked(path: file.path, pageCount: pageCount, bytes: bytes);

View File

@ -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<Offset> onDragSignature;
final ValueChanged<Offset> onResizeSignature;
final VoidCallback onConfirmSignature;
final VoidCallback onClearActiveOverlay;
final ValueChanged<int?> onSelectPlaced;
final GlobalKey Function(int page)? pageKeyBuilder;
final void Function(int page)? scrollToPage;
@override
ConsumerState<PdfViewerWidget> createState() => _PdfViewerWidgetState();
}
class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
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,
);
},
),
),
],
);
}
}

View File

@ -22,7 +22,7 @@ class WelcomeViewModel {
ref ref
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: path, pageCount: pageCount, bytes: bytes); .openPicked(path: path, pageCount: pageCount, bytes: bytes);
ref.read(signatureCardProvider.notifier).clearAll(); ref.read(signatureCardRepositoryProvider.notifier).clearAll();
} }
} }

View File

@ -13,7 +13,7 @@ Future<void> aMultipageDocumentIsOpen(WidgetTester tester) async {
container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(signatureAssetRepositoryProvider.notifier).state = [];
container.read(documentRepositoryProvider.notifier).state = container.read(documentRepositoryProvider.notifier).state =
Document.initial(); Document.initial();
container.read(signatureCardProvider.notifier).state = [ container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(), SignatureCard.initial(),
]; ];
container container

View File

@ -14,7 +14,7 @@ Future<void> aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async {
container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(signatureAssetRepositoryProvider.notifier).state = [];
container.read(documentRepositoryProvider.notifier).state = container.read(documentRepositoryProvider.notifier).state =
Document.initial(); Document.initial();
container.read(signatureCardProvider.notifier).state = [ container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(), SignatureCard.initial(),
]; ];
final bytes = Uint8List.fromList([1, 2, 3, 4, 5]); final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);

View File

@ -16,7 +16,7 @@ Future<void> aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard(
container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(signatureAssetRepositoryProvider.notifier).state = [];
container.read(documentRepositoryProvider.notifier).state = container.read(documentRepositoryProvider.notifier).state =
Document.initial(); Document.initial();
container.read(signatureCardProvider.notifier).state = [ container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(), SignatureCard.initial(),
]; ];
final bytes = Uint8List.fromList([1, 2, 3, 4, 5]); final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);

View File

@ -17,7 +17,7 @@ Future<void> threeSignaturePlacementsArePlacedOnTheCurrentPage(
container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(signatureAssetRepositoryProvider.notifier).state = [];
container.read(documentRepositoryProvider.notifier).state = container.read(documentRepositoryProvider.notifier).state =
Document.initial(); Document.initial();
container.read(signatureCardProvider.notifier).state = [ container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(), SignatureCard.initial(),
]; ];
container container

View File

@ -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/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_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'; import 'package:pdf_signature/l10n/app_localizations.dart';
// preferences_providers.dart no longer exports pageViewModeProvider // preferences_providers.dart no longer exports pageViewModeProvider
@ -48,13 +49,329 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
color: img.ColorUint8.rgb(0, 0, 0), color: img.ColorUint8.rgb(0, 0, 0),
); );
final bytes = img.encodePng(canvas); 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 // keep drawing for determinism even if bytes unused in simplified UI
await tester.pumpWidget( await tester.pumpWidget(
ProviderScope( ProviderScope(
overrides: [ overrides: [
documentRepositoryProvider.overrideWith( documentRepositoryProvider.overrideWith((ref) {
(ref) => DocumentStateNotifier()..openSample(), 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) { signatureAssetRepositoryProvider.overrideWith((ref) {
final repo = SignatureAssetRepository(); final repo = SignatureAssetRepository();
repo.add(Uint8List.fromList(bytes), name: 'test'); repo.add(Uint8List.fromList(bytes), name: 'test');

View File

@ -69,7 +69,12 @@ void main() {
// Use a persistent container across rebuilds // Use a persistent container across rebuilds
final container = ProviderContainer( final container = ProviderContainer(
overrides: [useMockViewerProvider.overrideWithValue(true)], overrides: [
useMockViewerProvider.overrideWithValue(true),
documentRepositoryProvider.overrideWith(
(ref) => DocumentStateNotifier()..openSample(),
),
],
); );
addTearDown(container.dispose); addTearDown(container.dispose);
@ -100,8 +105,6 @@ void main() {
// Initial pump at base width // Initial pump at base width
await tester.pumpWidget(buildHarness(width: 480)); await tester.pumpWidget(buildHarness(width: 480));
// Open sample
container.read(documentRepositoryProvider.notifier).openSample();
// Add a tiny non-empty asset to avoid decode errors // Add a tiny non-empty asset to avoid decode errors
final canvas = img.Image(width: 10, height: 5); final canvas = img.Image(width: 10, height: 5);
img.fill(canvas, color: img.ColorUint8.rgb(0, 0, 0)); img.fill(canvas, color: img.ColorUint8.rgb(0, 0, 0));
@ -117,6 +120,9 @@ void main() {
await tester.pumpAndSettle(); 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 // Find the first page stack and the placed signature widget
final pageStackFinder = find.byKey(const ValueKey('page_stack_1')); final pageStackFinder = find.byKey(const ValueKey('page_stack_1'));
expect(pageStackFinder, findsOneWidget); expect(pageStackFinder, findsOneWidget);
@ -124,13 +130,13 @@ void main() {
final placedFinder = find.byKey(const Key('placed_signature_0')); final placedFinder = find.byKey(const Key('placed_signature_0'));
expect(placedFinder, findsOneWidget); expect(placedFinder, findsOneWidget);
// Ensure the widget is fully laid out
await tester.pumpAndSettle();
final pageBox = tester.getRect(pageStackFinder); final pageBox = tester.getRect(pageStackFinder);
// Measure the positioned overlay area via its DecoratedBox ancestor
final placedBox1 = tester.getRect( // The placed signature widget itself is a DecoratedBox
find final placedBox1 = tester.getRect(placedFinder);
.ancestor(of: placedFinder, matching: find.byType(DecoratedBox))
.first,
);
// Compute normalized position within the page container // Compute normalized position within the page container
final relX1 = (placedBox1.left - pageBox.left) / pageBox.width; final relX1 = (placedBox1.left - pageBox.left) / pageBox.width;
@ -142,11 +148,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final pageBox2 = tester.getRect(pageStackFinder); final pageBox2 = tester.getRect(pageStackFinder);
final placedBox2 = tester.getRect( final placedBox2 = tester.getRect(placedFinder);
find
.ancestor(of: placedFinder, matching: find.byType(DecoratedBox))
.first,
);
final relX2 = (placedBox2.left - pageBox2.left) / pageBox2.width; final relX2 = (placedBox2.left - pageBox2.left) / pageBox2.width;
final relY2 = (placedBox2.top - pageBox2.top) / pageBox2.height; final relY2 = (placedBox2.top - pageBox2.top) / pageBox2.height;