refactor: ui_services.dart to PdfExportViewModel for export functionality

This commit is contained in:
insleker 2025-09-18 21:31:30 +08:00
parent eee75f6fdb
commit 0c38178502
8 changed files with 104 additions and 55 deletions

View File

@ -14,7 +14,7 @@ import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_view_model.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:shared_preferences/shared_preferences.dart';
@ -77,13 +77,16 @@ void main() {
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: false),
),
exportServiceProvider.overrideWith((_) => fake),
savePathPickerProvider.overrideWith(
(_) => () async {
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(
ref,
exporter: fake,
savePathPicker: () async {
final dir = Directory.systemTemp.createTempSync('pdfsig_');
return '${dir.path}/output.pdf';
},
),
),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
@ -432,13 +435,18 @@ void main() {
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: false),
),
exportServiceProvider.overrideWith((ref) => LightweightExporter()),
savePathPickerProvider.overrideWith(
(_) => () async {
final dir = Directory.systemTemp.createTempSync('pdfsig_after_');
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(
ref,
exporter: LightweightExporter(),
savePathPicker: () async {
final dir = Directory.systemTemp.createTempSync(
'pdfsig_after_',
);
return '${dir.path}/output-after-export.pdf';
},
),
),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,

View File

@ -0,0 +1,53 @@
import 'package:file_selector/file_selector.dart' as fs;
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/services/export_service.dart';
/// ViewModel for export-related UI state and helpers.
class PdfExportViewModel extends ChangeNotifier {
final Ref ref;
bool _exporting = false;
// Dependencies (injectable via constructor for tests)
final ExportService _exporter;
final Future<String?> Function() _savePathPicker;
PdfExportViewModel(
this.ref, {
ExportService? exporter,
Future<String?> Function()? savePathPicker,
}) : _exporter = exporter ?? ExportService(),
_savePathPicker = savePathPicker ?? _defaultSavePathPicker;
bool get exporting => _exporting;
void setExporting(bool value) {
if (_exporting == value) return;
_exporting = value;
notifyListeners();
}
/// Get the export service (overridable in tests via constructor).
ExportService get exporter => _exporter;
/// Show save dialog and return the chosen path (null if canceled).
Future<String?> pickSavePath() async {
return _savePathPicker();
}
static Future<String?> _defaultSavePathPicker() async {
final group = fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
final location = await fs.getSaveLocation(
acceptedTypeGroups: [group],
suggestedName: 'signed.pdf',
confirmButtonText: 'Save',
);
return location?.path; // null if user cancels
}
}
final pdfExportViewModelProvider = ChangeNotifierProvider<PdfExportViewModel>((
ref,
) {
return PdfExportViewModel(ref);
});

View File

@ -12,7 +12,7 @@ import 'pdf_toolbar.dart';
import 'pdf_page_area.dart';
import 'pages_sidebar.dart';
import 'signatures_sidebar.dart';
import 'ui_services.dart';
import '../view_model/pdf_export_view_model.dart';
import 'package:pdf_signature/utils/download.dart';
import '../view_model/pdf_view_model.dart';
@ -133,7 +133,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
}
Future<void> _saveSignedPdf() async {
ref.read(exportingProvider.notifier).state = true;
ref.read(pdfExportViewModelProvider.notifier).setExporting(true);
try {
final pdf = _viewModel.document;
final messenger = ScaffoldMessenger.of(context);
@ -145,7 +145,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
);
return;
}
final exporter = ref.read(exportServiceProvider);
final exporter = ref.read(pdfExportViewModelProvider).exporter;
// get DPI from preferences
final targetDpi = ref.read(preferencesRepositoryProvider).exportDpi;
@ -153,8 +153,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
String? savedPath;
if (!kIsWeb) {
final pick = ref.read(savePathPickerProvider);
final path = await pick();
final path = await ref.read(pdfExportViewModelProvider).pickSavePath();
if (path == null || path.trim().isEmpty) return;
final fullPath = _ensurePdfExtension(path.trim());
savedPath = fullPath;
@ -216,7 +215,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
);
}
} finally {
ref.read(exportingProvider.notifier).state = false;
ref.read(pdfExportViewModelProvider.notifier).setExporting(false);
}
}
@ -362,7 +361,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
}
Widget _buildScaffold(BuildContext context) {
final isExporting = ref.watch(exportingProvider);
final isExporting = ref.watch(pdfExportViewModelProvider).exporting;
final l = AppLocalizations.of(context);
return Scaffold(
body: Padding(

View File

@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
import '../../signature/widgets/signature_drawer.dart';
import 'ui_services.dart';
import '../view_model/pdf_export_view_model.dart';
class SignaturesSidebar extends ConsumerWidget {
const SignaturesSidebar({
@ -21,7 +21,7 @@ class SignaturesSidebar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final isExporting = ref.watch(exportingProvider);
final isExporting = ref.watch(pdfExportViewModelProvider).exporting;
return AbsorbPointer(
absorbing: isExporting,
child: Card(

View File

@ -1,23 +0,0 @@
import 'package:file_selector/file_selector.dart' as fs;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/services/export_service.dart';
/// Global exporting flag used to disable parts of the UI during long tasks.
final exportingProvider = StateProvider<bool>((ref) => false);
/// Provider for the export service. Can be overridden in tests.
final exportServiceProvider = Provider<ExportService>((ref) => ExportService());
/// Provider for a function that picks a save path. Tests may override.
final savePathPickerProvider = Provider<Future<String?> Function()>((ref) {
return () async {
// Desktop save dialog with PDF filter; mobile platforms may not support this.
final group = fs.XTypeGroup(label: 'PDF', extensions: ['pdf']);
final location = await fs.getSaveLocation(
acceptedTypeGroups: [group],
suggestedName: 'signed.pdf',
confirmButtonText: 'Save',
);
return location?.path; // null if user cancels
};
});

View File

@ -7,7 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/app.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_view_model.dart';
import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/domain/models/model.dart';
@ -51,8 +51,13 @@ Future<ProviderContainer> pumpApp(
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: true),
),
exportServiceProvider.overrideWith((ref) => fakeExport),
savePathPickerProvider.overrideWith((ref) => () async => 'out.pdf'),
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(
ref,
exporter: fakeExport,
savePathPicker: () async => 'out.pdf',
),
),
],
);
await tester.pumpWidget(

View File

@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_view_model.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
@ -62,9 +62,12 @@ void main() {
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: true),
),
exportServiceProvider.overrideWith((_) => fake),
savePathPickerProvider.overrideWith(
(_) => () async => 'C:/tmp/output.pdf',
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(
ref,
exporter: fake,
savePathPicker: () async => 'C:/tmp/output.pdf',
),
),
],
child: MaterialApp(

View File

@ -7,7 +7,7 @@ import 'package:image/image.dart' as img;
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_view_model.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_card_repository.dart';
@ -26,7 +26,9 @@ Future<void> pumpWithOpenPdf(WidgetTester tester) async {
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: true),
),
exportingProvider.overrideWith((ref) => false),
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(ref),
),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
@ -398,7 +400,9 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
pdfViewModelProvider.overrideWith(
(ref) => PdfViewModel(ref, useMockViewer: true),
),
exportingProvider.overrideWith((ref) => false),
pdfExportViewModelProvider.overrideWith(
(ref) => PdfExportViewModel(ref),
),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,