From afbd4bddb407b34096594031681452cf3b9d3f61 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Sat, 12 Nov 2022 20:48:09 +0100 Subject: [PATCH] Changed file directories, added clear storage setting, updated readme w/ Google Play reference --- README.md | 7 +- lib/core/bloc/global_error_cubit.dart | 3 +- lib/core/logic/timeout_client.dart | 52 +++++++--- lib/core/model/error_message.dart | 4 +- lib/core/service/file_service.dart | 95 +++++++++++++++++++ lib/core/util.dart | 13 --- lib/features/home/view/home_page.dart | 68 ++++++------- lib/features/labels/tags/model/tag.model.dart | 19 +++- .../login/bloc/authentication_cubit.dart | 11 +++ .../scan/bloc/document_scanner_cubit.dart | 6 ++ lib/features/scan/view/scanner_page.dart | 46 ++++++--- .../view/pages/storage_settings_page.dart | 21 ++++ lib/features/settings/view/settings_page.dart | 20 ++-- .../view/widgets/clear_storage_setting.dart | 23 +++++ lib/l10n/intl_de.arb | 7 +- lib/l10n/intl_en.arb | 4 +- lib/main.dart | 89 ++++++++++------- lib/util.dart | 8 +- pubspec.lock | 12 ++- pubspec.yaml | 6 +- resources/get_it_on_google_play_en.svg | 1 + 21 files changed, 380 insertions(+), 135 deletions(-) create mode 100644 lib/core/service/file_service.dart create mode 100644 lib/features/settings/view/pages/storage_settings_page.dart create mode 100644 lib/features/settings/view/widgets/clear_storage_setting.dart create mode 100644 resources/get_it_on_google_play_en.svg diff --git a/README.md b/README.md index d87b241..22c7f9a 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,13 @@ An (almost) fully fledged mobile paperless client.

+

+ + + +

