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>>(
(ref) => SignatureCardStateNotifier(),
);

View File

@ -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<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
/// 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<PdfMockContinuousList> {
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<PdfMockContinuousList> {
),
),
),
Consumer(
builder: (context, ref, _) {
final visible = ref.watch(signatureVisibilityProvider);
if (!visible) return const SizedBox.shrink();
final overlays = <Widget>[];
// 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(),
],
),
),

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.
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<PdfPageArea> {
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();

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.
/// 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.
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/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<PdfSignatureHomePage> {
}
// 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);

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
.read(documentRepositoryProvider.notifier)
.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(documentRepositoryProvider.notifier).state =
Document.initial();
container.read(signatureCardProvider.notifier).state = [
container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(),
];
container

View File

@ -14,7 +14,7 @@ Future<void> 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]);

View File

@ -16,7 +16,7 @@ Future<void> 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]);

View File

@ -17,7 +17,7 @@ Future<void> 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

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/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<void> 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');

View File

@ -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;