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