From dc552dc4a7a064162c48a2ae8c2413503ec4bf64 Mon Sep 17 00:00:00 2001 From: "konrad.lys@eu.equinix.com" Date: Thu, 1 Jun 2023 12:59:29 +0200 Subject: [PATCH 1/7] FEATURE download a document pdf file without uploading it to server --- lib/core/navigation/push_routes.dart | 1 + .../cubit/document_upload_cubit.dart | 18 +++++- .../document_upload_preparation_page.dart | 56 +++++++++++++++++++ .../services/local_notification_service.dart | 44 +++++++++++++++ packages/mock_server/pubspec.yaml | 2 +- 5 files changed, 119 insertions(+), 2 deletions(-) diff --git a/lib/core/navigation/push_routes.dart b/lib/core/navigation/push_routes.dart index 07b5beb..17b03a1 100644 --- a/lib/core/navigation/push_routes.dart +++ b/lib/core/navigation/push_routes.dart @@ -347,6 +347,7 @@ Future pushDocumentUploadPreparationPage( create: (_) => DocumentUploadCubit( context.read(), context.read(), + context.read(), ), child: DocumentUploadPreparationPage( fileBytes: bytes, diff --git a/lib/features/document_upload/cubit/document_upload_cubit.dart b/lib/features/document_upload/cubit/document_upload_cubit.dart index e066c15..0bc5a23 100644 --- a/lib/features/document_upload/cubit/document_upload_cubit.dart +++ b/lib/features/document_upload/cubit/document_upload_cubit.dart @@ -5,6 +5,8 @@ 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'; @@ -13,7 +15,9 @@ class DocumentUploadCubit extends Cubit { final LabelRepository _labelRepository; - DocumentUploadCubit(this._labelRepository, this._documentApi) + final LocalNotificationService _notificationService; + + DocumentUploadCubit(this._labelRepository, this._documentApi, this._notificationService) : super(const DocumentUploadState()) { _labelRepository.addListener( this, @@ -49,6 +53,18 @@ 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 cb5be3a..0b94bce 100644 --- a/lib/features/document_upload/view/document_upload_preparation_page.dart +++ b/lib/features/document_upload/view/document_upload_preparation_page.dart @@ -1,12 +1,17 @@ +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'; @@ -19,6 +24,8 @@ 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; @@ -75,6 +82,8 @@ 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'; @@ -299,4 +332,27 @@ 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/notifications/services/local_notification_service.dart b/lib/features/notifications/services/local_notification_service.dart index 089132c..9f69782 100644 --- a/lib/features/notifications/services/local_notification_service.dart +++ b/lib/features/notifications/services/local_notification_service.dart @@ -90,6 +90,50 @@ class LocalNotificationService { ); //TODO: INTL } + Future notifyFileSaved({ + required String filename, + required String filePath, + required bool finished, + required String locale, + }) async { + final tr = await S.delegate.load(Locale(locale)); + + await _plugin.show( + filePath.hashCode, + filename, + finished + ? tr.notificationDownloadComplete + : tr.notificationDownloadingDocument, + NotificationDetails( + android: AndroidNotificationDetails( + NotificationChannel.documentDownload.id + "_$filename", + NotificationChannel.documentDownload.name, + ongoing: !finished, + indeterminate: true, + importance: Importance.max, + priority: Priority.high, + showProgress: !finished, + when: DateTime.now().millisecondsSinceEpoch, + category: AndroidNotificationCategory.progress, + icon: finished ? 'file_download_done' : 'downloading', + ), + iOS: DarwinNotificationDetails( + attachments: [ + DarwinNotificationAttachment( + filePath, + ), + ], + ), + ), + payload: jsonEncode( + OpenDownloadedDocumentPayload( + filePath: filePath, + ).toJson(), + ), + ); //TODO: INTL + } + + //TODO: INTL Future notifyTaskChanged(Task task) { log("[LocalNotificationService] notifyTaskChanged: ${task.toString()}"); diff --git a/packages/mock_server/pubspec.yaml b/packages/mock_server/pubspec.yaml index 4963a32..ccbc099 100644 --- a/packages/mock_server/pubspec.yaml +++ b/packages/mock_server/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: logging: ^1.1.1 flutter: sdk: flutter - http: ^0.13.4 + http: ^0.13.5 dev_dependencies: flutter_test: From bf0351f23f58bdca5e2dd220e866a116cd80d837 Mon Sep 17 00:00:00 2001 From: "konrad.lys@eu.equinix.com" Date: Mon, 5 Jun 2023 20:07:38 +0200 Subject: [PATCH 2/7] FEATURE move button to the scanner page --- lib/core/navigation/push_routes.dart | 1 - .../cubit/document_scanner_cubit.dart | 17 ++- .../document_scan/view/scanner_page.dart | 130 +++++++++++++++++- .../cubit/document_upload_cubit.dart | 18 +-- .../document_upload_preparation_page.dart | 56 -------- lib/features/home/view/home_route.dart | 2 +- 6 files changed, 142 insertions(+), 82 deletions(-) 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) => From fab33afc87df2c261b13c25ba87d45f6f44d77c9 Mon Sep 17 00:00:00 2001 From: "konrad.lys@eu.equinix.com" Date: Tue, 6 Jun 2023 06:26:38 +0200 Subject: [PATCH 3/7] FEATURE add translation --- .../document_scan/view/scanner_page.dart | 23 +++++++++++-------- lib/l10n/intl_en.arb | 4 ++++ lib/l10n/intl_pl.arb | 4 ++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index e58294b..e0d3ec2 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -42,6 +42,8 @@ class ScannerPage extends StatefulWidget { class _ScannerPageState extends State with SingleTickerProviderStateMixin { + static const fkFileName = "filename"; + final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle(); final SliverOverlapAbsorberHandle actionsHandle = @@ -147,7 +149,7 @@ class _ScannerPageState extends State BlocBuilder>( builder: (context, state) { return TextButton.icon( - label: Text("Export"), + label: Text(S.of(context)!.export), style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), ), @@ -172,9 +174,11 @@ class _ScannerPageState extends State autovalidateMode: AutovalidateMode.always, validator: (value) { - if (value == null || - value.isEmpty) { - return 'Please enter some text'; + if (value?.trim().isEmpty ?? + true) { + return S + .of(context)! + .thisFieldIsRequired; } return null; }, @@ -182,12 +186,11 @@ class _ScannerPageState extends State labelText: S.of(context)!.fileName, ), - initialValue: "test", - name: 'filename', + name: fkFileName, )), TextButton.icon( - label: const Text( - "Save a local copy"), + label: + Text(S.of(context)!.export), icon: const Icon(Icons.download), onPressed: () => { if (_downloadFormKey @@ -306,8 +309,8 @@ class _ScannerPageState extends State //TODO: Ask user to grant permissions } } - final name = (_downloadFormKey.currentState?.fields['filename']?.value ?? - "") as String; + final name = + _downloadFormKey.currentState?.fields[fkFileName]!.value as String; var fileName = "$name.pdf"; diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index dbf767f..9347625 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -811,5 +811,9 @@ "goToLogin": "Go to login", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" + }, + "export": "Export", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 8d9911b..f9b2801 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -811,5 +811,9 @@ "goToLogin": "Przejdź do logowania", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" + }, + "export": "Eksport", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" } } \ No newline at end of file From 421479d2800194acc2822a73b649120281541a29 Mon Sep 17 00:00:00 2001 From: "konrad.lys@eu.equinix.com" Date: Fri, 9 Jun 2023 07:49:14 +0200 Subject: [PATCH 4/7] FEATURE dialog styling --- .../document_scan/view/scanner_page.dart | 104 ++++++++++-------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index e0d3ec2..142ecd9 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -19,6 +19,8 @@ import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/navigation/push_routes.dart'; import 'package:paperless_mobile/core/service/file_description.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart'; import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart'; @@ -160,59 +162,67 @@ class _ScannerPageState extends State 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?.trim().isEmpty ?? - true) { - return S - .of(context)! - .thisFieldIsRequired; - } - return null; - }, - decoration: InputDecoration( - labelText: - S.of(context)!.fileName, - ), - name: fkFileName, - )), - TextButton.icon( - label: - Text(S.of(context)!.export), - icon: const Icon(Icons.download), - onPressed: () => { - if (_downloadFormKey - .currentState! - .validate()) - { - _onLocalSave().then( - (value) => Navigator.of( - context) - .pop()) - } + title: Text(S.of(context)!.export), + content: 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?.trim().isEmpty ?? + true) { + return S + .of(context)! + .thisFieldIsRequired; + } + return null; }, - ) - ], + decoration: InputDecoration( + labelText: + S.of(context)!.fileName, + ), + name: fkFileName, + )), + ], + ), + ), + actions: [ + const DialogCancelButton(), + ElevatedButton( + child: Text(S.of(context)!.export), + style: ButtonStyle( + backgroundColor: + MaterialStatePropertyAll( + Theme.of(context) + .colorScheme + .primaryContainer, + ), + foregroundColor: + MaterialStatePropertyAll( + Theme.of(context) + .colorScheme + .onPrimaryContainer, ), ), - ], - ), + onPressed: () => { + if (_downloadFormKey.currentState! + .validate()) + { + _onLocalSave().then((value) => + Navigator.of(context).pop()) + } + }, + ), + ], ); }); }, - icon: const Icon(Icons.download), + icon: const Icon(Icons.download_outlined), ); }, ), From 5270c37a5b13e86f97736a6b53eb2364d31d207c Mon Sep 17 00:00:00 2001 From: "konrad.lys@eu.equinix.com" Date: Fri, 9 Jun 2023 19:24:05 +0200 Subject: [PATCH 5/7] FEATURE character validation --- lib/features/document_scan/view/scanner_page.dart | 9 ++++++++- lib/l10n/intl_en.arb | 4 ++++ lib/l10n/intl_pl.arb | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 142ecd9..6c8d0df 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -180,6 +180,14 @@ class _ScannerPageState extends State .of(context)! .thisFieldIsRequired; } + if (value?.trim().contains( + RegExp( + r'[<>:"/|?*]')) ?? + true) { + return S + .of(context)! + .invalidFilenameCharacter; + } return null; }, decoration: InputDecoration( @@ -326,7 +334,6 @@ class _ScannerPageState extends State await cubit.saveLocally( file.bytes, fileName, globalSettings.preferredLocaleSubtag); - _downloadFormKey.currentState!.save(); } catch (error) { showGenericError(context, error); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 9347625..f652a77 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -815,5 +815,9 @@ "export": "Export", "@export": { "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid filename character found", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index f9b2801..e640345 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -815,5 +815,9 @@ "export": "Eksport", "@export": { "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Znaleziono niedozwolony znak w nazwie pliku", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" } } \ No newline at end of file From 46e30d833bf6f7612ecc1c37bb68e1d49895e1e1 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Sat, 10 Jun 2023 19:09:28 +0200 Subject: [PATCH 6/7] feat: Refactor export functionality, add new translations --- .../dialog_utils/dialog_confirm_button.dart | 12 +- .../cubit/document_scanner_cubit.dart | 7 +- .../document_scan/view/scanner_page.dart | 343 +++++++----------- .../view/widgets/export_scans_dialog.dart | 88 +++++ .../view/widgets/scanned_image_item.dart | 2 +- lib/l10n/intl_ca.arb | 15 +- lib/l10n/intl_cs.arb | 15 +- lib/l10n/intl_de.arb | 15 +- lib/l10n/intl_en.arb | 9 +- lib/l10n/intl_fr.arb | 31 +- lib/l10n/intl_pl.arb | 7 +- lib/l10n/intl_ru.arb | 15 +- lib/l10n/intl_tr.arb | 15 +- 13 files changed, 348 insertions(+), 226 deletions(-) create mode 100644 lib/features/document_scan/view/widgets/export_scans_dialog.dart diff --git a/lib/core/widgets/dialog_utils/dialog_confirm_button.dart b/lib/core/widgets/dialog_utils/dialog_confirm_button.dart index f1083c4..037d187 100644 --- a/lib/core/widgets/dialog_utils/dialog_confirm_button.dart +++ b/lib/core/widgets/dialog_utils/dialog_confirm_button.dart @@ -9,12 +9,19 @@ enum DialogConfirmButtonStyle { class DialogConfirmButton extends StatelessWidget { final DialogConfirmButtonStyle style; final String? label; + + /// The value [Navigator.pop] will be called with. If [onPressed] is + /// specified, this value will be ignored. final T? returnValue; + + /// Function called when the button is pressed. Takes precedence over [returnValue]. + final void Function()? onPressed; const DialogConfirmButton({ super.key, this.style = DialogConfirmButtonStyle.normal, this.label, this.returnValue, + this.onPressed, }); @override @@ -45,10 +52,13 @@ class DialogConfirmButton extends StatelessWidget { _style = _dangerStyle; break; } + + final effectiveOnPressed = + onPressed ?? () => Navigator.of(context).pop(returnValue ?? true); return ElevatedButton( child: Text(label ?? S.of(context)!.confirm), style: _style, - onPressed: () => Navigator.of(context).pop(returnValue ?? true), + onPressed: effectiveOnPressed, ); } } diff --git a/lib/features/document_scan/cubit/document_scanner_cubit.dart b/lib/features/document_scan/cubit/document_scanner_cubit.dart index 37f699f..3128a56 100644 --- a/lib/features/document_scan/cubit/document_scanner_cubit.dart +++ b/lib/features/document_scan/cubit/document_scanner_cubit.dart @@ -41,8 +41,11 @@ class DocumentScannerCubit extends Cubit> { } } - Future saveLocally( - Uint8List bytes, String fileName, String preferredLocaleSubtag) async { + Future saveToFile( + Uint8List bytes, + String fileName, + String preferredLocaleSubtag, + ) async { var file = await FileService.saveToFile(bytes, fileName); _notificationService.notifyFileSaved( filename: fileName, diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 6c8d0df..932c0e3 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -7,22 +7,19 @@ 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'; import 'package:paperless_mobile/core/service/file_description.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; -import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; -import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart'; +import 'package:paperless_mobile/features/document_scan/view/widgets/export_scans_dialog.dart'; import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; @@ -34,6 +31,7 @@ import 'package:path/path.dart' as p; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:permission_handler/permission_handler.dart'; +import 'package:sliver_tools/sliver_tools.dart'; class ScannerPage extends StatefulWidget { const ScannerPage({Key? key}) : super(key: key); @@ -44,13 +42,12 @@ class ScannerPage extends StatefulWidget { class _ScannerPageState extends State with SingleTickerProviderStateMixin { - static const fkFileName = "filename"; - final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle(); final SliverOverlapAbsorberHandle actionsHandle = SliverOverlapAbsorberHandle(); - final _downloadFormKey = GlobalKey(); + + final _scrollController = ScrollController(); @override Widget build(BuildContext context) { @@ -80,13 +77,8 @@ class _ScannerPageState extends State ), SliverOverlapAbsorber( handle: actionsHandle, - sliver: SliverPersistentHeader( - pinned: true, - delegate: CustomizableSliverPersistentHeaderDelegate( - child: _buildActions(connectedState.isConnected), - maxExtent: kTextTabBarHeight, - minExtent: kTextTabBarHeight, - ), + sliver: SliverPinnedHeader( + child: _buildActions(connectedState.isConnected), ), ), ], @@ -121,151 +113,117 @@ class _ScannerPageState extends State color: Theme.of(context).colorScheme.background, child: SizedBox( height: kTextTabBarHeight, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - BlocBuilder>( - 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, - ).then((file) => file.bytes), + child: BlocBuilder>( + builder: (context, state) { + return RawScrollbar( + padding: EdgeInsets.fromLTRB(16, 0, 16, 4), + interactive: false, + thumbVisibility: true, + thickness: 2, + radius: Radius.circular(2), + controller: _scrollController, + child: ListView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + children: [ + SizedBox(width: 12), + 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, + ).then((file) => file.bytes), + ), ), - ), - ) - : null, - icon: const Icon(Icons.visibility_outlined), - ); - }, - ), - BlocBuilder>( - builder: (context, state) { - return TextButton.icon( - label: Text(S.of(context)!.export), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ) + : null, + icon: const Icon(Icons.visibility_outlined), ), - onPressed: state.isEmpty - ? null - : () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(S.of(context)!.export), - content: 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?.trim().isEmpty ?? - true) { - return S - .of(context)! - .thisFieldIsRequired; - } - if (value?.trim().contains( - RegExp( - r'[<>:"/|?*]')) ?? - true) { - return S - .of(context)! - .invalidFilenameCharacter; - } - return null; - }, - decoration: InputDecoration( - labelText: - S.of(context)!.fileName, - ), - name: fkFileName, - )), - ], - ), - ), - actions: [ - const DialogCancelButton(), - ElevatedButton( - child: Text(S.of(context)!.export), - style: ButtonStyle( - backgroundColor: - MaterialStatePropertyAll( - Theme.of(context) - .colorScheme - .primaryContainer, - ), - foregroundColor: - MaterialStatePropertyAll( - Theme.of(context) - .colorScheme - .onPrimaryContainer, - ), - ), - onPressed: () => { - if (_downloadFormKey.currentState! - .validate()) - { - _onLocalSave().then((value) => - Navigator.of(context).pop()) - } - }, - ), - ], - ); - }); - }, - icon: const Icon(Icons.download_outlined), - ); - }, - ), - BlocBuilder>( - builder: (context, state) { - return TextButton.icon( - label: Text(S.of(context)!.clearAll), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + SizedBox(width: 8), + 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), ), - onPressed: state.isEmpty ? null : () => _reset(context), - icon: const Icon(Icons.delete_sweep_outlined), - ); - }, - ), - BlocBuilder>( - builder: (context, state) { - return TextButton.icon( - label: Text(S.of(context)!.upload), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + SizedBox(width: 8), + 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), + icon: const Icon(Icons.upload_outlined), ), - onPressed: state.isEmpty || !isConnected - ? null - : () => _onPrepareDocumentUpload(context), - icon: const Icon(Icons.upload_outlined), - ); - }, - ), - ], + SizedBox(width: 8), + TextButton.icon( + label: Text(S.of(context)!.export), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), + ), + onPressed: state.isEmpty ? null : _onSaveToFile, + icon: const Icon(Icons.save_alt_outlined), + ), + SizedBox(width: 12), + ], + ), + ); + }, ), ), ); } + void _onSaveToFile() async { + final fileName = await showDialog( + context: context, + builder: (context) => const ExportScansDialog(), + ); + if (fileName != null) { + 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) { + showSnackBar( + context, + "Please grant permissions for Paperless Mobile to access your filesystem.", + action: SnackBarActionConfig( + label: "GO", + onPressed: openAppSettings, + ), + ); + return; + } + } + await cubit.saveToFile( + file.bytes, + "$fileName.pdf", + globalSettings.preferredLocaleSubtag, + ); + } catch (error) { + showGenericError(context, error); + } + } + } + void _openDocumentScanner(BuildContext context) async { final isGranted = await askForPermission(Permission.camera); if (!isGranted) { @@ -311,34 +269,6 @@ class _ScannerPageState extends State } } - 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[fkFileName]!.value as String; - - var fileName = "$name.pdf"; - - await cubit.saveLocally( - file.bytes, fileName, globalSettings.preferredLocaleSubtag); - } catch (error) { - showGenericError(context, error); - } - } - Widget _buildEmptyState(bool isConnected, List scans) { if (scans.isNotEmpty) { return _buildImageGrid(scans); @@ -369,34 +299,37 @@ class _ScannerPageState extends State } Widget _buildImageGrid(List scans) { - return CustomScrollView( - slivers: [ - SliverOverlapInjector(handle: searchBarHandle), - SliverOverlapInjector(handle: actionsHandle), - SliverGrid.builder( - itemCount: scans.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - childAspectRatio: 1 / sqrt(2), - crossAxisSpacing: 10, - mainAxisSpacing: 10, + return Padding( + padding: const EdgeInsets.all(8.0), + child: CustomScrollView( + slivers: [ + SliverOverlapInjector(handle: searchBarHandle), + SliverOverlapInjector(handle: actionsHandle), + SliverGrid.builder( + itemCount: scans.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1 / sqrt(2), + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemBuilder: (context, index) { + return ScannedImageItem( + file: scans[index], + onDelete: () async { + try { + context.read().removeScan(index); + } on PaperlessServerException catch (error, stackTrace) { + showErrorMessage(context, error, stackTrace); + } + }, + index: index, + totalNumberOfFiles: scans.length, + ); + }, ), - itemBuilder: (context, index) { - return ScannedImageItem( - file: scans[index], - onDelete: () async { - try { - context.read().removeScan(index); - } on PaperlessServerException catch (error, stackTrace) { - showErrorMessage(context, error, stackTrace); - } - }, - index: index, - totalNumberOfFiles: scans.length, - ); - }, - ), - ], + ], + ), ); } diff --git a/lib/features/document_scan/view/widgets/export_scans_dialog.dart b/lib/features/document_scan/view/widgets/export_scans_dialog.dart new file mode 100644 index 0000000..9fd9f66 --- /dev/null +++ b/lib/features/document_scan/view/widgets/export_scans_dialog.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart'; +import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; + +class ExportScansDialog extends StatefulWidget { + const ExportScansDialog({super.key}); + + @override + State createState() => _ExportScansDialogState(); +} + +class _ExportScansDialogState extends State { + final _formKey = GlobalKey(); + String? _filename; + late String _placeholder; + + @override + void initState() { + super.initState(); + final date = DateFormat("yyyy_MM_ddThhmmss").format(DateTime.now()); + _placeholder = "paperless_mobile_scan_$date"; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + insetPadding: EdgeInsets.all(8), + title: Text(S.of(context)!.exportScansToPdf), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(S.of(context)!.allScansWillBeMerged), + SizedBox(height: 16), + TextFormField( + onSaved: (newValue) { + _filename = newValue; + }, + autofocus: true, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) { + final matches = RegExp(r'[<>:"/\|?*]').allMatches(value!); + if (matches.isNotEmpty) { + final illegalCharacters = matches + .map((match) => match.group(0)) + .toList() + .toSet() + .join(" "); + return S + .of(context)! + .invalidFilenameCharacter(illegalCharacters); + } + + return null; + }, + decoration: InputDecoration( + labelText: S.of(context)!.fileName, + errorMaxLines: 5, + suffixText: ".pdf", + hintText: _placeholder, + ), + ), + ], + ), + ), + actions: [ + const DialogCancelButton(), + DialogConfirmButton( + label: S.of(context)!.export, + onPressed: () async { + if (_formKey.currentState?.validate() ?? false) { + _formKey.currentState?.save(); + final effectiveFilename = (_filename?.trim().isEmpty ?? true) + ? _placeholder + : _filename; + Navigator.pop(context, effectiveFilename); + } + }, + ), + ], + ); + } +} diff --git a/lib/features/document_scan/view/widgets/scanned_image_item.dart b/lib/features/document_scan/view/widgets/scanned_image_item.dart index adb597f..40b32bc 100644 --- a/lib/features/document_scan/view/widgets/scanned_image_item.dart +++ b/lib/features/document_scan/view/widgets/scanned_image_item.dart @@ -61,7 +61,7 @@ class _ScannedImageItemState extends State { width: double.infinity, height: 100, child: FittedBox( - fit: BoxFit.fill, + fit: BoxFit.cover, clipBehavior: Clip.antiAliasWithSaveLayer, alignment: Alignment.center, child: Image.file( diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index bb5a11a..abb8dbb 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -811,5 +811,18 @@ "goToLogin": "Anar al login", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Exporta", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index d115c5b..eafbf76 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -811,5 +811,18 @@ "goToLogin": "Go to login", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Export", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 7ff7eb1..d33e552 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -811,5 +811,18 @@ "goToLogin": "Gehe zur Anmeldung", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Exportieren", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Ungültige(s) Zeichen im Dateinamen gefunden: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Scans als PDF exportieren", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "Alle Scans werden in eine einzige PDF-Datei zusammengeführt." } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index f652a77..c07a420 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -816,8 +816,13 @@ "@export": { "description": "Label for button that exports scanned images to pdf (before upload)" }, - "invalidFilenameCharacter": "Invalid filename character found", + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", "@invalidFilenameCharacter": { "description": "For validating filename in export dialogue" - } + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 54d7ccc..47d1aea 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -780,36 +780,49 @@ "@alwaysAsk": { "description": "Option to choose when the app should always ask the user which filetype to use" }, - "disableMatching": "Do not tag documents automatically", + "disableMatching": "Ne pas étiqueter les documents automatiquement", "@disableMatching": { "description": "One of the options for automatic tagging of documents" }, - "none": "None", + "none": "Aucun", "@none": { "description": "One of available enum values of matching algorithm for tags" }, - "logInToExistingAccount": "Log in to existing account", + "logInToExistingAccount": "Se connecter à un compte existant", "@logInToExistingAccount": { "description": "Title shown on login page if at least one user is already known to the app." }, - "print": "Print", + "print": "Imprimer", "@print": { "description": "Tooltip for print button" }, - "managePermissions": "Manage permissions", + "managePermissions": "Gérer les permissions", "@managePermissions": { "description": "Button which leads user to manage permissions page" }, - "errorRetrievingServerVersion": "An error occurred trying to resolve the server version.", + "errorRetrievingServerVersion": "Une erreur est survenue en essayant de résoudre la version du serveur.", "@errorRetrievingServerVersion": { "description": "Message shown at the bottom of the settings page when the remote server version could not be resolved." }, - "resolvingServerVersion": "Resolving server version...", + "resolvingServerVersion": "Résolution de la version du serveur...", "@resolvingServerVersion": { "description": "Message shown while the app is loading the remote server version." }, - "goToLogin": "Go to login", + "goToLogin": "Se connecter", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Export", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index e640345..9dcb967 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -819,5 +819,10 @@ "invalidFilenameCharacter": "Znaleziono niedozwolony znak w nazwie pliku", "@invalidFilenameCharacter": { "description": "For validating filename in export dialogue" - } + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index b96d8ba..2636866 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -811,5 +811,18 @@ "goToLogin": "Go to login", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Export", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index e3f25a8..a86b54a 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -811,5 +811,18 @@ "goToLogin": "Go to login", "@goToLogin": { "description": "Label of the button shown on the login page to skip logging in to existing accounts and navigate user to login page" - } + }, + "export": "Export", + "@export": { + "description": "Label for button that exports scanned images to pdf (before upload)" + }, + "invalidFilenameCharacter": "Invalid character(s) found in filename: {characters}", + "@invalidFilenameCharacter": { + "description": "For validating filename in export dialogue" + }, + "exportScansToPdf": "Export scans to PDF", + "@exportScansToPdf": { + "description": "title of the alert dialog when exporting scans to pdf" + }, + "allScansWillBeMerged": "All scans will be merged into a single PDF file." } \ No newline at end of file From 09ddea44733f3d0204459fe9ed22350adae6debf Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Sat, 10 Jun 2023 19:12:52 +0200 Subject: [PATCH 7/7] chore: Rename variables, cleanup code --- lib/features/document_scan/cubit/document_scanner_cubit.dart | 4 ++-- lib/features/document_scan/view/scanner_page.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/features/document_scan/cubit/document_scanner_cubit.dart b/lib/features/document_scan/cubit/document_scanner_cubit.dart index 3128a56..93657c2 100644 --- a/lib/features/document_scan/cubit/document_scanner_cubit.dart +++ b/lib/features/document_scan/cubit/document_scanner_cubit.dart @@ -44,14 +44,14 @@ class DocumentScannerCubit extends Cubit> { Future saveToFile( Uint8List bytes, String fileName, - String preferredLocaleSubtag, + String locale, ) async { var file = await FileService.saveToFile(bytes, fileName); _notificationService.notifyFileSaved( filename: fileName, filePath: file.path, finished: true, - locale: preferredLocaleSubtag, + locale: locale, ); } } diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 932c0e3..bd7c756 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -204,9 +204,9 @@ class _ScannerPageState extends State if (!isGranted) { showSnackBar( context, - "Please grant permissions for Paperless Mobile to access your filesystem.", + "Please grant Paperless Mobile permissions to access your filesystem.", action: SnackBarActionConfig( - label: "GO", + label: "OK", onPressed: openAppSettings, ), );