From 1acd95fc94a29cb652b56fa247f00e32422d5f6b Mon Sep 17 00:00:00 2001 From: insleker Date: Tue, 2 Sep 2025 22:17:48 +0800 Subject: [PATCH] feat: modify layout of signature sidebar --- integration_test/export_flow_test.dart | 2 +- lib/app.dart | 2 +- lib/data/model/model.dart | 6 + .../{providers.dart => export_providers.dart} | 0 lib/data/services/export_service.dart | 172 ++++++++---- .../services/preferences_providers.dart} | 0 .../features/pdf/view_model/view_model.dart | 82 +++++- .../features/pdf/widgets/pages_sidebar.dart | 11 + .../features/pdf/widgets/pdf_page_area.dart | 63 +++-- .../pdf/widgets/pdf_pages_overview.dart | 2 +- lib/ui/features/pdf/widgets/pdf_screen.dart | 116 +++----- lib/ui/features/pdf/widgets/pdf_toolbar.dart | 26 +- .../features/pdf/widgets/signature_card.dart | 153 ++++++++++ .../pdf/widgets/signature_drag_data.dart | 4 + .../pdf/widgets/signature_drawer.dart | 263 ++++++++---------- .../pdf/widgets/signatures_sidebar.dart | 54 ++++ .../preferences/widgets/settings_screen.dart | 2 +- test/widget/export_flow_test.dart | 2 +- test/widget/helpers.dart | 4 +- test/widget/pdf_navigation_widget_test.dart | 2 +- .../widget/pdf_page_area_early_jump_test.dart | 4 +- test/widget/pdf_page_area_jump_test.dart | 4 +- 22 files changed, 643 insertions(+), 331 deletions(-) rename lib/data/services/{providers.dart => export_providers.dart} (100%) rename lib/{ui/features/preferences/providers.dart => data/services/preferences_providers.dart} (100%) create mode 100644 lib/ui/features/pdf/widgets/pages_sidebar.dart create mode 100644 lib/ui/features/pdf/widgets/signature_card.dart create mode 100644 lib/ui/features/pdf/widgets/signature_drag_data.dart create mode 100644 lib/ui/features/pdf/widgets/signatures_sidebar.dart diff --git a/integration_test/export_flow_test.dart b/integration_test/export_flow_test.dart index 6e89bbf..3326aa2 100644 --- a/integration_test/export_flow_test.dart +++ b/integration_test/export_flow_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:pdf_signature/data/services/export_service.dart'; -import 'package:pdf_signature/data/services/providers.dart'; +import 'package:pdf_signature/data/services/export_providers.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; diff --git a/lib/app.dart b/lib/app.dart index 84bbc39..f1c6cb3 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,7 +5,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart'; 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/ui/features/welcome/widgets/welcome_screen.dart'; -import 'ui/features/preferences/providers.dart'; +import 'data/services/preferences_providers.dart'; import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart'; class MyApp extends StatelessWidget { diff --git a/lib/data/model/model.dart b/lib/data/model/model.dart index 828fedd..256bc2b 100644 --- a/lib/data/model/model.dart +++ b/lib/data/model/model.dart @@ -69,6 +69,8 @@ class SignatureState { final double rotation; final List> strokes; final Uint8List? imageBytes; + // The ID of the signature asset the current overlay is based on (from library) + final String? assetId; // When true, the active signature overlay is movable/resizable and should not be exported. // When false, the overlay is confirmed (unmovable) and eligible for export. final bool editingEnabled; @@ -81,6 +83,7 @@ class SignatureState { this.rotation = 0.0, required this.strokes, this.imageBytes, + this.assetId, this.editingEnabled = false, }); factory SignatureState.initial() => const SignatureState( @@ -92,6 +95,7 @@ class SignatureState { rotation: 0.0, strokes: [], imageBytes: null, + assetId: null, editingEnabled: false, ); SignatureState copyWith({ @@ -103,6 +107,7 @@ class SignatureState { double? rotation, List>? strokes, Uint8List? imageBytes, + String? assetId, bool? editingEnabled, }) => SignatureState( rect: rect ?? this.rect, @@ -113,6 +118,7 @@ class SignatureState { rotation: rotation ?? this.rotation, strokes: strokes ?? this.strokes, imageBytes: imageBytes ?? this.imageBytes, + assetId: assetId ?? this.assetId, editingEnabled: editingEnabled ?? this.editingEnabled, ); } diff --git a/lib/data/services/providers.dart b/lib/data/services/export_providers.dart similarity index 100% rename from lib/data/services/providers.dart rename to lib/data/services/export_providers.dart diff --git a/lib/data/services/export_service.dart b/lib/data/services/export_service.dart index 3a16cab..6cefcc8 100644 --- a/lib/data/services/export_service.dart +++ b/lib/data/services/export_service.dart @@ -33,6 +33,8 @@ class ExportService { required Size uiPageSize, required Uint8List? signatureImageBytes, Map>? placementsByPage, + Map>? placementImageByPage, + Map? libraryBytes, double targetDpi = 144.0, }) async { // print( @@ -53,6 +55,8 @@ class ExportService { uiPageSize: uiPageSize, signatureImageBytes: signatureImageBytes, placementsByPage: placementsByPage, + placementImageByPage: placementImageByPage, + libraryBytes: libraryBytes, targetDpi: targetDpi, ); if (bytes == null) return false; @@ -73,6 +77,8 @@ class ExportService { required Size uiPageSize, required Uint8List? signatureImageBytes, Map>? placementsByPage, + Map>? placementImageByPage, + Map? libraryBytes, double targetDpi = 144.0, }) async { final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false); @@ -100,6 +106,10 @@ class ExportService { hasMulti ? (placementsByPage[pageIndex] ?? const []) : const []; + final pageImageIds = + hasMulti + ? (placementImageByPage?[pageIndex] ?? const []) + : const []; final shouldStampSingle = !hasMulti && signedPage != null && @@ -107,12 +117,7 @@ class ExportService { signatureRectUi != null && signatureImageBytes != null && signatureImageBytes.isNotEmpty; - final shouldStampMulti = - hasMulti && - pagePlacements.isNotEmpty && - signatureImageBytes != null && - signatureImageBytes.isNotEmpty; - if (shouldStampSingle || shouldStampMulti) { + if (shouldStampSingle) { try { sigImgObj = pw.MemoryImage(signatureImageBytes); } catch (_) { @@ -139,35 +144,52 @@ class ExportService { ), ), ]; - if (sigImgObj != null) { - if (hasMulti && pagePlacements.isNotEmpty) { - for (final r in pagePlacements) { - final left = r.left / uiPageSize.width * widthPts; - final top = r.top / uiPageSize.height * heightPts; - final w = r.width / uiPageSize.width * widthPts; - final h = r.height / uiPageSize.height * heightPts; - children.add( - pw.Positioned( - left: left, - top: top, - child: pw.Image(sigImgObj, width: w, height: h), - ), - ); - } - } else if (shouldStampSingle) { - final r = signatureRectUi; + // Multi-placement stamping: per-placement image from libraryBytes + if (hasMulti && pagePlacements.isNotEmpty) { + for (var i = 0; i < pagePlacements.length; i++) { + final r = pagePlacements[i]; final left = r.left / uiPageSize.width * widthPts; final top = r.top / uiPageSize.height * heightPts; final w = r.width / uiPageSize.width * widthPts; final h = r.height / uiPageSize.height * heightPts; - children.add( - pw.Positioned( - left: left, - top: top, - child: pw.Image(sigImgObj, width: w, height: h), - ), - ); + Uint8List? bytes; + if (i < pageImageIds.length) { + final id = pageImageIds[i]; + bytes = libraryBytes?[id]; + } + bytes ??= + signatureImageBytes; // fallback to single image if provided + if (bytes != null && bytes.isNotEmpty) { + pw.MemoryImage? imgObj; + try { + imgObj = pw.MemoryImage(bytes); + } catch (_) { + imgObj = null; + } + if (imgObj != null) { + children.add( + pw.Positioned( + left: left, + top: top, + child: pw.Image(imgObj, width: w, height: h), + ), + ); + } + } } + } else if (shouldStampSingle && sigImgObj != null) { + final r = signatureRectUi; + final left = r.left / uiPageSize.width * widthPts; + final top = r.top / uiPageSize.height * heightPts; + final w = r.width / uiPageSize.width * widthPts; + final h = r.height / uiPageSize.height * heightPts; + children.add( + pw.Positioned( + left: left, + top: top, + child: pw.Image(sigImgObj, width: w, height: h), + ), + ); } return pw.Stack(children: children); }, @@ -187,6 +209,10 @@ class ExportService { (placementsByPage != null && placementsByPage.isNotEmpty); final pagePlacements = hasMulti ? (placementsByPage[1] ?? const []) : const []; + final pageImageIds = + hasMulti + ? (placementImageByPage?[1] ?? const []) + : const []; final shouldStampSingle = !hasMulti && signedPage != null && @@ -194,12 +220,7 @@ class ExportService { signatureRectUi != null && signatureImageBytes != null && signatureImageBytes.isNotEmpty; - final shouldStampMulti = - hasMulti && - pagePlacements.isNotEmpty && - signatureImageBytes != null && - signatureImageBytes.isNotEmpty; - if (shouldStampSingle || shouldStampMulti) { + if (shouldStampSingle) { try { // If it's already PNG, keep as-is to preserve alpha; otherwise decode/encode PNG final asStr = String.fromCharCodes(signatureImageBytes.take(8)); @@ -232,35 +253,66 @@ class ExportService { color: pdf.PdfColors.white, ), ]; - if (sigImgObj != null) { - if (hasMulti && pagePlacements.isNotEmpty) { - for (final r in pagePlacements) { - final left = r.left / uiPageSize.width * widthPts; - final top = r.top / uiPageSize.height * heightPts; - final w = r.width / uiPageSize.width * widthPts; - final h = r.height / uiPageSize.height * heightPts; - children.add( - pw.Positioned( - left: left, - top: top, - child: pw.Image(sigImgObj, width: w, height: h), - ), - ); - } - } else if (shouldStampSingle) { - final r = signatureRectUi; + // Multi-placement stamping on fallback page + if (hasMulti && pagePlacements.isNotEmpty) { + for (var i = 0; i < pagePlacements.length; i++) { + final r = pagePlacements[i]; final left = r.left / uiPageSize.width * widthPts; final top = r.top / uiPageSize.height * heightPts; final w = r.width / uiPageSize.width * widthPts; final h = r.height / uiPageSize.height * heightPts; - children.add( - pw.Positioned( - left: left, - top: top, - child: pw.Image(sigImgObj, width: w, height: h), - ), - ); + Uint8List? bytes; + if (i < pageImageIds.length) { + final id = pageImageIds[i]; + bytes = libraryBytes?[id]; + } + bytes ??= + signatureImageBytes; // fallback to single image if provided + if (bytes != null && bytes.isNotEmpty) { + pw.MemoryImage? imgObj; + try { + // Ensure PNG for transparency if not already + final asStr = String.fromCharCodes(bytes.take(8)); + final isPng = + bytes.length > 8 && + bytes[0] == 0x89 && + asStr.startsWith('\u0089PNG'); + if (isPng) { + imgObj = pw.MemoryImage(bytes); + } else { + final decoded = img.decodeImage(bytes); + if (decoded != null) { + final png = img.encodePng(decoded, level: 6); + imgObj = pw.MemoryImage(Uint8List.fromList(png)); + } + } + } catch (_) { + imgObj = null; + } + if (imgObj != null) { + children.add( + pw.Positioned( + left: left, + top: top, + child: pw.Image(imgObj, width: w, height: h), + ), + ); + } + } } + } else if (shouldStampSingle && sigImgObj != null) { + final r = signatureRectUi; + final left = r.left / uiPageSize.width * widthPts; + final top = r.top / uiPageSize.height * heightPts; + final w = r.width / uiPageSize.width * widthPts; + final h = r.height / uiPageSize.height * heightPts; + children.add( + pw.Positioned( + left: left, + top: top, + child: pw.Image(sigImgObj, width: w, height: h), + ), + ); } return pw.Stack(children: children); }, diff --git a/lib/ui/features/preferences/providers.dart b/lib/data/services/preferences_providers.dart similarity index 100% rename from lib/ui/features/preferences/providers.dart rename to lib/data/services/preferences_providers.dart diff --git a/lib/ui/features/pdf/view_model/view_model.dart b/lib/ui/features/pdf/view_model/view_model.dart index e187c79..308524a 100644 --- a/lib/ui/features/pdf/view_model/view_model.dart +++ b/lib/ui/features/pdf/view_model/view_model.dart @@ -169,6 +169,44 @@ final pdfProvider = StateNotifierProvider( (ref) => PdfController(), ); +/// A simple library of signature images available to the user in the sidebar. +class SignatureAsset { + final String id; // unique id + final Uint8List bytes; + final String? name; // optional display name (e.g., filename) + const SignatureAsset({required this.id, required this.bytes, this.name}); +} + +class SignatureLibraryController extends StateNotifier> { + SignatureLibraryController() : super(const []); + + String add(Uint8List bytes, {String? name}) { + // Always add a new asset (allow duplicates). This lets users create multiple cards + // even when loading the same image repeatedly for different adjustments/usages. + if (bytes.isEmpty) return ''; + final id = DateTime.now().microsecondsSinceEpoch.toString(); + state = List.of(state) + ..add(SignatureAsset(id: id, bytes: bytes, name: name)); + return id; + } + + void remove(String id) { + state = state.where((a) => a.id != id).toList(growable: false); + } + + SignatureAsset? byId(String id) { + for (final a in state) { + if (a.id == id) return a; + } + return null; + } +} + +final signatureLibraryProvider = + StateNotifierProvider>( + (ref) => SignatureLibraryController(), + ); + class SignatureController extends StateNotifier { SignatureController() : super(SignatureState.initial()); static const Size pageSize = Size(400, 560); @@ -291,7 +329,7 @@ class SignatureController extends StateNotifier { } void setImageBytes(Uint8List bytes) { - state = state.copyWith(imageBytes: bytes); + state = state.copyWith(imageBytes: bytes, assetId: null); if (state.rect == null) { placeDefaultRect(); } @@ -299,6 +337,15 @@ class SignatureController extends StateNotifier { state = state.copyWith(editingEnabled: true); } + // Select image from the shared signature library + void setImageFromLibrary({required String assetId}) { + state = state.copyWith(assetId: assetId); + if (state.rect == null) { + placeDefaultRect(); + } + state = state.copyWith(editingEnabled: true); + } + void clearImage() { state = state.copyWith(imageBytes: null, rect: null, editingEnabled: false); } @@ -318,6 +365,25 @@ class SignatureController extends StateNotifier { final pdf = ref.read(pdfProvider); if (!pdf.loaded) return null; ref.read(pdfProvider.notifier).addPlacement(page: pdf.currentPage, rect: r); + // 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) { + final bytes = + ref.read(processedSignatureImageProvider) ?? state.imageBytes; + if (bytes != null) { + id = ref + .read(signatureLibraryProvider.notifier) + .add(bytes, name: 'image'); + } + } + if (id != null && id.isNotEmpty && idx >= 0) { + ref + .read(pdfProvider.notifier) + .assignImageToPlacement(page: pdf.currentPage, index: idx, image: id); + } // Freeze editing: keep rect for preview but disable interaction state = state.copyWith(editingEnabled: false); return r; @@ -339,7 +405,19 @@ final signatureProvider = /// Returns null if no image is loaded. The output is a PNG to preserve alpha. final processedSignatureImageProvider = Provider((ref) { final s = ref.watch(signatureProvider); - final bytes = s.imageBytes; + // If active overlay is based on a library asset, pull its bytes + Uint8List? bytes; + if (s.assetId != null) { + final lib = ref.watch(signatureLibraryProvider); + for (final a in lib) { + if (a.id == s.assetId) { + bytes = a.bytes; + break; + } + } + } else { + bytes = s.imageBytes; + } if (bytes == null || bytes.isEmpty) return null; // Decode (supports PNG/JPEG, etc.) diff --git a/lib/ui/features/pdf/widgets/pages_sidebar.dart b/lib/ui/features/pdf/widgets/pages_sidebar.dart new file mode 100644 index 0000000..5da2e20 --- /dev/null +++ b/lib/ui/features/pdf/widgets/pages_sidebar.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'pdf_pages_overview.dart'; + +class PagesSidebar extends StatelessWidget { + const PagesSidebar({super.key}); + + @override + Widget build(BuildContext context) { + return Card(margin: EdgeInsets.zero, child: const PdfPagesOverview()); + } +} diff --git a/lib/ui/features/pdf/widgets/pdf_page_area.dart b/lib/ui/features/pdf/widgets/pdf_page_area.dart index 9b66686..89ad34a 100644 --- a/lib/ui/features/pdf/widgets/pdf_page_area.dart +++ b/lib/ui/features/pdf/widgets/pdf_page_area.dart @@ -1,15 +1,16 @@ import 'dart:math' as math; import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdfrx/pdfrx.dart'; -import '../../../../data/services/providers.dart'; +import '../../../../data/services/export_providers.dart'; import '../../../../data/model/model.dart'; import '../view_model/view_model.dart'; -import '../../preferences/providers.dart'; -import 'signature_drawer.dart'; +import '../../../../data/services/preferences_providers.dart'; +import 'signature_drag_data.dart'; import 'image_editor_dialog.dart'; class PdfPageArea extends ConsumerStatefulWidget { @@ -332,25 +333,10 @@ class _PdfPageAreaState extends ConsumerState { final target = _pendingPage ?? pdf.currentPage; _pendingPage = null; _scrollRetryCount = 0; - _programmaticTargetPage = target; - controller.goToPage(pageNumber: target, anchor: PdfPageAnchor.top); - // Fallback: if the viewer doesn't emit onPageChanged (e.g., already at target), - // ensure we don't keep blocking provider-driven jumps. + // Defer navigation to the next frame to ensure controller state is fully ready. WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - if (_programmaticTargetPage == target) { - _programmaticTargetPage = null; - } - }); - }); - // Also ensure a scroll attempt is queued in case current state suppressed earlier. - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - if (_visiblePage != ref.read(pdfProvider).currentPage) { - _scrollToPage(ref.read(pdfProvider).currentPage); - } + _scrollToPage(target); }); }, onPageChanged: (n) { @@ -393,6 +379,13 @@ class _PdfPageAreaState extends ConsumerState { // Assume drop targets the current visible page; compute relative center final cx = (local.dx / size.width) * widget.pageSize.width; final cy = (local.dy / size.height) * widget.pageSize.height; + final data = details.data; + if (data is SignatureDragData && data.assetId != null) { + // Set current overlay to use this asset + ref + .read(signatureProvider.notifier) + .setImageFromLibrary(assetId: data.assetId!); + } ref.read(signatureProvider.notifier).placeAtCenter(Offset(cx, cy)); ref .read(pdfProvider.notifier) @@ -558,10 +551,32 @@ class _PdfPageAreaState extends ConsumerState { children: [ Consumer( builder: (context, ref, _) { - final processed = ref.watch( - processedSignatureImageProvider, - ); - final bytes = processed ?? sig.imageBytes; + Uint8List? bytes; + if (interactive) { + final processed = ref.watch( + processedSignatureImageProvider, + ); + bytes = processed ?? sig.imageBytes; + } else if (placedIndex != null) { + // Use the image assigned to this placement + final imgId = ref + .read(pdfProvider) + .placementImageByPage[pageNumber] + ?.elementAt(placedIndex); + if (imgId != null) { + final lib = ref.watch(signatureLibraryProvider); + for (final a in lib) { + if (a.id == imgId) { + bytes = a.bytes; + break; + } + } + } + // Fallback to current processed + bytes ??= + ref.read(processedSignatureImageProvider) ?? + sig.imageBytes; + } if (bytes == null) { return Center( child: Text( diff --git a/lib/ui/features/pdf/widgets/pdf_pages_overview.dart b/lib/ui/features/pdf/widgets/pdf_pages_overview.dart index 5bfa2f7..e14b337 100644 --- a/lib/ui/features/pdf/widgets/pdf_pages_overview.dart +++ b/lib/ui/features/pdf/widgets/pdf_pages_overview.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdfrx/pdfrx.dart'; -import '../../../../data/services/providers.dart'; +import '../../../../data/services/export_providers.dart'; import '../view_model/view_model.dart'; class PdfPagesOverview extends ConsumerWidget { diff --git a/lib/ui/features/pdf/widgets/pdf_screen.dart b/lib/ui/features/pdf/widgets/pdf_screen.dart index 94ca433..ca67bc9 100644 --- a/lib/ui/features/pdf/widgets/pdf_screen.dart +++ b/lib/ui/features/pdf/widgets/pdf_screen.dart @@ -7,13 +7,13 @@ import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:printing/printing.dart' as printing; import 'package:pdfrx/pdfrx.dart'; -import '../../../../data/services/providers.dart'; +import '../../../../data/services/export_providers.dart'; import '../view_model/view_model.dart'; import 'draw_canvas.dart'; import 'pdf_toolbar.dart'; import 'pdf_page_area.dart'; -import 'pdf_pages_overview.dart'; -import 'signature_drawer.dart'; +import 'pages_sidebar.dart'; +import 'signatures_sidebar.dart'; // adjustments are available via ImageEditorDialog class PdfSignatureHomePage extends ConsumerStatefulWidget { @@ -30,6 +30,7 @@ class _PdfSignatureHomePageState extends ConsumerState { bool _showPagesSidebar = true; bool _showSignaturesSidebar = true; int _zoomLevel = 100; // percentage for display only + // No split view controller; using a simple Row layout. // Exposed for tests to trigger the invalid-file SnackBar without UI. @visibleForTesting @@ -58,13 +59,13 @@ class _PdfSignatureHomePageState extends ConsumerState { // Zoom is managed by pdfrx viewer (Ctrl +/- etc.). No custom zoom here. - Future _loadSignatureFromFile() async { + Future _loadSignatureFromFile() async { final typeGroup = const fs.XTypeGroup( label: 'Image', extensions: ['png', 'jpg', 'jpeg', 'webp'], ); final file = await fs.openFile(acceptedTypeGroups: [typeGroup]); - if (file == null) return; + if (file == null) return null; final bytes = await file.readAsBytes(); final sig = ref.read(signatureProvider.notifier); sig.setImageBytes(bytes); @@ -72,6 +73,7 @@ class _PdfSignatureHomePageState extends ConsumerState { if (p.loaded) { ref.read(pdfProvider.notifier).setSignedPage(p.currentPage); } + return bytes; } // _createNewSignature was removed as the toolbar no longer exposes this action. @@ -92,7 +94,7 @@ class _PdfSignatureHomePageState extends ConsumerState { ref.read(pdfProvider.notifier).selectPlacement(index); } - Future _openDrawCanvas() async { + Future _openDrawCanvas() async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, @@ -106,6 +108,7 @@ class _PdfSignatureHomePageState extends ConsumerState { ref.read(pdfProvider.notifier).setSignedPage(p.currentPage); } } + return result; } Future _saveSignedPdf() async { @@ -138,6 +141,10 @@ class _PdfSignatureHomePageState extends ConsumerState { uiPageSize: SignatureController.pageSize, signatureImageBytes: processed ?? sig.imageBytes, placementsByPage: pdf.placementsByPage, + placementImageByPage: pdf.placementImageByPage, + libraryBytes: { + for (final a in ref.read(signatureLibraryProvider)) a.id: a.bytes, + }, targetDpi: targetDpi, ); if (bytes != null) { @@ -167,6 +174,10 @@ class _PdfSignatureHomePageState extends ConsumerState { uiPageSize: SignatureController.pageSize, signatureImageBytes: processed ?? sig.imageBytes, placementsByPage: pdf.placementsByPage, + placementImageByPage: pdf.placementImageByPage, + libraryBytes: { + for (final a in ref.read(signatureLibraryProvider)) a.id: a.bytes, + }, targetDpi: targetDpi, ); if (useMock) { @@ -190,6 +201,11 @@ class _PdfSignatureHomePageState extends ConsumerState { uiPageSize: SignatureController.pageSize, signatureImageBytes: processed ?? sig.imageBytes, placementsByPage: pdf.placementsByPage, + placementImageByPage: pdf.placementImageByPage, + libraryBytes: { + for (final a in ref.read(signatureLibraryProvider)) + a.id: a.bytes, + }, targetDpi: targetDpi, ); } @@ -236,10 +252,7 @@ class _PdfSignatureHomePageState extends ConsumerState { return name; } - @override - void dispose() { - super.dispose(); - } + // No initState/dispose needed for a controller. @override Widget build(BuildContext context) { @@ -273,7 +286,7 @@ class _PdfSignatureHomePageState extends ConsumerState { _zoomLevel = (_zoomLevel + 10).clamp(10, 800); }); }, - // zoomLevel omitted to avoid compact overflows in tight tests + zoomLevel: _zoomLevel, fileName: ref.watch(pdfProvider).pickedPdfPath, showPagesSidebar: _showPagesSidebar, showSignaturesSidebar: _showSignaturesSidebar, @@ -289,74 +302,37 @@ class _PdfSignatureHomePageState extends ConsumerState { const SizedBox(height: 8), Expanded( child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_showPagesSidebar) - ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 140, - maxWidth: 180, - ), - child: Card( - margin: EdgeInsets.zero, - child: const PdfPagesOverview(), - ), - ), - if (_showPagesSidebar) const SizedBox(width: 12), + const SizedBox(width: 160, child: PagesSidebar()), Expanded( child: AbsorbPointer( absorbing: isExporting, - child: PdfPageArea( - pageSize: _pageSize, - viewerController: _viewerController, - onDragSignature: _onDragSignature, - onResizeSignature: _onResizeSignature, - onConfirmSignature: _confirmSignature, - onClearActiveOverlay: - () => - ref - .read(signatureProvider.notifier) - .clearActiveOverlay(), - onSelectPlaced: _onSelectPlaced, + child: RepaintBoundary( + child: PdfPageArea( + key: const ValueKey('pdf_page_area'), + pageSize: _pageSize, + viewerController: _viewerController, + onDragSignature: _onDragSignature, + onResizeSignature: _onResizeSignature, + onConfirmSignature: _confirmSignature, + onClearActiveOverlay: + () => + ref + .read(signatureProvider.notifier) + .clearActiveOverlay(), + onSelectPlaced: _onSelectPlaced, + ), ), ), ), - if (_showSignaturesSidebar) const SizedBox(width: 12), if (_showSignaturesSidebar) - ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 140, - maxWidth: 250, - ), - child: AbsorbPointer( - absorbing: isExporting, - child: Card( - margin: EdgeInsets.zero, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: SingleChildScrollView( - child: SignatureDrawer( - disabled: isExporting, - onLoadSignatureFromFile: - _loadSignatureFromFile, - onOpenDrawCanvas: _openDrawCanvas, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(12), - child: ElevatedButton( - key: const Key('btn_save_pdf'), - onPressed: - isExporting ? null : _saveSignedPdf, - child: Text(l.saveSignedPdf), - ), - ), - ], - ), - ), + SizedBox( + width: 220, + child: SignaturesSidebar( + onLoadSignatureFromFile: _loadSignatureFromFile, + onOpenDrawCanvas: _openDrawCanvas, + onSave: _saveSignedPdf, ), ), ], diff --git a/lib/ui/features/pdf/widgets/pdf_toolbar.dart b/lib/ui/features/pdf/widgets/pdf_toolbar.dart index bb450bd..9d18b6f 100644 --- a/lib/ui/features/pdf/widgets/pdf_toolbar.dart +++ b/lib/ui/features/pdf/widgets/pdf_toolbar.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; -import '../../../../data/services/providers.dart'; +import '../../../../data/services/export_providers.dart'; import '../view_model/view_model.dart'; class PdfToolbar extends ConsumerStatefulWidget { @@ -94,8 +94,8 @@ class _PdfToolbarState extends ConsumerState { ), ), if (pdf.loaded) ...[ - Row( - mainAxisSize: MainAxisSize.min, + Wrap( + spacing: 8, children: [ IconButton( key: const Key('btn_prev'), @@ -156,25 +156,19 @@ class _PdfToolbarState extends ConsumerState { onPressed: widget.disabled ? null : widget.onZoomOut, icon: const Icon(Icons.zoom_out), ), + Text( + //if not null + widget.zoomLevel != null ? '${widget.zoomLevel}%' : '', + style: const TextStyle(fontSize: 12), + ), IconButton( key: const Key('btn_zoom_in'), tooltip: 'Zoom in', onPressed: widget.disabled ? null : widget.onZoomIn, icon: const Icon(Icons.zoom_in), ), - if (!compact && widget.zoomLevel != null) ...[ - const SizedBox(width: 6), - // show zoom ratio - Text( - '${widget.zoomLevel}%', - style: const TextStyle(fontSize: 12), - ), - ], - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ + SizedBox(width: 6), + // show zoom ratio Text(l.dpi), const SizedBox(width: 8), DropdownButton( diff --git a/lib/ui/features/pdf/widgets/signature_card.dart b/lib/ui/features/pdf/widgets/signature_card.dart new file mode 100644 index 0000000..8e2537f --- /dev/null +++ b/lib/ui/features/pdf/widgets/signature_card.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import '../view_model/view_model.dart'; +import 'signature_drag_data.dart'; + +class SignatureCard extends StatelessWidget { + const SignatureCard({ + super.key, + required this.asset, + required this.disabled, + required this.onDelete, + this.onTap, + this.onAdjust, + this.useCurrentBytesForDrag = false, + }); + final SignatureAsset asset; + final bool disabled; + final VoidCallback onDelete; + final VoidCallback? onTap; + final VoidCallback? onAdjust; + final bool useCurrentBytesForDrag; + + @override + Widget build(BuildContext context) { + final img = Image.memory(asset.bytes, fit: BoxFit.contain); + Widget base = SizedBox( + width: 96, + height: 64, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Stack( + children: [ + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: Theme.of(context).dividerColor), + borderRadius: BorderRadius.circular(8), + ), + child: Padding(padding: const EdgeInsets.all(6), child: img), + ), + ), + Positioned( + right: 0, + top: 0, + child: IconButton( + icon: const Icon(Icons.close, size: 16), + onPressed: disabled ? null : onDelete, + tooltip: 'Remove', + padding: const EdgeInsets.all(2), + ), + ), + ], + ), + ), + ); + Widget child = onTap != null ? InkWell(onTap: onTap, child: base) : base; + // Add context menu for adjust/delete on right-click or long-press + child = GestureDetector( + key: const Key('gd_signature_card_area'), + behavior: HitTestBehavior.opaque, + onSecondaryTapDown: + disabled + ? null + : (details) async { + final selected = await showMenu( + context: context, + position: RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + details.globalPosition.dy, + ), + items: const [ + PopupMenuItem( + key: Key('mi_signature_adjust'), + value: 'adjust', + child: Text('Adjust graphic'), + ), + PopupMenuItem( + key: Key('mi_signature_delete'), + value: 'delete', + child: Text('Delete'), + ), + ], + ); + if (selected == 'adjust') { + onAdjust?.call(); + } else if (selected == 'delete') { + onDelete(); + } + }, + onLongPressStart: + disabled + ? null + : (details) async { + final selected = await showMenu( + context: context, + position: RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + details.globalPosition.dy, + ), + items: const [ + PopupMenuItem( + key: Key('mi_signature_adjust'), + value: 'adjust', + child: Text('Adjust graphic'), + ), + PopupMenuItem( + key: Key('mi_signature_delete'), + value: 'delete', + child: Text('Delete'), + ), + ], + ); + if (selected == 'adjust') { + onAdjust?.call(); + } else if (selected == 'delete') { + onDelete(); + } + }, + child: child, + ); + if (disabled) return child; + return Draggable( + data: + useCurrentBytesForDrag + ? const SignatureDragData() + : SignatureDragData(assetId: asset.id), + feedback: Opacity( + opacity: 0.9, + child: ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 160, height: 100), + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + boxShadow: const [ + BoxShadow(blurRadius: 8, color: Colors.black26), + ], + ), + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Image.memory(asset.bytes, fit: BoxFit.contain), + ), + ), + ), + ), + childWhenDragging: Opacity(opacity: 0.5, child: child), + child: child, + ); + } +} diff --git a/lib/ui/features/pdf/widgets/signature_drag_data.dart b/lib/ui/features/pdf/widgets/signature_drag_data.dart new file mode 100644 index 0000000..c972698 --- /dev/null +++ b/lib/ui/features/pdf/widgets/signature_drag_data.dart @@ -0,0 +1,4 @@ +class SignatureDragData { + final String? assetId; // null means use current processed signature + const SignatureDragData({this.assetId}); +} diff --git a/lib/ui/features/pdf/widgets/signature_drawer.dart b/lib/ui/features/pdf/widgets/signature_drawer.dart index b16c1e1..f39ef66 100644 --- a/lib/ui/features/pdf/widgets/signature_drawer.dart +++ b/lib/ui/features/pdf/widgets/signature_drawer.dart @@ -3,14 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; -import '../../../../data/services/providers.dart'; +import '../../../../data/services/export_providers.dart'; import '../view_model/view_model.dart'; import 'image_editor_dialog.dart'; +import 'signature_card.dart'; -/// Data passed when dragging a signature card. -class SignatureDragData { - const SignatureDragData(); -} +/// Data for drag-and-drop is in signature_drag_data.dart class SignatureDrawer extends ConsumerStatefulWidget { const SignatureDrawer({ @@ -21,121 +19,107 @@ class SignatureDrawer extends ConsumerStatefulWidget { }); final bool disabled; - final VoidCallback onLoadSignatureFromFile; - final VoidCallback onOpenDrawCanvas; + // Return the loaded bytes (if any) so we can add the exact image to the library immediately. + final Future Function() onLoadSignatureFromFile; + // Return the drawn bytes (if any) so we can add it to the library immediately. + final Future Function() onOpenDrawCanvas; @override ConsumerState createState() => _SignatureDrawerState(); } class _SignatureDrawerState extends ConsumerState { - Future _openSignatureMenuAt(Offset globalPosition) async { - final l = AppLocalizations.of(context); - final selected = await showMenu( - context: context, - position: RelativeRect.fromLTRB( - globalPosition.dx, - globalPosition.dy, - globalPosition.dx, - globalPosition.dy, - ), - items: [ - PopupMenuItem( - key: const Key('mi_signature_delete'), - value: 'delete', - child: Text(l.delete), - ), - PopupMenuItem( - key: const Key('mi_signature_adjust'), - value: 'adjust', - child: const Text('Adjust graphic'), - ), - ], - ); - - switch (selected) { - case 'delete': - ref.read(signatureProvider.notifier).clearActiveOverlay(); - ref.read(signatureProvider.notifier).clearImage(); - break; - case 'adjust': - if (!mounted) return; - // Open ImageEditorDialog - await showDialog( - context: context, - builder: (_) => const ImageEditorDialog(), - ); - break; - default: - break; - } - } - @override Widget build(BuildContext context) { final l = AppLocalizations.of(context); final sig = ref.watch(signatureProvider); final processed = ref.watch(processedSignatureImageProvider); final bytes = processed ?? sig.imageBytes; + final library = ref.watch(signatureLibraryProvider); final isExporting = ref.watch(exportingProvider); final disabled = widget.disabled || isExporting; - return Card( - margin: EdgeInsets.zero, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Header - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), - child: Text( - l.signature, - style: Theme.of(context).textTheme.titleSmall, - ), - ), - // Existing signature card (draggable when bytes available) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all(color: Theme.of(context).dividerColor), - borderRadius: BorderRadius.circular(8), - ), - child: GestureDetector( - key: const Key('gd_signature_card_area'), - behavior: HitTestBehavior.opaque, - onSecondaryTapDown: (details) { - if (bytes != null && !disabled) { - _openSignatureMenuAt(details.globalPosition); - } - }, - onLongPressStart: (details) { - if (bytes != null && !disabled) { - _openSignatureMenuAt(details.globalPosition); - } - }, - child: SizedBox( - height: 120, - child: - bytes == null - ? Center( - child: Text( - l.noPdfLoaded, - textAlign: TextAlign.center, - ), - ) - : _DraggableSignaturePreview( - bytes: bytes, - disabled: disabled, - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (library.isNotEmpty) ...[ + for (final a in library) ...[ + Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(12), + child: SignatureCard( + key: ValueKey('sig_card_${a.id}'), + asset: a, + disabled: disabled, + onDelete: + () => ref + .read(signatureLibraryProvider.notifier) + .remove(a.id), + onAdjust: () async { + ref + .read(signatureProvider.notifier) + .setImageFromLibrary(assetId: a.id); + if (!mounted) return; + await showDialog( + context: context, + builder: (_) => const ImageEditorDialog(), + ); + }, + 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); + } + }, ), ), ), + const SizedBox(height: 12), + ], + ], + if (library.isEmpty) + Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(12), + child: + bytes == null + ? Text(l.noSignatureLoaded) + : SignatureCard( + asset: SignatureAsset(id: '', bytes: bytes, name: ''), + disabled: disabled, + useCurrentBytesForDrag: true, + onDelete: () { + ref + .read(signatureProvider.notifier) + .clearActiveOverlay(); + ref.read(signatureProvider.notifier).clearImage(); + }, + onAdjust: () async { + if (!mounted) return; + await showDialog( + context: context, + builder: (_) => const ImageEditorDialog(), + ); + }, + ), + ), ), - const SizedBox(height: 12), - const Divider(height: 1), - // New signature card - Padding( + Card( + margin: EdgeInsets.zero, + child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -152,13 +136,41 @@ class _SignatureDrawerState extends ConsumerState { OutlinedButton.icon( key: const Key('btn_drawer_load_signature'), onPressed: - disabled ? null : widget.onLoadSignatureFromFile, + disabled + ? null + : () async { + final loaded = + await widget.onLoadSignatureFromFile(); + final b = + loaded ?? + ref.read(processedSignatureImageProvider) ?? + ref.read(signatureProvider).imageBytes; + if (b != null) { + ref + .read(signatureLibraryProvider.notifier) + .add(b, name: 'image'); + } + }, icon: const Icon(Icons.image_outlined), label: Text(l.loadSignatureFromFile), ), OutlinedButton.icon( key: const Key('btn_drawer_draw_signature'), - onPressed: disabled ? null : widget.onOpenDrawCanvas, + onPressed: + disabled + ? null + : () async { + final drawn = await widget.onOpenDrawCanvas(); + final b = + drawn ?? + ref.read(processedSignatureImageProvider) ?? + ref.read(signatureProvider).imageBytes; + if (b != null) { + ref + .read(signatureLibraryProvider.notifier) + .add(b, name: 'drawing'); + } + }, icon: const Icon(Icons.gesture), label: Text(l.drawSignature), ), @@ -167,51 +179,8 @@ class _SignatureDrawerState extends ConsumerState { ], ), ), - // Adjustments are accessed via "Adjust graphic" in the popup menu - ], - ), - ); - } -} - -class _DraggableSignaturePreview extends StatelessWidget { - const _DraggableSignaturePreview({ - required this.bytes, - required this.disabled, - }); - final Uint8List bytes; - final bool disabled; - - @override - Widget build(BuildContext context) { - final child = Padding( - padding: const EdgeInsets.all(8.0), - child: Image.memory(bytes, fit: BoxFit.contain), - ); - if (disabled) return child; - return Draggable( - data: const SignatureDragData(), - feedback: Opacity( - opacity: 0.8, - child: ConstrainedBox( - constraints: const BoxConstraints.tightFor(width: 160, height: 80), - child: DecoratedBox( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(6), - boxShadow: const [ - BoxShadow(blurRadius: 8, color: Colors.black26), - ], - ), - child: Padding( - padding: const EdgeInsets.all(6.0), - child: Image.memory(bytes, fit: BoxFit.contain), - ), - ), ), - ), - childWhenDragging: Opacity(opacity: 0.5, child: child), - child: child, + ], ); } } diff --git a/lib/ui/features/pdf/widgets/signatures_sidebar.dart b/lib/ui/features/pdf/widgets/signatures_sidebar.dart new file mode 100644 index 0000000..3398570 --- /dev/null +++ b/lib/ui/features/pdf/widgets/signatures_sidebar.dart @@ -0,0 +1,54 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:pdf_signature/l10n/app_localizations.dart'; + +import '../../../../data/services/export_providers.dart'; +import 'signature_drawer.dart'; + +class SignaturesSidebar extends ConsumerWidget { + const SignaturesSidebar({ + super.key, + required this.onLoadSignatureFromFile, + required this.onOpenDrawCanvas, + required this.onSave, + }); + + final Future Function() onLoadSignatureFromFile; + final Future Function() onOpenDrawCanvas; + final VoidCallback onSave; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l = AppLocalizations.of(context); + final isExporting = ref.watch(exportingProvider); + return AbsorbPointer( + absorbing: isExporting, + child: Card( + margin: EdgeInsets.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: SingleChildScrollView( + child: SignatureDrawer( + disabled: isExporting, + onLoadSignatureFromFile: onLoadSignatureFromFile, + onOpenDrawCanvas: onOpenDrawCanvas, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(12), + child: ElevatedButton( + key: const Key('btn_save_pdf'), + onPressed: isExporting ? null : onSave, + child: Text(l.saveSignedPdf), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/features/preferences/widgets/settings_screen.dart b/lib/ui/features/preferences/widgets/settings_screen.dart index 9a06358..9a453e9 100644 --- a/lib/ui/features/preferences/widgets/settings_screen.dart +++ b/lib/ui/features/preferences/widgets/settings_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; -import '../providers.dart'; +import '../../../../data/services/preferences_providers.dart'; class SettingsDialog extends ConsumerStatefulWidget { const SettingsDialog({super.key}); diff --git a/test/widget/export_flow_test.dart b/test/widget/export_flow_test.dart index f47fab5..6d535c2 100644 --- a/test/widget/export_flow_test.dart +++ b/test/widget/export_flow_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/data/services/export_service.dart'; -import 'package:pdf_signature/data/services/providers.dart'; +import 'package:pdf_signature/data/services/export_providers.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; diff --git a/test/widget/helpers.dart b/test/widget/helpers.dart index cdffd07..d93221d 100644 --- a/test/widget/helpers.dart +++ b/test/widget/helpers.dart @@ -6,9 +6,9 @@ import 'dart:typed_data'; 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/providers.dart'; +import 'package:pdf_signature/data/services/export_providers.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; -import 'package:pdf_signature/ui/features/preferences/providers.dart'; +import 'package:pdf_signature/data/services/preferences_providers.dart'; Future pumpWithOpenPdf(WidgetTester tester) async { await tester.pumpWidget( diff --git a/test/widget/pdf_navigation_widget_test.dart b/test/widget/pdf_navigation_widget_test.dart index c37ac2c..6f9ccc9 100644 --- a/test/widget/pdf_navigation_widget_test.dart +++ b/test/widget/pdf_navigation_widget_test.dart @@ -5,7 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; 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/model/model.dart'; -import 'package:pdf_signature/data/services/providers.dart'; +import 'package:pdf_signature/data/services/export_providers.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; class _TestPdfController extends PdfController { diff --git a/test/widget/pdf_page_area_early_jump_test.dart b/test/widget/pdf_page_area_early_jump_test.dart index 2f93983..93f99d2 100644 --- a/test/widget/pdf_page_area_early_jump_test.dart +++ b/test/widget/pdf_page_area_early_jump_test.dart @@ -4,10 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart'; -import 'package:pdf_signature/data/services/providers.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/ui/features/preferences/providers.dart'; +import 'package:pdf_signature/data/services/preferences_providers.dart'; class _TestPdfController extends PdfController { _TestPdfController() : super() { diff --git a/test/widget/pdf_page_area_jump_test.dart b/test/widget/pdf_page_area_jump_test.dart index dfc2245..108e3d4 100644 --- a/test/widget/pdf_page_area_jump_test.dart +++ b/test/widget/pdf_page_area_jump_test.dart @@ -4,10 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart'; -import 'package:pdf_signature/data/services/providers.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/ui/features/preferences/providers.dart'; +import 'package:pdf_signature/data/services/preferences_providers.dart'; class _TestPdfController extends PdfController { _TestPdfController() : super() {