+ ·--> Report Bug · Request Feature diff --git a/lib/core/bloc/global_error_cubit.dart b/lib/core/bloc/global_error_cubit.dart index 878b264..9fa10f4 100644 --- a/lib/core/bloc/global_error_cubit.dart +++ b/lib/core/bloc/global_error_cubit.dart @@ -25,7 +25,8 @@ class GlobalErrorCubit extends Cubit { bool _canEmitNewError() { if (state.errorTimestamp != null) { - return DateTime.now().difference(state.errorTimestamp!).inSeconds >= 5; + return DateTime.now().difference(state.errorTimestamp!) >= + _waitBeforeNextErrorDuration; } return true; } diff --git a/lib/core/logic/timeout_client.dart b/lib/core/logic/timeout_client.dart index 496f67e..f0fe55d 100644 --- a/lib/core/logic/timeout_client.dart +++ b/lib/core/logic/timeout_client.dart @@ -35,8 +35,12 @@ class TimeoutClient implements BaseClient { } @override - Future delete(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { + Future delete( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) async { await _handleOfflineState(); return _handle400Error( await getIt() @@ -50,7 +54,10 @@ class TimeoutClient implements BaseClient { } @override - Future get(Uri url, {Map? headers}) async { + Future get( + Uri url, { + Map? headers, + }) async { await _handleOfflineState(); return _handle400Error( await getIt().get(url, headers: headers).timeout( @@ -62,7 +69,10 @@ class TimeoutClient implements BaseClient { } @override - Future head(Uri url, {Map? headers}) async { + Future head( + Uri url, { + Map? headers, + }) async { await _handleOfflineState(); return _handle400Error( await getIt().head(url, headers: headers).timeout( @@ -74,8 +84,12 @@ class TimeoutClient implements BaseClient { } @override - Future patch(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { + Future patch( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) async { await _handleOfflineState(); return _handle400Error( await getIt() @@ -89,8 +103,12 @@ class TimeoutClient implements BaseClient { } @override - Future post(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { + Future post( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) async { await _handleOfflineState(); return _handle400Error( await getIt() @@ -104,8 +122,12 @@ class TimeoutClient implements BaseClient { } @override - Future put(Uri url, - {Map? headers, Object? body, Encoding? encoding}) async { + Future put( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) async { await _handleOfflineState(); return _handle400Error( await getIt() @@ -119,7 +141,10 @@ class TimeoutClient implements BaseClient { } @override - Future read(Uri url, {Map? headers}) async { + Future read( + Uri url, { + Map? headers, + }) async { await _handleOfflineState(); return getIt().read(url, headers: headers).timeout( requestTimeout, @@ -129,7 +154,10 @@ class TimeoutClient implements BaseClient { } @override - Future readBytes(Uri url, {Map? headers}) async { + Future readBytes( + Uri url, { + Map? headers, + }) async { await _handleOfflineState(); return getIt().readBytes(url, headers: headers).timeout( requestTimeout, diff --git a/lib/core/model/error_message.dart b/lib/core/model/error_message.dart index 1041af5..b7d0c79 100644 --- a/lib/core/model/error_message.dart +++ b/lib/core/model/error_message.dart @@ -1,9 +1,11 @@ class ErrorMessage implements Exception { final ErrorCode code; + final String? details; final StackTrace? stackTrace; final int? httpStatusCode; - const ErrorMessage(this.code, {this.stackTrace, this.httpStatusCode}); + const ErrorMessage(this.code, + {this.details, this.stackTrace, this.httpStatusCode}); factory ErrorMessage.unknown() { return const ErrorMessage(ErrorCode.unknown); diff --git a/lib/core/service/file_service.dart b/lib/core/service/file_service.dart new file mode 100644 index 0000000..b13151c --- /dev/null +++ b/lib/core/service/file_service.dart @@ -0,0 +1,95 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:paperless_mobile/core/model/error_message.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; + +class FileService { + static Future saveToFile( + Uint8List bytes, + String filename, + ) async { + final dir = await documentsDirectory; + if (dir == null) { + throw ErrorMessage.unknown(); //TODO: better handling + } + File file = File("${dir.path}/$filename"); + return file..writeAsBytes(bytes); + } + + static Future getDirectory(PaperlessDirectoryType type) { + switch (type) { + case PaperlessDirectoryType.documents: + return documentsDirectory; + case PaperlessDirectoryType.temporary: + return temporaryDirectory; + case PaperlessDirectoryType.scans: + return scanDirectory; + case PaperlessDirectoryType.download: + return downloadsDirectory; + } + } + + static Future allocateTemporaryFile( + PaperlessDirectoryType type, { + required String extension, + String? fileName, + }) async { + final dir = await getDirectory(type); + final _fileName = (fileName ?? const Uuid().v1()) + '.$extension'; + return File('${dir?.path}/$_fileName'); + } + + static Future get temporaryDirectory => getTemporaryDirectory(); + + static Future get documentsDirectory async { + if (Platform.isAndroid) { + return (await getExternalStorageDirectories( + type: StorageDirectory.documents, + ))! + .first; + } else if (Platform.isIOS) { + return getApplicationDocumentsDirectory(); + } else { + throw UnsupportedError("Platform not supported."); + } + } + + static Future get downloadsDirectory async { + if (Platform.isAndroid) { + return (await getExternalStorageDirectories( + type: StorageDirectory.downloads))! + .first; + } else if (Platform.isIOS) { + return getApplicationDocumentsDirectory(); + } else { + throw UnsupportedError("Platform not supported."); + } + } + + static Future get scanDirectory async { + if (Platform.isAndroid) { + return (await getExternalStorageDirectories(type: StorageDirectory.dcim))! + .first; + } else if (Platform.isIOS) { + return getApplicationDocumentsDirectory(); + } else { + throw UnsupportedError("Platform not supported."); + } + } + + static Future clearUserData() async { + final scanDir = await scanDirectory; + final tempDir = await temporaryDirectory; + scanDir?.delete(recursive: true); + tempDir.delete(recursive: true); + } +} + +enum PaperlessDirectoryType { + documents, + temporary, + scans, + download; +} diff --git a/lib/core/util.dart b/lib/core/util.dart index db3ef7c..d1f6b8f 100644 --- a/lib/core/util.dart +++ b/lib/core/util.dart @@ -54,16 +54,3 @@ Future> getCollection( } return Future.error(errorCode); } - -class FileUtils { - static Future saveToFile( - Uint8List bytes, - String filename, { - StorageDirectory directoryType = StorageDirectory.documents, - }) async { - final dir = (await getExternalStorageDirectories(type: directoryType)); - File file = File("$dir/$filename"); - file.writeAsBytesSync(bytes); - return file; - } -} diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index 735d65c..df658c0 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -37,47 +37,39 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { - return BlocListener( + return BlocConsumer( + //Only re-initialize data if the connectivity changed from not connected to connected + listenWhen: (previous, current) => current == ConnectivityState.connected, listener: (context, state) { - if (state.hasError) { - showSnackBar(context, translateError(context, state.error!.code)); - } + initializeLabelData(context); }, - child: BlocConsumer( - //Only re-initialize data if the connectivity changed from not connected to connected - listenWhen: (previous, current) => - current == ConnectivityState.connected, - listener: (context, state) { - initializeLabelData(context); - }, - builder: (context, connectivityState) { - return Scaffold( - appBar: connectivityState == ConnectivityState.connected - ? null - : const OfflineBanner(), - key: rootScaffoldKey, - bottomNavigationBar: BottomNavBar( - selectedIndex: _currentIndex, - onNavigationChanged: (index) => - setState(() => _currentIndex = index), + builder: (context, connectivityState) { + return Scaffold( + appBar: connectivityState == ConnectivityState.connected + ? null + : const OfflineBanner(), + key: rootScaffoldKey, + bottomNavigationBar: BottomNavBar( + selectedIndex: _currentIndex, + onNavigationChanged: (index) => + setState(() => _currentIndex = index), + ), + drawer: const InfoDrawer(), + body: [ + MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + ], + child: const DocumentsPage(), ), - drawer: const InfoDrawer(), - body: [ - MultiBlocProvider( - providers: [ - BlocProvider.value(value: getIt()), - ], - child: const DocumentsPage(), - ), - BlocProvider.value( - value: getIt(), - child: const ScannerPage(), - ), - const LabelsPage(), - ][_currentIndex], - ); - }, - ), + BlocProvider.value( + value: getIt(), + child: const ScannerPage(), + ), + const LabelsPage(), + ][_currentIndex], + ); + }, ); } diff --git a/lib/features/labels/tags/model/tag.model.dart b/lib/features/labels/tags/model/tag.model.dart index b0cec72..01b79de 100644 --- a/lib/features/labels/tags/model/tag.model.dart +++ b/lib/features/labels/tags/model/tag.model.dart @@ -10,6 +10,7 @@ class Tag extends Label { static const colorKey = 'color'; static const isInboxTagKey = 'is_inbox_tag'; static const textColorKey = 'text_color'; + static const legacyColourKey = 'colour'; final Color? color; final Color? textColor; @@ -31,11 +32,23 @@ class Tag extends Label { Tag.fromJson(JSON json) : isInboxTag = json[isInboxTagKey], textColor = Color(_colorStringToInt(json[textColorKey]) ?? 0), - color = (json[colorKey] is Color) - ? json[colorKey] - : Color(_colorStringToInt(json[colorKey]) ?? 0), + color = _parseColorFromJson(json), super.fromJson(json); + /// + /// The `color` field of the json object can either be of type [Color] or a hex [String]. + /// Since API version 2, the old attribute `colour` has been replaced with `color`. + /// + static Color _parseColorFromJson(JSON json) { + if (json.containsKey(legacyColourKey)) { + return Color(_colorStringToInt(json[legacyColourKey]) ?? 0); + } + if (json[colorKey] is Color) { + return json[colorKey]; + } + return Color(_colorStringToInt(json[colorKey]) ?? 0); + } + @override String toString() { return name; diff --git a/lib/features/login/bloc/authentication_cubit.dart b/lib/features/login/bloc/authentication_cubit.dart index 44a2c76..5a2b216 100644 --- a/lib/features/login/bloc/authentication_cubit.dart +++ b/lib/features/login/bloc/authentication_cubit.dart @@ -79,6 +79,17 @@ class AuthenticationCubit extends Cubit { errorCubit.add(error); } throw error; + } on SocketException catch (err) { + late ErrorMessage error; + if (err.message.contains("connection timed out")) { + error = const ErrorMessage(ErrorCode.requestTimedOut); + } else { + error = ErrorMessage.unknown(); + } + if (propagateEventOnError) { + errorCubit.add(error); + } + rethrow; } on ErrorMessage catch (error) { if (propagateEventOnError) { errorCubit.add(error); diff --git a/lib/features/scan/bloc/document_scanner_cubit.dart b/lib/features/scan/bloc/document_scanner_cubit.dart index 3be042b..85b2166 100644 --- a/lib/features/scan/bloc/document_scanner_cubit.dart +++ b/lib/features/scan/bloc/document_scanner_cubit.dart @@ -1,5 +1,7 @@ +import 'dart:developer'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_mobile/core/model/error_message.dart'; @@ -27,7 +29,11 @@ class DocumentScannerCubit extends Cubit> { void reset() { for (final doc in state) { doc.deleteSync(); + if (kDebugMode) { + log('[ScannerCubit]: Removed ${doc.path}'); + } } + imageCache.clear(); emit(initialState); } diff --git a/lib/features/scan/view/scanner_page.dart b/lib/features/scan/view/scanner_page.dart index 7230b16..215db7d 100644 --- a/lib/features/scan/view/scanner_page.dart +++ b/lib/features/scan/view/scanner_page.dart @@ -1,3 +1,4 @@ +import 'dart:developer' as dev; import 'dart:io'; import 'dart:math'; @@ -7,8 +8,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mime/mime.dart'; +import 'package:paperless_mobile/core/bloc/global_error_cubit.dart'; import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/model/error_message.dart'; +import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; @@ -16,7 +19,6 @@ import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart' import 'package:paperless_mobile/features/scan/view/document_upload_page.dart'; import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart'; import 'package:paperless_mobile/generated/l10n.dart'; -import 'package:paperless_mobile/util.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:permission_handler/permission_handler.dart'; @@ -38,8 +40,10 @@ class _ScannerPageState extends State 'jpg', 'jpeg' ]; + late final AnimationController _fabPulsingController; late final Animation _animation; + @override void initState() { super.initState(); @@ -47,9 +51,7 @@ class _ScannerPageState extends State AnimationController(vsync: this, duration: const Duration(seconds: 1)) ..repeat(reverse: true); _animation = Tween(begin: 1.0, end: 1.2).animate(_fabPulsingController) - ..addListener(() { - setState(() {}); - }); + ..addListener(() => setState((() {}))); } @override @@ -101,7 +103,9 @@ class _ScannerPageState extends State BlocBuilder>( builder: (context, state) { return IconButton( - onPressed: state.isEmpty ? null : () => _export(context), + onPressed: state.isEmpty + ? null + : () => _onPrepareDocumentUpload(context), icon: const Icon(Icons.done), tooltip: S.of(context).documentScannerPageUploadButtonTooltip, ); @@ -113,17 +117,31 @@ class _ScannerPageState extends State void _openDocumentScanner(BuildContext context) async { await _requestCameraPermissions(); - final imagePath = await EdgeDetection.detectEdge; - if (imagePath == null) { + final file = await FileService.allocateTemporaryFile( + PaperlessDirectoryType.scans, + extension: 'jpeg', + ); + if (kDebugMode) { + dev.log('[ScannerPage] Created temporary file: ${file.path}'); + } + final success = await EdgeDetection.detectEdge(file.path); + if (!success) { + if (kDebugMode) { + dev.log( + '[ScannerPage] Scan either not successful or canceled by user.'); + } return; } - final file = File(imagePath); + if (kDebugMode) { + dev.log('[ScannerPage] Wrote image to temporary file: ${file.path}'); + } BlocProvider.of(context).addScan(file); } - void _export(BuildContext context) async { + void _onPrepareDocumentUpload(BuildContext context) async { final doc = _buildDocumentFromImageFiles( - BlocProvider.of(context).state); + BlocProvider.of(context).state, + ); final bytes = await doc.save(); Navigator.of(context).push( MaterialPageRoute( @@ -202,7 +220,7 @@ class _ScannerPageState extends State Future _requestCameraPermissions() async { final hasPermission = await Permission.camera.isGranted; if (!hasPermission) { - Permission.camera.request(); + await Permission.camera.request(); } } @@ -214,7 +232,11 @@ class _ScannerPageState extends State ); if (result?.files.single.path != null) { File file = File(result!.files.single.path!); - + if (!_supportedExtensions.contains(file.path.split('.').last)) { + return getIt().add( + const ErrorMessage(ErrorCode.unsupportedFileFormat), + ); + } final mimeType = lookupMimeType(file.path) ?? ''; late Uint8List fileBytes; if (mimeType.startsWith('image')) { diff --git a/lib/features/settings/view/pages/storage_settings_page.dart b/lib/features/settings/view/pages/storage_settings_page.dart new file mode 100644 index 0000000..0aefad0 --- /dev/null +++ b/lib/features/settings/view/pages/storage_settings_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:paperless_mobile/features/settings/view/widgets/clear_storage_setting.dart'; +import 'package:paperless_mobile/generated/l10n.dart'; + +class StorageSettingsPage extends StatelessWidget { + const StorageSettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context).settingsPageStorageSettingsLabel), + ), + body: ListView( + children: const [ + ClearStorageSetting(), + ], + ), + ); + } +} diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart index bf8c8b7..68b4d0a 100644 --- a/lib/features/settings/view/settings_page.dart +++ b/lib/features/settings/view/settings_page.dart @@ -3,16 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; import 'package:paperless_mobile/features/settings/view/pages/application_settings_page.dart'; import 'package:paperless_mobile/features/settings/view/pages/security_settings_page.dart'; +import 'package:paperless_mobile/features/settings/view/pages/storage_settings_page.dart'; import 'package:paperless_mobile/generated/l10n.dart'; -class SettingsPage extends StatefulWidget { +class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); - @override - State createState() => _SettingsPageState(); -} - -class _SettingsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -25,20 +21,26 @@ class _SettingsPageState extends State { title: Text(S.of(context).settingsPageApplicationSettingsLabel), subtitle: Text( S.of(context).settingsPageApplicationSettingsDescriptionText), - onTap: () => _goto(const ApplicationSettingsPage()), + onTap: () => _goto(const ApplicationSettingsPage(), context), ), ListTile( title: Text(S.of(context).settingsPageSecuritySettingsLabel), subtitle: Text(S.of(context).settingsPageSecuritySettingsDescriptionText), - onTap: () => _goto(const SecuritySettingsPage()), + onTap: () => _goto(const SecuritySettingsPage(), context), + ), + ListTile( + title: Text(S.of(context).settingsPageStorageSettingsLabel), + subtitle: + Text(S.of(context).settingsPageStorageSettingsDescriptionText), + onTap: () => _goto(const StorageSettingsPage(), context), ), ], ), ); } - void _goto(Widget page) { + void _goto(Widget page, BuildContext context) { Navigator.push( context, MaterialPageRoute( diff --git a/lib/features/settings/view/widgets/clear_storage_setting.dart b/lib/features/settings/view/widgets/clear_storage_setting.dart new file mode 100644 index 0000000..e4cc2bf --- /dev/null +++ b/lib/features/settings/view/widgets/clear_storage_setting.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm; +import 'package:paperless_mobile/core/service/file_service.dart'; +import 'package:paperless_mobile/di_initializer.dart'; + +class ClearStorageSetting extends StatelessWidget { + const ClearStorageSetting({super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text("Clear data"), + subtitle: + Text("Remove downloaded files, scans and clear the cache's content"), + onTap: _clearCache, + ); + } + + void _clearCache() async { + getIt().emptyCache(); + FileService.clearUserData(); + } +} diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 1a41c98..b7a35c0 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -102,7 +102,7 @@ "documentScannerPageTitle": "Scanner", "documentScannerPageResetButtonTooltipText": "Alle scans löschen", "documentScannerPageUploadButtonTooltip": "Dokument hochladen", - "documentScannerPageAddScanButtonLabel": "Scanne ein Dokument", + "documentScannerPageAddScanButtonLabel": "Scanne ein Dokument", "documentScannerPageOrText": "oder", "documentScannerPageUploadFromThisDeviceButtonLabel": "Lade ein Dokument von diesem Gerät hoch", "addTagPageTitle": "Neuer Tag", @@ -185,6 +185,7 @@ "labelsPageStoragePathEmptyStateDescriptionText": "Es wurden noch keine Speicherpfade angelegt.", "referencedDocumentsReadOnlyHintText": "Dies ist eine schreibgeschützte Ansicht! Dokumente können nicht bearbeitet oder entfernt werden.", "editLabelPageConfirmDeletionDialogTitle": "Löschen bestätigen", - "editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?" - + "editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?", + "settingsPageStorageSettingsLabel": "Storage", + "settingsPageStorageSettingsDescriptionText": "Manage files and storage space" } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 6c2aa03..d3915ea 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -186,5 +186,7 @@ "labelsPageStoragePathEmptyStateDescriptionText": "You don't seem to have any storage paths set up.", "referencedDocumentsReadOnlyHintText": "This is a read-only view! You cannot edit or remove documents.", "editLabelPageConfirmDeletionDialogTitle": "Confirm deletion", - "editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?" + "editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?", + "settingsPageStorageSettingsLabel": "Storage", + "settingsPageStorageSettingsDescriptionText": "Manage files and storage space" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 785fb5c..9033ce4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:developer'; import 'dart:io'; +import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,6 +13,9 @@ import 'package:paperless_mobile/core/bloc/global_error_cubit.dart'; import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/global/asset_images.dart'; import 'package:paperless_mobile/core/global/http_self_signed_certificate_override.dart'; +import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:paperless_mobile/core/service/file_service.dart'; +import 'package:paperless_mobile/core/util.dart'; import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; @@ -39,8 +44,10 @@ void main() async { configureDependencies(); // Remove temporarily downloaded files. - (await getTemporaryDirectory()).deleteSync(recursive: true); - + (await FileService.temporaryDirectory).deleteSync(recursive: true); + if (kDebugMode) { + _printDeviceInformation(); + } kPackageInfo = await PackageInfo.fromPlatform(); // Load application settings and stored authentication data getIt().initialize(); @@ -50,6 +57,17 @@ void main() async { runApp(const MyApp()); } +void _printDeviceInformation() async { + final tempPath = await FileService.temporaryDirectory; + log('[DEVICE INFO] Temporary ${tempPath.absolute}'); + final docsPath = await FileService.documentsDirectory; + log('[DEVICE INFO] Documents ${docsPath?.absolute}'); + final downloadPath = await FileService.downloadsDirectory; + log('[DEVICE INFO] Download ${downloadPath?.absolute}'); + final scanPath = await FileService.scanDirectory; + log('[DEVICE INFO] Scan ${scanPath?.absolute}'); +} + class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @@ -190,37 +208,44 @@ class _AuthenticationWrapperState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: getIt(), - child: SafeArea( - top: true, - left: false, - right: false, - bottom: false, - child: BlocConsumer( - listener: (context, authState) { - final bool showIntroSlider = - authState.isAuthenticated && !authState.wasLoginStored; - if (showIntroSlider) { - for (final img in AssetImages.values) { - img.load(context); + child: BlocListener( + listener: (context, state) { + if (state.hasError) { + showSnackBar(context, translateError(context, state.error!.code)); + } + }, + child: SafeArea( + top: true, + left: false, + right: false, + bottom: false, + child: BlocConsumer( + listener: (context, authState) { + final bool showIntroSlider = + authState.isAuthenticated && !authState.wasLoginStored; + if (showIntroSlider) { + for (final img in AssetImages.values) { + img.load(context); + } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ApplicationIntroSlideshow(), + fullscreenDialog: true, + ), + ); } - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ApplicationIntroSlideshow(), - fullscreenDialog: true, - ), - ); - } - }, - builder: (context, authentication) { - if (authentication.isAuthenticated) { - return const LabelBlocProvider( - child: HomePage(), - ); - } else { - return const LoginPage(); - } - }, + }, + builder: (context, authentication) { + if (authentication.isAuthenticated) { + return const LabelBlocProvider( + child: HomePage(), + ); + } else { + return const LoginPage(); + } + }, + ), ), ), ); diff --git a/lib/util.dart b/lib/util.dart index 669ea58..e6a35cc 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -13,12 +13,14 @@ final dateFormat = DateFormat("yyyy-MM-dd"); final GlobalKey rootScaffoldKey = GlobalKey(); late PackageInfo kPackageInfo; -void showSnackBar(BuildContext context, String message) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); +void showSnackBar(BuildContext context, String message, [String? details]) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message + (details != null ? ' ($details)' : ''))), + ); } void showError(BuildContext context, ErrorMessage error) { - showSnackBar(context, translateError(context, error.code)); + showSnackBar(context, translateError(context, error.code), error.details); } bool isNotNull(dynamic value) { diff --git a/pubspec.lock b/pubspec.lock index 02f0b2e..050e6d9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -354,10 +354,12 @@ packages: edge_detection: dependency: "direct main" description: - name: edge_detection - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.9" + path: "." + ref: master + resolved-ref: "19fbebef99360e9cf0b59c6a90ff7cd26d4d6e7d" + url: "https://github.com/sawankumarbundelkhandi/edge_detection" + source: git + version: "1.1.1" encrypt: dependency: transitive description: @@ -1465,7 +1467,7 @@ packages: source: hosted version: "3.0.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 986e333..3fa6112 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,10 @@ dependencies: permission_handler: ^9.2.0 pdf: ^3.8.1 pdfx: ^2.3.0 - edge_detection: ^1.0.9 + edge_detection: + git: + url: https://github.com/sawankumarbundelkhandi/edge_detection + ref: master path_provider: ^2.0.10 image: ^3.1.3 photo_view: ^0.14.0 @@ -75,6 +78,7 @@ dependencies: introduction_screen: ^3.0.2 mime: ^1.0.2 receive_sharing_intent: ^1.4.5 + uuid: ^3.0.6 dev_dependencies: integration_test: diff --git a/resources/get_it_on_google_play_en.svg b/resources/get_it_on_google_play_en.svg new file mode 100644 index 0000000..e3382f2 --- /dev/null +++ b/resources/get_it_on_google_play_en.svg @@ -0,0 +1 @@ +fil_get \ No newline at end of file