feat: able to add multi signatures on document view

This commit is contained in:
insleker 2025-09-03 20:55:16 +08:00
parent 0a21045761
commit fdf0d1f7a9
17 changed files with 288 additions and 215 deletions

View File

@ -20,6 +20,8 @@ flutter pub run build_runner build --delete-conflicting-outputs
flutter analyze
# > run unit tests and widget tests
flutter test
# > run integration tests
flutter test integration_test/ -d linux
# dart run tool/gen_view_wireframe_md.dart
# flutter pub run dead_code_analyzer

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
// This file is intentionally skipped. The integrated E2E test lives in
// integration_test/export_flow_test.dart to avoid multiple app launches.
void main() {
testWidgets('skipped duplicate E2E (see export_flow_test.dart)', (
tester,
) async {
expect(true, isTrue);
}, skip: true);
}

View File

@ -1,7 +1,9 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:image/image.dart' as img;
import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/data/services/export_providers.dart';
@ -56,4 +58,92 @@ void main() {
// Expect success UI
expect(find.textContaining('Saved:'), findsOneWidget);
});
// Helper to build a simple in-memory PNG as a signature image
Uint8List _makeSig() {
final canvas = img.Image(width: 80, height: 40);
img.fill(canvas, color: img.ColorUint8.rgb(255, 255, 255));
img.drawLine(
canvas,
x1: 6,
y1: 20,
x2: 74,
y2: 20,
color: img.ColorUint8.rgb(0, 0, 0),
);
return Uint8List.fromList(img.encodePng(canvas));
}
testWidgets('E2E (integration): place and confirm keeps size', (
tester,
) async {
final sigBytes = _makeSig();
await tester.pumpWidget(
ProviderScope(
overrides: [
pdfProvider.overrideWith(
(ref) => PdfController()..openPicked(path: 'test.pdf'),
),
signatureLibraryProvider.overrideWith((ref) {
final c = SignatureLibraryController();
c.add(sigBytes, name: 'image');
return c;
}),
// Keep mock viewer for determinism on CI/desktop devices
useMockViewerProvider.overrideWithValue(true),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final card = find.byKey(const Key('gd_signature_card_area')).first;
await tester.tap(card);
await tester.pump();
final active = find.byKey(const Key('signature_overlay'));
expect(active, findsOneWidget);
final sizeBefore = tester.getSize(active);
await tester.ensureVisible(active);
await tester.pumpAndSettle();
// Programmatically simulate confirm: add placement with current rect and bound image, then clear active overlay.
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
final sigState = container.read(signatureProvider);
final r = sigState.rect!;
final Size pageSize = SignatureController.pageSize;
final normalized = Rect.fromLTWH(
(r.left / pageSize.width).clamp(0.0, 1.0),
(r.top / pageSize.height).clamp(0.0, 1.0),
(r.width / pageSize.width).clamp(0.0, 1.0),
(r.height / pageSize.height).clamp(0.0, 1.0),
);
final lib = container.read(signatureLibraryProvider);
final imageId = lib.isNotEmpty ? lib.first.id : 'default.png';
final pdf = container.read(pdfProvider);
container
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: normalized, image: imageId);
container.read(signatureProvider.notifier).clearActiveOverlay();
await tester.pumpAndSettle();
final placed = find.byKey(const Key('placed_signature_0'));
expect(placed, findsOneWidget);
final sizeAfter = tester.getSize(placed);
expect(
(sizeAfter.width - sizeBefore.width).abs() < sizeBefore.width * 0.15,
isTrue,
);
expect(
(sizeAfter.height - sizeBefore.height).abs() < sizeBefore.height * 0.15,
isTrue,
);
});
}

View File

@ -173,15 +173,7 @@ final preferencesProvider =
return PreferencesNotifier(prefs);
});
/// Safe accessor for page view mode that falls back to 'continuous' until
/// SharedPreferences is available (useful for lightweight widget tests).
final pageViewModeProvider = Provider<String>((ref) {
final sp = ref.watch(sharedPreferencesProvider);
return sp.maybeWhen(
data: (_) => ref.watch(preferencesProvider).pageView,
orElse: () => 'continuous',
);
});
// pageViewModeProvider removed; the app always runs in continuous mode.
/// Derive the active ThemeMode based on preference and platform brightness
final themeModeProvider = Provider<ThemeMode>((ref) {

View File

@ -1,4 +1,5 @@
import 'dart:math' as math;
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -10,6 +11,7 @@ class PdfController extends StateNotifier<PdfState> {
PdfController() : super(PdfState.initial());
static const int samplePageCount = 5;
@visibleForTesting
void openSample() {
state = state.copyWith(
loaded: true,
@ -158,22 +160,7 @@ class PdfController extends StateNotifier<PdfState> {
removePlacement(page: state.currentPage, index: idx);
}
// Assign a different image name to a placement on a page.
void assignImageToPlacement({
required int page,
required int index,
required String image,
}) {
if (!state.loaded) return;
final p = page.clamp(1, state.pageCount);
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
final list = List<String>.from(imgMap[p] ?? const []);
if (index >= 0 && index < list.length) {
list[index] = image;
imgMap[p] = list;
state = state.copyWith(placementImageByPage: imgMap);
}
}
// NOTE: Programmatic reassignment of images has been removed.
// Convenience to get image name for a placement
String? imageOfPlacement({required int page, required int index}) {
@ -239,8 +226,8 @@ class SignatureController extends StateNotifier<SignatureState> {
state = state.copyWith(
rect: Rect.fromCenter(
center: Offset(
(pageSize.width / 2) * Random().nextDouble() * 2 + 1,
(pageSize.height / 2) * Random().nextDouble() * 2 + 1,
(pageSize.width / 2) * (Random().nextDouble() * 1.5 + 1),
(pageSize.height / 2) * (Random().nextDouble() * 1.5 + 1),
),
width: w,
height: h,
@ -394,28 +381,26 @@ class SignatureController extends StateNotifier<SignatureState> {
(r.width / pageSize.width).clamp(0.0, 1.0),
(r.height / pageSize.height).clamp(0.0, 1.0),
);
ref
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: normalized);
// Assign image id to this placement (last index)
final idx =
(ref.read(pdfProvider).placementsByPage[pdf.currentPage]?.length ?? 1) -
1;
String? id = state.assetId;
if (id == null) {
// Determine the image id to bind at placement time
String id = state.assetId ?? '';
if (id.isEmpty) {
final bytes =
ref.read(processedSignatureImageProvider) ?? state.imageBytes;
if (bytes != null) {
if (bytes != null && bytes.isNotEmpty) {
id = ref
.read(signatureLibraryProvider.notifier)
.add(bytes, name: 'image');
} else {
id = 'default.png';
}
}
if (id != null && id.isNotEmpty && idx >= 0) {
ref
.read(pdfProvider.notifier)
.assignImageToPlacement(page: pdf.currentPage, index: idx, image: id);
}
ref
.read(pdfProvider.notifier)
.addPlacement(page: pdf.currentPage, rect: normalized, image: id);
// Newly placed index is the last one on the page
final idx =
(ref.read(pdfProvider).placementsByPage[pdf.currentPage]?.length ?? 1) -
1;
// Auto-select the newly placed item so the red box appears
if (idx >= 0) {
ref.read(pdfProvider.notifier).selectPlacement(idx);

View File

@ -5,7 +5,6 @@ import 'package:pdfrx/pdfrx.dart';
import '../../../../data/services/export_providers.dart';
import '../view_model/view_model.dart';
import '../../../../data/services/preferences_providers.dart';
import 'signature_drag_data.dart';
import 'pdf_mock_continuous_list.dart';
import 'pdf_page_overlays.dart';
@ -51,9 +50,8 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
// is instructed to align to the provider's current page once ready.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
final mode = ref.read(pageViewModeProvider);
final pdf = ref.read(pdfProvider);
if (mode == 'continuous' && pdf.pickedPdfPath != null && pdf.loaded) {
if (pdf.pickedPdfPath != null && pdf.loaded) {
_scrollToPage(pdf.currentPage);
}
});
@ -70,7 +68,7 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
final pdf = ref.read(pdfProvider);
final isContinuous = ref.read(pageViewModeProvider) == 'continuous';
const isContinuous = true;
// Real continuous: drive via PdfViewerController
if (pdf.pickedPdfPath != null && isContinuous) {
@ -158,13 +156,12 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
@override
Widget build(BuildContext context) {
final pdf = ref.watch(pdfProvider);
final pageViewMode = ref.watch(pageViewModeProvider);
const pageViewMode = 'continuous';
// React to provider currentPage changes (e.g., user tapped overview)
ref.listen(pdfProvider, (prev, next) {
final mode = ref.read(pageViewModeProvider);
if (_suppressProviderListen) return;
if (mode == 'continuous' && (prev?.currentPage != next.currentPage)) {
if ((prev?.currentPage != next.currentPage)) {
final target = next.currentPage;
// If we're already navigating to this target, ignore; otherwise allow new target.
if (_programmaticTargetPage != null &&
@ -177,19 +174,7 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
}
}
});
// When switching to continuous, bring current page into view
ref.listen<String>(pageViewModeProvider, (prev, next) {
if (next == 'continuous') {
// Skip initial auto-scroll in mock mode to avoid fighting with
// early provider-driven jumps during tests.
final isMock = ref.read(useMockViewerProvider);
if (isMock) return;
final p = ref.read(pdfProvider).currentPage;
if (_visiblePage != p) {
_scrollToPage(p);
}
}
});
// No page view mode switching; always continuous.
if (!pdf.loaded) {
// In tests, AppLocalizations delegate may not be injected; fallback.

View File

@ -33,10 +33,17 @@ class PdfPageOverlays extends ConsumerWidget {
final widgets = <Widget>[];
for (int i = 0; i < placed.length; i++) {
final r = placed[i]; // stored as normalized 0..1 of page size
final uiRect = Rect.fromLTWH(
r.left * pageSize.width,
r.top * pageSize.height,
r.width * pageSize.width,
r.height * pageSize.height,
);
widgets.add(
SignatureOverlay(
pageSize: pageSize,
rect: placed[i],
rect: uiRect,
sig: sig,
pageNumber: pageNumber,
interactive: false,

View File

@ -67,21 +67,10 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
);
},
onTap: () {
final sel = ref.read(pdfProvider).selectedPlacementIndex;
final page = ref.read(pdfProvider).currentPage;
if (sel != null) {
ref
.read(pdfProvider.notifier)
.assignImageToPlacement(
page: page,
index: sel,
image: a.id,
);
} else {
ref
.read(signatureProvider.notifier)
.setImageFromLibrary(assetId: a.id);
}
// Never reassign placed signatures via tap; only set active overlay source
ref
.read(signatureProvider.notifier)
.setImageFromLibrary(assetId: a.id);
},
),
),
@ -146,9 +135,12 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
ref.read(processedSignatureImageProvider) ??
ref.read(signatureProvider).imageBytes;
if (b != null) {
ref
final id = ref
.read(signatureLibraryProvider.notifier)
.add(b, name: 'image');
ref
.read(signatureProvider.notifier)
.setImageFromLibrary(assetId: id);
}
},
icon: const Icon(Icons.image_outlined),
@ -166,9 +158,12 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
ref.read(processedSignatureImageProvider) ??
ref.read(signatureProvider).imageBytes;
if (b != null) {
ref
final id = ref
.read(signatureLibraryProvider.notifier)
.add(b, name: 'drawing');
ref
.read(signatureProvider.notifier)
.setImageFromLibrary(assetId: id);
}
},
icon: const Icon(Icons.gesture),

View File

@ -1,26 +0,0 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import '_world.dart';
/// Usage: an image {"bob.png"} is loaded
Future<void> anImageIsLoaded(WidgetTester tester, String param1) async {
final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container;
// Remember current image name
TestWorld.currentImageName = param1;
// Map name to deterministic bytes for testing
Uint8List bytes;
switch (param1) {
case 'alice.png':
bytes = Uint8List.fromList([1, 2, 3]);
break;
case 'bob.png':
bytes = Uint8List.fromList([4, 5, 6]);
break;
default:
bytes = Uint8List.fromList(param1.codeUnits.take(10).toList());
}
container.read(signatureProvider.notifier).setImageBytes(bytes);
}

View File

@ -1,22 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import '_world.dart';
/// Usage: the selected signature is shown with image {"bob.png"}
Future<void> theSelectedSignatureIsShownWithImage(
WidgetTester tester,
String expected,
) async {
final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container;
final pdf = container.read(pdfProvider);
final page = pdf.currentPage;
final idx =
pdf.selectedPlacementIndex ??
((pdf.placementsByPage[page]?.length ?? 1) - 1);
final name = container
.read(pdfProvider.notifier)
.imageOfPlacement(page: page, index: idx);
expect(name, expected);
}

View File

@ -1,30 +0,0 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import '_world.dart';
/// Usage: the user assigns {"bob.png"} to the selected signature
Future<void> theUserAssignsToTheSelectedSignature(
WidgetTester tester,
String newImageName,
) async {
final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container;
// Load the new image into signature state (simulating pick)
Uint8List bytes =
newImageName == 'bob.png'
? Uint8List.fromList([4, 5, 6])
: Uint8List.fromList([1, 2, 3]);
container.read(signatureProvider.notifier).setImageBytes(bytes);
TestWorld.currentImageName = newImageName;
// Assign to currently selected placement
final pdf = container.read(pdfProvider);
final page = pdf.currentPage;
final idx =
pdf.selectedPlacementIndex ??
((pdf.placementsByPage[page]?.length ?? 1) - 1);
container
.read(pdfProvider.notifier)
.assignImageToPlacement(page: page, index: idx, image: newImageName);
}

View File

@ -1,40 +0,0 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import '_world.dart';
/// Usage: the user places a signature on the page
Future<void> theUserPlacesASignatureOnThePage(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container;
final pdf = container.read(pdfProvider);
if (!pdf.loaded) {
container
.read(pdfProvider.notifier)
.openPicked(path: 'mock.pdf', pageCount: 1);
container.read(pdfProvider.notifier).setSignedPage(1);
}
// Ensure image bytes
if (container.read(signatureProvider).imageBytes == null) {
final name = TestWorld.currentImageName ?? 'alice.png';
Uint8List bytes =
name == 'bob.png'
? Uint8List.fromList([4, 5, 6])
: Uint8List.fromList([1, 2, 3]);
container.read(signatureProvider.notifier).setImageBytes(bytes);
}
container.read(signatureProvider.notifier).placeDefaultRect();
final Rect r = container.read(signatureProvider).rect!;
final int page = container.read(pdfProvider).signedPage ?? 1;
final imgName = TestWorld.currentImageName ?? 'alice.png';
container
.read(pdfProvider.notifier)
.addPlacement(page: page, rect: r, image: imgName);
// Select the just placed signature (last index)
final list = container.read(pdfProvider).placementsByPage[page] ?? const [];
container
.read(pdfProvider.notifier)
.selectPlacement(list.isEmpty ? null : (list.length - 1));
}

View File

@ -22,14 +22,6 @@ Feature: support multiple signature pictures
Then identical signature instances appear in each location
And adjusting one instance does not affect the others
Scenario: Reassign a different image to an existing signature
Given a PDF page is selected for signing
And an image {"alice.png"} is loaded
And the user places a signature on the page
When an image {"bob.png"} is loaded
And the user assigns {"bob.png"} to the selected signature
Then the selected signature is shown with image {"bob.png"}
Scenario: Save/export uses the assigned image for each signature
Given a PDF is open and contains multiple placed signatures across pages
When the user saves/exports the document

View File

@ -0,0 +1,136 @@
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart' show kSecondaryMouseButton;
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image/image.dart' as img;
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import 'package:pdf_signature/data/services/export_providers.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
void main() {
// Open the active overlay context menu robustly (mouse right-click, fallback to long-press)
Future<void> _openActiveMenuAndConfirm(WidgetTester tester) async {
final overlay = find.byKey(const Key('signature_overlay'));
expect(overlay, findsOneWidget);
// Ensure visible before interacting
await tester.ensureVisible(overlay);
await tester.pumpAndSettle();
// Try right-click first
final center = tester.getCenter(overlay);
final TestGesture mouse = await tester.createGesture(
kind: ui.PointerDeviceKind.mouse,
buttons: kSecondaryMouseButton,
);
await mouse.addPointer(location: center);
addTearDown(mouse.removePointer);
await tester.pump();
await mouse.down(center);
await tester.pump(const Duration(milliseconds: 30));
await mouse.up();
await tester.pumpAndSettle();
// If menu didn't appear, try long-press
if (find.byKey(const Key('ctx_active_confirm')).evaluate().isEmpty) {
await tester.longPress(overlay, warnIfMissed: false);
await tester.pumpAndSettle();
}
await tester.tap(find.byKey(const Key('ctx_active_confirm')));
await tester.pumpAndSettle();
}
// Build a simple in-memory PNG as a signature image
Uint8List _makeSig() {
final canvas = img.Image(width: 80, height: 40);
img.fill(canvas, color: img.ColorUint8.rgb(255, 255, 255));
img.drawLine(
canvas,
x1: 6,
y1: 20,
x2: 74,
y2: 20,
color: img.ColorUint8.rgb(0, 0, 0),
);
return Uint8List.fromList(img.encodePng(canvas));
}
testWidgets('E2E: select, place default, and confirm signature', (
tester,
) async {
final sigBytes = _makeSig();
await tester.pumpWidget(
ProviderScope(
overrides: [
// Open a PDF
pdfProvider.overrideWith(
(ref) => PdfController()..openPicked(path: 'test.pdf'),
),
// Provide one signature asset in the library
signatureLibraryProvider.overrideWith((ref) {
final c = SignatureLibraryController();
c.add(sigBytes, name: 'image');
return c;
}),
// Use mock continuous viewer for deterministic layout in widget tests
useMockViewerProvider.overrideWithValue(true),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
// Tap the signature card to set it as active overlay
final card = find.byKey(const Key('gd_signature_card_area')).first;
expect(card, findsOneWidget);
await tester.tap(card);
await tester.pump();
// Active overlay should appear
final active = find.byKey(const Key('signature_overlay'));
expect(active, findsOneWidget);
final sizeBefore = tester.getSize(active);
// Bring the overlay into the viewport (it's near the bottom of the page by default)
final listFinder = find.byKey(const Key('pdf_continuous_mock_list'));
if (listFinder.evaluate().isNotEmpty) {
// Ensure the active overlay is fully visible within the scrollable viewport
await tester.ensureVisible(active);
await tester.pumpAndSettle();
}
// Open context menu and confirm using a robust flow
await _openActiveMenuAndConfirm(tester);
// Verify active overlay gone and placed overlay shown
expect(find.byKey(const Key('signature_overlay')), findsNothing);
final placed = find.byKey(const Key('placed_signature_0'));
expect(placed, findsOneWidget);
final sizeAfter = tester.getSize(placed);
// Compare sizes: should be roughly equal (allowing small layout variance)
expect(
(sizeAfter.width - sizeBefore.width).abs() < sizeBefore.width * 0.15,
isTrue,
);
expect(
(sizeAfter.height - sizeBefore.height).abs() < sizeBefore.height * 0.15,
isTrue,
);
// Verify provider state reflects one placement on current page
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
final pdf = container.read(pdfProvider);
final list = pdf.placementsByPage[pdf.currentPage] ?? const [];
expect(list.length, 1);
});
}

View File

@ -8,7 +8,7 @@ import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import 'package:pdf_signature/data/services/export_providers.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/services/preferences_providers.dart';
// preferences_providers.dart no longer exports pageViewModeProvider
Future<void> pumpWithOpenPdf(WidgetTester tester) async {
await tester.pumpWidget(
@ -18,8 +18,7 @@ Future<void> pumpWithOpenPdf(WidgetTester tester) async {
(ref) => PdfController()..openPicked(path: 'test.pdf'),
),
useMockViewerProvider.overrideWith((ref) => true),
// Force continuous mode regardless of prefs
pageViewModeProvider.overrideWithValue('continuous'),
// Continuous mode is always-on; no page view override needed
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
@ -59,7 +58,7 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
..placeDefaultRect(),
),
useMockViewerProvider.overrideWith((ref) => true),
pageViewModeProvider.overrideWithValue('continuous'),
// Continuous mode is always-on; no page view override needed
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,

View File

@ -7,7 +7,6 @@ import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import 'package:pdf_signature/data/services/export_providers.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/model/model.dart';
import 'package:pdf_signature/data/services/preferences_providers.dart';
class _TestPdfController extends PdfController {
_TestPdfController() : super() {
@ -30,7 +29,7 @@ void main() {
ProviderScope(
overrides: [
useMockViewerProvider.overrideWithValue(true),
pageViewModeProvider.overrideWithValue('continuous'),
// Continuous mode is always-on; no page view override needed
pdfProvider.overrideWith((ref) => ctrl),
],
child: MaterialApp(

View File

@ -7,7 +7,6 @@ import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
import 'package:pdf_signature/data/services/export_providers.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/model/model.dart';
import 'package:pdf_signature/data/services/preferences_providers.dart';
class _TestPdfController extends PdfController {
_TestPdfController() : super() {
@ -29,8 +28,7 @@ void main() {
ProviderScope(
overrides: [
useMockViewerProvider.overrideWithValue(true),
// Force continuous mode without SharedPreferences
pageViewModeProvider.overrideWithValue('continuous'),
// Continuous mode is always-on; no page view override needed
pdfProvider.overrideWith((ref) => ctrl),
],
child: MaterialApp(