diff --git a/lib/core/navigation/push_routes.dart b/lib/core/navigation/push_routes.dart index 17b03a1..07b5beb 100644 --- a/lib/core/navigation/push_routes.dart +++ b/lib/core/navigation/push_routes.dart @@ -347,7 +347,6 @@ Future pushDocumentUploadPreparationPage( create: (_) => DocumentUploadCubit( context.read(), context.read(), - context.read(), ), child: DocumentUploadPreparationPage( fileBytes: bytes, diff --git a/lib/features/document_scan/cubit/document_scanner_cubit.dart b/lib/features/document_scan/cubit/document_scanner_cubit.dart index 2d162d1..37f699f 100644 --- a/lib/features/document_scan/cubit/document_scanner_cubit.dart +++ b/lib/features/document_scan/cubit/document_scanner_cubit.dart @@ -5,9 +5,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/service/file_service.dart'; +import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; class DocumentScannerCubit extends Cubit> { - DocumentScannerCubit() : super(const []); + final LocalNotificationService _notificationService; + + DocumentScannerCubit(this._notificationService) : super(const []); void addScan(File file) => emit([...state, file]); @@ -36,4 +40,15 @@ class DocumentScannerCubit extends Cubit> { throw const PaperlessServerException(ErrorCode.scanRemoveFailed); } } + + Future saveLocally( + Uint8List bytes, String fileName, String preferredLocaleSubtag) async { + var file = await FileService.saveToFile(bytes, fileName); + _notificationService.notifyFileSaved( + filename: fileName, + filePath: file.path, + finished: true, + locale: preferredLocaleSubtag, + ); + } } diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 0f5c705..e58294b 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -7,8 +7,13 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:hive/hive.dart'; import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/constants.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; +import 'package:paperless_mobile/core/config/hive/hive_config.dart'; +import 'package:paperless_mobile/core/database/tables/global_settings.dart'; import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart'; import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/navigation/push_routes.dart'; @@ -35,9 +40,13 @@ class ScannerPage extends StatefulWidget { State createState() => _ScannerPageState(); } -class _ScannerPageState extends State with SingleTickerProviderStateMixin { - final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle(); - final SliverOverlapAbsorberHandle actionsHandle = SliverOverlapAbsorberHandle(); +class _ScannerPageState extends State + with SingleTickerProviderStateMixin { + final SliverOverlapAbsorberHandle searchBarHandle = + SliverOverlapAbsorberHandle(); + final SliverOverlapAbsorberHandle actionsHandle = + SliverOverlapAbsorberHandle(); + final _downloadFormKey = GlobalKey(); @override Widget build(BuildContext context) { @@ -116,11 +125,13 @@ class _ScannerPageState extends State with SingleTickerProviderStat builder: (context, state) { return TextButton.icon( label: Text(S.of(context)!.previewScan), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ), onPressed: state.isNotEmpty ? () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => DocumentView( - documentBytes: _assembleFileBytes( state, forcePdf: true, @@ -133,10 +144,82 @@ class _ScannerPageState extends State with SingleTickerProviderStat ); }, ), + BlocBuilder>( + builder: (context, state) { + return TextButton.icon( + label: Text("Export"), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ), + onPressed: state.isEmpty + ? null + : () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Stack( + children: [ + FormBuilder( + key: _downloadFormKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.all(8.0), + child: FormBuilderTextField( + autovalidateMode: + AutovalidateMode.always, + validator: (value) { + if (value == null || + value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + decoration: InputDecoration( + labelText: + S.of(context)!.fileName, + ), + initialValue: "test", + name: 'filename', + )), + TextButton.icon( + label: const Text( + "Save a local copy"), + icon: const Icon(Icons.download), + onPressed: () => { + if (_downloadFormKey + .currentState! + .validate()) + { + _onLocalSave().then( + (value) => Navigator.of( + context) + .pop()) + } + }, + ) + ], + ), + ), + ], + ), + ); + }); + }, + icon: const Icon(Icons.download), + ); + }, + ), BlocBuilder>( builder: (context, state) { return TextButton.icon( label: Text(S.of(context)!.clearAll), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ), onPressed: state.isEmpty ? null : () => _reset(context), icon: const Icon(Icons.delete_sweep_outlined), ); @@ -146,6 +229,9 @@ class _ScannerPageState extends State with SingleTickerProviderStat builder: (context, state) { return TextButton.icon( label: Text(S.of(context)!.upload), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ), onPressed: state.isEmpty || !isConnected ? null : () => _onPrepareDocumentUpload(context), @@ -175,7 +261,8 @@ class _ScannerPageState extends State with SingleTickerProviderStat final success = await EdgeDetection.detectEdge(file.path); if (!success) { if (kDebugMode) { - dev.log('[ScannerPage] Scan either not successful or canceled by user.'); + dev.log( + '[ScannerPage] Scan either not successful or canceled by user.'); } return; } @@ -197,7 +284,38 @@ class _ScannerPageState extends State with SingleTickerProviderStat if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) { // For paperless version older than 1.11.3, task id will always be null! context.read().reset(); - context.read().listenToTaskChanges(uploadResult!.taskId!); + context + .read() + .listenToTaskChanges(uploadResult!.taskId!); + } + } + + Future _onLocalSave() async { + final cubit = context.read(); + final file = await _assembleFileBytes( + forcePdf: true, + context.read().state, + ); + try { + final globalSettings = + Hive.box(HiveBoxes.globalSettings).getValue()!; + if (Platform.isAndroid && androidInfo!.version.sdkInt <= 29) { + final isGranted = await askForPermission(Permission.storage); + if (!isGranted) { + return; + //TODO: Ask user to grant permissions + } + } + final name = (_downloadFormKey.currentState?.fields['filename']?.value ?? + "") as String; + + var fileName = "$name.pdf"; + + await cubit.saveLocally( + file.bytes, fileName, globalSettings.preferredLocaleSubtag); + _downloadFormKey.currentState!.save(); + } catch (error) { + showGenericError(context, error); } } diff --git a/lib/features/document_upload/cubit/document_upload_cubit.dart b/lib/features/document_upload/cubit/document_upload_cubit.dart index 0bc5a23..e066c15 100644 --- a/lib/features/document_upload/cubit/document_upload_cubit.dart +++ b/lib/features/document_upload/cubit/document_upload_cubit.dart @@ -5,8 +5,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; -import 'package:paperless_mobile/core/service/file_service.dart'; part 'document_upload_state.dart'; @@ -15,9 +13,7 @@ class DocumentUploadCubit extends Cubit { final LabelRepository _labelRepository; - final LocalNotificationService _notificationService; - - DocumentUploadCubit(this._labelRepository, this._documentApi, this._notificationService) + DocumentUploadCubit(this._labelRepository, this._documentApi) : super(const DocumentUploadState()) { _labelRepository.addListener( this, @@ -53,18 +49,6 @@ class DocumentUploadCubit extends Cubit { ); } - Future saveLocally( - Uint8List bytes, String fileName, String preferredLocaleSubtag - ) async { - var file = await FileService.saveToFile(bytes, fileName); - _notificationService.notifyFileSaved( - filename: fileName, - filePath: file.path, - finished: true, - locale: preferredLocaleSubtag, - ); - } - @override Future close() async { _labelRepository.removeListener(this); diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart index 0b94bce..cb5be3a 100644 --- a/lib/features/document_upload/view/document_upload_preparation_page.dart +++ b/lib/features/document_upload/view/document_upload_preparation_page.dart @@ -1,17 +1,12 @@ -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:hive/hive.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/constants.dart'; -import 'package:paperless_mobile/core/config/hive/hive_config.dart'; -import 'package:paperless_mobile/core/database/tables/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/type/types.dart'; @@ -24,8 +19,6 @@ import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.d import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; -import 'package:paperless_mobile/helpers/permission_helpers.dart'; -import 'package:permission_handler/permission_handler.dart'; class DocumentUploadResult { final bool success; @@ -82,8 +75,6 @@ class _DocumentUploadPreparationPageState extends State _buildBottomAppBar() { - return BlocBuilder( - builder: (context, state) { - return BottomAppBar( - child: BlocBuilder( - builder: (context, connectivityState) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - IconButton( - tooltip: "Save a local copy", - icon: const Icon(Icons.download), - onPressed: () => _onLocalSave(), - ).paddedOnly(right: 4.0), - ], - ); - }, - ), - ); - }, - ); - } - - String _padWithExtension(String source, [String? extension]) { final ext = extension ?? '.pdf'; return source.endsWith(ext) ? source : '$source$ext'; @@ -332,27 +299,4 @@ class _DocumentUploadPreparationPageState extends State _onLocalSave() async { - final cubit = context.read(); - - try { - final globalSettings = Hive.box(HiveBoxes.globalSettings).getValue()!; - if (Platform.isAndroid && androidInfo!.version.sdkInt <= 29) { - final isGranted = await askForPermission(Permission.storage); - if (!isGranted) { - return; - //TODO: Ask user to grant permissions - } - } - final title = (_formKey.currentState?.fields[fkFileName]?.value ?? widget.filename) as String; - - var fileName = "$title.${widget.fileExtension}"; - - await cubit.saveLocally(widget.fileBytes, fileName, globalSettings.preferredLocaleSubtag); - } catch (error) { - showGenericError(context, error); - } - } - } diff --git a/lib/features/home/view/home_route.dart b/lib/features/home/view/home_route.dart index 076c218..c310000 100644 --- a/lib/features/home/view/home_route.dart +++ b/lib/features/home/view/home_route.dart @@ -117,7 +117,7 @@ class HomeRoute extends StatelessWidget { .get(currentLocalUserId)!, )..reload(), ), - Provider(create: (context) => DocumentScannerCubit()), + Provider(create: (context) => DocumentScannerCubit(context.read())), ProxyProvider4( update: (context, docApi, statsApi, labelRepo, notifier, previous) =>