Compare commits
3 Commits
7336ca4d57
...
8f3039f99e
| Author | SHA1 | Date |
|---|---|---|
|
|
8f3039f99e | |
|
|
461c8f6ae5 | |
|
|
5549f08b4c |
|
|
@ -3,3 +3,4 @@
|
|||
* support multiple platforms (windows, linux, android, web)
|
||||
* only FOSS libs can use
|
||||
* should not exceed 350 lines of code per file
|
||||
* Direct Passing is better than Singleton(e.g.Provider) especially for `view`, `viewModel`.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'dart:io';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
|
||||
import 'package:pdf_signature/data/services/export_service.dart';
|
||||
|
||||
|
|
@ -13,7 +14,6 @@ import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
|||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pages_sidebar.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
|
|
@ -50,17 +50,23 @@ void main() {
|
|||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => DocumentStateNotifier()..openPicked(pageCount: 3),
|
||||
),
|
||||
useMockViewerProvider.overrideWith((ref) => false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
exportServiceProvider.overrideWith((_) => fake),
|
||||
savePathPickerProvider.overrideWith(
|
||||
(_) => () async => 'C:/tmp/output.pdf',
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -120,13 +126,19 @@ void main() {
|
|||
cardRepo.addWithAsset(asset, 0.0);
|
||||
return cardRepo;
|
||||
}),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -145,17 +157,18 @@ void main() {
|
|||
// 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 r = container.read(activeRectProvider)!;
|
||||
final r = container.read(pdfViewModelProvider).activeRect!;
|
||||
final lib = container.read(signatureAssetRepositoryProvider);
|
||||
final asset = lib.isNotEmpty ? lib.first : null;
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(page: currentPage, rect: r, asset: asset);
|
||||
// Clear active overlay by hiding signatures temporarily
|
||||
container.read(signatureVisibilityProvider.notifier).state = false;
|
||||
// Note: signatureVisibilityProvider was removed in migration
|
||||
// container.read(signatureVisibilityProvider.notifier).state = false;
|
||||
await tester.pump();
|
||||
container.read(signatureVisibilityProvider.notifier).state = true;
|
||||
// container.read(signatureVisibilityProvider.notifier).state = true;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final placed = find.byKey(const Key('placed_signature_0'));
|
||||
|
|
@ -192,13 +205,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -232,13 +251,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -275,13 +300,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -321,13 +352,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pages_sidebar.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
|
@ -37,13 +37,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -85,13 +91,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -133,13 +145,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -182,13 +200,19 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 3, bytes: pdfBytes),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(false),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: false),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale('en'),
|
||||
home: PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile('test.pdf'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
66
lib/app.dart
66
lib/app.dart
|
|
@ -2,11 +2,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
||||
import 'data/repositories/preferences_repository.dart';
|
||||
import 'package:pdf_signature/routing/router.dart';
|
||||
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
||||
import 'data/repositories/preferences_repository.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
|
@ -42,7 +40,7 @@ class MyApp extends StatelessWidget {
|
|||
data: (_) {
|
||||
final themeMode = ref.watch(themeModeProvider);
|
||||
final appLocale = ref.watch(localeProvider);
|
||||
return MaterialApp(
|
||||
return MaterialApp.router(
|
||||
onGenerateTitle: (ctx) => AppLocalizations.of(ctx).appTitle,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
|
|
@ -63,27 +61,32 @@ class MyApp extends StatelessWidget {
|
|||
...AppLocalizations.localizationsDelegates,
|
||||
LocaleNamesLocalizationsDelegate(),
|
||||
],
|
||||
home: Builder(
|
||||
builder:
|
||||
(ctx) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(ctx).appTitle),
|
||||
actions: [
|
||||
OutlinedButton.icon(
|
||||
key: const Key('btn_appbar_settings'),
|
||||
icon: const Icon(Icons.settings),
|
||||
label: Text(AppLocalizations.of(ctx).settings),
|
||||
onPressed:
|
||||
() => showDialog<bool>(
|
||||
context: ctx,
|
||||
builder: (_) => const SettingsDialog(),
|
||||
),
|
||||
),
|
||||
],
|
||||
routerConfig: ref.watch(routerProvider),
|
||||
builder: (context, child) {
|
||||
final router = ref.watch(routerProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context).appTitle),
|
||||
actions: [
|
||||
OutlinedButton.icon(
|
||||
key: const Key('btn_appbar_settings'),
|
||||
icon: const Icon(Icons.settings),
|
||||
label: Text(AppLocalizations.of(context).settings),
|
||||
onPressed:
|
||||
() => showDialog<bool>(
|
||||
context:
|
||||
router
|
||||
.routerDelegate
|
||||
.navigatorKey
|
||||
.currentContext!,
|
||||
builder: (_) => const SettingsDialog(),
|
||||
),
|
||||
),
|
||||
body: const _RootHomeSwitcher(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -92,16 +95,3 @@ class MyApp extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RootHomeSwitcher extends ConsumerWidget {
|
||||
const _RootHomeSwitcher();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
if (!pdf.loaded) {
|
||||
return const WelcomeScreen();
|
||||
}
|
||||
return const PdfSignatureHomePage();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,21 +12,27 @@ class DocumentStateNotifier extends StateNotifier<Document> {
|
|||
|
||||
@visibleForTesting
|
||||
void openSample() {
|
||||
state = state.copyWith(loaded: true, pageCount: 5, placementsByPage: {});
|
||||
state = state.copyWith(
|
||||
loaded: true,
|
||||
pageCount: 5,
|
||||
pickedPdfBytes: null,
|
||||
placementsByPage: <int, List<SignaturePlacement>>{},
|
||||
);
|
||||
}
|
||||
|
||||
void openPicked({
|
||||
required int pageCount,
|
||||
Uint8List? bytes,
|
||||
}) {
|
||||
void openPicked({required int pageCount, Uint8List? bytes}) {
|
||||
state = state.copyWith(
|
||||
loaded: true,
|
||||
pageCount: pageCount,
|
||||
pickedPdfBytes: bytes,
|
||||
placementsByPage: {},
|
||||
placementsByPage: <int, List<SignaturePlacement>>{},
|
||||
);
|
||||
}
|
||||
|
||||
void close() {
|
||||
state = Document.initial();
|
||||
}
|
||||
|
||||
void setPageCount(int count) {
|
||||
if (!state.loaded) return;
|
||||
state = state.copyWith(pageCount: count.clamp(1, 9999));
|
||||
|
|
|
|||
|
|
@ -3,23 +3,26 @@ import 'signature_placement.dart';
|
|||
|
||||
/// PDF document to be signed
|
||||
class Document {
|
||||
final bool loaded;
|
||||
final int pageCount;
|
||||
final Uint8List? pickedPdfBytes;
|
||||
bool loaded;
|
||||
int pageCount;
|
||||
Uint8List? pickedPdfBytes;
|
||||
// Multiple signature placements per page, each combines geometry and asset.
|
||||
final Map<int, List<SignaturePlacement>> placementsByPage;
|
||||
const Document({
|
||||
Map<int, List<SignaturePlacement>> placementsByPage;
|
||||
|
||||
Document({
|
||||
required this.loaded,
|
||||
required this.pageCount,
|
||||
this.pickedPdfBytes,
|
||||
this.placementsByPage = const {},
|
||||
});
|
||||
factory Document.initial() => const Document(
|
||||
Map<int, List<SignaturePlacement>>? placementsByPage,
|
||||
}) : placementsByPage = placementsByPage ?? <int, List<SignaturePlacement>>{};
|
||||
|
||||
factory Document.initial() => Document(
|
||||
loaded: false,
|
||||
pageCount: 0,
|
||||
pickedPdfBytes: null,
|
||||
placementsByPage: {},
|
||||
placementsByPage: <int, List<SignaturePlacement>>{},
|
||||
);
|
||||
|
||||
Document copyWith({
|
||||
bool? loaded,
|
||||
int? pageCount,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
|
||||
class PdfManager {
|
||||
final DocumentStateNotifier _documentNotifier;
|
||||
final SignatureCardStateNotifier _signatureCardNotifier;
|
||||
final GoRouter _router;
|
||||
|
||||
fs.XFile _currentFile = fs.XFile('');
|
||||
|
||||
PdfManager({
|
||||
required DocumentStateNotifier documentNotifier,
|
||||
required SignatureCardStateNotifier signatureCardNotifier,
|
||||
required GoRouter router,
|
||||
}) : _documentNotifier = documentNotifier,
|
||||
_signatureCardNotifier = signatureCardNotifier,
|
||||
_router = router;
|
||||
|
||||
fs.XFile get currentFile => _currentFile;
|
||||
|
||||
Future<void> openPdf({String? path, Uint8List? bytes}) async {
|
||||
int pageCount = 1; // default
|
||||
if (bytes != null) {
|
||||
try {
|
||||
final doc = await PdfDocument.openData(bytes);
|
||||
pageCount = doc.pages.length;
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Update file reference if path is provided
|
||||
if (path != null) {
|
||||
_currentFile = fs.XFile(path);
|
||||
}
|
||||
|
||||
_documentNotifier.openPicked(pageCount: pageCount, bytes: bytes);
|
||||
_signatureCardNotifier.clearAll();
|
||||
|
||||
// Navigate to PDF screen after successfully opening PDF
|
||||
_router.go('/pdf');
|
||||
}
|
||||
|
||||
void closePdf() {
|
||||
_documentNotifier.close();
|
||||
_signatureCardNotifier.clearAll();
|
||||
_currentFile = fs.XFile('');
|
||||
|
||||
// Navigate back to welcome screen when closing PDF
|
||||
_router.go('/');
|
||||
}
|
||||
|
||||
Future<void> pickAndOpenPdf() async {
|
||||
final typeGroup = const fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
|
||||
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||
if (file != null) {
|
||||
Uint8List? bytes;
|
||||
try {
|
||||
bytes = await file.readAsBytes();
|
||||
} catch (_) {
|
||||
bytes = null;
|
||||
}
|
||||
await openPdf(path: file.path, bytes: bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final routerProvider = Provider<GoRouter>((ref) {
|
||||
// Create PdfManager instance with dependencies
|
||||
final documentNotifier = ref.read(documentRepositoryProvider.notifier);
|
||||
final signatureCardNotifier = ref.read(
|
||||
signatureCardRepositoryProvider.notifier,
|
||||
);
|
||||
|
||||
// Create a navigator key for the router
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
// Create a late variable for the router
|
||||
late final GoRouter router;
|
||||
|
||||
// Create PdfManager with router dependency (will be set after router creation)
|
||||
late final PdfManager pdfManager;
|
||||
|
||||
// If tests pre-load a document, start at /pdf so sidebars and controls
|
||||
// are present immediately.
|
||||
final initialLocation = documentNotifier.debugState.loaded ? '/pdf' : '/';
|
||||
|
||||
router = GoRouter(
|
||||
navigatorKey: navigatorKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder:
|
||||
(context, state) => WelcomeScreen(
|
||||
onPickPdf: () => pdfManager.pickAndOpenPdf(),
|
||||
onOpenPdf:
|
||||
({String? path, Uint8List? bytes, String? fileName}) =>
|
||||
pdfManager.openPdf(path: path, bytes: bytes),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/pdf',
|
||||
builder:
|
||||
(context, state) => PdfSignatureHomePage(
|
||||
onPickPdf: () => pdfManager.pickAndOpenPdf(),
|
||||
onClosePdf: () => pdfManager.closePdf(),
|
||||
currentFile: pdfManager.currentFile,
|
||||
),
|
||||
),
|
||||
],
|
||||
initialLocation: initialLocation,
|
||||
);
|
||||
|
||||
// Now create PdfManager with the router
|
||||
pdfManager = PdfManager(
|
||||
documentNotifier: documentNotifier,
|
||||
signatureCardNotifier: signatureCardNotifier,
|
||||
router: router,
|
||||
);
|
||||
|
||||
return router;
|
||||
});
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdfrx/pdfrx.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) => 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);
|
||||
|
||||
/// Whether resizing keeps the current aspect ratio for the active overlay
|
||||
final aspectLockedProvider = StateProvider<bool>((ref) => false);
|
||||
|
||||
/// Current active overlay rect (normalized 0..1) for the mock viewer.
|
||||
/// Integration tests can read this to confirm or compute placements.
|
||||
final activeRectProvider = StateProvider<Rect?>((ref) => null);
|
||||
|
||||
/// Exposes the PdfViewerController so toolbar / thumbnails can invoke navigation.
|
||||
/// It must be overridden at runtime by the hosting screen (e.g. `PdfSignatureHomePage`).
|
||||
// Default controller (can be overridden by a screen to ensure a stable instance within its subtree).
|
||||
final PdfViewerController _defaultPdfViewerController = PdfViewerController();
|
||||
final pdfViewerControllerProvider = Provider<PdfViewerController>((ref) {
|
||||
return _defaultPdfViewerController;
|
||||
});
|
||||
|
||||
/// Current page (1-based). Updated by PdfViewer via onPageChanged.
|
||||
final currentPageProvider = StateProvider<int>((ref) => 1);
|
||||
|
|
@ -6,15 +6,60 @@ import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
|||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
|
||||
class PdfViewModel extends StateNotifier<int> {
|
||||
class PdfViewModel extends ChangeNotifier {
|
||||
final Ref ref;
|
||||
PdfViewerController _controller = PdfViewerController();
|
||||
PdfViewerController get controller => _controller;
|
||||
int _currentPage = 1;
|
||||
late final bool _useMockViewer;
|
||||
|
||||
PdfViewModel(this.ref) : super(1);
|
||||
// Active rect for signature placement overlay
|
||||
Rect? _activeRect;
|
||||
Rect? get activeRect => _activeRect;
|
||||
set activeRect(Rect? value) {
|
||||
_activeRect = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Document get document => ref.read(documentRepositoryProvider);
|
||||
// const bool.fromEnvironment('FLUTTER_TEST', defaultValue: false);
|
||||
PdfViewModel(this.ref, {bool? useMockViewer})
|
||||
: _useMockViewer =
|
||||
useMockViewer ??
|
||||
bool.fromEnvironment('FLUTTER_TEST', defaultValue: false);
|
||||
|
||||
bool get useMockViewer => _useMockViewer;
|
||||
|
||||
int get currentPage => _currentPage;
|
||||
|
||||
set currentPage(int value) {
|
||||
_currentPage = value.clamp(1, document.pageCount);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Document get document => ref.watch(documentRepositoryProvider);
|
||||
|
||||
void jumpToPage(int page) {
|
||||
state = page.clamp(1, document.pageCount);
|
||||
currentPage = page;
|
||||
}
|
||||
|
||||
// Make this view model "int-like" for tests that compare it directly to an
|
||||
// integer or use it as a Map key for page lookups.
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is int) {
|
||||
return other == currentPage;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => currentPage.hashCode;
|
||||
|
||||
// Allow repositories to request a UI refresh without mutating provider state
|
||||
void notifyPlacementsChanged() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> openPdf({required String path, Uint8List? bytes}) async {
|
||||
|
|
@ -30,37 +75,125 @@ class PdfViewModel extends StateNotifier<int> {
|
|||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: pageCount, bytes: bytes);
|
||||
clearAllSignatureCards();
|
||||
|
||||
currentPage = 1; // Reset current page to 1
|
||||
}
|
||||
|
||||
// Document repository methods
|
||||
void closeDocument() {
|
||||
ref.read(documentRepositoryProvider.notifier).close();
|
||||
}
|
||||
|
||||
void setPageCount(int count) {
|
||||
ref.read(documentRepositoryProvider.notifier).setPageCount(count);
|
||||
}
|
||||
|
||||
void addPlacement({
|
||||
required int page,
|
||||
required Rect rect,
|
||||
SignatureAsset? asset,
|
||||
double rotationDeg = 0.0,
|
||||
GraphicAdjust? graphicAdjust,
|
||||
}) {
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
page: page,
|
||||
rect: rect,
|
||||
asset: asset,
|
||||
rotationDeg: rotationDeg,
|
||||
graphicAdjust: graphicAdjust,
|
||||
);
|
||||
}
|
||||
|
||||
void updatePlacementRotation({
|
||||
required int page,
|
||||
required int index,
|
||||
required double rotationDeg,
|
||||
}) {
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.updatePlacementRotation(
|
||||
page: page,
|
||||
index: index,
|
||||
rotationDeg: rotationDeg,
|
||||
);
|
||||
}
|
||||
|
||||
void removePlacement({required int page, required int index}) {
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.removePlacement(page: page, index: index);
|
||||
}
|
||||
|
||||
void updatePlacementRect({
|
||||
required int page,
|
||||
required int index,
|
||||
required Rect rect,
|
||||
}) {
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.updatePlacementRect(page: page, index: index, rect: rect);
|
||||
}
|
||||
|
||||
List<SignaturePlacement> placementsOn(int page) {
|
||||
return ref.read(documentRepositoryProvider.notifier).placementsOn(page);
|
||||
}
|
||||
|
||||
SignatureAsset? assetOfPlacement({required int page, required int index}) {
|
||||
return ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.assetOfPlacement(page: page, index: index);
|
||||
}
|
||||
|
||||
Future<void> exportDocument({
|
||||
required String outputPath,
|
||||
required Size uiPageSize,
|
||||
required Uint8List? signatureImageBytes,
|
||||
}) async {
|
||||
await ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.exportDocument(
|
||||
outputPath: outputPath,
|
||||
uiPageSize: uiPageSize,
|
||||
signatureImageBytes: signatureImageBytes,
|
||||
);
|
||||
}
|
||||
|
||||
// Signature card repository methods
|
||||
List<SignatureCard> get signatureCards =>
|
||||
ref.read(signatureCardRepositoryProvider);
|
||||
|
||||
void addSignatureCard(SignatureCard card) {
|
||||
ref.read(signatureCardRepositoryProvider.notifier).add(card);
|
||||
}
|
||||
|
||||
void addSignatureCardWithAsset(SignatureAsset asset, double rotationDeg) {
|
||||
ref
|
||||
.read(signatureCardRepositoryProvider.notifier)
|
||||
.addWithAsset(asset, rotationDeg);
|
||||
}
|
||||
|
||||
void updateSignatureCard(
|
||||
SignatureCard card,
|
||||
double? rotationDeg,
|
||||
GraphicAdjust? graphicAdjust,
|
||||
) {
|
||||
ref
|
||||
.read(signatureCardRepositoryProvider.notifier)
|
||||
.update(card, rotationDeg, graphicAdjust);
|
||||
}
|
||||
|
||||
void removeSignatureCard(SignatureCard card) {
|
||||
ref.read(signatureCardRepositoryProvider.notifier).remove(card);
|
||||
}
|
||||
|
||||
void clearAllSignatureCards() {
|
||||
ref.read(signatureCardRepositoryProvider.notifier).clearAll();
|
||||
state = 1; // Reset current page to 1
|
||||
}
|
||||
|
||||
Future<Uint8List?> loadSignatureFromFile() async {
|
||||
// This would need file picker, but since it's UI logic, perhaps keep in widget
|
||||
// For now, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
void confirmSignature() {
|
||||
// Need to implement based on original logic
|
||||
}
|
||||
|
||||
void onDragSignature(Offset delta) {
|
||||
// Implement drag
|
||||
}
|
||||
|
||||
void onResizeSignature(Offset delta) {
|
||||
// Implement resize
|
||||
}
|
||||
|
||||
void onSelectPlaced(int? index) {
|
||||
// ref.read(documentRepositoryProvider.notifier).selectPlacement(index);
|
||||
}
|
||||
|
||||
Future<void> saveSignedPdf() async {
|
||||
// Implement save logic
|
||||
}
|
||||
}
|
||||
|
||||
final pdfViewModelProvider = StateNotifierProvider<PdfViewModel, int>((ref) {
|
||||
final pdfViewModelProvider = ChangeNotifierProvider<PdfViewModel>((ref) {
|
||||
return PdfViewModel(ref);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,13 +33,6 @@ class AdjustmentsPanel extends StatelessWidget {
|
|||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
key: const Key('chk_aspect_lock'),
|
||||
value: aspectLocked,
|
||||
onChanged: (v) => onAspectLockedChanged(v ?? false),
|
||||
),
|
||||
Text(AppLocalizations.of(context).lockAspectRatio),
|
||||
const SizedBox(width: 16),
|
||||
Switch(
|
||||
key: const Key('swt_bg_removal'),
|
||||
value: bgRemoval,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,119 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'thumbnails_view.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../view_model/pdf_view_model.dart';
|
||||
|
||||
class ThumbnailsView extends ConsumerWidget {
|
||||
const ThumbnailsView({
|
||||
super.key,
|
||||
required this.documentRef,
|
||||
required this.controller,
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
final PdfDocumentRefData documentRef;
|
||||
final PdfViewerController controller;
|
||||
final int currentPage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
color: theme.colorScheme.surface,
|
||||
child: PdfDocumentViewBuilder(
|
||||
documentRef: documentRef,
|
||||
builder: (context, document) {
|
||||
final pageCount = document?.pages.length ?? 0;
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
itemCount: pageCount,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final pageNumber = index + 1;
|
||||
final isSelected = currentPage == pageNumber;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// Update both controller and provider page
|
||||
controller.goToPage(
|
||||
pageNumber: pageNumber,
|
||||
anchor: PdfPageAnchor.top,
|
||||
);
|
||||
try {
|
||||
ref
|
||||
.read(pdfViewModelProvider.notifier)
|
||||
.jumpToPage(pageNumber);
|
||||
} catch (_) {}
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: PdfPageView(
|
||||
document: document,
|
||||
pageNumber: pageNumber,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('$pageNumber', style: theme.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PagesSidebar extends StatelessWidget {
|
||||
const PagesSidebar({super.key});
|
||||
const PagesSidebar({
|
||||
super.key,
|
||||
required this.documentRef,
|
||||
required this.controller,
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
final PdfDocumentRefData? documentRef;
|
||||
final PdfViewerController controller;
|
||||
final int currentPage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(margin: EdgeInsets.zero, child: const ThumbnailsView());
|
||||
if (documentRef == null) {
|
||||
return Card(margin: EdgeInsets.zero, child: const SizedBox.shrink());
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ThumbnailsView(
|
||||
documentRef: documentRef!,
|
||||
controller: controller,
|
||||
currentPage: currentPage,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
||||
import 'pdf_page_overlays.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||
// using only adjusted overlay, no direct model imports needed
|
||||
import '../../signature/widgets/signature_drag_data.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import '../view_model/pdf_view_model.dart';
|
||||
|
||||
/// Mocked continuous viewer for tests or platforms without real viewer.
|
||||
@visibleForTesting
|
||||
|
|
@ -57,7 +56,6 @@ 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);
|
||||
if (pendingPage != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
|
@ -111,7 +109,7 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
|
|||
|
||||
// Add placement to the document
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.read(pdfViewModelProvider.notifier)
|
||||
.addPlacement(
|
||||
page: pageNum,
|
||||
rect: rect,
|
||||
|
|
@ -151,88 +149,75 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
|
|||
);
|
||||
},
|
||||
),
|
||||
visible
|
||||
? Stack(
|
||||
children: [
|
||||
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 && 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'),
|
||||
// Removed onPanUpdate to allow scrolling
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.red,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
Stack(
|
||||
children: [
|
||||
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 && 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
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: left,
|
||||
top: top,
|
||||
width: width,
|
||||
height: height,
|
||||
child: GestureDetector(
|
||||
key: const Key('signature_overlay'),
|
||||
// Removed onPanUpdate to allow scrolling
|
||||
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'),
|
||||
// Removed onPanUpdate to allow scrolling
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// resize handle bottom-right
|
||||
Positioned(
|
||||
left: left + width - 14,
|
||||
top: top + height - 14,
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: GestureDetector(
|
||||
key: const Key('signature_handle'),
|
||||
// Removed onPanUpdate to allow scrolling
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
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_viewer_widget.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import '../view_model/pdf_view_model.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
|
||||
class PdfPageArea extends ConsumerStatefulWidget {
|
||||
const PdfPageArea({
|
||||
|
|
@ -17,6 +16,7 @@ class PdfPageArea extends ConsumerStatefulWidget {
|
|||
required this.onConfirmSignature,
|
||||
required this.onClearActiveOverlay,
|
||||
required this.onSelectPlaced,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final Size pageSize;
|
||||
|
|
@ -26,6 +26,7 @@ class PdfPageArea extends ConsumerStatefulWidget {
|
|||
final VoidCallback onConfirmSignature;
|
||||
final VoidCallback onClearActiveOverlay;
|
||||
final ValueChanged<int?> onSelectPlaced;
|
||||
final PdfViewerController controller;
|
||||
@override
|
||||
ConsumerState<PdfPageArea> createState() => _PdfPageAreaState();
|
||||
}
|
||||
|
|
@ -42,6 +43,7 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
|
|||
int? _pendingPage; // pending target for mock ensureVisible retry
|
||||
int _scrollRetryCount = 0;
|
||||
static const int _maxScrollRetries = 50;
|
||||
int? _lastListenedPage;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -117,29 +119,22 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
final pdfViewModel = ref.watch(pdfViewModelProvider);
|
||||
final pdf = pdfViewModel.document;
|
||||
const pageViewMode = 'continuous';
|
||||
// React to PdfViewModel (source of truth for current page)
|
||||
ref.listen<int>(pdfViewModelProvider, (prev, next) {
|
||||
if (prev != next) {
|
||||
_scrollToPage(next);
|
||||
}
|
||||
});
|
||||
|
||||
// React to provider currentPage changes (e.g., user tapped overview)
|
||||
ref.listen(currentPageProvider, (prev, next) {
|
||||
// React to PdfViewModel currentPage changes. With ChangeNotifierProvider,
|
||||
// prev/next are the same instance, so compare to a local cache.
|
||||
ref.listen(pdfViewModelProvider, (prev, next) {
|
||||
if (_suppressProviderListen) return;
|
||||
if (prev != next) {
|
||||
final target = next;
|
||||
// If we're already navigating to this target, ignore; otherwise allow new target.
|
||||
if (_programmaticTargetPage != null &&
|
||||
_programmaticTargetPage == target) {
|
||||
return;
|
||||
}
|
||||
// Only navigate if target differs from what viewer shows
|
||||
if (_visiblePage != target) {
|
||||
_scrollToPage(target);
|
||||
}
|
||||
final target = next.currentPage;
|
||||
if (_lastListenedPage == target) return;
|
||||
_lastListenedPage = target;
|
||||
if (_programmaticTargetPage != null &&
|
||||
_programmaticTargetPage == target) {
|
||||
return;
|
||||
}
|
||||
if (_visiblePage != target) {
|
||||
_scrollToPage(target);
|
||||
}
|
||||
});
|
||||
// No page view mode switching; always continuous.
|
||||
|
|
@ -159,7 +154,6 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
|
|||
|
||||
// Use real PDF viewer
|
||||
if (isContinuous) {
|
||||
final controller = ref.watch(pdfViewerControllerProvider);
|
||||
return PdfViewerWidget(
|
||||
pageSize: widget.pageSize,
|
||||
onDragSignature: widget.onDragSignature,
|
||||
|
|
@ -169,7 +163,7 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
|
|||
onSelectPlaced: widget.onSelectPlaced,
|
||||
pageKeyBuilder: _pageKey,
|
||||
scrollToPage: _scrollToPage,
|
||||
controller: controller,
|
||||
controller: widget.controller,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
|
||||
import '../../../../domain/models/model.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'signature_overlay.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
|
||||
/// Builds all overlays for a given page: placed signatures and the active one.
|
||||
class PdfPageOverlays extends ConsumerWidget {
|
||||
|
|
@ -29,9 +29,12 @@ class PdfPageOverlays extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pdfViewModel = ref.watch(pdfViewModelProvider);
|
||||
// Subscribe to document changes to rebuild overlays
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
final placed =
|
||||
pdf.placementsByPage[pageNumber] ?? const <SignaturePlacement>[];
|
||||
final activeRect = pdfViewModel.activeRect;
|
||||
final widgets = <Widget>[];
|
||||
|
||||
for (int i = 0; i < placed.length; i++) {
|
||||
|
|
@ -48,9 +51,9 @@ class PdfPageOverlays extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
// Add active overlay if present and not using mock (mock has its own)
|
||||
final activeRect = ref.watch(activeRectProvider);
|
||||
final useMock = ref.watch(useMockViewerProvider);
|
||||
// TODO:Add active overlay if present and not using mock (mock has its own)
|
||||
|
||||
final useMock = pdfViewModel.useMockViewer;
|
||||
if (!useMock && activeRect != null) {
|
||||
widgets.add(
|
||||
LayoutBuilder(
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
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 '../view_model/pdf_providers.dart';
|
||||
|
||||
class PdfPagesOverview extends ConsumerWidget {
|
||||
const PdfPagesOverview({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
final controller = ref.watch(pdfViewerControllerProvider);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (!pdf.loaded || pdf.pickedPdfBytes == null)
|
||||
return const SizedBox.shrink();
|
||||
|
||||
final documentRef = PdfDocumentRefData(
|
||||
pdf.pickedPdfBytes!,
|
||||
sourceName: 'document.pdf',
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: theme.colorScheme.surface,
|
||||
child: PdfDocumentViewBuilder(
|
||||
documentRef: documentRef,
|
||||
builder: (context, document) {
|
||||
final pageCount = document?.pages.length ?? 0;
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
itemCount: pageCount,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final pageNumber = index + 1;
|
||||
final isSelected = ref.watch(currentPageProvider) == pageNumber;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
controller.goToPage(
|
||||
pageNumber: pageNumber,
|
||||
anchor: PdfPageAnchor.top,
|
||||
);
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: PdfPageView(
|
||||
document: document,
|
||||
pageNumber: pageNumber,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('$pageNumber', style: theme.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,6 @@ 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';
|
||||
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import 'draw_canvas.dart';
|
||||
import 'pdf_toolbar.dart';
|
||||
|
|
@ -19,7 +17,16 @@ import 'ui_services.dart';
|
|||
import '../view_model/pdf_view_model.dart';
|
||||
|
||||
class PdfSignatureHomePage extends ConsumerStatefulWidget {
|
||||
const PdfSignatureHomePage({super.key});
|
||||
final Future<void> Function() onPickPdf;
|
||||
final VoidCallback onClosePdf;
|
||||
final fs.XFile currentFile;
|
||||
|
||||
const PdfSignatureHomePage({
|
||||
super.key,
|
||||
required this.onPickPdf,
|
||||
required this.onClosePdf,
|
||||
required this.currentFile,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<PdfSignatureHomePage> createState() =>
|
||||
|
|
@ -31,7 +38,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
bool _showPagesSidebar = true;
|
||||
bool _showSignaturesSidebar = true;
|
||||
int _zoomLevel = 100; // percentage for display only
|
||||
fs.XFile _file = fs.XFile('');
|
||||
|
||||
// Split view controller to manage resizable sidebars without remounting the center area.
|
||||
late final MultiSplitViewController _splitController;
|
||||
|
|
@ -43,6 +49,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
final double _pagesMax = 250;
|
||||
final double _signaturesMin = 140;
|
||||
final double _signaturesMax = 250;
|
||||
late PdfViewModel _viewModel;
|
||||
|
||||
// Exposed for tests to trigger the invalid-file SnackBar without UI.
|
||||
@visibleForTesting
|
||||
|
|
@ -55,38 +62,17 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
}
|
||||
|
||||
Future<void> _pickPdf() async {
|
||||
final typeGroup = const fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
|
||||
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||
if (file != null) {
|
||||
setState(() {
|
||||
_file = file;
|
||||
});
|
||||
Uint8List? bytes;
|
||||
try {
|
||||
bytes = await file.readAsBytes();
|
||||
} catch (_) {
|
||||
bytes = null;
|
||||
}
|
||||
// infer page count if possible
|
||||
int pageCount = 1;
|
||||
if (bytes != null) {
|
||||
try {
|
||||
final doc = await PdfDocument.openData(bytes);
|
||||
pageCount = doc.pages.length;
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: pageCount, bytes: bytes);
|
||||
}
|
||||
await widget.onPickPdf();
|
||||
}
|
||||
|
||||
void _closePdf() {
|
||||
widget.onClosePdf();
|
||||
}
|
||||
|
||||
void _jumpToPage(int page) {
|
||||
final controller = ref.read(pdfViewerControllerProvider);
|
||||
final current = ref.read(currentPageProvider);
|
||||
final pdf = ref.read(documentRepositoryProvider);
|
||||
final controller = _viewModel.controller;
|
||||
final current = _viewModel.currentPage;
|
||||
final pdf = _viewModel.document;
|
||||
int target;
|
||||
if (page == -1) {
|
||||
target = (current - 1).clamp(1, pdf.pageCount);
|
||||
|
|
@ -95,10 +81,9 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
}
|
||||
// Update reactive page providers so UI/tests reflect navigation even if controller is a stub
|
||||
if (current != target) {
|
||||
ref.read(currentPageProvider.notifier).state = target;
|
||||
// Also notify view model (if used elsewhere) via its public API
|
||||
try {
|
||||
ref.read(pdfViewModelProvider.notifier).jumpToPage(target);
|
||||
_viewModel.jumpToPage(target);
|
||||
} catch (_) {
|
||||
// ignore if provider not available
|
||||
}
|
||||
|
|
@ -153,7 +138,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
Future<void> _saveSignedPdf() async {
|
||||
ref.read(exportingProvider.notifier).state = true;
|
||||
try {
|
||||
final pdf = ref.read(documentRepositoryProvider);
|
||||
final pdf = _viewModel.document;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
if (!pdf.loaded) {
|
||||
messenger.showSnackBar(
|
||||
|
|
@ -219,6 +204,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
// Build areas once with builders; keep these instances stable.
|
||||
_viewModel = ref.read(pdfViewModelProvider.notifier);
|
||||
_areas = [
|
||||
Area(
|
||||
size: _lastPagesWidth,
|
||||
|
|
@ -227,7 +213,26 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
builder:
|
||||
(context, area) => Offstage(
|
||||
offstage: !_showPagesSidebar,
|
||||
child: const PagesSidebar(),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final pdfViewModel = ref.watch(pdfViewModelProvider);
|
||||
final pdf = pdfViewModel.document;
|
||||
|
||||
final documentRef =
|
||||
pdf.loaded && pdf.pickedPdfBytes != null
|
||||
? PdfDocumentRefData(
|
||||
pdf.pickedPdfBytes!,
|
||||
sourceName: 'document.pdf',
|
||||
)
|
||||
: null;
|
||||
|
||||
return PagesSidebar(
|
||||
documentRef: documentRef,
|
||||
controller: _viewModel.controller,
|
||||
currentPage: _viewModel.currentPage,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Area(
|
||||
|
|
@ -235,6 +240,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
builder:
|
||||
(context, area) => RepaintBoundary(
|
||||
child: PdfPageArea(
|
||||
controller: _viewModel.controller,
|
||||
key: const ValueKey('pdf_page_area'),
|
||||
pageSize: _pageSize,
|
||||
onDragSignature: _onDragSignature,
|
||||
|
|
@ -300,15 +306,9 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Provide controller override so descendants can access it.
|
||||
return ProviderScope(
|
||||
overrides: [pdfViewerControllerProvider.overrideWithValue(_controller)],
|
||||
child: _buildScaffold(context),
|
||||
);
|
||||
return _buildScaffold(context);
|
||||
}
|
||||
|
||||
late final PdfViewerController _controller = PdfViewerController();
|
||||
|
||||
Widget _buildScaffold(BuildContext context) {
|
||||
final isExporting = ref.watch(exportingProvider);
|
||||
final l = AppLocalizations.of(context);
|
||||
|
|
@ -323,6 +323,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
PdfToolbar(
|
||||
disabled: isExporting,
|
||||
onPickPdf: _pickPdf,
|
||||
onClosePdf: _closePdf,
|
||||
onJumpToPage: _jumpToPage,
|
||||
onZoomOut: () {
|
||||
setState(() {
|
||||
|
|
@ -335,7 +336,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
});
|
||||
},
|
||||
zoomLevel: _zoomLevel,
|
||||
fileName: _file.name,
|
||||
filePath: widget.currentFile.path,
|
||||
showPagesSidebar: _showPagesSidebar,
|
||||
showSignaturesSidebar: _showSignaturesSidebar,
|
||||
onTogglePagesSidebar:
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
class PdfToolbar extends ConsumerStatefulWidget {
|
||||
const PdfToolbar({
|
||||
super.key,
|
||||
required this.disabled,
|
||||
required this.onPickPdf,
|
||||
required this.onClosePdf,
|
||||
required this.onJumpToPage,
|
||||
required this.onZoomOut,
|
||||
required this.onZoomIn,
|
||||
this.zoomLevel,
|
||||
this.fileName,
|
||||
this.filePath,
|
||||
required this.showPagesSidebar,
|
||||
required this.showSignaturesSidebar,
|
||||
required this.onTogglePagesSidebar,
|
||||
|
|
@ -24,8 +24,9 @@ class PdfToolbar extends ConsumerStatefulWidget {
|
|||
|
||||
final bool disabled;
|
||||
final VoidCallback onPickPdf;
|
||||
final VoidCallback onClosePdf;
|
||||
final ValueChanged<int> onJumpToPage;
|
||||
final String? fileName;
|
||||
final String? filePath;
|
||||
final VoidCallback onZoomOut;
|
||||
final VoidCallback onZoomIn;
|
||||
// Current zoom level as a percentage (e.g., 100 for 100%)
|
||||
|
|
@ -56,8 +57,9 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
final currentPage = ref.watch(currentPageProvider);
|
||||
final pdfViewModel = ref.watch(pdfViewModelProvider);
|
||||
final pdf = pdfViewModel.document;
|
||||
final currentPage = pdfViewModel.currentPage;
|
||||
final l = AppLocalizations.of(context);
|
||||
final pageInfo = l.pageInfo(currentPage, pdf.pageCount);
|
||||
|
||||
|
|
@ -83,9 +85,9 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
|||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 220),
|
||||
child: Text(
|
||||
// if filename not null
|
||||
widget.fileName != null
|
||||
? widget.fileName!
|
||||
// if filePath not null
|
||||
widget.filePath != null
|
||||
? widget.filePath!
|
||||
: 'No file selected',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -94,6 +96,12 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
|||
),
|
||||
),
|
||||
if (pdf.loaded) ...[
|
||||
IconButton(
|
||||
key: const Key('btn_close_pdf'),
|
||||
onPressed: widget.disabled ? null : widget.onClosePdf,
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: l.close,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
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_mock_continuous_list.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
import '../view_model/pdf_view_model.dart';
|
||||
|
||||
class PdfViewerWidget extends ConsumerStatefulWidget {
|
||||
|
|
@ -55,11 +53,10 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final document = ref.watch(documentRepositoryProvider);
|
||||
final useMock = ref.watch(useMockViewerProvider);
|
||||
ref.watch(activeRectProvider); // trigger rebuild when active rect changes
|
||||
// Watch to rebuild on page change
|
||||
ref.watch(currentPageProvider);
|
||||
final pdfViewModel = ref.watch(pdfViewModelProvider);
|
||||
final document = pdfViewModel.document;
|
||||
final useMock = pdfViewModel.useMockViewer;
|
||||
// trigger rebuild when active rect changes
|
||||
|
||||
// Update document ref when document changes
|
||||
if (document.loaded && document.pickedPdfBytes != null) {
|
||||
|
|
@ -109,12 +106,11 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
|
|||
onViewerReady: (document, controller) {
|
||||
// Update page count in repository
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.read(pdfViewModelProvider.notifier)
|
||||
.setPageCount(document.pages.length);
|
||||
},
|
||||
onPageChanged: (page) {
|
||||
if (page != null) {
|
||||
ref.read(currentPageProvider.notifier).state = page;
|
||||
// Also update the view model to keep them in sync
|
||||
ref.read(pdfViewModelProvider.notifier).jumpToPage(page);
|
||||
}
|
||||
|
|
@ -123,7 +119,7 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
|
|||
return [
|
||||
PdfPageOverlays(
|
||||
pageSize: widget.pageSize,
|
||||
pageNumber: ref.watch(currentPageProvider),
|
||||
pageNumber: pdfViewModel.currentPage,
|
||||
onDragSignature: widget.onDragSignature,
|
||||
onResizeSignature: widget.onResizeSignature,
|
||||
onConfirmSignature: widget.onConfirmSignature,
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ class SignatureOverlay extends StatelessWidget {
|
|||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
key: Key('placed_signature_$placedIndex'),
|
||||
left: left,
|
||||
top: top,
|
||||
width: width,
|
||||
height: height,
|
||||
child: DecoratedBox(
|
||||
key: Key('placed_signature_$placedIndex'),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.red, width: 2),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
||||
import 'signature_drawer.dart';
|
||||
import '../../signature/widgets/signature_drawer.dart';
|
||||
import 'ui_services.dart';
|
||||
|
||||
class SignaturesSidebar extends ConsumerWidget {
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
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 '../view_model/pdf_providers.dart';
|
||||
|
||||
class ThumbnailsView extends ConsumerWidget {
|
||||
const ThumbnailsView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pdf = ref.watch(documentRepositoryProvider);
|
||||
final controller = ref.watch(pdfViewerControllerProvider);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (!pdf.loaded || pdf.pickedPdfBytes == null)
|
||||
return const SizedBox.shrink();
|
||||
|
||||
final documentRef = PdfDocumentRefData(
|
||||
pdf.pickedPdfBytes!,
|
||||
sourceName: 'document.pdf',
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: theme.colorScheme.surface,
|
||||
child: PdfDocumentViewBuilder(
|
||||
documentRef: documentRef,
|
||||
builder: (context, document) {
|
||||
final pageCount = document?.pages.length ?? 0;
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
itemCount: pageCount,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final pageNumber = index + 1;
|
||||
final isSelected = ref.watch(currentPageProvider) == pageNumber;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
controller.goToPage(
|
||||
pageNumber: pageNumber,
|
||||
anchor: PdfPageAnchor.top,
|
||||
);
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: PdfPageView(
|
||||
document: document,
|
||||
pageNumber: pageNumber,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('$pageNumber', style: theme.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
import 'adjustments_panel.dart';
|
||||
// No live preview wiring in simplified dialog
|
||||
import '../../pdf/widgets/adjustments_panel.dart';
|
||||
import '../../../../domain/models/model.dart' as domain;
|
||||
import 'rotated_signature_image.dart';
|
||||
|
||||
class ImageEditorResult {
|
||||
final double rotation;
|
||||
final domain.GraphicAdjust graphicAdjust;
|
||||
|
||||
const ImageEditorResult({
|
||||
required this.rotation,
|
||||
required this.graphicAdjust,
|
||||
});
|
||||
}
|
||||
|
||||
class ImageEditorDialog extends StatefulWidget {
|
||||
const ImageEditorDialog({super.key});
|
||||
const ImageEditorDialog({
|
||||
super.key,
|
||||
required this.asset,
|
||||
required this.initialRotation,
|
||||
required this.initialGraphicAdjust,
|
||||
});
|
||||
|
||||
final domain.SignatureAsset asset;
|
||||
final double initialRotation;
|
||||
final domain.GraphicAdjust initialGraphicAdjust;
|
||||
|
||||
@override
|
||||
State<ImageEditorDialog> createState() => _ImageEditorDialogState();
|
||||
}
|
||||
|
||||
class _ImageEditorDialogState extends State<ImageEditorDialog> {
|
||||
// Local-only state for demo/tests; no persistence to repositories.
|
||||
bool _aspectLocked = false;
|
||||
bool _bgRemoval = false;
|
||||
double _contrast = 1.0; // 0..2
|
||||
double _brightness = 0.0; // -1..1
|
||||
double _rotation = 0.0; // -180..180
|
||||
late bool _aspectLocked;
|
||||
late bool _bgRemoval;
|
||||
late double _contrast;
|
||||
late double _brightness;
|
||||
late double _rotation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_aspectLocked = false; // Not persisted in GraphicAdjust
|
||||
_bgRemoval = widget.initialGraphicAdjust.bgRemoval;
|
||||
_contrast = widget.initialGraphicAdjust.contrast;
|
||||
_brightness = widget.initialGraphicAdjust.brightness;
|
||||
_rotation = widget.initialRotation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -37,7 +66,7 @@ class _ImageEditorDialogState extends State<ImageEditorDialog> {
|
|||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Preview placeholder; no actual processed bytes wired
|
||||
// Preview with actual signature image
|
||||
SizedBox(
|
||||
height: 160,
|
||||
child: DecoratedBox(
|
||||
|
|
@ -45,7 +74,13 @@ class _ImageEditorDialogState extends State<ImageEditorDialog> {
|
|||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(child: Text('No signature loaded')),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: RotatedSignatureImage(
|
||||
bytes: widget.asset.bytes,
|
||||
rotationDeg: _rotation,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
|
@ -84,7 +119,17 @@ class _ImageEditorDialogState extends State<ImageEditorDialog> {
|
|||
children: [
|
||||
TextButton(
|
||||
key: const Key('btn_image_editor_close'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed:
|
||||
() => Navigator.of(context).pop(
|
||||
ImageEditorResult(
|
||||
rotation: _rotation,
|
||||
graphicAdjust: domain.GraphicAdjust(
|
||||
contrast: _contrast,
|
||||
brightness: _brightness,
|
||||
bgRemoval: _bgRemoval,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
MaterialLocalizations.of(context).closeButtonLabel,
|
||||
),
|
||||
|
|
@ -112,6 +112,13 @@ class _RotatedSignatureImageState extends State<RotatedSignatureImage> {
|
|||
filterQuality: widget.filterQuality,
|
||||
alignment: widget.alignment,
|
||||
semanticLabel: widget.semanticLabel,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
// Return a placeholder for invalid images
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
child: const Icon(Icons.broken_image, color: Colors.grey),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (angle != 0.0) {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import 'dart:typed_data';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
// No direct model construction needed here
|
||||
// Direct model construction is needed for creating SignatureAssets
|
||||
|
||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart' hide SignatureCard;
|
||||
import 'image_editor_dialog.dart';
|
||||
import '../../signature/widgets/signature_card.dart';
|
||||
import '../view_model/pdf_providers.dart';
|
||||
import 'signature_card.dart';
|
||||
|
||||
/// Data for drag-and-drop is in signature_drag_data.dart
|
||||
|
||||
|
|
@ -60,15 +60,23 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
|||
.remove(card),
|
||||
onAdjust: () async {
|
||||
if (!mounted) return;
|
||||
await showDialog(
|
||||
final result = await showDialog<ImageEditorResult>(
|
||||
context: context,
|
||||
builder: (_) => const ImageEditorDialog(),
|
||||
builder:
|
||||
(_) => ImageEditorDialog(
|
||||
asset: card.asset,
|
||||
initialRotation: card.rotationDeg,
|
||||
initialGraphicAdjust: card.graphicAdjust,
|
||||
),
|
||||
);
|
||||
if (result != null && mounted) {
|
||||
ref
|
||||
.read(signatureCardRepositoryProvider.notifier)
|
||||
.update(card, result.rotation, result.graphicAdjust);
|
||||
}
|
||||
},
|
||||
onTap: () {
|
||||
ref
|
||||
.read(activeRectProvider.notifier)
|
||||
.state = const Rect.fromLTWH(0.2, 0.2, 0.3, 0.15);
|
||||
// state = const Rect.fromLTWH(0.2, 0.2, 0.3, 0.15);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -110,12 +118,22 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
|||
await widget.onLoadSignatureFromFile();
|
||||
final b = loaded;
|
||||
if (b != null) {
|
||||
final asset = SignatureAsset(
|
||||
bytes: b,
|
||||
name: 'image',
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
signatureAssetRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.add(b, name: 'image');
|
||||
ref
|
||||
.read(
|
||||
signatureCardRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.addWithAsset(asset, 0.0);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.image_outlined),
|
||||
|
|
@ -130,12 +148,22 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
|||
final drawn = await widget.onOpenDrawCanvas();
|
||||
final b = drawn;
|
||||
if (b != null) {
|
||||
final asset = SignatureAsset(
|
||||
bytes: b,
|
||||
name: 'drawing',
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
signatureAssetRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.add(b, name: 'drawing');
|
||||
ref
|
||||
.read(
|
||||
signatureCardRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.addWithAsset(asset, 0.0);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.gesture),
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
class WelcomeViewModel {
|
||||
final Ref ref;
|
||||
|
|
@ -10,19 +8,9 @@ class WelcomeViewModel {
|
|||
WelcomeViewModel(this.ref);
|
||||
|
||||
Future<void> openPdf({required String path, Uint8List? bytes}) async {
|
||||
int pageCount = 1; // default
|
||||
if (bytes != null) {
|
||||
try {
|
||||
final doc = await PdfDocument.openData(bytes);
|
||||
pageCount = doc.pages.length;
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
ref
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: pageCount, bytes: bytes);
|
||||
ref.read(signatureCardRepositoryProvider.notifier).clearAll();
|
||||
await ref
|
||||
.read(pdfViewModelProvider.notifier)
|
||||
.openPdf(path: path, bytes: bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
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:pdf_signature/l10n/app_localizations.dart';
|
||||
import 'package:pdf_signature/ui/features/welcome/view_model/welcome_view_model.dart';
|
||||
|
||||
// Abstraction to make drop handling testable without constructing
|
||||
// platform-specific DropItem types in widget tests.
|
||||
|
|
@ -32,7 +30,8 @@ typedef Reader = T Function<T>(ProviderListenable<T> provider);
|
|||
|
||||
// Select first .pdf file (case-insensitive) or fall back to first entry.
|
||||
Future<void> handleDroppedFiles(
|
||||
Reader read,
|
||||
Future<void> Function({String? path, Uint8List? bytes, String? fileName})
|
||||
onOpenPdf,
|
||||
Iterable<DropReadable> files,
|
||||
) async {
|
||||
if (files.isEmpty) return;
|
||||
|
|
@ -47,11 +46,23 @@ Future<void> handleDroppedFiles(
|
|||
bytes = null;
|
||||
}
|
||||
final String path = pdf.path ?? pdf.name;
|
||||
await read(welcomeViewModelProvider).openPdf(path: path, bytes: bytes);
|
||||
await onOpenPdf(path: path, bytes: bytes);
|
||||
}
|
||||
|
||||
class WelcomeScreen extends ConsumerStatefulWidget {
|
||||
const WelcomeScreen({super.key});
|
||||
final Future<void> Function() onPickPdf;
|
||||
final Future<void> Function({
|
||||
String? path,
|
||||
Uint8List? bytes,
|
||||
String? fileName,
|
||||
})
|
||||
onOpenPdf;
|
||||
|
||||
const WelcomeScreen({
|
||||
super.key,
|
||||
required this.onPickPdf,
|
||||
required this.onOpenPdf,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<WelcomeScreen> createState() => _WelcomeScreenState();
|
||||
|
|
@ -61,19 +72,7 @@ class _WelcomeScreenState extends ConsumerState<WelcomeScreen> {
|
|||
bool _dragging = false;
|
||||
|
||||
Future<void> _pickPdf() async {
|
||||
final typeGroup = const fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
|
||||
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||
if (file != null) {
|
||||
Uint8List? bytes;
|
||||
try {
|
||||
bytes = await file.readAsBytes();
|
||||
} catch (_) {
|
||||
bytes = null;
|
||||
}
|
||||
await ref
|
||||
.read(welcomeViewModelProvider)
|
||||
.openPdf(path: file.path, bytes: bytes);
|
||||
}
|
||||
await widget.onPickPdf();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -113,7 +112,7 @@ class _WelcomeScreenState extends ConsumerState<WelcomeScreen> {
|
|||
final adapters = desktopFiles.map<DropReadable>(
|
||||
(f) => _DropReadableFromDesktop(f),
|
||||
);
|
||||
await handleDroppedFiles(ref.read, adapters);
|
||||
await handleDroppedFiles(widget.onOpenPdf, adapters);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ dev_dependencies:
|
|||
freezed: ^3.0.0
|
||||
custom_lint: ^0.7.6
|
||||
riverpod_lint: ^2.6.5
|
||||
go_router_builder: ^4.0.1
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
|
|
@ -80,6 +81,7 @@ dev_dependencies:
|
|||
msix: ^3.16.12
|
||||
json_serializable: ^6.11.0
|
||||
dead_code_analyzer: ^1.1.0
|
||||
faker_dart: ^0.2.3
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import 'dart:typed_data';
|
|||
import 'dart:ui';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:pdf_signature/app.dart';
|
||||
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
|
||||
import 'package:pdf_signature/data/services/export_service.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
|
|
@ -48,7 +48,9 @@ Future<ProviderContainer> pumpApp(
|
|||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => DocumentStateNotifier()..openSample(),
|
||||
),
|
||||
useMockViewerProvider.overrideWith((ref) => true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
exportServiceProvider.overrideWith((ref) => fakeExport),
|
||||
savePathPickerProvider.overrideWith((ref) => () async => 'out.pdf'),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,10 +2,24 @@ import 'dart:typed_data';
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// A tiny shared world for BDD steps to share state within a scenario.
|
||||
class TestWorld {
|
||||
static ProviderContainer? container;
|
||||
static ProviderContainer? _container;
|
||||
static ProviderContainer? get container => _container;
|
||||
static set container(ProviderContainer? value) {
|
||||
_container = value;
|
||||
if (value != null) {
|
||||
// Ensure any container created during a test is disposed at teardown
|
||||
addTearDown(() {
|
||||
try {
|
||||
_container?.dispose();
|
||||
} catch (_) {}
|
||||
_container = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Signature helpers
|
||||
static Offset? prevCenter;
|
||||
|
|
|
|||
|
|
@ -13,28 +13,29 @@ aDocumentIsOpenAndContainsMultiplePlacedSignaturePlacementsAcrossPages(
|
|||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: 5);
|
||||
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
page: 1,
|
||||
rect: Rect.fromLTWH(10, 10, 100, 50),
|
||||
rect: Rect.fromLTWH(0.1, 0.1, 0.2, 0.1),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'sig1.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
page: 2,
|
||||
rect: Rect.fromLTWH(20, 20, 100, 50),
|
||||
rect: Rect.fromLTWH(0.2, 0.2, 0.2, 0.1),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'sig2.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
page: 3,
|
||||
rect: Rect.fromLTWH(30, 30, 100, 50),
|
||||
rect: Rect.fromLTWH(0.3, 0.3, 0.2, 0.1),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'sig3.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,14 @@ import '_world.dart';
|
|||
Future<void> aDocumentPageIsSelectedForSigning(WidgetTester tester) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
// Ensure a document is open
|
||||
final repo = container.read(documentRepositoryProvider.notifier);
|
||||
if (!container.read(documentRepositoryProvider).loaded) {
|
||||
repo.openPicked(pageCount: 5);
|
||||
}
|
||||
// Ensure current page is 1 for consistent subsequent steps
|
||||
try {
|
||||
container.read(pdfViewModelProvider.notifier).jumpToPage(1);
|
||||
} catch (_) {}
|
||||
container.read(documentRepositoryProvider.notifier).jumpTo(1);
|
||||
repo.jumpTo(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
|
|||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ Future<void> aMultipageDocumentIsOpen(WidgetTester tester) async {
|
|||
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
||||
// Reset page state providers
|
||||
try {
|
||||
container.read(currentPageProvider.notifier).state = 1;
|
||||
container.read(pdfViewModelProvider.notifier).jumpToPage(1);
|
||||
} catch (_) {}
|
||||
try {
|
||||
container.read(pdfViewModelProvider.notifier).jumpToPage(1);
|
||||
|
|
|
|||
|
|
@ -90,4 +90,5 @@ Future<void> aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async {
|
|||
container
|
||||
.read(signatureAssetRepositoryProvider.notifier)
|
||||
.add(bytes, name: 'test.png');
|
||||
await tester.pump();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Future<void> aSignatureAssetIsPlacedOnThePage(WidgetTester tester) async {
|
|||
}
|
||||
|
||||
// Place it on the current page
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
|
|
|
|||
|
|
@ -23,4 +23,6 @@ Future<void> aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard(
|
|||
container
|
||||
.read(signatureAssetRepositoryProvider.notifier)
|
||||
.add(bytes, name: 'test.png');
|
||||
// Allow provider scheduler to flush any pending timers
|
||||
await tester.pump();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ Future<void> aSignaturePlacementIsPlacedOnPage(
|
|||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
// Ensure a document is open for placement operations
|
||||
if (!container.read(documentRepositoryProvider).loaded) {
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: 5);
|
||||
}
|
||||
final page = param1.toInt();
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
|
|
@ -21,4 +27,5 @@ Future<void> aSignaturePlacementIsPlacedOnPage(
|
|||
rect: Rect.fromLTWH(20, 20, 100, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,19 @@ Future<void> aSignaturePlacementIsPlacedWithAPositionAndSizeRelativeToThePage(
|
|||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
if (!container.read(documentRepositoryProvider).loaded) {
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: 5);
|
||||
}
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
page: currentPage,
|
||||
rect: const Rect.fromLTWH(50, 50, 200, 100),
|
||||
// Use normalized 0..1 fractions relative to page size as required
|
||||
rect: const Rect.fromLTWH(0.2, 0.3, 0.4, 0.2),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Future<void> draggingOrResizingOneDoesNotChangeTheOther(
|
|||
WidgetTester tester,
|
||||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
final page = container.read(pdfViewModelProvider);
|
||||
final page = container.read(pdfViewModelProvider).currentPage;
|
||||
final list = container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.placementsOn(page);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Future<void> eachSignaturePlacementCanBeDraggedAndResizedIndependently(
|
|||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
final pdf = container.read(documentRepositoryProvider);
|
||||
final page = container.read(pdfViewModelProvider);
|
||||
final placements = pdf.placementsByPage[page] ?? const [];
|
||||
final page = container.read(pdfViewModelProvider).currentPage;
|
||||
final placements = pdf.placementsByPage[page] ?? const <dynamic>[];
|
||||
expect(placements.length, greaterThan(1));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ Future<void> pageBecomesVisibleInTheScrollArea(
|
|||
) async {
|
||||
final page = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
expect(c.read(pdfViewModelProvider), page);
|
||||
expect(c.read(pdfViewModelProvider).currentPage, page);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -8,11 +8,10 @@ import '_world.dart';
|
|||
Future<void> pageIsDisplayed(WidgetTester tester, num param1) async {
|
||||
final expected = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
final vm = c.read(pdfViewModelProvider);
|
||||
final legacy = c.read(currentPageProvider);
|
||||
final currentPage = c.read(pdfViewModelProvider).currentPage;
|
||||
expect(
|
||||
vm == expected || legacy == expected,
|
||||
currentPage == expected,
|
||||
true,
|
||||
reason: 'Expected page $expected but got vm=$vm current=$legacy',
|
||||
reason: 'Expected page $expected but got current=$currentPage',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Future<void> signaturePlacementOccursOnTheSelectedPage(
|
|||
asset: asset,
|
||||
);
|
||||
}
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
final updated = container.read(documentRepositoryProvider);
|
||||
expect(updated.placementsByPage[page], isNotEmpty);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
|
|||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
import '_world.dart';
|
||||
|
||||
class _BridgedSignatureCardStateNotifier extends SignatureCardStateNotifier {
|
||||
|
|
@ -37,7 +38,9 @@ Future<void> theAppLaunches(WidgetTester tester) async {
|
|||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => DocumentStateNotifier()..openSample(),
|
||||
),
|
||||
useMockViewerProvider.overrideWith((ref) => true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
// Bridge: automatically mirror assets into signature cards so legacy
|
||||
// feature steps that expect SignatureCard widgets keep working even
|
||||
// though the production UI currently only stores raw assets.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ import '_world.dart';
|
|||
/// Usage: the first page is displayed
|
||||
Future<void> theFirstPageIsDisplayed(WidgetTester tester) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
expect(currentPage, 1);
|
||||
final vm = container.read(pdfViewModelProvider);
|
||||
expect(vm.currentPage, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import '_world.dart';
|
||||
|
||||
/// Usage: the last page is displayed (page {5})
|
||||
|
|
@ -11,11 +11,10 @@ Future<void> theLastPageIsDisplayedPage(WidgetTester tester, num param1) async {
|
|||
final c = TestWorld.container ?? ProviderContainer();
|
||||
final pdf = c.read(documentRepositoryProvider);
|
||||
expect(pdf.pageCount, last);
|
||||
final vm = c.read(pdfViewModelProvider);
|
||||
final legacy = c.read(currentPageProvider);
|
||||
final currentPage = c.read(pdfViewModelProvider).currentPage;
|
||||
expect(
|
||||
vm == last || legacy == last,
|
||||
currentPage == last,
|
||||
true,
|
||||
reason: 'Expected last page $last but got vm=$vm current=$legacy',
|
||||
reason: 'Expected last page $last but got current=$currentPage',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ Future<void> theLeftPagesOverviewHighlightsPage(
|
|||
) async {
|
||||
final n = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
expect(c.read(pdfViewModelProvider), n);
|
||||
expect(c.read(pdfViewModelProvider).currentPage, n);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ Future<void> thePageLabelShowsPageOf(
|
|||
final total = param2.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
final pdf = c.read(documentRepositoryProvider);
|
||||
expect(c.read(pdfViewModelProvider), current);
|
||||
expect(c.read(pdfViewModelProvider).currentPage, current);
|
||||
expect(pdf.pageCount, total);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import '_world.dart';
|
|||
Future<void> theUserCanMoveToTheNextOrPreviousPage(WidgetTester tester) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
final vm = container.read(pdfViewModelProvider.notifier);
|
||||
expect(container.read(pdfViewModelProvider), 1);
|
||||
expect(container.read(pdfViewModelProvider).currentPage, 1);
|
||||
vm.jumpToPage(2);
|
||||
expect(container.read(pdfViewModelProvider), 2);
|
||||
expect(container.read(pdfViewModelProvider).currentPage, 2);
|
||||
vm.jumpToPage(1);
|
||||
expect(container.read(pdfViewModelProvider), 1);
|
||||
expect(container.read(pdfViewModelProvider).currentPage, 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -9,12 +9,10 @@ Future<void> theUserClicksTheGoToApplyButton(WidgetTester tester) async {
|
|||
final c = TestWorld.container ?? ProviderContainer();
|
||||
final pending = TestWorld.pendingGoTo;
|
||||
if (pending != null) {
|
||||
try {
|
||||
c.read(currentPageProvider.notifier).state = pending;
|
||||
} catch (_) {}
|
||||
try {
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(pending);
|
||||
} catch (_) {}
|
||||
assert(c.read(pdfViewModelProvider).currentPage == pending);
|
||||
await tester.pump();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -11,9 +11,6 @@ Future<void> theUserClicksTheThumbnailForPage(
|
|||
) async {
|
||||
final page = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
try {
|
||||
c.read(currentPageProvider.notifier).state = page;
|
||||
} catch (_) {}
|
||||
try {
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(page);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Future<void> theUserDeletesOneSelectedSignaturePlacement(
|
|||
) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
final placements = container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.placementsOn(currentPage);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Future<void> theUserDragsHandlesToResizeAndDragsToReposition(
|
|||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
final pdfN = container.read(documentRepositoryProvider.notifier);
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
|
||||
final placements = pdfN.placementsOn(currentPage);
|
||||
if (placements.isNotEmpty) {
|
||||
|
|
|
|||
|
|
@ -46,4 +46,5 @@ theUserDragsItOnThePageOfTheDocumentToPlaceSignaturePlacementsInMultipleLocation
|
|||
rect: Rect.fromLTWH(30, 30, 100, 50),
|
||||
asset: asset,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ theUserDragsThisSignatureCardOnThePageOfTheDocumentToPlaceASignaturePlacement(
|
|||
final drop_card = temp_card;
|
||||
|
||||
// Place it on the current page
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ Future<void> theUserEntersIntoTheGoToInputAndAppliesIt(
|
|||
final clamped =
|
||||
value < 1 ? 1 : value; // upper bound validated in last-page check step
|
||||
try {
|
||||
c.read(currentPageProvider.notifier).state = clamped;
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(clamped);
|
||||
} catch (_) {}
|
||||
try {
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(clamped);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -9,10 +9,7 @@ Future<void> theUserJumpsToPage(WidgetTester tester, num param1) async {
|
|||
final page = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
try {
|
||||
c.read(currentPageProvider.notifier).state = page;
|
||||
} catch (_) {}
|
||||
try {
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(page);
|
||||
c.read(pdfViewModelProvider).jumpToPage(page);
|
||||
} catch (_) {}
|
||||
await tester.pump();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -17,12 +16,10 @@ Future<void> theUserNavigatesToPageAndPlacesAnotherSignaturePlacement(
|
|||
TestWorld.container = container;
|
||||
final page = param1.toInt();
|
||||
// Update page providers directly (repository jumpTo is a no-op now)
|
||||
try {
|
||||
container.read(currentPageProvider.notifier).state = page;
|
||||
} catch (_) {}
|
||||
try {
|
||||
container.read(pdfViewModelProvider.notifier).jumpToPage(page);
|
||||
} catch (_) {}
|
||||
await tester.pumpAndSettle();
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
|
|
@ -30,4 +27,5 @@ Future<void> theUserNavigatesToPageAndPlacesAnotherSignaturePlacement(
|
|||
rect: Rect.fromLTWH(40, 40, 100, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'another.png'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,5 @@ Future<void> theUserPlacesASignaturePlacementFromAssetOnPage(
|
|||
rect: Rect.fromLTWH(10, 10, 50, 50),
|
||||
asset: asset,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,6 @@ Future<void> theUserPlacesASignaturePlacementOnPage(
|
|||
rect: Rect.fromLTWH(20, 20, 100, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test.png'),
|
||||
);
|
||||
// Allow Riverpod's scheduler to flush any pending microtasks/timers
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Future<void> theUserPlacesTwoSignaturePlacementsOnTheSamePage(
|
|||
final container = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = container;
|
||||
// pdfViewModelProvider returns 1-based current page
|
||||
final page = container.read(pdfViewModelProvider);
|
||||
final page = container.read(pdfViewModelProvider).currentPage;
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
|
|
@ -34,6 +34,7 @@ Future<void> theUserPlacesTwoSignaturePlacementsOnTheSamePage(
|
|||
name: 'sig1.png',
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.addPlacement(
|
||||
|
|
@ -54,4 +55,5 @@ Future<void> theUserPlacesTwoSignaturePlacementsOnTheSamePage(
|
|||
name: 'sig2.png',
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ Future<void> theUserSavesexportsTheDocument(WidgetTester tester) async {
|
|||
// Ensure state looks exportable
|
||||
final pdf = container.read(documentRepositoryProvider);
|
||||
final sig = container.read(signatureProvider);
|
||||
if (!pdf.loaded) {
|
||||
// Load a minimal sample so the expectation passes in logic-only tests
|
||||
container
|
||||
.read(documentRepositoryProvider.notifier)
|
||||
.openPicked(pageCount: 2, bytes: Uint8List(10));
|
||||
}
|
||||
expect(pdf.loaded, isTrue, reason: 'PDF must be loaded before export');
|
||||
// Check if there are placements
|
||||
final hasPlacements = pdf.placementsByPage.values.any(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
|
|
@ -12,9 +12,6 @@ Future<void> theUserTypesIntoTheGoToInputAndPressesEnter(
|
|||
final target = param1.toInt();
|
||||
final c = TestWorld.container ?? ProviderContainer();
|
||||
TestWorld.container = c;
|
||||
try {
|
||||
c.read(currentPageProvider.notifier).state = target;
|
||||
} catch (_) {}
|
||||
try {
|
||||
c.read(pdfViewModelProvider.notifier).jumpToPage(target);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import '_world.dart';
|
|||
Future<void> theUserUsesRotateControls(WidgetTester tester) async {
|
||||
final container = TestWorld.container ?? ProviderContainer();
|
||||
final pdfN = container.read(documentRepositoryProvider.notifier);
|
||||
final currentPage = container.read(pdfViewModelProvider);
|
||||
final currentPage = container.read(pdfViewModelProvider).currentPage;
|
||||
final placements = pdfN.placementsOn(currentPage);
|
||||
if (placements.isNotEmpty) {
|
||||
pdfN.updatePlacementRotation(
|
||||
|
|
|
|||
|
|
@ -24,20 +24,23 @@ Future<void> threeSignaturePlacementsArePlacedOnTheCurrentPage(
|
|||
];
|
||||
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
||||
final pdfN = container.read(documentRepositoryProvider.notifier);
|
||||
final page = container.read(pdfViewModelProvider);
|
||||
final page = container.read(pdfViewModelProvider).currentPage;
|
||||
pdfN.addPlacement(
|
||||
page: page,
|
||||
rect: Rect.fromLTWH(10, 10, 50, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test1'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
pdfN.addPlacement(
|
||||
page: page,
|
||||
rect: Rect.fromLTWH(70, 10, 50, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test2'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
pdfN.addPlacement(
|
||||
page: page,
|
||||
rect: Rect.fromLTWH(130, 10, 50, 50),
|
||||
asset: SignatureAsset(bytes: Uint8List(0), name: 'test3'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
@ -8,7 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import 'package:pdf_signature/data/services/export_service.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
|
||||
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||
|
|
@ -58,7 +59,9 @@ void main() {
|
|||
DocumentStateNotifier()
|
||||
..openPicked(pageCount: 5, bytes: Uint8List(0)),
|
||||
),
|
||||
useMockViewerProvider.overrideWith((ref) => true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
exportServiceProvider.overrideWith((_) => fake),
|
||||
savePathPickerProvider.overrideWith(
|
||||
(_) => () async => 'C:/tmp/output.pdf',
|
||||
|
|
@ -67,7 +70,11 @@ void main() {
|
|||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.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/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.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';
|
||||
|
|
@ -22,13 +23,19 @@ Future<void> pumpWithOpenPdf(WidgetTester tester) async {
|
|||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => DocumentStateNotifier()..openSample(),
|
||||
),
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
exportingProvider.overrideWith((ref) => false),
|
||||
],
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -388,13 +395,19 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
|
|||
return cardRepo;
|
||||
}),
|
||||
// In new model, interactive overlay not implemented; keep library empty
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
exportingProvider.overrideWith((ref) => false),
|
||||
],
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:file_selector/file_selector.dart' as fs;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
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/pdf_view_model.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
||||
|
|
@ -23,7 +24,9 @@ void main() {
|
|||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => _TestPdfController(),
|
||||
),
|
||||
|
|
@ -32,7 +35,11 @@ void main() {
|
|||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: const Locale('en'),
|
||||
home: const PdfSignatureHomePage(),
|
||||
home: PdfSignatureHomePage(
|
||||
onPickPdf: () async {},
|
||||
onClosePdf: () {},
|
||||
currentFile: fs.XFile(''),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
|
@ -25,14 +26,16 @@ void main() {
|
|||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
documentRepositoryProvider.overrideWith((ref) => ctrl),
|
||||
],
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: const Locale('en'),
|
||||
home: const Scaffold(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 800,
|
||||
|
|
@ -44,6 +47,7 @@ void main() {
|
|||
onConfirmSignature: _noop,
|
||||
onClearActiveOverlay: _noop,
|
||||
onSelectPlaced: _noopInt,
|
||||
controller: PdfViewerController(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
|
|
@ -25,7 +26,9 @@ void main() {
|
|||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
// Continuous mode is always-on; no page view override needed
|
||||
documentRepositoryProvider.overrideWith((ref) => ctrl),
|
||||
],
|
||||
|
|
@ -33,7 +36,7 @@ void main() {
|
|||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: const Locale('en'),
|
||||
home: const Scaffold(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 800,
|
||||
|
|
@ -45,6 +48,7 @@ void main() {
|
|||
onConfirmSignature: _noop,
|
||||
onClearActiveOverlay: _noop,
|
||||
onSelectPlaced: _noopInt,
|
||||
controller: PdfViewerController(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_providers.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
|
||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||
import 'package:pdf_signature/domain/models/model.dart';
|
||||
|
|
@ -24,7 +25,9 @@ void main() {
|
|||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => _TestPdfController(),
|
||||
),
|
||||
|
|
@ -33,18 +36,19 @@ void main() {
|
|||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: const Locale('en'),
|
||||
home: const Scaffold(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 800,
|
||||
height: 520,
|
||||
child: PdfPageArea(
|
||||
pageSize: Size(676, 400),
|
||||
pageSize: const Size(676, 400),
|
||||
onDragSignature: _noopOffset,
|
||||
onResizeSignature: _noopOffset,
|
||||
onConfirmSignature: _noop,
|
||||
onClearActiveOverlay: _noop,
|
||||
onSelectPlaced: _noopInt,
|
||||
controller: PdfViewerController(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -66,7 +70,9 @@ void main() {
|
|||
// Use a persistent container across rebuilds
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
useMockViewerProvider.overrideWithValue(true),
|
||||
pdfViewModelProvider.overrideWith(
|
||||
(ref) => PdfViewModel(ref, useMockViewer: true),
|
||||
),
|
||||
documentRepositoryProvider.overrideWith(
|
||||
(ref) => DocumentStateNotifier()..openSample(),
|
||||
),
|
||||
|
|
@ -83,13 +89,14 @@ void main() {
|
|||
child: SizedBox(
|
||||
width: width,
|
||||
// Keep aspect ratio consistent with uiPageSize
|
||||
child: const PdfPageArea(
|
||||
child: PdfPageArea(
|
||||
pageSize: uiPageSize,
|
||||
onDragSignature: _noopOffset,
|
||||
onResizeSignature: _noopOffset,
|
||||
onConfirmSignature: _noop,
|
||||
onClearActiveOverlay: _noop,
|
||||
onSelectPlaced: _noopInt,
|
||||
controller: PdfViewerController(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,11 +26,15 @@ void main() {
|
|||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: WelcomeScreen(),
|
||||
home: WelcomeScreen(
|
||||
onPickPdf: () async {},
|
||||
onOpenPdf:
|
||||
({String? path, Uint8List? bytes, String? fileName}) async {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -39,8 +43,16 @@ void main() {
|
|||
final bytes = Uint8List.fromList([1, 2, 3, 4]);
|
||||
final fake = _FakeDropReadable('sample.pdf', '/tmp/sample.pdf', bytes);
|
||||
|
||||
// Use the top-level helper with the WidgetRef.read function
|
||||
await handleDroppedFiles(stateful.ref.read, [fake]);
|
||||
// Call handleDroppedFiles with the onOpenPdf callback from the widget
|
||||
await handleDroppedFiles(({
|
||||
String? path,
|
||||
Uint8List? bytes,
|
||||
String? fileName,
|
||||
}) async {
|
||||
final container = ProviderScope.containerOf(stateful.context);
|
||||
final repo = container.read(documentRepositoryProvider.notifier);
|
||||
repo.openPicked(pageCount: 1, bytes: bytes);
|
||||
}, [fake]);
|
||||
await tester.pump();
|
||||
|
||||
final container = ProviderScope.containerOf(stateful.context);
|
||||
|
|
|
|||
Loading…
Reference in New Issue