From cb797df7d2628396c4767ad954d90246d97489c8 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Sun, 30 Oct 2022 14:15:37 +0100 Subject: [PATCH] Initial commit --- .gitignore | 56 + .metadata | 10 + LICENSE | 674 ++++++++ README.md | 166 ++ analysis_options.yaml | 29 + android/.gitignore | 13 + android/app/build.gradle | 68 + android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 21 + .../MainActivity.kt | 13 + .../app/src/main/res/drawable-hdpi/splash.png | Bin 0 -> 13406 bytes .../app/src/main/res/drawable-mdpi/splash.png | Bin 0 -> 4811 bytes .../main/res/drawable-night-hdpi/splash.png | Bin 0 -> 8467 bytes .../main/res/drawable-night-mdpi/splash.png | Bin 0 -> 3648 bytes .../res/drawable-night-v21/background.png | Bin 0 -> 70 bytes .../drawable-night-v21/launch_background.xml | 9 + .../main/res/drawable-night-xhdpi/splash.png | Bin 0 -> 10210 bytes .../main/res/drawable-night-xxhdpi/splash.png | Bin 0 -> 22050 bytes .../res/drawable-night-xxxhdpi/splash.png | Bin 0 -> 28567 bytes .../main/res/drawable-night/background.png | Bin 0 -> 70 bytes .../res/drawable-night/launch_background.xml | 9 + .../src/main/res/drawable-v21/background.png | Bin 0 -> 70 bytes .../res/drawable-v21/launch_background.xml | 9 + .../src/main/res/drawable-xhdpi/splash.png | Bin 0 -> 14303 bytes .../src/main/res/drawable-xxhdpi/splash.png | Bin 0 -> 33674 bytes .../src/main/res/drawable-xxxhdpi/splash.png | Bin 0 -> 39507 bytes .../app/src/main/res/drawable/background.png | Bin 0 -> 70 bytes .../main/res/drawable/launch_background.xml | 9 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../main/res/mipmap-hdpi/launcher_icon.png | Bin 0 -> 2954 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../main/res/mipmap-mdpi/launcher_icon.png | Bin 0 -> 1886 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xhdpi/launcher_icon.png | Bin 0 -> 4071 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxhdpi/launcher_icon.png | Bin 0 -> 6330 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../main/res/mipmap-xxxhdpi/launcher_icon.png | Bin 0 -> 8615 bytes .../app/src/main/res/values-night/styles.xml | 21 + .../app/src/main/res/values-v31/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 21 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle | 31 + android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + android/settings.gradle | 11 + assets/images/empty-state.svg | 1 + assets/images/splash.png | Bin 0 -> 96512 bytes assets/logos/paperless_logo_black.png | Bin 0 -> 28326 bytes assets/logos/paperless_logo_black.svg | 86 + assets/logos/paperless_logo_green.png | Bin 0 -> 35355 bytes assets/logos/paperless_logo_green.svg | 82 + assets/logos/paperless_logo_white.png | Bin 0 -> 26109 bytes assets/logos/paperless_logo_white.svg | 86 + flutter_launcher_icons.yaml | 5 + integration_test/test_add_document.dart | 17 + ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 26 + ios/Flutter/Debug.xcconfig | 2 + ios/Flutter/Release.xcconfig | 2 + ios/Podfile | 41 + ios/Podfile.lock | 167 ++ ios/Runner.xcodeproj/project.pbxproj | 549 ++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 + .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 70917 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 679 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1492 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 2391 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1015 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 2317 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 3647 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1492 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 3338 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 5132 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 0 -> 1949 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 0 -> 4229 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 0 -> 2287 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 0 -> 4913 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 5132 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 7861 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 0 -> 2954 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 0 -> 6330 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 3134 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 6604 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 7419 bytes .../LaunchBackground.imageset/Contents.json | 52 + .../LaunchBackground.imageset/background.png | Bin 0 -> 70 bytes .../darkbackground.png | Bin 0 -> 70 bytes .../LaunchImage.imageset/Contents.json | 56 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 4811 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 14303 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 33674 bytes .../LaunchImage.imageset/LaunchImageDark.png | Bin 0 -> 3648 bytes .../LaunchImageDark@2x.png | Bin 0 -> 10210 bytes .../LaunchImageDark@3x.png | Bin 0 -> 22050 bytes .../LaunchImage.imageset/README.md | 5 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 44 + ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 53 + ios/Runner/Runner-Bridging-Header.h | 1 + l10n.yaml | 4 + lib/core/bloc/connectivity_cubit.dart | 29 + lib/core/bloc/document_status_cubit.dart | 10 + lib/core/bloc/label_bloc_provider.dart | 27 + lib/core/bloc/label_cubit.dart | 53 + ...http_self_signed_certificate_override.dart | 11 + .../authentication.interceptor.dart | 34 + .../connection_state.interceptor.dart | 31 + .../language_header.interceptor.dart | 25 + .../response_conversion.interceptor.dart | 32 + .../logic/error_code_localization_mapper.dart | 70 + lib/core/logic/timeout_client.dart | 135 ++ .../model/document_processing_status.dart | 46 + lib/core/model/error_message.dart | 50 + .../service/connectivity_status.service.dart | 51 + lib/core/service/status.service.dart | 112 ++ lib/core/store/local_vault.dart | 55 + lib/core/type/json.dart | 1 + lib/core/util.dart | 66 + lib/core/widgets/coming_soon_placeholder.dart | 15 + lib/core/widgets/confirm_button.dart | 70 + .../documents_list_loading_widget.dart | 86 + lib/core/widgets/empty_state.dart | 45 + .../expandable_floating_action_button.dart | 215 +++ lib/core/widgets/highlighted_text.dart | 125 ++ lib/core/widgets/offline_banner.dart | 30 + lib/core/widgets/offline_widget.dart | 23 + lib/core/widgets/paperless_logo.dart | 23 + lib/di_initializer.dart | 31 + lib/di_modules.dart | 55 + lib/extensions/dart_extensions.dart | 9 + lib/extensions/flutter_extensions.dart | 19 + .../application_intro_slideshow.dart | 40 + .../biometric_authentication_intro_slide.dart | 83 + .../configuration_done_intro_slide.dart | 27 + .../widgets/welcome_intro_slide.dart | 26 + .../documents/bloc/documents_cubit.dart | 130 ++ .../documents/bloc/documents_state.dart | 82 + .../documents/bloc/saved_view_cubit.dart | 50 + .../documents/bloc/saved_view_state.dart | 15 + .../documents/model/bulk_edit.model.dart | 19 + .../documents/model/document.model.dart | 148 ++ .../documents/model/document_filter.dart | 168 ++ .../model/document_meta_data.model.dart | 40 + .../documents/model/filter_rule.model.dart | 166 ++ .../documents/model/paged_search_result.dart | 84 + .../model/query_parameters/asn_query.dart | 10 + .../query_parameters/correspondent_query.dart | 10 + .../query_parameters/document_type_query.dart | 10 + .../query_parameters/id_query_parameter.dart | 39 + .../query_parameters/ids_query_parameter.dart | 29 + .../model/query_parameters/query_type.dart | 9 + .../model/query_parameters/sort_field.dart | 18 + .../model/query_parameters/sort_order.dart | 11 + .../query_parameters/storage_path_query.dart | 10 + .../model/query_parameters/tags_query.dart | 15 + .../documents/model/saved_view.model.dart | 88 + .../model/similar_document.model.dart | 59 + .../repository/document_repository.dart | 33 + .../repository/document_repository_impl.dart | 275 +++ .../repository/saved_views_repository.dart | 53 + .../view/pages/document_details_page.dart | 418 +++++ .../view/pages/document_edit_page.dart | 204 +++ .../documents/view/pages/document_view.dart | 50 + .../documents/view/pages/documents_page.dart | 239 +++ .../delete_document_confirmation_dialog.dart | 49 + .../view/widgets/document_preview.dart | 45 + .../view/widgets/documents_empty_state.dart | 38 + .../view/widgets/grid/document_grid.dart | 48 + .../view/widgets/grid/document_grid_item.dart | 87 + .../view/widgets/list/document_list.dart | 44 + .../view/widgets/list/document_list_item.dart | 88 + .../view/widgets/order_by_dropdown.dart | 21 + .../widgets/search/document_filter_panel.dart | 530 ++++++ .../widgets/search/query_type_form_field.dart | 51 + .../selection/add_saved_view_page.dart | 85 + .../bulk_delete_confirmation_dialog.dart | 72 + .../confirm_delete_saved_view_dialog.dart | 40 + .../selection/documents_page_app_bar.dart | 91 + .../saved_view_selection_widget.dart | 117 ++ .../view/widgets/sort_documents_button.dart | 55 + lib/features/home/view/home_page.dart | 76 + .../view/widget/bottom_navigation_bar.dart | 36 + .../home/view/widget/info_drawer.dart | 117 ++ .../bloc/correspondents_cubit.dart | 22 + .../model/correspondent.model.dart | 65 + .../view/pages/add_correspondent_page.dart | 21 + .../view/pages/edit_correspondent_page.dart | 41 + .../view/widgets/correspondent_widget.dart | 59 + .../bloc/document_type_cubit.dart | 22 + .../model/document_type.model.dart | 44 + .../model/matching_algorithm.dart | 22 + .../view/pages/add_document_type_page.dart | 21 + .../view/pages/edit_document_type_page.dart | 41 + .../view/widgets/document_type_widget.dart | 52 + lib/features/labels/model/label.model.dart | 82 + .../labels/repository/label.repository.dart | 246 +++ .../labels/repository/label_repository.dart | 32 + .../storage_path/bloc/storage_path_cubit.dart | 23 + .../model/storage_path.model.dart | 64 + .../view/pages/add_storage_path_page.dart | 26 + .../view/pages/edit_storage_path_page.dart | 48 + ...rage_path_autofill_form_builder_field.dart | 163 ++ .../view/widgets/storage_path_widget.dart | 59 + lib/features/labels/tags/bloc/tags_cubit.dart | 22 + lib/features/labels/tags/model/tag.model.dart | 96 ++ .../labels/tags/view/pages/add_tag_page.dart | 35 + .../labels/tags/view/pages/edit_tag_page.dart | 61 + .../form_builder_tag_selection_field.dart | 1 + .../labels/tags/view/widgets/tag_widget.dart | 55 + .../tags/view/widgets/tags_form_field.dart | 99 ++ .../labels/tags/view/widgets/tags_widget.dart | 55 + .../labels/view/pages/add_label_page.dart | 114 ++ .../labels/view/pages/edit_label_page.dart | 124 ++ .../labels/view/pages/labels_page.dart | 237 +++ .../labels/view/widgets/label_form_field.dart | 161 ++ .../labels/view/widgets/label_item.dart | 70 + .../labels/view/widgets/label_list_tile.dart | 73 + .../labels/view/widgets/label_tab_view.dart | 52 + .../widgets/linked_documents_preview.dart | 66 + .../login/bloc/authentication_cubit.dart | 131 ++ .../bloc/local_authentication_cubit.dart | 24 + .../model/authentication_information.dart | 66 + .../login/model/client_certificate.dart | 39 + .../login/model/user_credentials.model.dart | 13 + .../services/authentication.service.dart | 57 + lib/features/login/view/login_page.dart | 103 ++ .../client_certificate_form_field.dart | 116 ++ .../view/widgets/password_text_field.dart | 53 + .../widgets/server_address_form_field.dart | 74 + .../widgets/user_credentials_form_field.dart | 96 ++ .../scan/bloc/document_scanner_cubit.dart | 34 + .../scan/logic/services/decode.isolate.dart | 44 + .../scan/view/document_upload_page.dart | 224 +++ lib/features/scan/view/scanner_page.dart | 184 ++ .../view/widgets/grid_image_item_widget.dart | 106 ++ lib/features/scan/view/widgets/scanner.dart | 41 + .../scan/view/widgets/upload_dialog.dart | 64 + .../bloc/application_settings_cubit.dart | 38 + .../model/application_settings_state.dart | 61 + .../view/pages/application_settings_page.dart | 22 + .../view/pages/security_settings_page.dart | 19 + lib/features/settings/view/settings_page.dart | 49 + .../biometric_authentication_setting.dart | 35 + .../widgets/language_selection_setting.dart | 56 + .../view/widgets/radio_settings_dialog.dart | 67 + .../view/widgets/theme_mode_setting.dart | 57 + lib/l10n/intl_de.arb | 171 ++ lib/l10n/intl_en.arb | 172 ++ lib/main.dart | 156 ++ lib/util.dart | 53 + pubspec.lock | 1504 +++++++++++++++++ pubspec.yaml | 143 ++ .../correspondents/correspondents.json | 28 + .../document_types/document_types.json | 47 + test/fixtures/documents/documents.json | 161 ++ .../preview/example_document_preview.png | Bin 0 -> 70927 bytes test/fixtures/tags/tags.json | 45 + test/src/bloc/documents_cubit_test.dart | 100 ++ test/src/bloc/documents_cubit_test.mocks.dart | 293 ++++ test/src/saved_view_test.dart | 256 +++ test/src/widget_test.dart | 12 + test/utils.dart | 34 + test_driver/integration_test.dart | 3 + 272 files changed, 16278 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/com/example/flutter_paperless_ng_scan_and_share/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-hdpi/splash.png create mode 100644 android/app/src/main/res/drawable-mdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night-hdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night-mdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night-v21/background.png create mode 100644 android/app/src/main/res/drawable-night-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable-night-xhdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night-xxhdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night-xxxhdpi/splash.png create mode 100644 android/app/src/main/res/drawable-night/background.png create mode 100644 android/app/src/main/res/drawable-night/launch_background.xml create mode 100644 android/app/src/main/res/drawable-v21/background.png create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable-xhdpi/splash.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/splash.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/splash.png create mode 100644 android/app/src/main/res/drawable/background.png create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-hdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values-v31/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 assets/images/empty-state.svg create mode 100644 assets/images/splash.png create mode 100644 assets/logos/paperless_logo_black.png create mode 100644 assets/logos/paperless_logo_black.svg create mode 100644 assets/logos/paperless_logo_green.png create mode 100644 assets/logos/paperless_logo_green.svg create mode 100644 assets/logos/paperless_logo_white.png create mode 100644 assets/logos/paperless_logo_white.svg create mode 100644 flutter_launcher_icons.yaml create mode 100644 integration_test/test_add_document.dart create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png create mode 100644 ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 l10n.yaml create mode 100644 lib/core/bloc/connectivity_cubit.dart create mode 100644 lib/core/bloc/document_status_cubit.dart create mode 100644 lib/core/bloc/label_bloc_provider.dart create mode 100644 lib/core/bloc/label_cubit.dart create mode 100644 lib/core/global/http_self_signed_certificate_override.dart create mode 100644 lib/core/interceptor/authentication.interceptor.dart create mode 100644 lib/core/interceptor/connection_state.interceptor.dart create mode 100644 lib/core/interceptor/language_header.interceptor.dart create mode 100644 lib/core/interceptor/response_conversion.interceptor.dart create mode 100644 lib/core/logic/error_code_localization_mapper.dart create mode 100644 lib/core/logic/timeout_client.dart create mode 100644 lib/core/model/document_processing_status.dart create mode 100644 lib/core/model/error_message.dart create mode 100644 lib/core/service/connectivity_status.service.dart create mode 100644 lib/core/service/status.service.dart create mode 100644 lib/core/store/local_vault.dart create mode 100644 lib/core/type/json.dart create mode 100644 lib/core/util.dart create mode 100644 lib/core/widgets/coming_soon_placeholder.dart create mode 100644 lib/core/widgets/confirm_button.dart create mode 100644 lib/core/widgets/documents_list_loading_widget.dart create mode 100644 lib/core/widgets/empty_state.dart create mode 100644 lib/core/widgets/expandable_floating_action_button.dart create mode 100644 lib/core/widgets/highlighted_text.dart create mode 100644 lib/core/widgets/offline_banner.dart create mode 100644 lib/core/widgets/offline_widget.dart create mode 100644 lib/core/widgets/paperless_logo.dart create mode 100644 lib/di_initializer.dart create mode 100644 lib/di_modules.dart create mode 100644 lib/extensions/dart_extensions.dart create mode 100644 lib/extensions/flutter_extensions.dart create mode 100644 lib/features/app_intro/application_intro_slideshow.dart create mode 100644 lib/features/app_intro/widgets/biometric_authentication_intro_slide.dart create mode 100644 lib/features/app_intro/widgets/configuration_done_intro_slide.dart create mode 100644 lib/features/app_intro/widgets/welcome_intro_slide.dart create mode 100644 lib/features/documents/bloc/documents_cubit.dart create mode 100644 lib/features/documents/bloc/documents_state.dart create mode 100644 lib/features/documents/bloc/saved_view_cubit.dart create mode 100644 lib/features/documents/bloc/saved_view_state.dart create mode 100644 lib/features/documents/model/bulk_edit.model.dart create mode 100644 lib/features/documents/model/document.model.dart create mode 100644 lib/features/documents/model/document_filter.dart create mode 100644 lib/features/documents/model/document_meta_data.model.dart create mode 100644 lib/features/documents/model/filter_rule.model.dart create mode 100644 lib/features/documents/model/paged_search_result.dart create mode 100644 lib/features/documents/model/query_parameters/asn_query.dart create mode 100644 lib/features/documents/model/query_parameters/correspondent_query.dart create mode 100644 lib/features/documents/model/query_parameters/document_type_query.dart create mode 100644 lib/features/documents/model/query_parameters/id_query_parameter.dart create mode 100644 lib/features/documents/model/query_parameters/ids_query_parameter.dart create mode 100644 lib/features/documents/model/query_parameters/query_type.dart create mode 100644 lib/features/documents/model/query_parameters/sort_field.dart create mode 100644 lib/features/documents/model/query_parameters/sort_order.dart create mode 100644 lib/features/documents/model/query_parameters/storage_path_query.dart create mode 100644 lib/features/documents/model/query_parameters/tags_query.dart create mode 100644 lib/features/documents/model/saved_view.model.dart create mode 100644 lib/features/documents/model/similar_document.model.dart create mode 100644 lib/features/documents/repository/document_repository.dart create mode 100644 lib/features/documents/repository/document_repository_impl.dart create mode 100644 lib/features/documents/repository/saved_views_repository.dart create mode 100644 lib/features/documents/view/pages/document_details_page.dart create mode 100644 lib/features/documents/view/pages/document_edit_page.dart create mode 100644 lib/features/documents/view/pages/document_view.dart create mode 100644 lib/features/documents/view/pages/documents_page.dart create mode 100644 lib/features/documents/view/widgets/delete_document_confirmation_dialog.dart create mode 100644 lib/features/documents/view/widgets/document_preview.dart create mode 100644 lib/features/documents/view/widgets/documents_empty_state.dart create mode 100644 lib/features/documents/view/widgets/grid/document_grid.dart create mode 100644 lib/features/documents/view/widgets/grid/document_grid_item.dart create mode 100644 lib/features/documents/view/widgets/list/document_list.dart create mode 100644 lib/features/documents/view/widgets/list/document_list_item.dart create mode 100644 lib/features/documents/view/widgets/order_by_dropdown.dart create mode 100644 lib/features/documents/view/widgets/search/document_filter_panel.dart create mode 100644 lib/features/documents/view/widgets/search/query_type_form_field.dart create mode 100644 lib/features/documents/view/widgets/selection/add_saved_view_page.dart create mode 100644 lib/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart create mode 100644 lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart create mode 100644 lib/features/documents/view/widgets/selection/documents_page_app_bar.dart create mode 100644 lib/features/documents/view/widgets/selection/saved_view_selection_widget.dart create mode 100644 lib/features/documents/view/widgets/sort_documents_button.dart create mode 100644 lib/features/home/view/home_page.dart create mode 100644 lib/features/home/view/widget/bottom_navigation_bar.dart create mode 100644 lib/features/home/view/widget/info_drawer.dart create mode 100644 lib/features/labels/correspondent/bloc/correspondents_cubit.dart create mode 100644 lib/features/labels/correspondent/model/correspondent.model.dart create mode 100644 lib/features/labels/correspondent/view/pages/add_correspondent_page.dart create mode 100644 lib/features/labels/correspondent/view/pages/edit_correspondent_page.dart create mode 100644 lib/features/labels/correspondent/view/widgets/correspondent_widget.dart create mode 100644 lib/features/labels/document_type/bloc/document_type_cubit.dart create mode 100644 lib/features/labels/document_type/model/document_type.model.dart create mode 100644 lib/features/labels/document_type/model/matching_algorithm.dart create mode 100644 lib/features/labels/document_type/view/pages/add_document_type_page.dart create mode 100644 lib/features/labels/document_type/view/pages/edit_document_type_page.dart create mode 100644 lib/features/labels/document_type/view/widgets/document_type_widget.dart create mode 100644 lib/features/labels/model/label.model.dart create mode 100644 lib/features/labels/repository/label.repository.dart create mode 100644 lib/features/labels/repository/label_repository.dart create mode 100644 lib/features/labels/storage_path/bloc/storage_path_cubit.dart create mode 100644 lib/features/labels/storage_path/model/storage_path.model.dart create mode 100644 lib/features/labels/storage_path/view/pages/add_storage_path_page.dart create mode 100644 lib/features/labels/storage_path/view/pages/edit_storage_path_page.dart create mode 100644 lib/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart create mode 100644 lib/features/labels/storage_path/view/widgets/storage_path_widget.dart create mode 100644 lib/features/labels/tags/bloc/tags_cubit.dart create mode 100644 lib/features/labels/tags/model/tag.model.dart create mode 100644 lib/features/labels/tags/view/pages/add_tag_page.dart create mode 100644 lib/features/labels/tags/view/pages/edit_tag_page.dart create mode 100644 lib/features/labels/tags/view/widgets/form_builder_tag_selection_field.dart create mode 100644 lib/features/labels/tags/view/widgets/tag_widget.dart create mode 100644 lib/features/labels/tags/view/widgets/tags_form_field.dart create mode 100644 lib/features/labels/tags/view/widgets/tags_widget.dart create mode 100644 lib/features/labels/view/pages/add_label_page.dart create mode 100644 lib/features/labels/view/pages/edit_label_page.dart create mode 100644 lib/features/labels/view/pages/labels_page.dart create mode 100644 lib/features/labels/view/widgets/label_form_field.dart create mode 100644 lib/features/labels/view/widgets/label_item.dart create mode 100644 lib/features/labels/view/widgets/label_list_tile.dart create mode 100644 lib/features/labels/view/widgets/label_tab_view.dart create mode 100644 lib/features/labels/view/widgets/linked_documents_preview.dart create mode 100644 lib/features/login/bloc/authentication_cubit.dart create mode 100644 lib/features/login/bloc/local_authentication_cubit.dart create mode 100644 lib/features/login/model/authentication_information.dart create mode 100644 lib/features/login/model/client_certificate.dart create mode 100644 lib/features/login/model/user_credentials.model.dart create mode 100644 lib/features/login/services/authentication.service.dart create mode 100644 lib/features/login/view/login_page.dart create mode 100644 lib/features/login/view/widgets/client_certificate_form_field.dart create mode 100644 lib/features/login/view/widgets/password_text_field.dart create mode 100644 lib/features/login/view/widgets/server_address_form_field.dart create mode 100644 lib/features/login/view/widgets/user_credentials_form_field.dart create mode 100644 lib/features/scan/bloc/document_scanner_cubit.dart create mode 100644 lib/features/scan/logic/services/decode.isolate.dart create mode 100644 lib/features/scan/view/document_upload_page.dart create mode 100644 lib/features/scan/view/scanner_page.dart create mode 100644 lib/features/scan/view/widgets/grid_image_item_widget.dart create mode 100644 lib/features/scan/view/widgets/scanner.dart create mode 100644 lib/features/scan/view/widgets/upload_dialog.dart create mode 100644 lib/features/settings/bloc/application_settings_cubit.dart create mode 100644 lib/features/settings/model/application_settings_state.dart create mode 100644 lib/features/settings/view/pages/application_settings_page.dart create mode 100644 lib/features/settings/view/pages/security_settings_page.dart create mode 100644 lib/features/settings/view/settings_page.dart create mode 100644 lib/features/settings/view/widgets/biometric_authentication_setting.dart create mode 100644 lib/features/settings/view/widgets/language_selection_setting.dart create mode 100644 lib/features/settings/view/widgets/radio_settings_dialog.dart create mode 100644 lib/features/settings/view/widgets/theme_mode_setting.dart create mode 100644 lib/l10n/intl_de.arb create mode 100644 lib/l10n/intl_en.arb create mode 100644 lib/main.dart create mode 100644 lib/util.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/fixtures/correspondents/correspondents.json create mode 100644 test/fixtures/document_types/document_types.json create mode 100644 test/fixtures/documents/documents.json create mode 100644 test/fixtures/preview/example_document_preview.png create mode 100644 test/fixtures/tags/tags.json create mode 100644 test/src/bloc/documents_cubit_test.dart create mode 100644 test/src/bloc/documents_cubit_test.mocks.dart create mode 100644 test/src/saved_view_test.dart create mode 100644 test/src/widget_test.dart create mode 100644 test/utils.dart create mode 100644 test_driver/integration_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6d9369 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Injectable generated files +di_initializer.config.dart + +# mockito generated files +*.mock.dart + +# l10n generated files: +lib/generated/* +untranslated_messages.txt \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..5a02328 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 + channel: stable + +project_type: app diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f4ca79 --- /dev/null +++ b/README.md @@ -0,0 +1,166 @@ + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] +[![MIT License][license-shield]][license-url] + + + + + +
+
+ + Logo + + +

Paperless Mobile

+ +

+ An (almost) fully fledged mobile paperless client. +
+
+ + Report Bug + · + Request Feature +

+
+ + +## About The Project +With this app you can conveniently add, manage or simply find documents stored in your paperless server without any comproimises. This project started as a small fun side project to learn more about the Flutter framework and because existing solutions didn't fulfill my needs, but it has grown much faster with far more features than I originally anticipated. + + +### :rocket: Features +:heavy_check_mark: **View** your documents at a glance, in a compact list or a more detailed grid view
+:heavy_check_mark: **Add**, **delete**, **edit**, ~**download**~ or **preview** your documents
+:heavy_check_mark: **Manage** and assign correspondents, document types, tags and storage paths
+:heavy_check_mark: **Scan** and upload documents with preset correspondent, document type, tags and creation date
+:heavy_check_mark: **Search** for documents using a wide range of filter criteria
+:heavy_check_mark: **Secure** your data with **biometric authentication** across sessions
+:heavy_check_mark: Support for **TLS mutual authentication** (client certificates)
+:heavy_check_mark: **Modern, intuitive UI** built according to the Material Design 3 specification
+:heavy_check_mark: Available in english and german language (more to come!) + + +### Built With + +[![Flutter][Flutter]][Flutter-url] + + + +## Getting Started + +To get a local copy up and running follow these simple steps. + +### Prerequisites + +* Install the Flutter SDK (https://docs.flutter.dev/get-started/install) +* Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions) + +### Installation + +1. Clone the repo + ```sh + git clone https://github.com/astubenbord/paperless-mobile.git + ``` +2. Install the dependencies (should be done automatically by your IDE) and generate localization files + ```sh + flutter pub get + ``` +3. Build generated files (e.g. for injectable library) + ```sh + flutter packages pub run build_runner build --delete-conflicting-outputs + ``` + + +## Roadmap +- [ ] Add download functionality (implemented, but flutter cannot download to useful directories except app directory) +- [ ] Improvements to UX (e.g. form fields show clear button while empty) +- [ ] Add more languages +- [ ] Support for IOS +- [ ] Automatic releases and CI/CD with fastlane +- [ ] Templates for recurring scans (e.g. monthly payrolls with same title, dates at end of month, fixed correspondent and document type) +- [ ] Custom document scanner optimized for common white A4 documents (currently using edge_detection, which is okay but not optimal for this use case) +- [ ] Support multiple instances (low prio) + +See the [open issues](https://github.com/astubenbord/paperless-mobile/issues) for a full list of proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. +All bug reports or feature requests are welcome, even if you can't contribute code! + +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". +Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + + +## License + +Distributed under the GNU General Public License v3.0. See `LICENSE.txt` for more information. + + + +## Screenshots +Here are some impressions from the app! + +#### Login Page + + +#### Documents Overview (List) + + +#### Documents Overview (Grid) + + +#### Document Filter/Search (More filters below!) + + +#### Document Details + + +#### Edit Document + + +#### Scan + + +#### Upload + + + + +[contributors-shield]: https://img.shields.io/github/contributors/astubenbord/paperless-mobile.svg?style=for-the-badge +[contributors-url]: https://github.com/astubenbord/paperless-mobile/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/astubenbord/paperless-mobile.svg?style=for-the-badge +[forks-url]: https://github.com/astubenbord/paperless-mobile/network/members +[stars-shield]: https://img.shields.io/github/stars/astubenbord/paperless-mobile.svg?style=for-the-badge +[stars-url]: https://github.com/astubenbord/paperless-mobile/stargazers +[issues-shield]: https://img.shields.io/github/issues/astubenbord/paperless-mobile.svg?style=for-the-badge +[issues-url]: https://github.com/astubenbord/paperless-mobile/issues +[license-shield]: https://img.shields.io/github/license/astubenbord/paperless-mobile.svg?style=for-the-badge +[license-url]: https://github.com/astubenbord/paperless-mobile/blob/main/LICENSE +[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 +[linkedin-url]: https://linkedin.com/in/linkedin_username +[product-screenshot]: images/screenshot.png +[Flutter]: https://img.shields.io/badge/Flutter-02569B?style=for-the-badge&logo=flutter&logoColor=white +[Flutter-url]: https://flutter.dev + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..a339628 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "de.antonjstu.flutter_paperless_mobile" + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..12e47ca --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..57e8b5c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/flutter_paperless_ng_scan_and_share/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_paperless_ng_scan_and_share/MainActivity.kt new file mode 100644 index 0000000..3a7ae22 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/flutter_paperless_ng_scan_and_share/MainActivity.kt @@ -0,0 +1,13 @@ +package com.example.flutter_paperless_mobile + +import android.os.Bundle +import android.view.WindowManager.LayoutParams +import io.flutter.embedding.android.FlutterFragmentActivity + +class MainActivity : FlutterFragmentActivity() { + + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + //getWindow().addFlags(LayoutParams.FLAG_SECURE) + } +} diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..970d2f71fcd9aba36a9c6d4b4ddc34d5fa3a3daf GIT binary patch literal 13406 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1743df48774cae21fa940776c5768ef1be1b2c GIT binary patch literal 4811 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-hdpi/splash.png b/android/app/src/main/res/drawable-night-hdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..207991ae7450edc2545e5f2c05f2761fd43ada70 GIT binary patch literal 8467 zcmX9kc|6nq|3gWRtdN+aB~mgenk&RCM?%PLA@><3XDF+=(~r3;?|o##4Sg4@@T`62cvykkLKF4(7b1J_GYUUmJ`h&GnMdu< z#S}ELC1v(IJ8llr=MulcK|2hkloxdilezxI@JtB z^_?mhElvG71$2N6cC{6XZyKlin!_)O0H{LYvf=4a{FisLNZz9Wu|H!+@p;~_qIf_T znjevm*95tX0u*|uS?Hfh?20+&2i|Cj6!5_(HFUY(H8155T(@Cn+aqd|asE46Z>C+5 zrx09IbD3F7F|L&^*Y&a7os>QD@r>A|HDd0L7yT9eDJoX1ZqZpyW&^kRu@41 zJY&y$p3r+DNio5-p$)^KTU#mWHB*}o>s{gpY~qWG2n*K8<_3PQ=`xs2>Tw$txw`kx z1L9k|PBQx?_4bq1?{63b#}#yFRJ_0L;|QLn+Uc+#04vM7Z*eZPuTcbzwJxF8Gpc~F z=tlCJwThwsD4B=Ae)d)@ZLY<7O+lyp&6*A`z~l&j|Bf@!$SB2;*c`@z{9A#Oym)M; z12<#$4Y1tCCRZc!Fa2hhnE)JZ!VmA?^HyWf07*}ZxwNp)>Hp*UB6LcDT5PtN1Mssw z|A!gGR5%Z4`aO4?x-e`m=o8Dl1Bfrt-FV?e*nK9F_xuzy*u3BgaPs+d8On#&#z$)U z=dR&)@(n_eds=&TE8yI=?Iiq_%Dh)a@1wT&kyvi*iV$k4I_MPlID+fW44YiwMnjw? zCv$hUyTg7wM0Jv~xTpQMlK?T@2o+;5hRvV%Z<^i!z^lP5^aHerJRs6_X}Z4|Q{mi6 z1lKFevGdXN^QqOEFm+tStL^@8Jral8$y4Hrd z(JaVu$L}To4?#F}TZ>Aa8%`(u!W=&%kKl?K-O*|zB_Da^ardTyXm#5}c$7VyV*TZlmMWCrrZl+Fao1gpiH!a+FRWr9S~jg08#jP?Y+M~y zGW3tb>w|Imbblcxa#@9KpZ^McFlDrb`f=F&q;QfVIG79dCCqJEXVvXu=Y1eQwEfqG zF*1Bon5t}X1ItaH{XJ7jF7To8AxU3Xr;-#ejAX|K2GR~FTy&uPL;qn;zY@><7eL@| zcY@#fCMho4U)zc2MvZ(NZOY%3iL}g}i)-9%j<$rb+kdp`sybtZxt4-H!oQN3~0`LWt?d~PvD=Q#ZZ+fMwTF42U&Yv9$Aqd10E&%N<3(&52LFD7pi*vQVeHml&X%555-UsxrY#{i2<-Cz@I z&?03t%CHb0GzCTpDDI9`_NMVkuAu+_$mbqvU1gqru?!(dpU*11SqkOv08d2H+-YBd z+#h&(^kZKPit~$<2su{}I-{`GCz z*Xv1&p>OwL#L?;8;q;T*nVX~`Qu03}ulwRMlIHDWR#N@zhJ zG->p7m-H zT6E!Ip|+CLxD=R7O&OQ0;sL1eyc+3xGPlB;q~5vVIk3>b}(mT5CvahNpvH%0uG_@ zbwS)s#bWbL>~Cf1P!eSjMZ7Bp%`IqIs~I*g%HH@GRFqbWVFY{aMyuDrNM;^aozy=c z$PcYX^LP@@B3KFZgFJZO=^uu?P2pKc9p|N%vO`6_dY^)dDlTIf!jC>*9;Nm{7K_V8 zi!7=ERxDK}?{_~l>|F9L$?rPRY%lc~z^hKJXYC?W;ooLPZu9aH)c${W zkIo8SDa%`bEMi$_=5pwf;j%R6vpj6C+FYL_>MVbnxL-yd2IWT=|HFu<(Zkx zy6*c@ct%R*

Fzviv|&F(hK(w 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 CYsNkR literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-v21/background.png b/android/app/src/main/res/drawable-night-v21/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b17092efb3f6fa7a6584cc81280add9d266635a2 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?USXnDB$2f$;;Q&o{+o Ql|TswPgg&ebxsLQ0JYi=m;e9( literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-v21/launch_background.xml b/android/app/src/main/res/drawable-night-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-night-xhdpi/splash.png b/android/app/src/main/res/drawable-night-xhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..c0cccda0b334a4e4dc5250cb855fa275756be4ec GIT binary patch literal 10210 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 PSHzTHVN`YQV(k9`+}ln% literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..ef702e9817df43cbd2e6d27186edefe106a7d8ef GIT binary patch literal 28567 zcmX^-cRbYp|8EjkgfviAcWL1W**iz2G=(@LD<6BEY@#HhOF1i)q%xzdvrDeb6got5 zA(Xw>@A+2W-yfex53hT@UeEb@?)N9+oWAb1t$Vg22(s;@(rMnH{?d^@XcXsfwvUIhS1*a*zOz|x=c~v)**o`sJ-5?O^VR%~yH7+cn6OvxzTbb~z)|0; zvO*@=5fu(m?{(D2DaYP1JsVqHA2lC8G9N%}+4V4CRsDQ?f?eL!YO0=O#cM!GN_G7p z75+sOIkXTYI6@l3gaoUVo@98?MW5A*upr3NRRZGyO#(~V@14LB%8ekyk$&29k*3O1 zZy5Mk3vOlv32+-5SvPUndkmxe_wRfc#$49=>|LDQkU;|H#-UKF&V^3-lMY9ZvhG8W zdrtGT)y{=^R!dzJu1I$*Zg0-yxOddpo<&XlN57;{G){Nx3cZS3FEE)v|a221Tj$krGR4NtAHa zaqA&k4DUs@4Gw%P)bnT3+elzMUJ-Zt$r6U1AgB8c={rhOY+STxa| zi}KI9D{Y#xvG-KPk%gDLE1SI{HA}cr)bbC-MD@JBnOEq|z)AQE=2maOuxy}Bn=D=N zE1vNxVMdWP3L7GjoPRm+hp}HpuXpMebN7riA1{*#l<3%yv6CM$;Wzh^SLqe&QCi-W zmBgLsxoe#j*m!h-}2|~U7L+zRiuQ=6RW;PD* zPSV!DlRZ2kGe0s>cwj@2wv**;tLM2{EgLp=lsfbHoT?qdkF+j-*wCz>&x`M*Y^NXU zFU)&5k&teSvZmp@%dT$+e-#$raTH!8v<<~XZ*bnABj<_3%a-kX!X!3ioq--Tc6Z)I zyeJr!s{co7&xSa5*6TXoBR;wQ`C8Tn$m5*1RgO{L{nsnoh20QDsuOnncJ@bnL89i@ ze=D(AKWmE7+hYanQ4uJt?1uAXHfu`B4WeG575*b$nswu9xz6ZvCF;8?UrY?WBmCv5 zG(Pk-RPSoX9cYelBonO#MdY3M4|UZ?(K!9NhHO=+*JxQh#%zVEPW8-~pkn&WdU?$(f9)h+#m0TF z9k(my$x7PrONZuf-Z-XPfn6w~%@#zywxIm)q^F9+lpS64%8#vonGk6wrCw?yARpU^B-oKqQG}-_x#`88be%OGec*y-K zDS}hM_v!|rQ4OgNCM(W+5;MV){Gsj6HYv=|hOneeGi2FX z%;l*kTKK{|P%EFdE_Wx{@R|3-%7y=}^o@{tT*I5H5E3@?NX-MF8?#6h!9ty0RfZ=!xY^IgQc2r z&&>8^OZJw^Qfb^M*{#A{catw9hP-XdzT>!kaU4|A8mQ4w@2Qfl*UN6iOKZ#?d910h z31#=xsxjkHMrl{wRQnL73N-UJdNa){l}5dzp49R-Nym=iq68}*6q+&lE~rJarUDK< z=`O(M<6<2s5c&STkw?J|0l#aMxwYDLVw|m@l z)j=63-fV?)iOQzorQLJ6n^DgaTzCBn<0oD8K&?nJ8P(I@e!dJF`1_qix(q7#G@|5f z0if}!>Ars(o=e_U+~@8|B5#rYi%#{*i01UHQ3RVUZ^EJCsK56vemUyy%)rwL2OK}Q zIVME{7w)3>7k1;PYG^qHkrV1~R}AT*x89>z(8qWd8)#~D!h$esKL^TAqsmc&+8(+D z6|?Tp0Lq2ZT|Lz!4hqLfCzK?gDP-r^Kv>T^6WG=MT&-(AwR%IC`LbLQ2ZRg%(=QIRTT)3f@wL#$hEs+Yz@ zCm2bGx^5U#(_XU-#mhgGU}^WirIk->WG-z|BfhqPKTKvrmEEdn-MAzhUdscV&r&y_ zX9(=@Ipdc7?p~?Zh6V*6HreG9z<5O`#3%jLso)n~Sog~eAv)pO&3`M0yK-bDV@gkw zwqN;&k;Q)_82=#a`7!M4kJG6ez~b+-Y4=_(Kj|68QOl1$w$n9tXw%ph;%Xj$ssVlT zEmX!9VFRB@mhI&fBDOIpG~KC4_$?dsqklH@q`~K};jn$x#ZkNlr4~=1?qW>vG zZcnAGl`DlI6!n?#KgoU?ae93AZn;9iky7>nufTuoFnYRF8!r=SqHV15Pfa7;GeIep z?RYJD{y}}6v&KKINFKOTe7jBX-!7-FbXhz0UL`6f_Mgh7Nz6wl@22Bn<=?}xFiX-l z+wS9U&-o~v;~(9FZjXS`N!GlUQ~z#n9GRdcVNI-gL#}fFm2D|R%iR7aNZXA<|IzdF zt2(&JV}+5`yZ(vR=uSiP?rh_SJ)1k;|7AnchW{hMv>FY(E>`5Ss<3f6OU4y;|5`s5kwCFG%0| z8sNLYCWr+8*FsAmZ@TYSEk3eWN3s!rbOTU0WzeTaq9C$5N=1FcI9rsFqRK_x!9PeH z4BfD))v|&+6(tJvNZYT446$vP=hF?gWC}q@*qGe2x$DQ){wT0#!M~o@pDb5(e>8?| zl?K&A%~_QBs!U%jPBO~zLC@yhHJ~7%V;O2A|Eo%2zSV-FIF8t}}5OL?HmHYkj@`WgH;*z9;iSXloe z(w}9xxle$;kBZs9XhRCw@b0%9x@n`ha9iFG>wkWM?-`ZQ%R?8XVrGKH!Ztj?Et^rA z7*-dUDdhIopH#c{l*-nh9E&5e_=3PttXhK%Bg$o`7TN?9xJ*;v@t}mxbmu_SnESA2 z)6&QfnhW^nhv?R`NrV($pZ`#`A1?Hz4rhQj_y}|ir>^RG7`Tozgrr!Uc^{$SQ>$>2 zPIQclXu*-x<;ayQpVf*1D5 zHBZdCA>V{&-ZVG;FG}&uTjS5JRDE4Z@Qh{Pr99Oe@KV;GSsKO8+=>$Jg&tU0W300NZj`&t{EZ3XLJQad2J|s?124n`X;PP z#MmPu%dlVzR}pGFmLP#=$LdlBot_|ukhpEE{rE%Pfun-5_%?|+qR-o!`X9r9{1&hY zwfMV7T5yjK(RvvKVBs%Dt&Rr_HEv}QdIc2WT}mnRfz3XEBABs@jKk$cIf8NLOPP+8 z!I+@tiAWc7KgBo;Xot>@ll2PpHgZ|aHyGB_ zH-^}F(fBdWA&J(217pZ^pl&*JqkPfe)Z z&)niT#Bdj_>w#ZSS4~JEN8wQhd(jV14bo8I#mFQnnqSR_5c>LAvIF|_2Jy-~GY*d;o z$}Ve1N0lEb080`)>((%Q11Od{(|HIK2Yu6DgZl`TvBay{fM$GLL+;i}r2+zM%~-g? zxDWab@~ZYUm6__;c$VU!-2}8Y6+CIe?_uB{*Km_%ervtjMOfv%V;KeWa4D#Kks7dyze zTB<$khD-F=JzzmdCFI3pD9AV`Y+j52=#WKCrSPH;NEj@eg~FdbK6=)jz-mEP>NPgl zI9#=1Pagt=h7UPY=Q5ZsngurrG$VMe)Qd4g zdvo6pv0e;dBtZdnWA*EysWF-dHVce^^~?G*rcmA3ih}unAEv=oLVS$_PBC5@Zca5w z6bs!e>diD9DQV3czZJMJWtxZj2T;ORnN$dHcEMK9yvk!UUuo>PBtT#|DH5VpWhew{ z6p#4a$khFFuV+(Q-Uz!*U@GzI5foVSK5{I!8~$N4@m(_*BX zAW6d?MXN-w?FbAScp1Z!f0(7T*C8DL4{o_uuTQ>+^KOBAEmDNX;sMk&^(;ez{2TP1 zqsOk0{WO{&7Iw={P0@A$lcTD~vF0D}M~}gkjf1zyh1gCYUWz9*()0oGDte_+%F>Vc zdV-m8qF5O6`t5Y+7Id*p5SvN(lK@DCsXZiG+(Fk8m9=|?P!CsqB0!p2y%Q8BKX7*b ze z7gEGw(AW6HoKqgj?=}IK&QWe~Xb`Z4Bd6u$wfhR%NH! z$9RB09DOfoZdrxka1p&}Z!nt`kbp^9E9*Aj=}{|XLmCE1pHjd#^1bZVZjKlOwFgVM$P`Z9wwluM+NN zZckaNTT6+rbpgFw--FwdB}*=nwj+bK+;d2Om9AWM}Q1e+;sEGM;}r_d;0#7#T&(_gtUADH`b(o?^x8Z zZea)^;vJjGF?N*iAJLsXqV6PXsEi`wJ(*a@XKEe-g4PNLrbcKlVb!yCI#d-5a%;xY za`PxI<^!HtBU45F>qS71NXJ9v1&Sod@E1vJY7@Su3(k7>`;xhIa#6W82Z+0$ByH({ z@Xg}W9|6UK1!sVi3t)cg0rc))&uK|_`dpA_jqEnQjV`T-xK@DQ@D_4CFh-=^5DPI! zO&Q$y+UYaoOvgb7@BIm-Z#Gf~F333v`vfsMP}n!CX!zg8L)#=_^D@;Pg)(2aVjcUG`emc3x}^G%bqn{TU#P%%iw?Bg+JaWE;>?q94#M-na+ zgGtlK^9ghn0V?B0{>$(YvQ7nF&UATW%|GI6+CW9c>yF`q3=#`jnJfa-?h%7C;Jtjk z7og`6_r(CrloSSV;s%^%OP%a1KT{z|LsbQ)CbS-3^BLV+8NH{q+N0L+&@_U0&+LqKGxmm6c1E^4F}%5Bj#qGvUdq&*H! zF<5h1n{n{pdYN2w@(wf_Lqdm0pF+TzJ+qIcrBVj7HIexGj5-kkaX9Up%j09ts0MyS z)!J>y*pWjk*#3pp#3nKt9azW;=%`z;?yL566k4>{P%pk+#gUv~$ECKJ=&JFQq7u^i zsYlMUp;(Y$0HA6BQ=}4UdRw@AC@)5q3ZnRnsOXd+^ zG+CyBKkr|t$r!opIL69k^q{bST?&-T!hhrw&#coEG`hC<(hN7Qpf}RRryiqKI)v|y zFW+isq29(`6}jkgs)8MNaGkO;75NQT<-Mf=Ly(LHvlcV>yU9t!8zd0;Tf_SAf0bBj zOX_X^gH#rD?=2?+;~gy&#fiTcuY?6^T-%Q>yr+=?PJC1(7UyjUxuv9mUi;wb0TpuRFli zR%kc424zOM9(KGDU-JVlpL;S@FVC|lOiBO>gepK>SXDGQRhCFA?&!|DSgtDred9vL z#);!o$g0;RMgs87;*Crpd%h8C|MiO3vYRHPXHWGX!}T@3*d$)VM3%RvAi?c{fo`*^j?=V39Ofe38$Il z-CnUrQ3%&NC4))e4s~7l!>wqMpl-L`mh8vsIrN=&6cvanG`jmhyNfw$`@36{2%Up+ zbs}MSVzKOyVh%($$f(;X5>uHSH>XK}vuj*;i@=R--{?f%Bbc&_m!g9Zi7n!MpIyhFpsB5r3@Igc%HvsFMGBDJc73Sjbf<2!U69d)^HfRJS zM5*)Y!+F!Q_-WM`Ywp^s->_3J$9Rx@7PA8~maP3!zjyjnC!42p);`QwjOK35^<7w@ zlkb8`SMyv?Xbj^Idwa>{c(-SB-mBx*eKfnlyiM8g@{?&PA5;;G!{w)Ke*k5rrx>3k z0&@J|nRJo-8Tu&Y5&C!lVb9t>A+ViZnUUv}D!rU^DnyLx$1vXby;%~fQBYAe8qu>( zY5mcel~6pDr%j2>tI>`{US+lZ0()84$%E6YQ zo8C5)kMCZX<&tFG*Z8E@IUpTn=IMhvP=L`?=L>r~ahs+CJ@;9s^x1n$LB#KN)nC69 zvO+|`gfv`=NxZH{+OqUkWUOjw>#{CsLJ@6)Ve|@}7_Wca-h$l96goB;JH|5;uK6{H zjW9R%-9t#g=Rdb)@T4c9@C4X$2pp^V$rrXd|2$gFuAWB33Xcs>TRb0;=$UT;7&5M3 zSPZXn&voaLsoo1dD4N+VZ?Jg{=x@HqNlZ)iB4fo%V-}S4z29&JCX8jlzylnF0JDiG z+HNpF7Od*G!KvO@JBnF$FtB$%GkRflxt~UxR$4RVyRoG6$;1Y%2!BQ8bwd^hOYS+t z3Io}Jj9t0}vr8*Cm>kM%88=bk>QqH1ClhZ3N@JF@vM3)+KQ)v6beqpC`41=gW({{^ zlfZ8T^Men_0%Bt|zn-1__+2w1bv^zncdd<(aH$hKt@!g5qYVw#Mh8RlQ%9VVvUt?j zImx5CmC0Y3meP8UEd2n^ONIM#ug(|fwVJb9u7BzN9DU+XKGxmw*II^LtNbkDTYLv1 z;=B)djVk`IRgdNS)6xlIz9$%pWMQbg?hn`DYy`e|ESz9W(?y5nL-T_;8}RvE2Qqv} zBgS-sNDYZ>Xb1EvR!D=2*m2K6DS8k;+eh1>I>mGAkw0}1|Dc*Qw=$% zP%qoEyG=#+x)y2s@VS8W;kT%&C7TLbpX8_w{MGRLiG%M2MpK>U?CH1fGZeGz383ro zZ;P=*;O_Z2YS*3@XU+uWqV=Xx}S=& zyX_=9`#_P&B#$U^Yg`?dX-Bb8SW?n>HrzVKJbIpMC^vLA3QGAJvpG*pP&c4h@%Z{a zkp>Iyxwo^}Wtd0^9>9)^=kiGFdq?%4#-;IBC%F@DF#>Fd2O6w%ujSD;;l{yYNpwG7 zXxb6%cKxEgGyhFd8O(H!c+>Cr;}gj=dYysR`=47fN>6bi$LG3f4iG)4uv%7jYm-{= zueAMih4%P3xN}`=;LKRmVM92+MUYd0-0K{*{*Z|0pKggM7X#meZN0HHKP`vqk$%vj zTuy0RsFb1V%F_2So`%D=l50+f!Z;@~)j#@Ehd_#LPMNuj;K+EeiGeLjIH{Js^M{i|? zI=tkP3CfoZf1+isDhdez3P>4i4oR2-Uzp;1IZWPm#i9lMU8@G+-_h6#XwgndCEl2n z!cdD%7=v`t-QSHf7MH$qI#T$zE(9#PTJ4Vc%Y^ z2lO4KzPr(Gb!e^VdJV_w_Yo1qYBGtIRiG!O#%fvYyo(NlH8Mp5aXw<&zA}SH4(E^gLri(7)4zgJxdA zNqcAB&B2eC_3b&Igx@@hp}soeZb6FJ?+u>V{W4%XaGQqTV17$vGBDpw2DX?(F_I@@ zgdP?o2sMZ8I(dQ9KM!wgGQU#}N-$u@O|rUE9GUx~a;+-5HXIs)Gk*!5@+GHAj{$OAR`f=`-)fpqqQ+GcFYb=n2<&Ymh=x z6puhI>;iWjF}(ZFPRx^G=*Mk3a-0$IFPI?RtUwi0FP_xiO@X~`bu{1Hz1Xb4ltL>Bzj&Z~%nL)xa-&+0`?#a&cPt737Gt3u)yIw(p2b7F|i1YJqwsI8k?`M&g< zTQOaBNY*T|=cZ+u^WD*X-PRmww;=VIz%j7^>RV zvd<20hE2|iM2l*YSd-*Et`B%Emv$F{>j@lSgu`gykJF?vyjE3X%$w%F)68}C&;HR= z|8}%M7X|alhYaNqCdyF^FSN@0_To~bS4?1HVCXS@GwQ}AoA%AL?q#)H9`QH>eu6Kg z2^S<(>cjBMLZ#Iet&Wk>Imh32jYf;=$y#)61{CO}CaQvrRJtjcqP2k4?Y zBdp|30(63hcYPr>0f<(+eY8BXV%ws9kNC=%x)6=vBj9*6*cQ zfHL)*mWj24r``X@tem(r*iun#>NkTke?P0GU(ubhL>Lv)zvF*+iav%F{}NEZ4)Lo0 zK6fqP_L0zh)lP|~S35lIKGXcL@^@FD(G~gXBqI$}G+&$YV2~Wg4mSM)u)Nfn6cSP9 zYwx_b!q7f&yRGRTKFBYCbDxqc@K7T2S?s3N>@cpneD*uR)gwT2$ zO7KrCzP5MG8%~AhPu(|&z!}Pa}Ac{Aeb1SVr05i+`CaanPyiv!vkiT zmcn#HJbHGuZgKtLAn$*-hM-I-vjqw83S{(?BY_Vp9JNrQSiP7!sv_@RNIM%#&G!eH zz%pHW#GkQpthD8a9|2NU@I$7;b+Th@yMCi$B8iv z5~0gw_$YECGv_M|W-P(BL~88is9kh2NF3lC&htGtHUt)l*Ye~@gY{)pfxNC;x@8^0 z!GM8F3{=xTM3D0+gTK>flDY{@!&yt)Y>V$mA7SlZ$xR^!i_#Cd>swdRxBySIlo#Ns z$+-Qaf+Y%?^Xj3l3c0&|&NAjQO4n1@UbJxii3N6dJb|#d4iE*q6X?t@7zd4ii`9OZ zugY4gYWWE4a@{=ZI6$$Z1QTF*5R%RoOY&LW-gsh|7);eLAtg7KH^yJES1(oNrE?ge1j=4!TpnC0V2d2t^Qm}n$DZ&CV zpzVqwF)%Tg4|6j}@I)l-c%*W8JO+NI+VZ9yo9*Z;JT-kG)&(wqas~&j4<@Al6ImD@ zd@cZh`3)qeOKz&BO(1$Li-EuB@wTn}=@vw~0;a~2Lq7nwpz6}%g;x6amhLZYcrcbk zM1nsoUuHyreRW{%_b+O}S8MesyPGl=VM_1abSPq71}F=xLsfG)QBy%U)U`atBvC%5 zQk_cEF44khJX>-vJqxc3=6?KP{f5t{ zrtc>d7Dh%v4@;x7l2;L|#Y_nN80vaur2YG6Y~O8vxzdKOP@waCp(eRd{bLoGQ0J(n zJ3&RZxt%vP*(8zDxS~?pVD!a2bPkMo^-~oYv|8QD%i2FCpm_8Qbo>)Sm38p69O9wb!qEW&UllgCZ&V7vO5+vy>4&voN*~s=C)24&u&&)L@ph**SfT z+3}oU$ZRsTp3rMlR&}N1$W(M&x%FVTV9*?QiOtSnxfK!jD%xhqWNw3SV04qC_)wF? z{_wa#x%;{6DSi8#t!Mo#Ux)Y3wkyoN|q$7JsD{E4fDmpJ}VxK zEp$-}4i2qnR|{#8*l^uuqt)?I;}BwHz-!LdVw4-}@CC%&CrM_MTj_z`y=v7LNJgCYe1C$)UNrBJ}cP-)9p^AVqkhh191o0sq`=H#C#DGDfc^*uy?k6l$e8R;346=Du<~oFfE}#WxwO`<<@^KZOJAxFWcAGp0o+DPj+Vb6y=E} zJ%GR|Nhc7cyXG;Y5X%UR8(N1!p@MbA9BigI((SJQ73tMeYuYSb-Qkbyl<8< zs?bp7ykpwaKV;i|0KSk4Jr|T(>CG?%rGODHIuTk#rC&6sqS^$Ef)bXJnpJ@`s{i}n z(c}SKz1Knl-ErLMnvA2j!dR+CX0;5ooa{UR1O z>r*>VT1ZGxIgY8mrGlS=6<|p@IiXNp0sxxWx8KmjROu0=gbpQ zAl`WfQ{Uf&3)oxlKrt21&~#XIn2oB1;_R>dv#PJx&XB{s7ZUiP`;2Hv2H@SV*+UQp@!RKq31DqD#4s6s-hmqjn{d$vOq5L6 zfX7`L?v#gIe-|2ZwW)Y~eTdV8h*()^rICPIUWwJE$925nW~G(w|)%Q~9(| zr)lrG(Gvk$;i;vEh!yc{!GTLx8TSh2?B+tE;S_W-9F}RZs8jTM(W27xM0QBj>*pjn zhBCNW9{p0|bZ7CYY4J5kuaRxwAJAs4mY_*uA<{Wp0L_v=U^nWE9nqO0IXIJ8vRQj_{$p}#|#8h5)W4sUyv`vDp zntiFlR5eW49)sBVw^F5Q{+z zTOoBRP8^64x|}G(R#hA#?0toiLJsBa9gZ2%b5i3|%zWFD+Qwoo_d2r_X6kFZrxB@;(Ex2?kK{^W}mp32wa zzmu4R@4#gAR{+#|1`8hWJbys@(5tQ0Q~p`9E&1!>Fh{r-__o)#1~X7-Z=cH}RQOIf z$dQ333-Wdhe@xl4=*kdQUTOUKTyoFzbXRb{NMlBz$Lr{t43$$%Gtf;+U5{Gvwr+sW z7+$LBE&QOJ1?qx?d@k5GTaA2t+18`v`?jsmPVv9I-%#|HDqLFbeuP_3A+p5BXew0s z&UMcg>b1UTI^}V5g08nRIUXf&B~k1qu~f(2*@^gZI+}k(9==2jwp{XJx1U;R3c&5mB@$W&$#6WgYpw5;Ol)aBQlW9=v*Ww+0HFk@<<9SB5t-P)rvq3=ba;(@58 z$e%H23wz%g)xaNPG*RHkdg;ue)>&aWS`huI{_r;T%g#!g-4~8T>x2es;|t>B#9laHWNR>~rA| zIyd-mH)Qi?0`}*mu_IktaQyVpm$xIreDs&AN~5m>7`rkQrE!raI!sG-d|?q#$C&&%#yq1H&^;`J z?K6MhvUrk_k@YzP2gza5S+A8+3Wz98JxViP3X=W4!%dRUOl*1gwd;`UbyF%EQ|HUt zlJ!dLx^H%*3&p~yUlWekd*SaP;gO`gQN9&V(smK3nny3afA9Oyb$tx`G^=G(N8wtW z$HbEGDH&{w@riV=tQM7y%7egcgVpuL&Wvso8>W(SU&oF|I2fH_W&lAKgN`1<&<{*a z(6%(J_yn0HWHBI=)hjxD4SxIW@?if}B59Odr>U*2$!_bXs zsMu8~M4SxL^H8=kguwIA*wUHIb}pRXL4SPpmw*geLsrUkqq5Wms4|rs-D)t!xu>?7 z`j|wY&0A9pf8Bd+RcO273-NJUl-x@GK+C4I#W!ns(fQ$hX}TP>bN+ZLQgOj$-Sm%0 zcFM)Xqx7HZNn*IX!%fC_h>%N`8B|RgnTt%P!l3RNlw~fOr<;%0SNTF&vA>ACSEn)` z_&7`ZMb<4MUfbl%tEezl$*DfAD;T)D`pc>lqnF$VZ+EEu;DIh5MkUI*;VX&pkEYZGn??mkYLv8n?4KQ z;$_n=DlKG>KO9u<$WmGm4}Rfe$tb0Z2CvCrx(&?xP1o;Hp?TpuaD^O-6;9|M>ey#o zRY)af-IdE$TQ+jpOPpD}#GAXu(ejP?b!K3C&RXx}dl+t(g159hbd}rPN0&lp?a$Nm zptelr6w&jSq?5o@h)cdR#=8t|zSgoxev1nNQ_II0njeq~y(7{V-wYw7+>p@+q|F}b zkHwl4cU^lcWR_@hx$4#Tmc`5V(Zh&^+U)sIsLHqOL-cI9i^ea)YkK56;TNFUC}Ruf zwq?Lkw;Z{y{3VCu0fCd6>}$y`k_)4oPS+Icd3;|ngqfL5P=3v;G#Z>o=gnHhxFnjM zQ(bSRnxqb8wS+p-Z6AwXhtkD?l^$mG0TcF>F683GP1?6R^75PDdzfy+^=fn7ekb&# zGU1P)qBoq334SigcHI`3<49mEW|VHWKM#I^9`IT=<^}37rA(n)fPVU~mHWI`PN_j8 z+iZuAmVL9_Seq(VK6<5UbHJ*L6w6gwo4N#yY=cT0TW~Ii zn{v{LFAcY(3MyJ_npX)a_wRNH{^{dm|7VZecNHml*uVBlmW`Z;el+*RZMIcaB zL9MUOcO9o)SDq{roVgca>VUL5#7H6s?4EeT^!{1OQKJ}XrNe-t5Q?IqdQj=YalGLi zt*yMmBhJLX(lF6PpLcbzMeODAqRM@*5QmL z_XK0M{wkd{xv9+G?Z)>F$e9nH`E>&ay_T=q_s*^SW(ZZE;17$O=Bo1u%l@Vbnc>0! ze^6k>sjssl*&`C^vzukqofby}D}Ls&N$nPnQrSP%2XA^3+6kE{f`(j?6>gx*nQI>N z-`mRf+wK;Ic{BI)jy>=NG`U_U+GTMRe3z?DUW|ZABJT%8JPJBM=vU|W-Y7n4RKsK~ zvE96AGEyKA69H*J`0lDfNt}A**w-IG(o1%1OmsOkzNNMS16R*5PNF33ja%gOR~MW5 zNF(JOgR~`Xu;Wv4(TZ2q6`*RV>D96h6j)f|IYV7vjqK72cP_+-Q|N4#tIIC~=NCQz z6ACi5^~IWG3Y=cp2?)W=$tJ$UrBRCjpT=VFBcHEw<1eos_X{X()PzqOGnSqNXHw|R z`?K}Mn|2N7nw!4%W>nL3O;!@k9VZ=_IGW5K44&908N;Mx$Bpkb-wkwS6`OZd$=j-0 zFZAijeEidOB|XFMlD#)~;N!Zy%~Jc#gf=||=H+qLcG^Wyl5BY1vIz=Jn2Jl`?Hy$EriE#BQ}?yC zmsKPc?9`38O0(7Vbw9G|l_g&!U~ZbWdTQEeiv~0a_QKP(f)NS5S;wkSoL%F>^k^he zAO2?>+(bWXMqNflck+lw80$VM((LZVW$iTpp<%;KO7G>l#$ABtI#kG2Td*`#`P_58 zW%>`9s;k!8*17y+1XSYk_@O45uT?Nj?aB{ugvFYY-a>I#OY+;{g5&ni`x5J7^gMPg z34yH2(ogw?PUuMl2nJWaaEZ;WXQ_Zs=FoP~HwE=6GHn%lBzk)v4jSK&9a`9vNlDf; z>bqZdk#SJMKd0r*#4%)PFr0iGn+g$cz8tHicdt2z76cn|N-ZKQ77RPGVE2nc>)FnG zU8}QjVu|^HVOz-2MQ!`Q@pbrcIoJzYvKP4?RfrUz8vBc6Q)wkI(r02vNNGhybiy7a zz{>#bP1;?#TchYA`sEJkd~mNc95c^apQJsC^=myVh6~!QXc?X7=KFy^Og?5zaPbPe z`DkIC2+OVC>&4!}7QP1Ud+H6phqP*zU0~5J+j=46fu!8WjKgR4d1{THQMx^Y z$6!v7d#Ro*4|ue;2x5GfaX6s_J&%@Kn$Lvxo zbR7Z@_P1CgG@(tFZ*qB9wPu=nj~o(>K&&|%C;kytdhfo zZwumo1|4EW9$JZH_lnkP+^$@Wd9W8_-rkdEd{qBY(aNQdTw!x}A4&xLRMkSfEQad4 zwnijXL?RmJIl|hhS{?;qg+sw9mpdDuE~dRZhTl~c;#!62w6x-f&0>L$eBx!wvWRs^ zNWSm*sfCk2xGEw=^hn|ntGx>z5e{E9t|y0yJ~z0cRd{c(^nRz0MxoWB5^^t0knHi= zR(MO+9we=U4->FL;KO{9H<7zyEAragU>&b^mL4eHFZf>iPt181gy*_R?fawR+c&rJ zafGQk7N1wny!Ickpsg&?UN-%d>03FENCwNfb9Zj@-lj^C!+z9HJ>Bl)GJ@_zO=M?IGR*b{Yd}=eqcsdU@i06h_y7`8t?svYjN#_Vd%ISA*Zo8Q=hU|$7 zX>B>fQ)$1R=)K+Az5>%(qW@AD({zQ$4!)r}Tcp|9QL8zP?1>I(jro0=^w>5Zw*4AS ztUGr0Vacs+VQlp;KF1xynZAuqS-2!4Rr>}>6HL7}_a%9l$b!7A))*cYuoThm8#`>7 z5Yozdeg12*jb_tIA_X{btEPT`c`ODo@VPuK))d=IK+=wIgjL?UCbTovCV-YIc*fl` zqNRI~P5xTZUv-?wzTcU@f&a^^LAG@M=QgD4tifGCvRzUG&@3~| zZaomjjCYLjJZ48`>`H9Kn)=Ekkzh7ftsq`Kg<_{YAdTjoiNYroF6JLnmTwp!mh+vR z`|~M^G7{5_v?}Jt<-0Yyj$@JUH$z%eRaajTys1;(=M9lMv>xX zzTFU)?(&R(uxAreP^}Rku3J_R1+2AWI$UKnL?oBJ7JIL8t*_)aH=rNYE|9CJUHj=Q zf>??)e)t7XqL+TLwouQ1&9bRJ(SG2IJ$>u;T+2<5f5$0_fDAnG%C&Yow$x9;|MLh= zgjeWZ)8%Mn5LxK2ex}&4)*j`Fc75(kA1+AzkkNDY$9j&>mBQ-}bP%PxtBY29iv>k2 zLnROmE{?Fp{DTU-*I0dcHA!Pz9;@z40_GMuz8(p2RobFw79Lj~?FD+$nroTZROdoh zLB23=vNY50a6IT(7$ccTEDQ@1l#eWv(SKNEJYKM=|50M!!#w7+(62AIb`w0GdT&Q) zf^gv0HRslqRw|~z>g<*Vjxa8J9fxPp7c*@l?Q_L_wsng>oW?M{{^8YBl<0uIa-tOX z;LvqTk$04wY{ggahJLGR?cG`>oT=ZiJJR*8_kKr~SDs-GOxF&WTnLJ@^==ApCL(E< zK^h)OgKkatL5L3xu%NMWF9tNOId5mxLMjTae2O}HMnGprZih&no!dWbI?hA;D3G}M zSPP}(eLBd)YWv};&(B|lxVB7rA+on6M2jA z{P$lb%bm_hI6JNNVYDZcKO^Cp<38l>gk)1(A{$D@x1-#HADT}o5j@Z*Wh2_;BdX@| zmce#m)tEAUYB8j>^1z=@*%vE0gZC89KmN`3EZlq5579UYLMgx27STgYT9dNe0Ony{ zRLSqo9oV!-*|phK!;eZo918HXE>j@VLtUh|!JqED-YF+>Y_kR@0!<_M#FneM@H3+K z%^G5*ceW?5HOzd}G@XzCV|`h}zSVmqU#v7uW>S62w+SukB4w2VSt@ctr&L1e_>X5+t2*eDdWPkJokEeOk9G4V?zAcLQH6cbVfOoOQ zfEfRT_RzoWeDp%=VFpSLNyUw2fOwRo(oo5ssf=qeUXp2;GaVmxiC}j{Jysb zF2#gJoGp{N{={T*7je)exON{pd{;l;qY6F=X#8Gug&re4n9v=e#+316%{;#JK4gLWiS)bzWA5GSO=AA4MZdLAi z!^TbWQ7;%jHuzx?5K53<8`}2hVr1@=N6l~5tv2)Cg@bX*OCf{{HVfUty~a&hlKRbP zMBlAOsaKEbN`TU$j)Kyf-6EUOqR~a>S>x+ceoB5lK6PF6#e8GazUSTfZ#Syx{Jbb( zbCKmbXtL7n#)E!|XJX&KY=^pdxG5_k*0?&NyXLe5z-b!o0oXm6%Y|{siZPNWuCAXD z<_P&aC{Fk-ivY%jR)BUEch3$v$kUf^6sJ}(cfb2QUSQ27u@vKwOG zy!`@us!NHih4lly@dyO~DjzW_!CM`*qwOq&etukVU^rVH<(=|J_2x&KuqNus`iGm@ zkto;HOL3Thky3R946Kj{g|aG$n@n;ic&$E12Si;p3mNpV!iPVzEhT=RBqCPNPWfk^ z;b8db`9$op;ULeP)@mr~EG}g(OBzCQbGfXKD7w%(5;6!mp*xe8e5|+)+}h8GA)*5q ze9d|F7Z$Q5p{Q0-M0#_|>>B!HSyQjF?PRsJlu+h>(l6#ahWthDb!KgKDyA9F!)!og z_=wcG2D&YEbkxYQrol4@VFl!vNw7c;_`CdxE6Z){nCWqq_Ic2P6yMoq z8Sp3s6INqlfuOom(1=#Q*Vv;}M^KKT{wvZx{HxrN74)NELork+C1^0ia=h9s^lq2K zGcDwA-6;9hyTpR0n!zGlpo_856?TktS5#RQ-b;W;4J{451(Em@nmfDL~3z<0BgJf*wdc z2;-1$nIIb^jw6wsRS6dh;7yd5Nr;S2OKw%{8$THde>j1nB3O`=q_v75zH3B2FBN+63$6 zSM@h$h}13G&5Z5(Hs6IQno13+D&p?a+w`urpX%(FINYx?#tro$xLj}}#bn#+r}*8n z>h{f@>VeWP-1BNqy^Gqx`g?1@XOPZF3F;rKETZHw9ZgqA=q_sRl%pmdoXm%oNnKnP~iE1j>Gg zn{j=Hg3DCbU|1;x{RI55?1TBe*Ew*h7bo;nq5I2G3b(AUvy3snZ9T7Jk6sxQ(ni{cw{OAN)VOP#lJ_-!${7{)v zg`&8xNHZjgJT~kp@@gIrejp!vzN=j(7YYz|pAgr`$O{6A{h@b)Dd*0NgCgNXti=GWW1` zTeC}CrPmDSJ9o_XrVh*c)cm$?<44zL^++6=SW0@Pd37rkAkBUTV_f^F;@4rG0nO77 z56M2WJ+bm+DdIHBoU(fl3Kaxh4lE+(9lDV?H-&_tWMH*vkjL)bW{u*U#Jw0O}kGdq%#9dxE{kPVT#I(hMJz=cR7tNylvkFII_u=XvWN7cO4e&hmIm242uv zv!^_G8YIs^v15Np09b&vpZCotJF9h(ci#7ANm7g+zgUtP{`5T=SiWO8qh*vj$vWA< z*Dq0|<*fFZyc2?)-=Gop=C!qSTad!0d$}go_kPKea7&X z(IC?|4jqnNO`V_D7GmW*e;aAq_$36{N)0>K-lqn1rTL`&6@wqkH5@@eXq97vuDKKN zE@UmH(v5GtD6_!B%5&dY>-^ZpqYJK+gct2uxq$zA>k<2>%y})iNGXevC%K>~6t_VD zoGnta&kSHvQ+(#;=XS*1@A6Q{rb2_A@H(V^AC7?g(WS znu2Qjc+o%hbw|^v%~?A`O4ZlhG0gLKW;Q&gn#13MZkY9ys1Utq=vNHf4?O}QAc?I_!*i}#}iA6La<{i#1k zjk=E54-A0Y+7itqvhn<-NkZ$DvAHpY2F%b8`6h7>0C$pKjt&G#-USc2rW4m3<`e1S z9gcJEE%s}HTN>&tx+ z)Q>P(IH;oNda7(ggp!5lfqvx~kcqUeYuB!9*irr)XNCWQAWJDeqDN}@*Oz4CmRn|8 zYcH$#ZnU&YGCWNkUvH6`aD)nU4!Iy+@XbF5L8l9ZAnYG4Sn;NRQl9=&&}-%;onb&x ztyIiRTUseGEwy`BzwI;DINd?n1rgTAPSBUYWx0%zmw`wD4=irB=YvyL>sT%ID%LD` z^Po`5&;IVCax)#J_u5kAoJ+}QTpsu^5_K(Y0zyn{C$&dJUxQQMjV!8c;%xeb!sE$(V0SCk;y&R_I?Lhp^(*jR(5 zWE(a`c`bZ?zE8Pd&T!n?Sas+IV|-~|PFY|(1o0jhl;Ixk_tm35?9kPwCYr2;6SPm- zQC5eo(kFOH**y~!fn)#uX$aeJ8qbe#2FZF)6tTPgywIrJ7i#@ko{*aC*Vn`geUg_p z6y%#Clxk2nJ}MM>P%*H#Y=%onb|3!7aR9?R^t~tC7||V?^Qt`uvwlo5*DtGKD1>4D z>PU;=^(6iU)vjg+qpiLE4=1kCKt}31F%m=lBotI@czJXBE6Jjqrue%t!6a<`B7^na zYgA7Aldur?o7d+y^;;nb;s=kU&+siB;k9oe{rpn!!RK^pQ&xpfnVs}R<8?f(RM<&B zS+St$me0u0yJIzb`ZUsi`g*E?Pp|^r_AovjBM9M&u!>_!@$MLA=&(y)7XNJK(x}G8 zcuZKr9&XQle^k4#2;Y4Zi|o__wGdKql{Qp5gw*f*^4zYAj1kryXmcJjtoEaZ#FR-P%e>&o4hu`+V`kqlu#t5o2lmo{>IxAC5I|Jfc{*5aSRphQ2>a3v{E z^K-%L7cV9LHTFZ&+J)}uqhuz{sS4%SK@fohPw~x6O%4CUYcMWVKG?o#?ZH4994<6A z*de)w2mFAIe3zM<_No4C{Llr<;pWB5yr*+iEa-7)2-**r#X7{PI)$jfSG{_vq!C3} z_U7@#ng(|YAlu|YN}+a&i@$WuCl=&rY;z)s&4M(9F5U)DRjIxAIo#k#5mwoQyPmMw zP=-x!W$BpDM*xW$r2NRDs*XD!rcURqd;8W80CKtGCT-ZiQDSF|izRX~x(}dcC+^(X zq&3~KNCjWrP-335RlTfPb1^`hDI`{@W+0U~r1KfAu61w?`_ed4=6^%*=&jK_ZV zH>-_&eUJEc&xIm?~dz*hzKaAi4-1`(ejhpYYl8WGWE$ zs{EJpjQ1e_y=?8@>-@<;+f3Uj^X2uc%=Hn4Z2j#oMozWWYitGa4?1<<)5x*Zxr=U2 zQ>x_Zuc)k_D=dOn3MNc{^%~BL?F@0oT1UO1LD1T*##K#!sKH+C{;_mUKX!6y*a6taJ^Y`7pO=?(f` zMnj>U5v6hm28I6+zYB3%PD13E#Uz3Ddt1G1ld(_Dm!&IAq3ydK&vmj?c~6g94r)M9 zPTny=8O@r~??2&ES)Y@)#(k(;;02{ugPw5FkyjR`i{);P9TH(@DC5^E56)SSf$zxW zJO*KHd`SJ5mulBq4;jO+msEkfx|g}DiKJD0bG7wF$wjrpA`7|1g033z;>_*uA@JSN zDp188;=6rhUC^|5IIr4WD2&9hW!K-% z`UT$KhjI|y27T1iFw^8DGWO8P?ro`@Jy`kn}{(ngvOKd^YceRH1gC$iCre)R# zi<4*$Y80|DYTqE^t7C4ueSwpO4G=948WJ=7U3$F`{}tyC=wxpWs+W2{NLf)yi<^*w*BdY|JVmJYfhk(2=8tmhtptsPRa~-P z;!>uN#-+6GYSY_#JwC_w`6H9CO8oA1+vO)+z#QdWW=bw|j0pv!jqPduzJ+ z0My*}N}+{Va7QsRbmoLqDBJ9_(b0mL?>iD#vxv=ZpyySj@t2AKHG3Cf>qblfjzo9m z>{tSJv@&xz7g9^yW%FOC%-&m)H)E8An^jt1S;GG6diwY?N+L&yUK54q+&dFM%*S0~R(- zpP;q~X}B$C;ZlP}^27i#QN3~`H#8%AGla7pxU@io(T?$=91rBvPOFi<(+3)Im&w>T z{6mfFxptjwd%g{%>`Gv%0{>WU2Lo4pP0Keu*c%zy6V_X6{`w3?l>070#IrX87H+`& zy|P&*HOG!O%e`Q2q=tlYiI#!dS;~zkT4}FhKDCS*?Ko+tR*jFjamH(Lk+V>a+u%yi z8IvJDdMK3!lf!<6H8Tscc#4KAMSl@erzZrd;{SAQ1tXGKd8!bs@1+qm>srpD!G`5b@V{oD+rW1B31;)Bc`f*^*M727 zsw1iFmAug1=(=*BzK1EKJ9J#Ko|83mphhL(pVG0sn1hja&Fh6_;|CNaik4xp7sK(L zHvb^--g$5r;y)x7Rf8+cbi2mf*O58rv{SwzNXgq%1JGH^Fx=9B0qcTYrMFGinN!U3 zjXi4~A*c4K=87F&plx&K=4}i77If$90in@0z&g{md_;7Z##Ns@vIC`;vR%e`tm($} z!(lRi0)9 z*%B{P7yd~ESGG&lMPWwV1_TRVFJ4p{{mp~^3a)9~D(-F84*JP_fYqz^8F9@V;gLcb z>CF`PInf=y3HC+DP)>R8R})JlNg*8^Sr34$NS3!=k13E9G)LeEH&UT>Mfez}29=2= zaC(9M-i#KUyN21%>@Kq(ul7DySrmJmmVKzEbb3#RZm62A8ilh_xhNu9=Zn%?Zi+jy z7jx2Yq`d=XYLC7?D&V2|&vZ3z18D))p0y0!WP3Z@=~mU5ClRbYzYu)%v`r ziYm*ON3Q%{y~^RCVlE&_ie*w6-Sr^!iT(WLK8^KsYuv@#Ebq2xShJBGoPAqBBqgk# z3mXU$*VVuDaNpV~HG8n2zZ!nbiQc~LV1WQCp-ba*TG^XxgDF43m}ggD%l+B@az|O^ z*WlcQ-U)0D3(EnN7aZOB8SQcF+fs3S(^`PChh`htXZ5 zKkP5e+kVk@{_hobqm^~oH4cmC=XF4&?10Fx8Ib zBfd+CejP>0|4>_PN$6h%?-T#mz)=q=({sUJG zXd*SN7?6a8fhMMEBziJJm!1@+>0OEEX0bSo$@XHzJFf!|=k%`ZuJQTHKxZFZQEolsN)EIbjo-x1gXzCZa+3X%`IGv*;lI>sxRK|uiRcE`t zFUMYt2lw8FYhKx2+yr(t^#Jc!NIQdTd2nMQ-YzjfzsC4<%3ev^sF_e06o)P5z$FjZ z_0K{geQLXYeJ&Poo7xMnomAG&(W5U1<;D&j1N*RtI`3`LScn|OZ57fZjcr8MxA zkElz9aK!9qiwW8{aqYs|ufEYkK7rCY25R=rtNB=CKZi+97Zt+}wfG*Lf@SmOPSOY% zC0(j^Zzg~h*BHfVV0;3IkG-aWR@I%$HDLfRs~=sM_m&^bP@VWfk;ptaVWJ2$5xN4D z#frE4I8M&j3{Q)#udUA@G0CqY&N00~FA6~Kx(fU<`q4+(ZABb0)g^K9&x)dCPDmRk z$w9lmHH2`P(!E=4&)V8$p_JPXetl<37NwPp2U=Bcju!p1M;qAVTT}8e5ANR?G0Lqs zjHE{&hzz%;-jNF}sHw)5aeylXrR&c%fIC0&U6A;Uo}jtn#E;>q69qzbrnqX(5_7hP znNPu`WAxnkjm;2EYOWk9?V|@3oIZdxnSh$!J9mE1nz`(xmPbELZGamwn&+ktQ;+@s zzmKKEgGijnT0?jy=_&aF?h*F$*+y4_HSS+0YYs{txMwrA4Uk3#t+gt^Qf(!VTUFr% zPLKqE2*^3W(4}N2FRj%%e(0A3!(ujn1OT7q9Qb&^Qr|gFC}v132E{Rb+7O_ptCPEI z3LHSVPdE9eM{&%U!&bAa%mLl!%Qg6i??-zjI8>Hn1pr-eX+M(=UeMVs%H+@&`<4gNzb>oxkGvHCfV8 zzP4bh8{D5kNK!vn&G{Ju1QH0++Gbw$e2)uk!(q87P~_6@HtbCLn@c~*(G>xLC^Y0v zJ=&z+L+>{`BBI!fQZKJ@XL`D$+hrPpPz}S85POu`V%q~PCo`=m?!fe-t zSyzKYNrXgBNbkRuY*}}8B{RUmLn><^gL6!PW&m&ebm9N7D!p|lWQ0O8-R^HBee%B@s9rVDdGI{W}6PU>};c@$G9{FOQGIcI4Sy9Rk%QGGxM ze*7}!F{e&SusL`OED5Zf@T10Z^t1%MQ)1BqGBz}I`mH7VxD5me^00r*Y~1;GqLy;z z1o$N$G)CX9`BV3U{roHG8XVF0z~uRTCrPg1^pnTC;Sr91rBwl899>i=KLmXTqco(2 z1KT8I=Oi)uGVr3FYOh3@ z*z^~-GCCgS?a&2c5=DW938~0i$Rba99K*kXuGqE;3U3a$#RUc4rqc!~0w%n#Hrs`= zE`Wf};R60xCY#Lf;%(Qs?v|Q%1T@~eN6yUq8?nG+QS64u;*h$r*GA6rL+oklXV_DC zbY6nDYxYEe(XS~4S3>eTXw<)HnDf`NrU-Qwt3<)(kt zsWPyy*S?=A%LR?6XKVYzM2qCXEIi~RpT&lQH&G#+>E6`^)1QLkyy2y(i9bhSBVq|Vu JZgA!1{{ZT!M!*07 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night/background.png b/android/app/src/main/res/drawable-night/background.png new file mode 100644 index 0000000000000000000000000000000000000000..b17092efb3f6fa7a6584cc81280add9d266635a2 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?USXnDB$2f$;;Q&o{+o Ql|TswPgg&ebxsLQ0JYi=m;e9( literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night/launch_background.xml b/android/app/src/main/res/drawable-night/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png new file mode 100644 index 0000000000000000000000000000000000000000..c47e9c5968ad822ebb1c3fdbd4a9f4a87e2e2c80 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?Xeg{QSJ0f%7Kw`~CxA QZ-5dEp00i_>zopr0P02(8~^|S literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..e0158f83ed1eed4de482b6c32fa058c99d43715f GIT binary patch literal 14303 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

2G=_Wr?JDgZ@XK?d?8q~ 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 0000000000000000000000000000000000000000..c47e9c5968ad822ebb1c3fdbd4a9f4a87e2e2c80 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?Xeg{QSJ0f%7Kw`~CxA QZ-5dEp00i_>zopr0P02(8~^|S literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf481f8c59dcdadbdf32e5e122f40c69ac3e240 GIT binary patch literal 2954 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 Y0EY#uk|AGz#Q*>R07*qoM6N<$g4yl1hyVZp literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..09d4391482be68e9e4a07fab769b5de337d16eb1 GIT binary patch literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy!iOI#yLg7ec#$`gxH85~pclTsBt za}(23gHjVyDhp4h+5i=O3-AeX1=1l$e`s#|#^}+&7(N@w0CIr{$Oe+Uk^K-ZP~83C zcc@hG6rikF&NPT(23>y!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1cb47aa04fe98be536c90a736a47c28aebe2c0 GIT binary patch literal 4071 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 Z{{woan1q`IW+ngt002ovPDHLkV1i-4@mBx< literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4b9ccd0ebdbf5e003cf6e1de1e3585fc0e2e57ed GIT binary patch literal 6330 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®H`s~MhCy$#{KS@#TUSUP? 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..de6a7a3530caccb8b24716b1b2335d7b9ce9b0fb GIT binary patch literal 8615 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 z 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 + + + + + + diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 0000000..f5fdb6b --- /dev/null +++ b/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..0d15186 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..12e47ca --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..d77ed1c --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.5.31' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..562c5e4 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/assets/images/empty-state.svg b/assets/images/empty-state.svg new file mode 100644 index 0000000..17d6e03 --- /dev/null +++ b/assets/images/empty-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/splash.png b/assets/images/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..ead6da8e7b694522a9279f3e318fccb91c2f5cca GIT binary patch literal 96512 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 literal 0 HcmV?d00001 diff --git a/assets/logos/paperless_logo_black.png b/assets/logos/paperless_logo_black.png new file mode 100644 index 0000000000000000000000000000000000000000..522f0d7ecf5db433471829df6d73b808df88145d GIT binary patch literal 28326 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

`Na!CI=5o6!3U3iQ^=>Fy z2D+L7yKwNUCh>eJVUQ{RayS3AuZl=8dQVmHKf3 zGc5DZN77za5+@YdEe2Z&{mM96!NL&)Dm^X-KtGqqOKkio=?iXM-N5r+E=Up%Z}C_R zhmW_x$IxY}9dT%Ppv2#FAW3J!JO>=VrcbZwEF?CS8iIqLcf?-xF_a_D-o!fq-H^c{ z_`_r1KS2xMaH2I}Qs~hwcvtS~mHWr5N(pEC{i;X8Tu3}E5}PBwn0S=rBfn6{k&@)N zdn9CnyDmq^juH1E?17ck6g)y9XyGU6RX;%sANhAnlZCRKN!p;TuMDn$lw`VuOwdl>*a3P==ai z$}b%EO4$LEzRk=`gW^o~B0B36rWpyk7ZQ)78Jz2r7@7pCjpnzV3uJMGOJBmXv>_#) zyaSSQm=yWyLCpOq%`Kc}QmG@HHu~c`yq{17#5lQ__>yI&SjI4B!pQty*|Mxq*)jZR$MeFhjtI7(|?WsLgfT>Fyj1Brz1+wBz zDkZQS(4W*>L0F|(V;jn$ucA@lbYFCTWAkyM9f&u1zL4HrH83REL3*Zz(6!1 zo`;K}SFOXOuDt7VU%e^Hg4&S9qVN}$G&{840Y-eN)}-#^bg`dYTwaoTzLqi;A=2Z2I6M~Rn!q57XZam zhzo&P;?V968-i+n^Pj6;HKF`MQaYp~b=cFQLTa5D+4(T_cWjlm9H1;M>4d zS_Xtl7IwtG|27S+KdM0S2kB~XOPgE}_FJH!04Q+M(6g#PLi&k5l#LUmQN9g%Bp8CR zwb*zhaMq;SlVq)fpi|^M+eWPOPMCSxYithX27Vb~Z-D}y+)j{(>jofyMi3x|3a3I+<|v+LH5=fS0*`g9kq?zd;bSn{?tr4zF=Q}W-!Eo4J6~xX z5g38TZ|}3_2sDC?!4`42&_Bl!A8I^}1iuZ%zI{EDN}?olM4dSqpo?1xK5HU60^aaB z!vt;{1v*!1{EA@*NWC`+|7xLVV`_FUpoBzBNetZqg(Lx|*`3m~I6YOSWZ_sAXRGtq zVWKd&X6OzvC5FmjPde;@Cc{V*kyWxYO51+i9`5NpCu=?{3uz&`aGSbMF@zrsC69fw zSFR!f+;&Jcp4r$WkvAsHT?hk|&|-2~u0lEe(kHIMlkuBT#b${!kjP2D;yMIMTyGuZ zO%=`EfIstKXX?NgJuDqH zfOOpAK)P`SG%(3MfK;u8;>>r6C2%K-MHZR5IQG0(PAd(bEYL_Dc(jS9Q9z-s<$6@p z*)K_yan4prcr_%a@5^I9ht!E9%sp;|oeX`Fs_jT^1sb7AHb%!%@Gw`y#jszvrrwl? z9RkyPoVy&{6CDHQc;VQoUSq8nqeNj)D#pi3H5hD$Ew@Ekth9f^BVCa^j_@Nn|2NQA zeK<3SPnJ?0yto_YjyPFxfL=Z-(k$C3z3CY+_try_Fgrhh{HgHB`##N_g|ZX|a4#BW}Rm zRJdDsRG9Ip+6!^x2&;2-j0)ozO<_F}L$^7iYVz=N@keP|;fE?a_B9g4dgymPHhe2K zTIG=-1j_qCDYgs>Yd3kAxh}4WKVOV;-hSuofH_XbG(+NS3}`=0_I62sf-~&8szx|7 zqjWKuY<35ibI;!+0AI(2RVF9L5Vrc_aw?+ys%r}C5A&hKQNQ3(6Gd}gYyQKfL~cXEAw$OWaH z0_rp}*Zzsyw-55}zi@?o<;BI@+m7zpdVAZcSc!!77Dmc6-k2+f%5r>mrEm7H+O%<$ zqVz7@hdliIw@2RP#sHt;+~A*mzEi<{B_qsKwYiD%eQ)W5=Q+p1RbHhvmr)?$K zHkg4(zYpJildf*~oL>^jNF@{V!nz#cfMlbP7U*i8g0)_-gT* zQ@f=1j}aUM_Nv(PEv?~p=SFeJ-M;FivDvVen|3@x zx9&3~x1X@3wJvH**Ku!_Ssk%^o4u+UHM8ib9t~fR$>BeKgYJ+nyv<4w_7ZV<&Hxbp_p>TV0&p?1bW z|8WS8Xfq|1Mt4(|Ho3k>tl@2X05?foyh7PA-_?sC8{p*;0N+*{bRC z9o0|zuv3wUoth#lfCk?R#fpuR+dgl;x^%+(y!3o>Mp;qQcb8-Jr!axB^aML=LIh48 za}+S6o*BiVFEx~xf_{l`8J<)ZvdjD!bqlV}y7+DvaZ4YaA*uO({Mp?MY_AXOeciJC zMB#xbb5=)D&RVntS9k~99t*k`Y>w!S978u8nA$15|GENDvXI&CxsZJ6 z?7w;Ot+jDu?6=*{tl%U2{d#7zPdgQ0*p2iE`Q_0aG5|YC;EhA zL*VKYU78Wo~1@C}wUaRDK9raECC`THtjOO}NKTr8Z#I(4`8bpLldZmq531=cc=H_P`=s||FoQ{S=6w-syyCR;$R(4czmaqI#ue}5R zzI07~yP;V0(7W$d{GFAI|;V6 z&TCkj5qU58K_J2GMYY8*Bkz5<3G*7NttwB1!8Uu}Kx-m$@yAT7{(}$!;#D6xsW!?z zo(F-uaI(if#cO;sAwL}+(*L*GNOoDCP>*TTuDUf5MX=YxTTUuD-_t5SXWNOKdyz_- z^?Xw8Cb@CXG~TcI?r%(hg2VbeCq| zRg{M&I6ss>`S_1$=MK%@b=!W>;sW%GMXi&cg1u(^ywzIE)p;*YU&wjw{IUJtzm$qO z01`oeq&nTF&p*2>Q!1L&Y@%aekNcVZnqTu<6m9$U#BZX`+{>W+cOC=6p1<~%Mtb)- z>s`9rRkpI(H8kl`xz9oumCIr8W^cZ{E0^*s^hcC>G$Y={2kZ0^gDggoBCQ{P@gZn+)NG zi1+YY#^Bl!rw^LZkSbbr^D{^;G9ZJ$HQ?eBCypy9M18sC0(Md)nnGor}a z-caSq{n$>`-S$~w;3(!F4u)=@e%Gp>A0kQ|3XBVga7Y8H9K5@CeoD*RBkpr> zoM*XjHoLL%4oLLfzyACckJcPYeQu(&?`fiSKJ~zh#`yU~LUe`0c;BXVyZyshT>z$y zVJ~m(PR%F`%yW(c&EOb9jwQ-juY299%c$Rz;qBdmi$CyUCx9cF)AMR4@!0K}Xa}ER zCcuLx10lhO_>ZX9n7ue;8nI+_b29HivB;utOEBx=u2|&5Zt78ZEWi5B7~kGvqElyq z?e*m%y#Cop*A&<>|GJQzxKfw-OMKN5M#+T(Wf=Py}&+bgAcC42S=xpFMaX; zJ=nS^J)O??t<`h$z(A6#;}kfmAB{P7{QpY10qx#EJ5_z4SNgqgFA6R7sUAxe+piu-PLYtO|Bhm9)`_nbShUz8tc~WeNyYnmdeuT+B~xUYHgn*@Y}R@ zs!dt??*y;%WWSL028!2_>j7d7=Ul*#Uil<(_GL<2#3r4G(aIK&w@tsibSYybhna{) zOR;h5fioK4VuAX-Z6=P%SHWckPDu^}v$AK!CuP;jbW1$|nim+S_iG*qr!07lZbd%1 z%N51%iamF7zrJr4zs9#UXnI3Mq{6n3N@`OLVEG>ms5s{u4J9jaA$J3oinLKqX|q7v zqEJA1uW`obw~Qr~wkoBk35RT+_83#Hf87C+Ih7e#Dl-BqVtw}X{vv}`9(tJc^wiof zPKdVI3vb?GR|ZCcaLdg-FO_3|R7y>eHYoe|xGnr|7C@xdCw63MLzeBCw0nXBH!nF> zdR}TDyIlz4UN&YpwV^*)%MJ?ZhAmw$=r6OL#aSV6tYEPcq)BkSF@7_i!!XKmld zV6pa^vD_ZzllWrMkABl@>+=&&9rfkzPMjrs_pM1gD~qQ^O6E4I+(GtKdqj&LPx& zl$g@Y?&bQcm#nT!=xC1ch-<4?;H_?kF$kK$yLV@2+JVXQXna!`$c7j}JR>WmzvoG7 z?fUuSE0;XmBDLJW*PL;e7$^<_@q874nflVCGkXXekK#k#+7k&QhOQ1Na(_Hx=GGs7 z^QIEnv!gh_+GF}FD2){;W3Y1UvFvLinaoyAn%jpguZj%beQ(}$fs`w>uDjH+$duY*(wXwSH_#`Y zb<6sg@Yz2PAUGvQ8-5V?`DoAG*UqTK5+qY;fm#L4DJ((qEECmgIeq6?ws!KuVs!ak zi9W*i6ZiD^TU(uJl4DY~$T>gN_Vo2NUZCdgc&mLv?q%og!O+G}dK{lWzbYWu)&1hM z#6aTOgz)n`9*1oDdvJbqy4!9z`g^IU*7(`l5a+8;haN!SHZUqUBk?ogQuQ)Nr)GEt zUvP$XUr&EH6U_3us#pWx+Pag5P{vU{N*;Wc8Zcmyrd#=sDY@`^^Pz%MfJ;$w@~Lt(hoyC*4IOLO9z&3A$*^o4F^ zc(PC5;c+XHe>q)|c&Q!@tSyT!3e+c#Rl|g^f&M|8lVS}Qy@nTP&PGpkx(~kC-gI`! zEN$UWxzuz;HD60>H*MGWmcxi1=OEhXap=#H!^l!{T&Z;N`cr}S+!eAv23xa5q+Hyx zZ`B%X%Xk>5-@iR=&sx@PJ^dz4K0M_DY&FY-K+x^pI#O?3{^P;vaa;-9FLdiJan6cd z)>``nBzKHq(wUUUQ>R?~GR$dz(LRhTl`rVNBX28@9H;j>$Om}`p53oo+cJf>5j7u)R*XbJ zEYDA@IsH80`*6skQ6Y?M?8IOZ(tpxJv7_H#fzm$%KSYURn}pnd*Fo;A zH@SC6Kb!8ro&Pb-%dtC|VC2ne99P8uXijA8(g?0^Ri0_oA#S-5R~l(}v-+m}b>{H8 zN1w7hq?{~}!9-U9{vNHJ1tQj~a<9>w!9^2?N<}M_HP3J_X2y&8@D8MVBn9Ht>Mtrc zeuBK_fqS-FhuTH_Uax)AJIF1qi?)Er9#tXt&C4YrILfEW?t>*f#j%{gDD*r-4 zGKTfYH(GmqOJBMo=OCqNI%+q!k*L|Fux;r?t9qR?(9k|3Gx&vONXh)L*RWCLHlmOc zS4wNG<(cmXm*o`2<)=F=(SN74;_Yjwu=XYlRU9R{s*87SI2-1D+*vOH-~QUfk)@64 z#%WC!Xx-lF8`mGN@z_lMwxubs`@vn+N~yCaz0WtzJ}+&RSk=Gw3LoH%7@&@xT_V(dp6WNm!x+IU(Pgf%#CmP zC-AuFx8x; zYoKq!6IHY$8Lu)CI&hj^73qE44IobUl$*G?8=L?O^%(y$vo}Hg%1FCf)8)Mhazr7* zuCN*!R`;lfPPd_8ovv==U1UZN!}f2-`HLxP^B^X7c2~{ufyu zX`NcX)CPd+q3~GbpgMo2hUVO>*7f-+7B;&b1!}$soo%g?8B=E{fU29U)jg>0@2`~U zY7-T?>FhD@Up(@}V}e5N@t(uHjAVvI*YF~grd(8#IUEpDVL8f43hU>@xRp0E}t>k4d;K2Q{&^_Ch zR^uyS_RdpB@~p8p2cc6lgtrqTz2zouKA-7yQ^u%hP|vH+(xAE)6F9gS!wrfZDFsN;7A6(0D59c zuUcwU0-$+Lj+>Y-}v>YWYfT z89zKGVPP}UD>%Uofjlz}m`iOn>5Tl%Ba#MCNKoUZ*F%x{xTD-eVEWFMW+;wUO9}g> z(c{;g7sV4#f%6&C_@;sSD&c7(Ng%$h7j=K^rME|}FwV&k$FUeHZs#c#)l#0A30_u2i0XX0Q6y=Hr-;a%>!vlzA>ts#u)@ML8P<)}>>t<9T%J;dp`UW>6%Ei0u;!bI&(Q}2>=Zal zPb;MU0zzy` zf}`sR(PjlwFtIUK3xCj8X|`g$DAvLI?KIxcWNc+?8&nE2o@V%uh@gakE`n4LS*r{R z7XPeD>SrHQp)3O;fbYN+stcs7@8+a5+r#cS8d#z2dXN!q&QGue{2f?1R7b`UQtdH( z#wXLKhD-t;mxgGr3BS15k)u;RhSGY4ggvn|%Utd?8}v(hU=Rn8fp5oq-B{PKJ;aiK z)ReV3!OZWwJ955DxuTm;y}gK&b?QIZekdU#Xfgc#wF)+c;vJ;(@MBwC^CFIj&x(d# zrKK(w{EM}b2Edf0<#o*Ta)EwFb=a8bfWOk}9Axx&-(O(1+>=OuuYwy|JCunn6_jq< zaFr&5E`*BhLEP}R-dazQxl_SRfW8+GJ53UtW&-KeI8HY$#s##R3vj4(CXmFRKX#8! z4^#>|WiA2+9^a3s??Cna1vUabCcq)3hL15BaEt${6s z0c#}$A&|#v_UHB;jz$;q7hI*S1hNhmqw$%IfP7Jegpt|fhN;KruVV^BB2w5h(o^S# z?tS-JK&PVY5afwAXMn3QBfd&`_OzwVOe!QT^$wERv_3Nu_NqpYN>u+Z`Z&FV)<7WE z{?M$zmIkUPUki&aTd>e+Sgtf19571uX9duM!n#$WUdPjA?$uuWdYvYdD}lw&=!%XD zbRnPTPq*Rqf|Wu!3T>hud7{iAjb4?g))Q;Z2fmNcVgjcB%q&W%l(KzV!KVT~t|PrM z;PC^qMO3&0Awq+#Q1;~W;H+<)N zLV^k`;>%w*JHGw;U#~^DUfRFdpcB$<)AJV0L9p2-fc{hyuc$ncQcNkIxS^gV-5Np= z&cxaXiH>J&3so2qtbYzs|YZ=$>X);_$yBUap2$u|bdA zL`sejy(DJc4O4^lUe&&Fuv1YOXwc}!ZLYgq*85P%MC3lM-hNaq8H>LRtSGENneI_A ztBD;H>3!@VGAFn;*k+u@iJ&i(_vuZcjQBnWvevjk6eAmyHh#Xf2L3|*CE(Aw;0x_ffgKBa~?Y_?Y(3GG{5~f}|8*W`R z^0|Yoo+FMrfei3zkEAv-Bj{Ddhfn!)F0QUxa?dY>E&GOb%W8*(S7rJS?bmvN?C=LC zdJBs`4k2FpghkaZb_v5>CsP=4Clt&%sO*- zeu{lsHq9F?u{ZxPA&Wjzy&C#<$YScvyqSLZ5uh^fCK*izRVLDwvXQ(mlE=Zz8~})c zD9K!8C_Q$o6F)m0^;Y;*p7G5G<;4N4_w+sBTk8eqgq}()mLtRQ?6||3MWw**O#!T5 z&e5Tsj)rN($X}uGC^dc$-Mo(wCcH}yLw3La9jcID_XYAmn`1Zi}eQkt=NNupBU{Qy=Xdi|JT zs-l0XS~8|etbgN~Ys-&4Bl#qAX-5|vvww9{#BmXYNb_y3s0>3I0Btndf!0Nx2UrPp z;y_w&vxr+$tJnf+EGT@{(w}93nKxp-FRnfnx?y$GqI9@Fanw;i1B-uhQmM~g8@Fph z!&z`LC1p~CSp99j@gnX*MduiK#VfQW-^zu*C7TwpG4LN~#j7W8KTaH#!wjKI`0dp5 z17B%WNm;gkb1rGuEzMOqZ`r<=9$`K8`t?J>EC24Lv~W}TqNmkT!B|?1pLwYG>UOs~ zkp+9sFF8Jas!|i%_jnQD(A$vO=e#O=VA{?gI7mQR;i4&0$a%J<2AB zUE!{c-G0*^mSby9ZHG7W0{}aIbv8hZB`q6)j1`iX4M7&{^hYH=(BgeAiaJn2ZE(LS_*(lU>q~ zVN>M+)KB&NfJ=ET^;%*QN8dM$FG-#vty-G5i&7p~Pf6wStrMbBKkhj=8mo?Xtbko5 zlJ*MwI4s7YyX`TA#Sb17D?R;%Qh&R?`S1+CV|20Bi(ljc07epgIWbasS2T=>;PR-Po8)4E$jl`__Yh(a}w z#aH_&Iq!l5lS=v{d{gCMIY#FZPMY)i48Lw@_^4da`5#r7By@&}qhV(LlJ{y*@J!tNsd*OBJk%C-OQP5Hd_3NA?T+`k0#`y#7^XPg8;-%gXR?7krH_8^kF zAzUV0K3pt3eL=uFvoG=K;d>q8nnz4AZY7a98y=0{9{Q#{@r%AD-V>w3UPPL2wHgG= zV0wjZZ@o8?ReAr>S&*Gb*{sbOZHvL;9WSdV2V0mM|5LWbq!zY^dlDbku(izFXPk56 zOmDZwEOa4WVNL1OkDxK011FIZ!`7dt$~KWMM#JSg4a3@H$NX8(G4tAGNkl~_ecQ48 zea|Ys5UK;%$Cx#CsjescyNfvC`RUgVhEBMZLGBH4yl|H3T?6NTUYwnHD$F zy`Is8@6%{{heAA*&)klUX8+_#Q(nCJQREeg7}Vl{{uQ!SAnSb!=SXIe1T?W8K+sh7 zC6x95eZYEZn&u>v)k+yTl|c9}@q4A%nnKPYvS?|-;x{Me;?rLXs`$(sQyXtx`kQ*0 zTsV0M_iWLJ_*HU!p>Rx={z|+j8r?s`%opzgLP`4le@rL37!vsO9Kv3%$@c5MHVBx6 zJ0HTJ#B+bvQc~Q;#Gc3mj>#oI=6da=gd}3uPI}9aQZb2Dxwo8^X_GRw1Jd{T3urV> zmt`gvA3p*2rcqVw9|Yg`FeG$(=Fl>PsniU6&)U%EdD7x+-BtR88`DtnpLI4&+SVhm@Gkf|iTFL?z^PYbeJKa!JZ&DtdK ze(BeR{HEPVh$uJ^{ATD*Mp>HQxA}2-?qYFuq&>f!!;@X-cURgz<0#yuCB9|L*Ic45 z@E2<2-w}F;QsGlUtAKmuad!hz$+^V5eLFpa5}4b=*ssEW(`iI@;nKyaSQIIiSTMvk zu8|!!AW5(*8Sa+*Qp->UB6)cfkP;WV{j^>cZoU&WI`CDEfH(zlDa~!~PLk;D>X1p4n5CZq81|Hz=_$Gu^{_YMZ_+3BR0M3YmctT&toyAbf?d;DljUG9ZqL zZiell7VXl`CFg7By*@G!!GiZCMsk1PGo@KM4nJ&(UP8@M`nSTf@a*;2V?wTc625-1-foKWF3Im2Qfaj#yMF(;GpjY2 zElckxt`?KerFp;r7akC57^lN%bDEszw)X6iFP1zlaDIj$D?ga}HLJBDGH|F2i)XmO zNZ&TvlCY1HaVRlp^nLY%+jhdEu7;gvKYO{38B*Y~BoE62XO(>gnLB-^819CjDv{yR zNb37b)Q^<6kcyiXlN>EdIVkv&PM zxoSw*itu2gyN{cPN?Z^o9R)S&7Os7PY$gcP9#Bae*+xFk8SX*}IW&)K?mL`S;2m$Y zIV7e#whdFGeySR}E{}OakHA8@>SP3eo2%*@QsZdg(z`+!<~s z16z6;cwOeta-c_0g51BnFAfX{mu1>FItkq}Jkp^JPHuN*ioH`{c>MBp)tzX2-ge!cOYR8mxKUTE;-O?>p%FjYL$n3U-_WC?-~V2G1IsTp)(C4 zjHSgWvW=N^hI{Z;`Vizlj6)Y6Jva5KHzd=;Wf);}wE*kAcsA(H;6(+JI zty8yw*Ak!gHtbY3$)d%t#qWe8)t(+!)lgO*KtHvKVjnHc6KjU5Fh@c=ovJTHvY3_^ zT@q)nB6%=D0#Weo38gIQe`tVXG_DHls|dZ}-o(GSqL|aF_vV52b-JksSRdmtM3Ccu zQAs9jZ~8UsK=;4m$hrQkGuFbgaJ^t2+PEw-C*{H}+?b;wRI`pAaAwX37$<0tOm!327a<*WAHwo3`&V7>3xfJ{DZ8YYN&=p}0kXI#S zb!5@T1ayV2ga*CZdi+Mo=|Vw855sn4iBEnFIberr9+t)1E!x#6TIdix{qFa)r9Pp~ z3QzN9F?|Ut!(IBELM5Q>$9O^kwXr_%3h^V`kPn~wI@)$w+4uRyj)$J+#`24Mxo_C< zm}`hZFgy_qoe^mB(Ff?o;zz6GD;?02x*whBjbJ$wkxVv^)7I5e*5ZKJJ3f&9Y86Go z=f_qB=qT$7p9lYi;ZPR1)i2iz^VYpW;9Z9>BNdBziy9s{(1Eto2Kc9hQ&!=1>6!J#KGcL-S$45N*(t1*Rf zw&wSbhh&aFks%!$7sD+b2q*of6Rf`RWZ%}dj})1HS^sUADn)jcBS)+Cf=}J|+b(d# zAG{;tm|ro4@$N9*qo&NSzs0S%eU)(5jacBt)8KdRBE=FfkQIu9C~_={DBE^(kF#a& zghbh9JKz^If6PjaY-Q&gN-Wzy)a-V@M0HabZ0R;gt+XS0Iq^=|0k8SGtwzdhrR1`r z$o)6p@~Y38u$Oq+wcKW=8r50qoO&yL;$xJMdnR>Ih~(+P5f92}{po?v#CDsJL<;*9 zFlTkySs2A97&jcwN+YX!6Q&%MC@^s5Tmk0SK3tuef*CkWJFwlxfWELh<>=mfxEH<` z{yLr0+CX37#j+VW(OZ2Qlg(CxpK9MwNt%@**CNu#akQ9|JZ8m%P#XjC%mxR4YzA(J z@2d=Tg3pgLukkeO$3H3dGgpyjfo(rtCCk9*&kRz1Rg%$@)8{Ztqt)@EZl4wKBF19LfmePA2ftk}Inc zf^gTYSf~T%del-daqTW|mkCGFtkh50H->qPsbwiYaTbQ^#SI4ddju5?zPgFmD-7<# zRcP=6MlZbI5C%nr6O1~fqQc}3Qcpu}p22llnH^y6jnl4v#zsHk`GXRRO=mlUUc}I4 zc%kWr0FZvuR&MgW#xCFgOouR4d;4d6T)&vOS!6@1a9uswQSY*o$PRJixb&$7z5&I_ zrQHhI+y*)~+t>piL(+MGp6w^LpTRv|fYiH}gFR zf9$+ohcE0w(tcl$h3lJacTB#QF^8ec3q%#>AAZRDB2&IXn7fAphQU>#?SU!bF&9;% zG4ra8p7o=J+*Tg<`XIPI`j<4xCw(e4CWAdjInZ&v9N)qZ(*h7s7+7FC&>D@NO5fjp zf8$B>(2dRU=`lBSb_{CZs(G0U9->KOSYFs2IrH{q7!Ed97nMI6y{@T`<<5C>4=I|N z(W7MUos?Hc6HLP*U70k$o;O(@Jfyl^3@#2GRKrTwZ*@C}o9f>8vyS{O15<&Y;_n3ugUKOC&;7xDE9B5Pl(EbE z+k@$MVYJ@7c^heD+obN7C)}uC!~%=_Di=NUDalsuGIc9i3%#FJZ?klos0ckNpjD?5 z^i1z-t9F`~X_X55zx|z|uMX0Me{)(IIK7f^kj3duqgtWN+Z*YllUBjV8qJ`Dr*1*u zbqzIf6?_l3`$6@pA+)|QMRDnn=ucOptgG^baYG$O_ln#N#N9v{>Ml{^D6a3aN;bs# zvpj!Gd$X%Zal*ERgKc4CtpQAtQYQ?@|Gr%p9`|eCSNxsN{g~TI9P$XyG;dgP|IGH_ zHb1t1dyW{F|0ZP%&D}7m$el_xMcJw4-y!Ou0ei54`6H+hcEKHM+oFd~$$SZu&BEgM zcABaF=&Tp+itL`FRAliKqZ_1>c=W@@@vk(4ir+_qnJrJsGk-Fag1>!1jH`bC6>K9G zn)T?wtR9D?CW<6atpPyk)4Ns~7TE%z8Fw8P`2KWyrByo@YB??^@LOStR*R;B&rx_h z_iG>EgeIPUckDH9&!&GUj!0+LBg2iny}xK`$&Tya&klV$!Oe*wV1D-jQ6w&tDiJ&QSE|pAScX3--+`W%U+(nu|^q$iKYsEa`G-z;3 zNxrqP+a;Jk>;8S%%eHrMMG1dXJJB+zuI!#i+X7L&-AD|N{Xs9m)hq{|Y4i}ndlb>xBz zPj1^Y;FIVtNA$Xw2c6Y&f$uX0Ajw1E=ubl~+M&Wt%elehJ?q>9d*BQ1ID58pKuS8}h&)Kjn!%bZKtxK}A+$csIcWx&2H*9lrx4ho^;q z^1LJd2iL=G0+77|YF?ooFOhPdSvfx8dZp%#Zsn0wi#)*#r94@~n7C?Gmu)dx-PeR| zM_c+6X2KxDEQE8ZRFYVw&9r=)z7Edrw}y9feMyx<<%MY}(FVmudma(NS77lKae}a0 zJ4|2a@Ltvj-H$$ncexUkSFe%R_wvl6`x2s_e`u2w<}-SVPfhyDIa4+Q6Zzp1U`q!O zU_F3-+Q2cDxSa0>+=`hI3AMt^b?V@DeR;P#^zXT7yO#jdA{xM=97x-ujuI6`jp29x z>KnRCDW2*{K>6EeJQ)IF=|cZZ&PA8&=#RMKIA6Gvq?tumsz56-7&TNcT35J0)L7W9 z`tEoIzwS$9C>Ul0dENA^SaoGZ(NF>74TC + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/assets/logos/paperless_logo_green.png b/assets/logos/paperless_logo_green.png new file mode 100644 index 0000000000000000000000000000000000000000..4a6f7f7cea7defa62862375db392c4aefd5c81d7 GIT binary patch literal 35355 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

a7TBh9n)F&s_yp2@ZNT zHO;W!YZ{4r5e;?YEcACBnP7)3gmA%{PVx3nd-;8yDVh^NveZA6l00c|<9!PgH)9B9 zmEYacV>C`N5XI)N{qcAg8$>|7GE4Ej&oMlvb3s1I$3XhtZ(Fm4L$<%0Y>)!Z3J<-{ zilgkC!ZIWR9+Sp}m%m;M$K^-OtW_Pi*VL#dohy-~wlc4-H}wf`09YW(X;3lJ0o za#{Et{m>aJRQV>xCH*^6^{{u4+q;~KvTYG#ALwB8zs>JviRPSyib>9EotnRyQv;AR z7Ud6I8kJ%n(iVH5LCHk~8|8k#nYkh`sZf2L(!{NxWc*`UvcXKFlBFB!*dA#C)jPE((>ik3g5MTH|PvBS4%iKA)u{xX#O5yD0|s zfVwVt#1jH%XQcuWO_!DZ#-}=CUW~jR<4Zh^Kn!aMD{>eL7kFHaA8-;BB_4ZLML(3h@ zoYezHB8pv3NT(BwY6(WI+#I*~f@c8I`&qJDbI*JC3(vz6f1-*NEvNq$xJE(tl4AJ$ zvP%Z(YOBPIpKH3ef&aHJ1Cmogbjly$*6`!IuoFYosprbvAq536#v@L}QzVc)7>ctd znpCi^xm~>Bc1BO?@x^0Z0Js8#EF1zM$WWxfByyg+r%#JiC8BA<&%8spL`O3M=_wIe zE0qd?gH*TOyBz$5F6^yAB32lzC3kz{HU>x&1hdL?DDsB=5(vN`yp}*v7~{>$`>&|) z%;;sUu-59E%@#)HZyrAc#U%8sP@abRMIja8H_|lOi6N=JfBx9RV043Lft6%4V~G-`I3)^*1BMMd@K+_Gw&sy1R)AV_Z*hQ> z{u4CeApJ|)x8bY`vW``EQZay@`a0Nru7pIQfel=X5cmXdX9aVlIP1_$H-h$w?0D|| z<&>2PX|p2U8Vu^=*Ww@+Ru})8CATstW{(!&hH;PzaqMJ-a%!xS$BQsB$|A`HKzgUt+VLm@76)ca zyFgoF^@l?ii`{EQt9@6KJP>SjbV^~lw5$RZNKhQ#s<@*JuqR#a|epvxd&z*H* zzavc;L~qbL>NrT~8#<(QQj5R^fMCS>Ft1u#ysE`OuR;1eLp?|twIX!n@cvFemG-wv z3+_8q5nBnEDe(0W7D_Fo%aQuOS^&A<+Ny001^affJ7>4Dyy7$x7V+{oo9!>Xqks+q zH9G@s3laNhQF^P)8Vi+n$r}HDrf5=0=gEB+<8qYh{B&P;3%eA=4-sANW&wU;Af;!* zpH>H%R=6z_{);pkqe>r=cZ%Pf-|+^HSK5+G&n7W1llotO>o!QA%xbaqi%~!}eK#_j z4Gh)X>V@)pMR92Mti5ANSf~(M39vWIkr?U+;#(07CGUC| zjSa+GerAA_9}NEm11&&b36WQw;0028okiUFVv-*brxFV~3U+A3x~!3r=$K5r1KkYb zaeWSl>EU@X05Hsla4A?reZFq~K=HDp`Yq2;lBN<8NysWmZy$LZuoiStGgC#^+&SW7 zuV@}}9;+ZiB9RFIFVslxeW0&-@EVkRg?B|kNouY0TByz`!pTeD-tuZVWRL0 zSqWcnmIR5E33&hnO96?ic6}Y=0~n&aTQDb7Fz|#a_(LoHQe^npTkPj(C(h7oz2Ab? zf)*Z6KBBro@BlBbK&5~)$SN|z9)aqjoro^(Lcib6KY2seG2H>S1L(wh9WPC3x{#<% z8CR!xn$8SYHv`o;u=LstG`>Q6#CPK{)yNPpzK@udKrQok9X^g${oTkf1nr0k!+*iII&W2xh^qMbs+ker)t0=~Iu}fvk;i#! zYY%CZp=zPR1fgRwfJ^|v+K_*em}@C$vG zWC>IDT`` z#4c}U5eZ%?S3}(gK&z1rJy+6q#v|@*0b{STv%Uv6Z|Z?Jf5{6t(*?+aNxphEmMLKN zC1M9JQ~p(`nrs~G;kVOpRvQCyZSMs}9bhTHZ4JKJy9YAQV@#Ako~(2XqLmJaVq?iM zYZZ}Hx0z;AAc*H1WN=}j=0X^)89!2DIf9;0oG9=x&OOQ)NEeEuH?U&>ReM>{p{0Qo zi9Ae!0q$f%4~q&&V$66d-EkRIGw@#Dg9WU+W)8@a6Pl^jJ={t-;?610panW+2x1ig z@P1%xD=quoLNNkc^0A;gssk0Gg{!EMI+GaskP0T9ZNU#qPnGXCB@1AxW%R=P+ZO~O znj1iNHI2c1Qwz8-nXpS|Me(srW33sTv~o`v%)QBV;0Iq1_!6LeT5B4}g_@hNF;m$} z@Z34qZDIpuLt|i#(80XeBCmnB9r(L3CCrrdpC8U7BmCk)lxOv#-WSh^plY;tZ^hqV z;^p^E@&thaK@#bX`r4o&PgV`cxX&pkhCVypHsec0;A+JqsQ(D~VbpS|L|b4mQ(66( zgw%)xlzIiZ3w1NF&qxaj7CIhjza<#Dx*oPEVOM5M?Dk51VvL6(dDo~70 z`oWTb<3Hs|`&N7Jw7RLfnoneGo4D&?Yp>=q*)7L!eQ92pZ&rmr)QbjbBn$v z3x{3=hQ_2npFd5rNss_vTgK}Hf}#9Elesx)_3Gm`HYm}RK;f}G*3wdp7=;n&gZJtd z61YwqJcRG(@m!~1%rZP4RF4Q++EO#=62%d$Y>=jmWPiD$kvyu49TA>q1fUSu=Th_2 z@Vj2D0gf9ynl$RWJ(o66@u=o~?k@3wd)(R1+`(yI1X47$HA~6{9uxj$)av6d?si=U z7G|&8qOb@^K3pp(^xc&Od$PB5>+!9PXf~Be?ht*U1!Y{X!rGpHficFnA2Bx5<9b!_ z2uAQ`)#{CTx&jMY`ElTAV@7ZIsRYCZE>zcJA~b6!Ex7UNLW2kENWVfCNxc&y`9p!dyWiaF*V#8jTlRYs{*X-XUeQ ze3=h@(>^WCO_|=^7c0ZhvgNJ$;v#R+F{a34Ud%n`&?ZpQK1M+tG#9b{6^lkEi?e8x z1l03og#(1*W7##1HY` z;W_z8Mchrw-*HI3lYq0t6d$P4F`riDt-6zYmnfw&(>u3 zR~h)`d9YFU&qK7B?ZR4(r|3G6v$BYANRmE}eC=ci8 z-nO>xBze0)4u@98VSD|`Msi5Rr}EH&W`16PP&Mh6`3oh}iYwP=9DMJ<;(Ea;5nTHG z$&E$<_P}o2=50EKs!>K_pfCbE0)F+o4O4}CC4mrD+6K)bTAGMA2j^%oS74R!t~P;A z7*G9VMRHlF?kXDeum#XyqG7u0H=0vSAW?1Z#x0sG8 zZ$g87KVbN&aIkk4YtWI*g+b$jjHo!_qk+E8hi*OK?c5YgXqG{F*tEOuj2kg`88J&U zT+>&o_YoZUL<*HMD_F@b6x-#@oy1Q&tr+4zlM#UpOu-G)Sqe9*=K>)G@CwwULB%20 z&k4ekzaJ-j*^yVcCMmAzFPB?;|81`R87~r{ZTa15#>7l6>svTD zKH$J9k|NZ_Ell4{?99t6Qzr&R6iXXSm!Ydu?!*3h7o%dZp;$)T@rvLoB|57U%PWC< zAmM-EM7a7Fu%*X%fM1zAxV@#`g*C355*d8g{cJzokAM{|ot@{=FEbiUn5yX z-tdu?gtyG$s}1q!0l2TZw{enWMAet`ROZ#^sQSk-Odn!AXXIp-=0J%zMeAIqycqvM ziC*f=?jwGnRJt(nSx%pBkB{^9nFe<8wLHDUL)1sc6$9BpUFF+S-hM|WAWLSIVg#91;nPO08D^9(!uR^hum6F8t3cMyLKXh*Dx-s z%0`MRgK!t$$%%9J@i$w1I3hKXKHBl?*dlTy&o|zvzC|UBi_rdFL{~^K9dIqAD)H!n z6cs!nEc6|E85Ec)fBLh3^)dJywz*@CSrEg;I6FHbHnjgZbc+e~>-B3T=H)#F$BiQ7qs2s*`Toe(m-7a&@PP#Ps&>2WJ1VSEd z>6en#`yO6=$d`JnR^)c#M=grY)i3r={H2HKP?dDd5lmQsVWKNXO*Ao>URZL$y_^nj zBQLy0*r(&`#4Fk%PY-@7=W$3DnhEu4#B^+7mBzXzVyp3FmoxnF%iLcY$OomfA^{TU zpR^qPSkj6LZIhhx0J!URoxENCybWU~T-uIu*Dp<#P`pnJJpxpWQ)%OJl0yOBdFtmKSrD2TY@RJld|#Uyk{grUx2$kkOzv4WT7ZBsnEgvnoH)ng%qaY}>$e_Y>1k+adZnBnkT5Kh0QwEyJ~ z|8=OS2)7Bo6H;h6c@ZD5x-<4c!*V;F;hhQd_Fn4HOMIaOAc;A?7cI z(>$|45$cO#uV$SiCrQ@mB^9G${K@+FH>}88 zwppBVF}4RT9%IC=)vfD7HYV$=_4VB@gYyW(^T>@0BIzX8#PDA7XOp_HA^zsoLg~`N zU_5b=^5b8@hjC#Y!Kt09P*wV4hSkTI=PF-sW0~jY&Hfd2vNY}e?fV~e1N>~{;alzy z^Ws_$llFsnueqnNgT4Ks|ycM!A<&+v|Lw66(Av zFQ8EhX?A3EFn0(!4OC9Zzty%)YU01a1J4E}n$jD;*QVmgyfIlH1g9z??Fa8VZxcEZ zHM{n0ToNFg?bCkZc;?OCeRq!5csFA};w9v>1ChmNk?nzMqBJ}GF;Iu&x~wa+rlRhz z&M&q;WeRhl>YlQmt@{WZd$jDItCi;o91HgTzn@0SpFA=me3jvvi0kn@umZD|HULw6VzjL7gc}0Q z$#%q>tTN_&Q>2S42g=B}Go6wmkIy~C9UtK{18P8*4U25zfxjCPP?xPNK#2mB^PSmkTLe;U>6wm?vJbd0LP?M~&&)xUHLn>W0YR_iJbE}} z6Yq>syjQIp+%UpN^^SXfod#?@`QQZa%Vn}JG}~Q*2(3VnDss1Vu@tZT|J>{Uk!`r#s)1k zV8&XJq3_n8Ku%Sg2)XpIsA4e9j`{w?HyKUV`2;`Q&!eI^Fpi}wQ~xceo1RMK7)a}X zj|0_V4>0W~yQ$Nh#Gv=t*$6PDMta%&Fzey?c9{_{I+mx-X649r?@hY?Zmnw1r_1X6MBXdx}f@+5IV z-73`U^~KiGiD>%>dOgkzqtn0($sJgO~T+pxsUH~nf?`WSmcK0-Q>UGS)7=lkf9_pnB{*>>k(?vY;4DGFef7l_QBq zn0>yjp%;z)TZOnivykYO?SFjp0|CpMiidSBpp3?kAPXNBrN0_MAY1(~bJ=EtI^mUm z&59yq;5+e=2hKOjW#BPj_YxH&mkgop*>t+hSgR?_c3d~glhsK_GP!O-RK6|;Bv$2R zukb)a8aO4_K^vfjKSZ&?yxwPku!N9zE~BxYeQSgHq|ud9vEal=BH^(vM@*zaWZs9z zpF`q~Y5tcsGeBDnNMsvKzh2K&s~Zx1i-pEG>8r5-z+SH5>dNy9*sOKhA{P2Z>5an# zItE!;n;m;O1QVc2yvQ{Q*iq>KX&fPd5TK;m2t-?2?KZbr@%barHdrBEa{o>l#{bpe z13$?gG@9i+Q?j&kFe`h67@{wf2*Ntl#J0#3j?w;3Fw7YofO5WxNq*%4zxVGqsD+UD z(=574nQNjnr=NlR%aV#1^@t<6ddb^d+m!>m=xB@_IZkhHT5#c5p`aOQMBd3<1_Z+j zvXZwM2un@ z*Y!Cd&{WlN+bCDx02n?zaD5P_R6VfgzqDZLWx;m&9Yn^gUNn>lYp+5|%WDzG@`hXQ z-hQh%k)}8SfsDQtLpCX>CLh3Dk{B1M<`>ZW0rIDi00n`Q)FZ0-ef^GoLJjatR5y6E z)ayz?IB3T3h4iMX+-X4({x#m1f{7DU&sWAkfdueLX?KIa)&0QtsKoz+_#ych;`5MUR9&esUHEHk_9PxBtyXaavkdsS>G2fekM z{84=4kGY0HmU=!<65GUngNILTEdkUQ$lP@7f-F2Jdlv&3-vz&ro>%`q&H!?^{w`iZ z>@R&9%z%&Usw9~LxF(HM6MXt%5clxNKyiN0FUgW)dH5w(fXW8XwOq5I{PKEno6Esr zI&7>RaPD>x-%|YKH3<12HrxShNXY*m3H@!^z@(hG)Dxsi9 zU&P4tbcrzHNv&H5;CCL>^-bXc0lgqn)C@dA)3HpD3A|eaZrZedW>`ot=F~|hg+64L zW~QGd>^)i3`vhuqg~LOmL*M1$ooe0#z<Kv-(lGIjzx3G za*d&aPXpkM_sSn=c~$}7&}x1KC~GD{;M`B^k~yGizMrQCN(Ru(`dqC^e)Z;)=Bpmq zB{kz0&Zr`SW@QfKHSbBg%NA|zE(V&y8-dJMiX8hVHoY88#3oD|^naeUhaTz_1CJCc z^pjr-{t>s#2Euks_(eUs44P_-*cW-Um ziuCd`OjrBNg@8%9emg?*O_-91>(Lg;DHg13pqy9zbm99_q~(8e2XzMQdy~+=K}Hvy zLV~oV2bL2q^{7>XIlvKPSU}VbEdVri^5yv7qfc#|x>3LH1wnc?fIPlVjBH^baKQ&= z)bi*qh&Bn{g7MKoFPzd1LA+$BA)OfO6a<;&pjHCP%nkn**=mwoN;?arjH?K|gq zu`e+pmRjYuC$_Ts0r(mbwstDQrcAX2n=7>QwJl#MB|To zbYkL%OqXvMF195aRd!r@+O`^Wi~=Fl#D-R;V#Fs?fkQbgx9XZ!>!m((VTzRvN=!7* zmCcn%#iu&YUQ_Q5i%44Tto&MEmA_K6yDN0wKRqzUu6WOIk5ctOOZF%)FT0tkc+>Nt z3nnFT~ z?Em=WXQNDGNz^25_tpnZdz9vOim}n*c*FO-aosHu)s+m~8ApL6N#|032Vv-h5@{Z2 z1n<9e7*fSYL^g?kPU9}^J4V@ytZ0|bzHk>C@XHl5#68W_HyZqUy;17v{$>jr3EVA) zn?3btCtUo}(BeW|)|3%E-5Ik+89zWY<5=G2I-`-0yXRX+3`S_>#|;c9@Rjjq_vh&? zyLag}D-o?%={H#yckQ_r<9N~QFo$Y>b29-#@Yz1|xdD$g&za?|BLg$q(&~ol{`ir` zI6Xv7>JqY1Gtw~|oRNbG4hu#+I9KUl8_dghF)HRI^caqv=J5=fldI&T_i;ETmeB@u zwt-`~F{`DW-Jgoa{WY2R?(@3*Z-eP_y}_9#7&+?Kz^gim8$N%d4v{ud)k+tgAMv*o zzxZKbCQ#}+p2a@Hk}v4e+ilQv*j^&#{vXGnX9<+9(|5bv-3_W&`V91Q@(RsPt5(hq z-w`efNQh6>95W#-q2Z$*<06^!b9J{#s;es*{EdtGHA-JCMl!nF;=5jI$SZfhbRRrW z089#)1UDojKYNY;xXaGa?^3#faXPLPlbk$p$*ABgV{rYjNyuPw|J53FJKS%;Pu!Cu zLqUaP?5Iz^_g7OD{!R_;hh=uGSzT17udfWj!Pv6ke0S6NGoua+f6u>XUlsSGMXGLp zN=#ke%19e{t&;f3PUN?gFg-Z&2=*Bl%!s|OoCuL`S2TK7J?vFe;Vw*1Gm* z@XYjP+$?Yg{d-}_mtwcS>c+U9%8INiy&8U#{`R(QYVn40sKP|!(Fr)05_SjNPlR0f zYEN*XGg6UEBxGn_qc`fGUN1Q!IPtYU#W&4n^V849;A_h7U?D$)%i*P!rpqOTyv7&q zCS6+>^Dc^vgOhM6l$!^BrR62XZOF)hkCFq?2oKj!xje>I9RJn%gepP1Np7z&#WQ;I z!k$R7_BXPOfgtUP{Kvoc?rldQ0Duck8d4!q=CEU(m*;Xl(r%M4>Um!I;P= zjMMe{&&E~nG}yoJoNo~K_RFa-J2jTKzGBO#sjXc8{{@EwhIf6ee6W~l`}Kf#drJ+OnQbw-^EX?>3}`TznkE5)S-=Woj?Ja_xJ>;)+8s_`80?mZ0`jpYxLe|i zLcw`Y++O9G;%NWYR;CO@EV%9!*)h^*Pn+;z`>loOBc5|I1*#>_0s0xS>E`3I|JD&4 zLeBc2+@y6eF6O29MN;AiZQkB5W-mNtlcr65b$jQ3ZH{KLL0>M{D04TFDhmnCsQ6=j@1XNJ1G|L{;Rjn1ZVBQUV=*- zNiu&m20qTiGE{ZDeli}cuNOOKIx>P?JSU#x&-iy{Uu}&5NNqAC;A^@5ki)4?YUdUaxEj#c%9? z@jXw+PXpY(AAcV=Q2us~9G&n*4dum^nBNO79G7BazpvSIR;ZMI7iQ&{jE>=5T%v2P`ck;7SJXoKm`jskkzy$kj z*INtrQ_eY|spNb0JztGqZ_+C7KN}G}O8Sp6;6gf*in7Z0RJ~m@cj!aXK0SBq*&Kf| zFlW&8ujLZQ2-on2F+f_wqc3`zEZG?byc!gkO3M>EF6+tuGQ`a$9!@JC^>tmu)Zc8X zK5$$+vuRVmM)Fl|phiPc#jNvIw|}k2WzTVMWm(|9Py%<|IFP7NNaue9=eJ95KC|B} z8-8~7rY5?Wx5QSFw^2V%@YmSGaj}v5)yy0DssK06gs0veA9`!CnY|5fLdjKk9Sn37 zq^x|bdiDB7liVn~dqo@bZ&XlT+ZTHUHBU$4_6jWG?E1bG(kLBG&f{QX%iQpW5>V)b zbB+^cI5pS8si&vaEFHI=?x#L7?bGA5(lq!d*7x3xz}k$Eio#m{0*RQedbq!vFIoFa zX3$X~Y2{-Ax1!6;j_+8~e_y;a&4L-(LU%6Rs7O`6(d)UqG;lC8-=k)HBc_vY#6qOu zI(y;MbizgwbyZ6xTUJiW|4|(denLA*HLGWj4&8bc#}|9>&r<+0A1yZEVhOy@XY1SJ z5@g@M+#;&_)oDivDTVv`U^NzAtzjJUJ55HPUt3+j{cqoJW3#By6E^DHL8wMeq2_Mv z_s4#~VRaqfCo?P$D_%D&91ej)`(PE|zG;r|_s&OBynh|mVhn1xn2HH5EdQex?0NDK z`+d!k#Y{0LhsqBh>~NuB!L!wmExR{s=#fz8y@n0b7^=&(%urC2aaT%{()`D8rnUxMBYi>d zjvE-H5P2%`gY34dP#9#oTj;<qM|;LmfJNuGOpHj{Vk{yMheOs32JVYO$1*Qx3bhlCrkeV8 z_4j7_!h+DF+HZ)2Z|{FP(mGa)yz2U(LY)z@MoCaZ%lgMZ!_t^1x}80M!DcJChE1@ENOBsZgq z|N9!P^YIWNOy!I7Xpc(aSB$tRnVP6xmlst6Crp_Sz*p}J0%^u$&i^{WJa0tp#8#K zlj>J{Hw%Us7M=dtWmvk041f`&-5?!dHoK>wVEJZ?I_XppLkv46?7yEHEXcz(-RtTb zSH7|+Fl6d?>=s#Xqi)|U^APnGq$=lmnbcFwc0Nu^(cJJM8mtan;K76A{!Fnu4&GJv zuT-Jk1$zjYue{ek_lSEB28I~yR|cyFD+a@gArd6Pz6hCiwOxC2>w=Rn4$SeR2>35^ zO2e`XdIhSGE$U8;vDwV+5J(cG{lHHyo3Xcb*CONTi)*s=5s(AHYz*eXT0NjQUCHUv z`RTMDhw1mZ&y74Uy#+&?&j+O|vb%4e={ToiZLWP zjp6(JVV~{BkQ7lP`HjH#gS!#3=D%d(`*rkR7fwle7()m1!5&gU&wo06>UG;CEL$&b zeQC#S^AQ{qN-&f|JAX77Pz+pim)Lq=osm}QTQcK* zn-2nu!S&PuRyV%H$fKQqXAvTWN>`i;VL7`aTXsx^v)aC%v?aH|GXYV?WatZHhTwnq zmq?RrKY$C=>y+z1Zp>QGq2xZf`GGNzVPKcGP3=upt4m!NJ$Hx2mdpUT+nev-95jfuVo_&Bb#v z4L~{x3pUGr4Bg46ZxstEC)TT$6AL;?_3vrN_6`W0(kd`WT~9PMPKh1tnv?+=aDk`S yhkz`3)k6{Emr2!w6@@r%1oXr+K(Ph + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/assets/logos/paperless_logo_white.png b/assets/logos/paperless_logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1d09d105c53e62d79beabc7272031b6bbe6ea7 GIT binary patch literal 26109 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 + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml new file mode 100644 index 0000000..c6067c0 --- /dev/null +++ b/flutter_launcher_icons.yaml @@ -0,0 +1,5 @@ +flutter_icons: + android: "launcher_icon" + ios: true + image_path: "assets/logos/paperless_logo_green.png" + min_sdk_android: 21 # android min sdk min:16, default 21 \ No newline at end of file diff --git a/integration_test/test_add_document.dart b/integration_test/test_add_document.dart new file mode 100644 index 0000000..6f8d044 --- /dev/null +++ b/integration_test/test_add_document.dart @@ -0,0 +1,17 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('screenshot', (WidgetTester tester) async { + // Build the app. + + // This is required prior to taking the screenshot (Android only). + await binding.convertFlutterSurfaceToImage(); + + // Trigger a frame. + await tester.pumpAndSettle(); + await binding.takeScreenshot('screenshot-1'); + }); +} diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..25334de --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,167 @@ +PODS: + - connectivity_plus (0.0.1): + - Flutter + - ReachabilitySwift + - device_info_plus (0.0.1): + - Flutter + - DKImagePickerController/Core (4.3.4): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.4) + - DKImagePickerController/PhotoGallery (4.3.4): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.4) + - DKPhotoGallery (0.0.17): + - DKPhotoGallery/Core (= 0.0.17) + - DKPhotoGallery/Model (= 0.0.17) + - DKPhotoGallery/Preview (= 0.0.17) + - DKPhotoGallery/Resource (= 0.0.17) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.17): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.17): + - SDWebImage + - SwiftyGif + - edge_detection (1.0.9): + - Flutter + - WeScan + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - Flutter (1.0.0) + - flutter_keyboard_visibility (0.0.1): + - Flutter + - flutter_native_splash (0.0.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - integration_test (0.0.1): + - Flutter + - local_auth_ios (0.0.1): + - Flutter + - package_info_plus (0.4.5): + - Flutter + - path_provider_ios (0.0.1): + - Flutter + - pdfx (1.0.0): + - Flutter + - permission_handler_apple (9.0.4): + - Flutter + - ReachabilitySwift (5.0.0) + - SDWebImage (5.13.5): + - SDWebImage/Core (= 5.13.5) + - SDWebImage/Core (5.13.5) + - shared_preferences_ios (0.0.1): + - Flutter + - sqflite (0.0.2): + - Flutter + - FMDB (>= 2.7.5) + - SwiftyGif (5.4.3) + - url_launcher_ios (0.0.1): + - Flutter + - WeScan (1.7.0) + +DEPENDENCIES: + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - edge_detection (from `.symlinks/plugins/edge_detection/ios`) + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - Flutter (from `Flutter`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - pdfx (from `.symlinks/plugins/pdfx/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - FMDB + - ReachabilitySwift + - SDWebImage + - SwiftyGif + - WeScan + +EXTERNAL SOURCES: + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + edge_detection: + :path: ".symlinks/plugins/edge_detection/ios" + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + Flutter: + :path: Flutter + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_native_splash: + :path: ".symlinks/plugins/flutter_native_splash/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + local_auth_ios: + :path: ".symlinks/plugins/local_auth_ios/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + pdfx: + :path: ".symlinks/plugins/pdfx/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e + device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac + DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + edge_detection: 9bc5ee35073b5a17c0b3b679908f01017ce3062a + file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 + flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 + local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d + package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + pdfx: 7b876b09de8b7a0bf444a4f82b439ffcff4ee1ec + permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SDWebImage: 23d714cd599354ee7906dbae26dff89b421c4370 + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7 + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..cef9439 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,549 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B8F579F7EE511C92B2614EE2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40BF6B81A87C86D22CDB775A /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 40BF6B81A87C86D22CDB775A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 895AB075C3F1E3E87F6C3D1A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F0882A26646B3A4713EAA3B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B6A32B98ED8D2D9794070543 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B8F579F7EE511C92B2614EE2 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + E525DE4999AE527627D97DCA /* Pods */, + FB6F7F4A953DAAA3FFE794E6 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + E525DE4999AE527627D97DCA /* Pods */ = { + isa = PBXGroup; + children = ( + B6A32B98ED8D2D9794070543 /* Pods-Runner.debug.xcconfig */, + 9F0882A26646B3A4713EAA3B /* Pods-Runner.release.xcconfig */, + 895AB075C3F1E3E87F6C3D1A /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + FB6F7F4A953DAAA3FFE794E6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 40BF6B81A87C86D22CDB775A /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0D9B1245DFA4E4B02C265385 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 13B6B31A3992BA65B73408A6 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0D9B1245DFA4E4B02C265385 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 13B6B31A3992BA65B73408A6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterPaperlessNgScanAndShare; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterPaperlessNgScanAndShare; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterPaperlessNgScanAndShare; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..04baef2e1dc32ea18e31011737494d4b20528ab2 GIT binary patch literal 70917 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..2deb5cfc7e1b100c0c07cb5020a0883f7ab00b48 GIT binary patch literal 679 zcmV;Y0$BZtP)aC=elH9XBiq2M53Xsa^k%IF8m0;2|NZ-yg)9UA{Qbi;ee>*s?v?#5zyAEu_`-(SWl8>W}^teV*T`|oddba_s8&d+jU zvX6)|@YA=?JZXK|%MV;UX6_qcY`B3Hvuh{yW_Dz9v2*=^iIvZ;o|3kEnvX2P2MoQ+Ct0hu%m(f-mNEh z1OcTO@fikmz^(kC>`r7cuz}xxe&+_!|Nj4Dsh!{0y|ijUz=M|$mGKz{irG2E)55qp zxW8c-C@v`W3?{(J!uGdgMQ;N;3;Qozh5-%vxS(vdA1Lpl%YhA4l~%bWB`kUW+1uxe z*B{+5R+U!1{*RdjtAXmWY8Ph|Oper)Q@@T)9&8{J6Vw0v!0guYIW=>DT*hxdzj2wU z8y{PFV9kF;F4x18O{`ng+*3EtSOa4=@$suC^6n-s zTYNkld~L6#eE2VS{_Xzd1qt_n+U3Aj=P^ z0}o$5YMs=bzVY+-FTyafFWWe#%Ca~Qs+$W z$yf`dg(!6#2*kTZ&%h>*;lPEvmZTrMdeZgQliP}FGOG7+8Q8eEt?l8f$C}h|oQ9nG z8GAk3Lj)ZNY*{dFKea3P-~WHC4GUX)mQ^nZ#-{1^(>sbw_AJk)hT}k>ASk;9SO#L1 zLvbLmh|vD@?UNXYz3;+-z-9YZMn*Y>FGtt3aOaW?V4ccJ4aXU%=pXX4^x23@9>syz zAKlbL;xbmxYnTD-ADq>cQ@@3*ZriDyLDX;@GZXXA?zFZ{CMMi9A&LW^y?G{!%m>yC zd~qGgn|5?;vJmDI`T!CGR{EmP-ac2Rh64+Na_WKI%xn16pg8d7?_a#=3f_KtE08%M zZ_)Cah5kSv|9|lEA*fY?J;9IwY;>%)7KY?@5mJZZKxQVUpXdsN`Gw!S{`gwFdu4w` zMPy0)r>~zeJV7c@l~y@Fr+8W<3o{GBQ3DhQiV285M_2Ip)f4TUfXvD%8)wwH8ar+0 zWas!riR0wO<*u)+UF<2qBk-9R^(YS1l-Ia~P0{HaXPkp<{1*Y+cRgKcty$zcPDerO z#FEPSeiB0BuZh)z;y@!6gLA+>!P_rL-9QjHdE>NGx^GGcu>ZOn*y-T{M$LC%naN3t zYCC4|o3xnQm?t9JFpyO)v< z`gS{saNxfHYu|+paW&oIvUvT0M!WOq%pD_2m-uZyx)N3 zm3Qf^s!3}Otxx;*=N?_ano=G07g(6iaJvUW)T)m=oa13`e3o%7d}-2M!A z6W1*pj&2SGW|BL_Gs-8v`S@0Hb=^`|T04%Q17X0|(re4&J{KC(QLh?Fk=_q7Cv;%<-d-dkiTe%5Y-4XoUd|znk uC}cpi1A(0>BVbQ&Tew5WN?JMy9RL6=X&s)(<#eO~0000GvIsTj#yNoBUr}bK6G>TY)~+Z!;bvxr8@vQ9mVsY!5s63$@&ORuB z!KjN4Pmb6gd^PlNH>+oaBAxDmPjaO|;=ZdNx@l68+Jj;PLbNi!u*enHUK4-y9<9kTH0Ruh~ zciip7A19B3Wh+LS&ru{(%C*&KrAU6M14HSd$2l+m=(NlG`>oNXBJ){h$yf{)(a#sS ztEUSihnM?iLt9XJM;-L~3_CztVYn=Tm!BtaW>LlPq_v=H zFz@T%)2?M-Uutb+byLuXABhXzSKU;jDQLVTw9?q>obsqKu-VnuLIl3~__lT5@4&d@ zfflou+#$#k*!(I4>sac=v>3YJO%-1_EVk3t+WDwJv2ez>HSegP;o5!omG$qnf)YYJ zyw>bJ$gjdPDU6+L4jXohR9bRaWFIz3d1AF7v9Q3;UOzWP^&4zXr!c%sbQh-0)0+DP zsj{w~ZrmT4e>w`)S}{ddAl2@%r3VNa4+{(iES>%~kU-$KhVF>Jd*r5p%4p>#xN@0A z*+y+0?PCOQuK4m??e0b)jcye*9v1jrZMg=Vz2Q@%>V}}LaY?@M=CU$)PJe%9arRGw zH&uKY(&k)Wv)KNMFlAtY8(JEbkqYF$d0`3`07p+a9d+yPD}Gm^C(Oge5m}y86XqPe z2_s}`4hzg;vk({LU_@+6;`eo(63$Ppu%kgvU~12647(r%&6aWLne616-mFc^h- z&S}?jZaBKzxzW8Y_+9jj^gb58GjtndClVC8+dG+}wuP^(=2AU6SEsZ2}O^H+qQbPjd~V~AKEmcat+ zPML|AT=eNu`V}WP8&@ANP2D*Zd-TvP?PSvlW5!ohHCFRG#uQ3i6qmbo*(PuAwfp=r z64*e11qSn5whR`>WO3Mtdr#TLdFCNb$2N|oP+#T0`ODwK*#<1|Y1N}rgE-KsCb z<7Ce?BhfG3WbP`ulAmlqg`dG0t$LOl1hprxRLiCxH* z2l3zJ&5*#~1Y6Zq^Z66MDdROhpVnE;w~P%-KJ6pOYdv*>W}cJf<{)b$t0Wm5Za}_c z=DSUMaJ~25g13cpN-E%A?c(y%&%J4^JwFY)YN^uRXsJ%mL5i2d%e7d{GE4Zy)SdzU zpijkVyeQ%eEH5X|irIVK%F=69M*mNRMi3P0Z$C}51=ofZ^Ohw+wx-r5(uc;X|65+l z%MzLw%rHt_I>#buiNWGqMEMb2lEn~Q0-ZciDb&V4ed0}Q{2XqV*Zprm;&3?JhXt9d zJnYl~89(6f&hX zet(QrSaP^aaN@~BSS*$uK@Yc+m67GNb+i-qob@~hU@`V4Kc5_ou#kj4OoNj5T`PBl zTFhROV(zj0F?F;`EoAqN4)BBaqKSkN^`7>72JCo6Sc!ak2&R;`ATcS-Eqy#!L6*>d z`ChCmWT)Hg+^;lJ5%#07!631YOZBGc=HAV}XOUH$V+tlw+~l$9`5P@9BPG>1X-GU^ zjhRojpLC7y<@U;fTW)}(j|)g3L_S&_2#Ey@dbI2dEDFr8F$-rJ=SZem(vw)gaSsyL zKvy}uY^%TH0e8tXNxBjXxQE??S0q#3Z7^RK3>E-2l4_JR^&gXDM=d4Bdno__002ov JPDHLkV1klwqnQ8z literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..83804fbbdc0163c903fd209b9eb490fe115d6d52 GIT binary patch literal 1015 zcmV`c`t9l2-XQ8!)AWzlJZ|V!N4_ZPuOQ&v}k+=KYz91`Y%Y!g6 znp(PW*V4q+WnFzB_WLjIg=lK&-+zA@JC^s`?`iT(QXn}cSSzIgXiEN61z zf+JUsc_NE5F*5z7iKS}~ZwRiQ*D&qFmk(m-ay*>epQ&o;^S95%%VyV1*?o3z2re~Z z0-`UdVksy)EZw^zwsBG0gwNkU3*l3%ATEE0VoP6rcr8*et#tOj^9KTmQ2tL#UgI)F zmY%qN$~?6%d(G<)uVsi)uPmu_RftdMBiWX&Ke8z>Z))+<-+z8{6066_(0&IpAH~w= z?_P*X2#UXEWMssudCQ4yKG~BBR{s0{kChaM`dWIg#%bx44b#iR9YPoADd}86*Lds6 zZI#@~#mh*wP)$blf~mUkX{?t1{_}?kn9pM0eR?l6ygS zm)4CQa&Sv8-o0q};mb#{^+z|w18an(&V~+qVRC0~oijRq?W8NI7ODe%8DbZ>0$m(# z>9t2U^`T6r+{uMYc6M$ykrkDB3KH0Ma%T_&vHWJBdvWZYdKJBfLNcdWn&lnfZIg zoZ1N!vwJdu!7fOOg&wA^E6XB^T5!q3EtL|MdW0+iEO8WhIC*~TJ-6SPi-YSMu@<@* zIj);oI3<>ui5X`*2yQ7bSsw*bAcf$#d-D3JN|25J{NB|Q>(yjcuK^R13qH$$XDMSg~uvnXO{0_xU~LDH~=KF*7m!iFS@yNDT||S_%R(qS8+*X4lSG*RaA4U%sQNrNF!@ l11zxTnQNM!qLzgq001#aZC>A~Qc(Z^002ovPDHLkV1k&h=}Z6s literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8cbac36010f3386e016b97ddeb83c39904ce3d60 GIT binary patch literal 2317 zcmV+o3G()dP)05S7YKqvRUtDp6{?q02^hS{kIQ6)P>3HceNNAL?gYWn9+8*48Tb z$abM5En+Lf)KJL%l9}1_^m=){TF#?+<`Vk+<9WW{^F8PL_B@{RJ?D_-^ZB^1MXeoe zaxXbACm87(R`mBt4@E+fQpEKruhFAO&0jTWO?|D_|E@(Jbh2J|0p|ZIK)E~~ZeOHt zL|0F@GOLa?3D)MT06mp-c3jbSMyR=?vC(D{LuSB{!=@T&r|H|?!?Q>%KvBH9Fd^m zsgbm!aUtP+c>aX$4?VH~wChvjhyn#qis%#Ne>yoY#S`4(2Wd*^Zx}J4RqTHzxSXIR zRkEur1Zd9dZy5HVNQrtmov|S(_Gnl~cgJV}LU?jS+3xUx%z*SWpFJgg{MBF=uN>gYX% z*@px)DTnEBASNiPwX;=2oc$8;_#VSa215_p^Pxw|C))p1Vovfd+~6ggP@;~b7Ah$y zb-{dJ43yJYr}q6R_k^P7j~7X53@hV#nP|Q*0$Ro@r#J;~NvmtB|3;GIEShd}6U`@a z&U)1aVn840T$t=FbVOG#tile(ltq%O@a&xDjnmPv{)ZZ znTnv%N+k9c+YOgK*A5EW)Y_zYx3pj>at&%w)ssn~{wHWZ3zU4|uSZVs`Ft5n*@_^y z<=ajw$|(-mQRpuyXnyHm^SL~pY~OucR>H2;mi7s=^=B3h)>*-`duER+izQYN^q0B{ zx!QH9bCe)$p`eeRKc4mJJy>NBcbwZ3pLycX=_Do9Hnh$XbCX?g5-9D^6tKz98>~DMB@MiL^^WM?1z85#(@dH_qC0BZ3rXA*Nh14v4iyP~#)-VNa4+X8R zuYsOOr59zcKb~;vk>C0sPWJbeu_`E_`hgzsh+A?**_IRAk9op=;b?+_HnudPZe^Uk z8XhujqJHVh*~`*CwLg7TYAW!kqKO~ua>#A+*l$=^f}o&W9#;W60&po3<=^g!@QI_2 zqnPWBr@rj-)zsBO-vV*ValxAKO^ck8u*RXxqj)aVio;2nQcZG7Up}*Trf*NV6>@Ek zvZ8Va;%n|`hMr=HQ)ZJ*Gmg0idkYf_1yzuhf8Wy4LWcS7mp!oG@94ejX9hj`Kui!l zZ0Bh&#NG`S)xsnosC2a^6~*~qS_N|ZAWS?I6x5D2oTfSo%-751%7ZU3#n#LwKIsnA zW5ukc%q25yvsD#U+J#9#5CO<1$$^YzU@3+=wS7)dQ0n-}&x$G^TcZUf2*-g7~Z?FSH=!u&1!1 zXrBGt6_>B({q6<|1;M5QIXCj2 z-nB;y1U0P{MO(LASwWfesH)f!lw$2#qsLUfY2;8vNoWj7^Pf}hA=?&Cx4t7vKmCRJ z#60b`p9G*tNe6vG$O@(SMTvze#rUt>lDt(_>=%@|(`ILbCoL=}7nF1nhn;`&7v-aa zCR9-HfytjaWk%Zl@`p>&+WcKzZZJ-L>?`EDqpL$Y%tfcknN0> zvzEt$r|OQ@r;LF-<=MD#2^xv#9LuOv;fa!*SCv<~X z+s4sq?S;NQ8(r;5m5^?DM!E(C4s-1o-_2jmP*){4i=H|*3AC?!#*@CMtEWrh#{INy zpW1VZa~JY?d@0%r*HGl1{qD6!#I@)j!6l!pti}Rs#yj&2=49HKEV!kkIbI?Kg`g4$ zlqgN)M(;cSeL&11&M&uO5s;6sF}vZ7siz(7AXv$~LhcX51R|%^15-=fS91jmX1~pQ;JEppObe8;!nMMefggc(>KXV6WfBe2KY)Y zW#NSRNCM1tDPX?5>k+!^vaQ*|KZr6!?%U>fs(u#(E`Z03_poyd1|KHq{}BwOggScx ztDAZ+dw2S!+gIFoI(UQ~j5&1teOt4ZwwiXu?o~U^h&xGfNa#bR8G7dTXlHjaZWm@) zrW9OvMgD00H~8#VkW=UpV}4?j(4ZtG)pk&qay&wJCj&@=g`WAXHI@$7#F?BpCDO4W zSSyyisdOQ nB#l}XMdf-h#T*f5Qj*ZWo}o7GKEcN|00000NkvXXu0mjfvQ&tY literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..27a1ccb6b63e64743effc317f704ac660a61be42 GIT binary patch literal 3647 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 RdD;K~002ovPDHLkV1mVn6x;v+ literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..89d1d460b43e381511ebbcde870acb27bf69ca0a GIT binary patch literal 1492 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 UgXG^+G5`Po07*qoM6N<$f|EN^7XSbN literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..2fba5fe8fcb978cf224dcd88ff7350674c2dfc76 GIT binary patch literal 5132 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 j%>^U%1wjgw(y78m1>s=53 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 JPDHLkV1oKlaPa^D literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..066d126fb1a1ccb66ccb4c347ca0af096bda0372 GIT binary patch literal 4913 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®H`s~MhCy$#{KS@#TUSUP? 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 KMNUMnLSTZbv8t^A literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6ab370d516c3862b34bd721a4b32094cf0f7a8 GIT binary patch literal 7419 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json new file mode 100644 index 0000000..fa31327 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images" : [ + { + "filename" : "background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "darkbackground.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png new file mode 100644 index 0000000000000000000000000000000000000000..c47e9c5968ad822ebb1c3fdbd4a9f4a87e2e2c80 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?Xeg{QSJ0f%7Kw`~CxA QZ-5dEp00i_>zopr0P02(8~^|S literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png new file mode 100644 index 0000000000000000000000000000000000000000..b17092efb3f6fa7a6584cc81280add9d266635a2 GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9x7?USXnDB$2f$;;Q&o{+o Ql|TswPgg&ebxsLQ0JYi=m;e9( literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..f3387d4 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "LaunchImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchImage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchImage@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1743df48774cae21fa940776c5768ef1be1b2c GIT binary patch literal 4811 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e0158f83ed1eed4de482b6c32fa058c99d43715f GIT binary patch literal 14303 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

x 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%0Ax00ksT-I!SoER|n42C7lHmDi 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]) +import 'documents_cubit_test.mocks.dart'; + +void main() async { + TestWidgetsFlutterBinding.ensureInitialized(); + + final List documents = List.unmodifiable( + await loadCollection("test/fixtures/documents/documents.json", DocumentModel.fromJson), + ); + final List tags = List.unmodifiable( + await loadCollection("test/fixtures/tags/tags.json", Tag.fromJson), + ); + final List correspondents = List.unmodifiable( + await loadCollection( + "test/fixtures/correspondents/correspondents.json", Correspondent.fromJson), + ); + final List documentTypes = List.unmodifiable( + await loadCollection("test/fixtures/document_types/document_types.json", DocumentType.fromJson), + ); + + final MockDocumentRepository documentRepository = MockDocumentRepository(); + group("Test DocumentsCubit reloadDocuments", () { + test("Assert correct initial state", () { + expect(DocumentsCubit(documentRepository).state, DocumentsState.initial); + }); + + blocTest( + "Load documents shall emit new state containing the found documents", + setUp: () => when(documentRepository.find(any)).thenAnswer( + (_) async => PagedSearchResult( + count: 10, + next: null, + previous: null, + results: documents, + ), + ), + build: () => DocumentsCubit(documentRepository), + seed: () => DocumentsState.initial, + act: (bloc) => bloc.loadDocuments(), + expect: () => [ + DocumentsState( + isLoaded: true, + value: [ + PagedSearchResult( + count: 10, + next: null, + previous: null, + results: documents, + ), + ], + filter: DocumentFilter.initial) + ], + verify: (bloc) => verify(documentRepository.find(any)).called(1), + ); + + blocTest( + "Reload documents shall emit new state containing the same documents as before", + setUp: () => when(documentRepository.find(any)).thenAnswer( + (_) async => PagedSearchResult( + count: 10, + next: null, + previous: null, + results: documents, + ), + ), + build: () => DocumentsCubit(documentRepository), + seed: () => DocumentsState.initial, + act: (bloc) => bloc.loadDocuments(), + expect: () => [ + DocumentsState( + isLoaded: true, + value: [ + PagedSearchResult( + count: 10, + next: null, + previous: null, + results: documents, + ), + ], + filter: DocumentFilter.initial) + ], + verify: (bloc) => verify(documentRepository.find(any)).called(1), + ); + }); +} diff --git a/test/src/bloc/documents_cubit_test.mocks.dart b/test/src/bloc/documents_cubit_test.mocks.dart new file mode 100644 index 0000000..53e3340 --- /dev/null +++ b/test/src/bloc/documents_cubit_test.mocks.dart @@ -0,0 +1,293 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in flutter_paperless_mobile/test/src/bloc/documents_cubit_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; +import 'dart:typed_data' as _i7; + +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart' + as _i2; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart' + as _i8; +import 'package:flutter_paperless_mobile/features/documents/model/document_meta_data.model.dart' + as _i4; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart' + as _i3; +import 'package:flutter_paperless_mobile/features/documents/model/similar_document.model.dart' + as _i9; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart' + as _i5; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeDocumentModel_0 extends _i1.SmartFake implements _i2.DocumentModel { + _FakeDocumentModel_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePagedSearchResult_1 extends _i1.SmartFake + implements _i3.PagedSearchResult { + _FakePagedSearchResult_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDocumentMetaData_2 extends _i1.SmartFake + implements _i4.DocumentMetaData { + _FakeDocumentMetaData_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [DocumentRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDocumentRepository extends _i1.Mock + implements _i5.DocumentRepository { + @override + _i6.Future create( + _i7.Uint8List? documentBytes, + String? filename, { + required String? title, + int? documentType, + int? correspondent, + List? tags, + DateTime? createdAt, + }) => + (super.noSuchMethod( + Invocation.method( + #create, + [ + documentBytes, + filename, + ], + { + #title: title, + #documentType: documentType, + #correspondent: correspondent, + #tags: tags, + #createdAt: createdAt, + }, + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future<_i2.DocumentModel> update(_i2.DocumentModel? doc) => + (super.noSuchMethod( + Invocation.method( + #update, + [doc], + ), + returnValue: _i6.Future<_i2.DocumentModel>.value(_FakeDocumentModel_0( + this, + Invocation.method( + #update, + [doc], + ), + )), + returnValueForMissingStub: + _i6.Future<_i2.DocumentModel>.value(_FakeDocumentModel_0( + this, + Invocation.method( + #update, + [doc], + ), + )), + ) as _i6.Future<_i2.DocumentModel>); + @override + _i6.Future findNextAsn() => (super.noSuchMethod( + Invocation.method( + #findNextAsn, + [], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future<_i3.PagedSearchResult> find(_i8.DocumentFilter? filter) => + (super.noSuchMethod( + Invocation.method( + #find, + [filter], + ), + returnValue: _i6.Future<_i3.PagedSearchResult>.value( + _FakePagedSearchResult_1( + this, + Invocation.method( + #find, + [filter], + ), + )), + returnValueForMissingStub: + _i6.Future<_i3.PagedSearchResult>.value( + _FakePagedSearchResult_1( + this, + Invocation.method( + #find, + [filter], + ), + )), + ) as _i6.Future<_i3.PagedSearchResult>); + @override + _i6.Future> findSimilar(int? docId) => + (super.noSuchMethod( + Invocation.method( + #findSimilar, + [docId], + ), + returnValue: _i6.Future>.value( + <_i9.SimilarDocumentModel>[]), + returnValueForMissingStub: + _i6.Future>.value( + <_i9.SimilarDocumentModel>[]), + ) as _i6.Future>); + @override + _i6.Future delete(_i2.DocumentModel? doc) => (super.noSuchMethod( + Invocation.method( + #delete, + [doc], + ), + returnValue: _i6.Future.value(0), + returnValueForMissingStub: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future<_i4.DocumentMetaData> getMetaData(_i2.DocumentModel? document) => + (super.noSuchMethod( + Invocation.method( + #getMetaData, + [document], + ), + returnValue: + _i6.Future<_i4.DocumentMetaData>.value(_FakeDocumentMetaData_2( + this, + Invocation.method( + #getMetaData, + [document], + ), + )), + returnValueForMissingStub: + _i6.Future<_i4.DocumentMetaData>.value(_FakeDocumentMetaData_2( + this, + Invocation.method( + #getMetaData, + [document], + ), + )), + ) as _i6.Future<_i4.DocumentMetaData>); + @override + _i6.Future> bulkDelete(List<_i2.DocumentModel>? models) => + (super.noSuchMethod( + Invocation.method( + #bulkDelete, + [models], + ), + returnValue: _i6.Future>.value([]), + returnValueForMissingStub: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future<_i7.Uint8List> getPreview(int? docId) => (super.noSuchMethod( + Invocation.method( + #getPreview, + [docId], + ), + returnValue: _i6.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + returnValueForMissingStub: + _i6.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + ) as _i6.Future<_i7.Uint8List>); + @override + String getThumbnailUrl(int? docId) => (super.noSuchMethod( + Invocation.method( + #getThumbnailUrl, + [docId], + ), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + _i6.Future<_i2.DocumentModel> waitForConsumptionFinished( + String? filename, + String? title, + ) => + (super.noSuchMethod( + Invocation.method( + #waitForConsumptionFinished, + [ + filename, + title, + ], + ), + returnValue: _i6.Future<_i2.DocumentModel>.value(_FakeDocumentModel_0( + this, + Invocation.method( + #waitForConsumptionFinished, + [ + filename, + title, + ], + ), + )), + returnValueForMissingStub: + _i6.Future<_i2.DocumentModel>.value(_FakeDocumentModel_0( + this, + Invocation.method( + #waitForConsumptionFinished, + [ + filename, + title, + ], + ), + )), + ) as _i6.Future<_i2.DocumentModel>); + @override + _i6.Future<_i7.Uint8List> download(_i2.DocumentModel? document) => + (super.noSuchMethod( + Invocation.method( + #download, + [document], + ), + returnValue: _i6.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + returnValueForMissingStub: + _i6.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + ) as _i6.Future<_i7.Uint8List>); + @override + _i6.Future> autocomplete( + String? query, [ + int? limit = 10, + ]) => + (super.noSuchMethod( + Invocation.method( + #autocomplete, + [ + query, + limit, + ], + ), + returnValue: _i6.Future>.value([]), + returnValueForMissingStub: _i6.Future>.value([]), + ) as _i6.Future>); +} diff --git a/test/src/saved_view_test.dart b/test/src/saved_view_test.dart new file mode 100644 index 0000000..85f5b8c --- /dev/null +++ b/test/src/saved_view_test.dart @@ -0,0 +1,256 @@ +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/filter_rule.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_order.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Validate parsing logic from [SavedView] to [DocumentFilter]:', () { + test('Values are correctly parsed if set.', () { + expect( + SavedView.fromJson({ + "id": 1, + "name": "test_name", + "show_on_dashboard": false, + "show_in_sidebar": false, + "sort_field": SortField.created.name, + "sort_reverse": true, + "filter_rules": [ + { + 'rule_type': FilterRule.correspondentRule, + 'value': "42", + }, + { + 'rule_type': FilterRule.documentTypeRule, + 'value': "69", + }, + { + 'rule_type': FilterRule.tagRule, + 'value': "1", + }, + { + 'rule_type': FilterRule.tagRule, + 'value': "2", + }, + { + 'rule_type': FilterRule.tagRule, + 'value': "3", + }, + { + 'rule_type': FilterRule.extendedRule, + 'value': "Never gonna give you up", + }, + { + 'rule_type': FilterRule.storagePathRule, + 'value': "14", + }, + { + 'rule_type': FilterRule.createdBeforeRule, + 'value': "2022-10-27", + }, + { + 'rule_type': FilterRule.createdAfterRule, + 'value': "2022-09-27", + }, + { + 'rule_type': FilterRule.addedBeforeRule, + 'value': "2022-09-26", + }, + { + 'rule_type': FilterRule.addedAfterRule, + 'value': "2000-01-01", + } + ] + }).toDocumentFilter(), + equals( + DocumentFilter.initial.copyWith( + correspondent: const CorrespondentQuery.fromId(42), + documentType: const DocumentTypeQuery.fromId(69), + storagePath: const StoragePathQuery.fromId(14), + tags: const TagsQuery.fromIds([1, 2, 3]), + createdDateBefore: DateTime.parse("2022-10-27"), + createdDateAfter: DateTime.parse("2022-09-27"), + addedDateBefore: DateTime.parse("2022-09-26"), + addedDateAfter: DateTime.parse("2000-01-01"), + sortField: SortField.created, + sortOrder: SortOrder.ascending, + queryText: "Never gonna give you up", + queryType: QueryType.extended, + ), + ), + ); + }); + + test('Values are correctly parsed if unset.', () { + expect( + SavedView.fromJson({ + "id": 1, + "name": "test_name", + "show_on_dashboard": false, + "show_in_sidebar": false, + "sort_field": SortField.created.name, + "sort_reverse": false, + "filter_rules": [], + }).toDocumentFilter(), + equals(DocumentFilter.initial), + ); + }); + + test('Values are correctly parsed if not assigned.', () { + expect( + SavedView.fromJson({ + "id": 1, + "name": "test_name", + "show_on_dashboard": false, + "show_in_sidebar": false, + "sort_field": SortField.created.name, + "sort_reverse": false, + "filter_rules": [ + { + 'rule_type': FilterRule.correspondentRule, + 'value': null, + }, + { + 'rule_type': FilterRule.documentTypeRule, + 'value': null, + }, + { + 'rule_type': FilterRule.tagRule, + 'value': null, + }, + { + 'rule_type': FilterRule.storagePathRule, + 'value': null, + }, + ], + }).toDocumentFilter(), + equals(DocumentFilter.initial.copyWith( + correspondent: const CorrespondentQuery.notAssigned(), + documentType: const DocumentTypeQuery.notAssigned(), + storagePath: const StoragePathQuery.notAssigned(), + tags: const TagsQuery.notAssigned(), + )), + ); + }); + }); + + group('Validate parsing logic from [DocumentFilter] to [SavedView]:', () { + test('Values are correctly parsed if set.', () { + expect( + SavedView.fromDocumentFilter( + DocumentFilter( + correspondent: const CorrespondentQuery.fromId(1), + documentType: const DocumentTypeQuery.fromId(2), + storagePath: const StoragePathQuery.fromId(3), + tags: const TagsQuery.fromIds([4, 5, 6]), + sortField: SortField.added, + sortOrder: SortOrder.ascending, + addedDateAfter: DateTime.parse("2020-01-01"), + addedDateBefore: DateTime.parse("2020-03-01"), + createdDateAfter: DateTime.parse("2020-02-01"), + createdDateBefore: DateTime.parse("2020-04-01"), + queryText: "Never gonna let you down", + queryType: QueryType.title, + ), + name: "test_name", + showInSidebar: false, + showOnDashboard: false, + ), + equals( + SavedView( + name: "test_name", + showOnDashboard: false, + showInSidebar: false, + sortField: SortField.added, + sortReverse: true, + filterRules: [ + FilterRule(FilterRule.correspondentRule, "1"), + FilterRule(FilterRule.documentTypeRule, "2"), + FilterRule(FilterRule.storagePathRule, "3"), + FilterRule(FilterRule.tagRule, "4"), + FilterRule(FilterRule.tagRule, "5"), + FilterRule(FilterRule.tagRule, "6"), + FilterRule(FilterRule.addedAfterRule, "2020-01-01"), + FilterRule(FilterRule.addedBeforeRule, "2020-03-01"), + FilterRule(FilterRule.createdAfterRule, "2020-02-01"), + FilterRule(FilterRule.createdBeforeRule, "2020-04-01"), + FilterRule(FilterRule.titleRule, "Never gonna let you down"), + ], + ), + ), + ); + }); + + test('Values are correctly parsed if unset.', () { + expect( + SavedView.fromDocumentFilter( + const DocumentFilter( + correspondent: CorrespondentQuery.unset(), + documentType: DocumentTypeQuery.unset(), + storagePath: StoragePathQuery.unset(), + tags: TagsQuery.unset(), + sortField: SortField.created, + sortOrder: SortOrder.descending, + addedDateAfter: null, + addedDateBefore: null, + createdDateAfter: null, + createdDateBefore: null, + queryText: null, + ), + name: "test_name", + showInSidebar: false, + showOnDashboard: false, + ), + equals( + SavedView( + name: "test_name", + showOnDashboard: false, + showInSidebar: false, + sortField: SortField.created, + sortReverse: false, + filterRules: [], + ), + ), + ); + }); + + test('Values are correctly parsed if not assigned.', () { + expect( + SavedView.fromDocumentFilter( + const DocumentFilter( + correspondent: CorrespondentQuery.notAssigned(), + documentType: DocumentTypeQuery.notAssigned(), + storagePath: StoragePathQuery.notAssigned(), + tags: TagsQuery.notAssigned(), + sortField: SortField.created, + sortOrder: SortOrder.descending, + ), + name: "test_name", + showInSidebar: false, + showOnDashboard: false, + ), + equals( + SavedView( + name: "test_name", + showOnDashboard: false, + showInSidebar: false, + sortField: SortField.created, + sortReverse: false, + filterRules: [ + FilterRule(FilterRule.correspondentRule, null), + FilterRule(FilterRule.documentTypeRule, null), + FilterRule(FilterRule.storagePathRule, null), + FilterRule(FilterRule.tagRule, null), + ], + ), + ), + ); + }); + }); +} diff --git a/test/src/widget_test.dart b/test/src/widget_test.dart new file mode 100644 index 0000000..a9c689a --- /dev/null +++ b/test/src/widget_test.dart @@ -0,0 +1,12 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Login validation error fields test', (WidgetTester tester) async {}); +} diff --git a/test/utils.dart b/test/utils.dart new file mode 100644 index 0000000..711bd41 --- /dev/null +++ b/test/utils.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:flutter/services.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; + +Future loadOne(String filePath, T Function(JSON) transformFn, int? id) async { + if (id != null) { + final coll = await loadCollection(filePath, transformFn); + return coll.firstWhere((dynamic element) => element.id == id); + } + final String response = await rootBundle.loadString(filePath); + return transformFn(jsonDecode(response)); +} + +Future> loadCollection(String filePath, T Function(JSON) transformFn, + {int? numItems, List? ids}) async { + assert(((numItems != null) ^ (ids != null)) || (numItems == null && ids == null)); + final String response = await rootBundle.loadString(filePath); + final lst = (jsonDecode(response) as List); + final res = (jsonDecode(response) as List).map((e) => transformFn(e)).toList(); + if (ids != null) { + return res.where((dynamic element) => ids.contains(element.id)).toList(); + } + if (numItems != null && lst.length < numItems) { + throw Exception("The requested collection contains only ${lst.length} items!"); + } else { + return res.sublist(0, numItems); + } +} + +const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; +String getRandomString(int length) => String.fromCharCodes( + Iterable.generate(length, (_) => _chars.codeUnitAt(Random().nextInt(_chars.length)))); diff --git a/test_driver/integration_test.dart b/test_driver/integration_test.dart new file mode 100644 index 0000000..b38629c --- /dev/null +++ b/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver();

2G=_Wr?JDgZ@XK?d?8q~ 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 CYsNkR literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c0cccda0b334a4e4dc5250cb855fa275756be4ec GIT binary patch literal 10210 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 PSHzTHVN`YQV(k9`+}ln% literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..ec7f3b2 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..3aaff9b --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,53 @@ + + + + + NSFaceIDUsageDescription + Why is my app authenticating using face id? + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Paperless Mobile + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_paperless_mobile + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIStatusBarHidden + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..05fecc9 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n +template-arb-file: intl_en.arb +output-localization-file: app_localizations.dart +untranslated-messages-file: untranslated_messages.txt \ No newline at end of file diff --git a/lib/core/bloc/connectivity_cubit.dart b/lib/core/bloc/connectivity_cubit.dart new file mode 100644 index 0000000..2b9b97f --- /dev/null +++ b/lib/core/bloc/connectivity_cubit.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/service/connectivity_status.service.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class ConnectivityCubit extends Cubit { + final ConnectivityStatusService connectivityStatusService; + late final StreamSubscription _sub; + + ConnectivityCubit(this.connectivityStatusService) : super(ConnectivityState.undefined); + + Future initialize() async { + final bool isConnected = await connectivityStatusService.isConnectedToInternet(); + emit(isConnected ? ConnectivityState.connected : ConnectivityState.notConnected); + _sub = connectivityStatusService.connectivityChanges().listen((isConnected) { + emit(isConnected ? ConnectivityState.connected : ConnectivityState.notConnected); + }); + } + + @override + Future close() { + _sub.cancel(); + return super.close(); + } +} + +enum ConnectivityState { connected, notConnected, undefined } diff --git a/lib/core/bloc/document_status_cubit.dart b/lib/core/bloc/document_status_cubit.dart new file mode 100644 index 0000000..e996fdd --- /dev/null +++ b/lib/core/bloc/document_status_cubit.dart @@ -0,0 +1,10 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/document_processing_status.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class DocumentStatusCubit extends Cubit { + DocumentStatusCubit() : super(null); + + void updateStatus(DocumentProcessingStatus? status) => emit(status); +} diff --git a/lib/core/bloc/label_bloc_provider.dart b/lib/core/bloc/label_bloc_provider.dart new file mode 100644 index 0000000..1354282 --- /dev/null +++ b/lib/core/bloc/label_bloc_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; + +class LabelBlocProvider extends StatelessWidget { + final Widget child; + const LabelBlocProvider({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + ], + child: child, + ); + } +} diff --git a/lib/core/bloc/label_cubit.dart b/lib/core/bloc/label_cubit.dart new file mode 100644 index 0000000..20776da --- /dev/null +++ b/lib/core/bloc/label_cubit.dart @@ -0,0 +1,53 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/repository/label_repository.dart'; + +abstract class LabelCubit extends Cubit> { + final LabelRepository labelRepository; + LabelCubit(this.labelRepository) : super({}); + + @protected + void loadFrom(Iterable items) => emit(Map.fromIterable(items, key: (e) => (e as T).id!)); + + Future add(T item) async { + assert(item.id == null); + final addedItem = await save(item); + final newState = {...state}; + newState.putIfAbsent(addedItem.id!, () => addedItem); + emit(newState); + return addedItem; + } + + Future replace(T item) async { + assert(item.id != null); + final updatedItem = await update(item); + final newState = {...state}; + newState[item.id!] = updatedItem; + emit(newState); + return updatedItem; + } + + Future remove(T item) async { + assert(item.id != null); + if (state.containsKey(item.id)) { + final deletedId = await delete(item); + final newState = {...state}; + newState.remove(deletedId); + emit(newState); + } + } + + void reset() => emit({}); + + Future initialize(); + + @protected + Future save(T item); + + @protected + Future update(T item); + + @protected + Future delete(T item); +} diff --git a/lib/core/global/http_self_signed_certificate_override.dart b/lib/core/global/http_self_signed_certificate_override.dart new file mode 100644 index 0000000..b8b9ed0 --- /dev/null +++ b/lib/core/global/http_self_signed_certificate_override.dart @@ -0,0 +1,11 @@ +// Fix for accepting self signed certificates. +import 'dart:io'; + +class X509HttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/lib/core/interceptor/authentication.interceptor.dart b/lib/core/interceptor/authentication.interceptor.dart new file mode 100644 index 0000000..cd6f8ae --- /dev/null +++ b/lib/core/interceptor/authentication.interceptor.dart @@ -0,0 +1,34 @@ +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class AuthenticationInterceptor implements InterceptorContract { + AuthenticationCubit authenticationCubit; + AuthenticationInterceptor(this.authenticationCubit); + + @override + Future interceptRequest({required BaseRequest request}) async { + final authState = authenticationCubit.state; + if (kDebugMode) { + log("Intercepted request to ${request.url.toString()}"); + } + if (authState.authentication == null) { + throw const ErrorMessage(ErrorCode.notAuthenticated); + } + return request.copyWith( + //Append server Url + url: Uri.parse(authState.authentication!.serverUrl + request.url.toString()), + headers: authState.authentication!.token.isEmpty + ? request.headers + : {...request.headers, 'Authorization': 'Token ${authState.authentication!.token}'}, + ); + } + + @override + Future interceptResponse({required BaseResponse response}) async => response; +} diff --git a/lib/core/interceptor/connection_state.interceptor.dart b/lib/core/interceptor/connection_state.interceptor.dart new file mode 100644 index 0000000..cfa19bf --- /dev/null +++ b/lib/core/interceptor/connection_state.interceptor.dart @@ -0,0 +1,31 @@ +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/service/connectivity_status.service.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class ConnectionStateInterceptor implements InterceptorContract { + final AuthenticationCubit authenticationCubit; + final ConnectivityStatusService connectivityStatusService; + ConnectionStateInterceptor( + this.authenticationCubit, this.connectivityStatusService); + + @override + Future interceptRequest({required BaseRequest request}) async { + if (!(await connectivityStatusService.isConnectedToInternet())) { + throw const ErrorMessage(ErrorCode.deviceOffline); + } + final isServerReachable = + await connectivityStatusService.isServerReachable(request.url.origin); + if (!isServerReachable) { + throw const ErrorMessage(ErrorCode.serverUnreachable); + } + return request; + } + + @override + Future interceptResponse( + {required BaseResponse response}) async => + response; +} diff --git a/lib/core/interceptor/language_header.interceptor.dart b/lib/core/interceptor/language_header.interceptor.dart new file mode 100644 index 0000000..5a7ec79 --- /dev/null +++ b/lib/core/interceptor/language_header.interceptor.dart @@ -0,0 +1,25 @@ +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class LanguageHeaderInterceptor implements InterceptorContract { + final ApplicationSettingsCubit appSettingsCubit; + + LanguageHeaderInterceptor(this.appSettingsCubit); + + @override + Future interceptRequest({required BaseRequest request}) async { + late String languages; + if (appSettingsCubit.state.preferredLocaleSubtag == "en") { + languages = "en"; + } else { + languages = appSettingsCubit.state.preferredLocaleSubtag + ",en;q=0.7,en-US;q=0.6"; + } + request.headers.addAll({"Accept-Language": languages}); + return request; + } + + @override + Future interceptResponse({required BaseResponse response}) async => response; +} diff --git a/lib/core/interceptor/response_conversion.interceptor.dart b/lib/core/interceptor/response_conversion.interceptor.dart new file mode 100644 index 0000000..5f0075b --- /dev/null +++ b/lib/core/interceptor/response_conversion.interceptor.dart @@ -0,0 +1,32 @@ +import 'package:http/http.dart'; +import 'package:http_interceptor/http/http.dart'; +import 'package:injectable/injectable.dart'; + +const interceptedRoutes = ['thumb/']; + +@injectable +class ResponseConversionInterceptor implements InterceptorContract { + @override + Future interceptRequest({required BaseRequest request}) async => request; + + @override + Future interceptResponse({required BaseResponse response}) async { + final String requestUrl = response.request?.url.toString().split("?").first ?? ''; + if (response.request?.method == "GET" && + interceptedRoutes.any((element) => requestUrl.endsWith(element))) { + final resp = response as Response; + + return StreamedResponse( + Stream.value(resp.bodyBytes.toList()).asBroadcastStream(), + resp.statusCode, + contentLength: resp.contentLength, + headers: resp.headers, + isRedirect: resp.isRedirect, + persistentConnection: false, + reasonPhrase: resp.reasonPhrase, + request: resp.request, + ); + } + return response; + } +} diff --git a/lib/core/logic/error_code_localization_mapper.dart b/lib/core/logic/error_code_localization_mapper.dart new file mode 100644 index 0000000..22838e8 --- /dev/null +++ b/lib/core/logic/error_code_localization_mapper.dart @@ -0,0 +1,70 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +String translateError(BuildContext context, ErrorCode code) { + switch (code) { + case ErrorCode.unknown: + return S.of(context).errorMessageUnknonwnError; + case ErrorCode.authenticationFailed: + return S.of(context).errorMessageAuthenticationFailed; + case ErrorCode.notAuthenticated: + return S.of(context).errorMessageNotAuthenticated; + case ErrorCode.documentUploadFailed: + return S.of(context).errorMessageDocumentUploadFailed; + case ErrorCode.documentUpdateFailed: + return S.of(context).errorMessageDocumentUpdateFailed; + case ErrorCode.documentLoadFailed: + return S.of(context).errorMessageDocumentLoadFailed; + case ErrorCode.documentDeleteFailed: + return S.of(context).errorMessageDocumentDeleteFailed; + case ErrorCode.documentPreviewFailed: + return S.of(context).errorMessageDocumentPreviewFailed; + case ErrorCode.documentAsnQueryFailed: + return S.of(context).errorMessageDocumentAsnQueryFailed; + case ErrorCode.tagCreateFailed: + return S.of(context).errorMessageTagCreateFailed; + case ErrorCode.tagLoadFailed: + return S.of(context).errorMessageTagLoadFailed; + case ErrorCode.documentTypeCreateFailed: + return S.of(context).errorMessageDocumentTypeCreateFailed; + case ErrorCode.documentTypeLoadFailed: + return S.of(context).errorMessageDocumentTypeLoadFailed; + case ErrorCode.correspondentCreateFailed: + return S.of(context).errorMessageCorrespondentCreateFailed; + case ErrorCode.correspondentLoadFailed: + return S.of(context).errorMessageCorrespondentLoadFailed; + case ErrorCode.scanRemoveFailed: + return S.of(context).errorMessageScanRemoveFailed; + case ErrorCode.invalidClientCertificateConfiguration: + return S.of(context).errorMessageInvalidClientCertificateConfiguration; + case ErrorCode.documentBulkDeleteFailed: + return S.of(context).errorMessageBulkDeleteDocumentsFailed; + case ErrorCode.biometricsNotSupported: + return S.of(context).errorMessageBiotmetricsNotSupported; + case ErrorCode.biometricAuthenticationFailed: + return S.of(context).errorMessageBiometricAuthenticationFailed; + case ErrorCode.deviceOffline: + return S.of(context).errorMessageDeviceOffline; + case ErrorCode.serverUnreachable: + return S.of(context).errorMessageServerUnreachable; + case ErrorCode.similarQueryError: + return S.of(context).errorMessageSimilarQueryError; + case ErrorCode.autocompleteQueryError: + return S.of(context).errorMessageAutocompleteQueryError; + case ErrorCode.storagePathLoadFailed: + return S.of(context).errorMessageStoragePathLoadFailed; + case ErrorCode.storagePathCreateFailed: + return S.of(context).errorMessageStoragePathCreateFailed; + case ErrorCode.loadSavedViewsError: + return S.of(context).errorMessageLoadSavedViewsError; + case ErrorCode.createSavedViewError: + return S.of(context).errorMessageCreateSavedViewError; + case ErrorCode.deleteSavedViewError: + return S.of(context).errorMessageDeleteSavedViewError; + case ErrorCode.requestTimedOut: + return S.of(context).errorMessageRequestTimedOut; + default: + return S.of(context).errorMessageUnknonwnError; + } +} diff --git a/lib/core/logic/timeout_client.dart b/lib/core/logic/timeout_client.dart new file mode 100644 index 0000000..a380945 --- /dev/null +++ b/lib/core/logic/timeout_client.dart @@ -0,0 +1,135 @@ +import 'dart:typed_data'; + +import 'dart:convert'; + +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:http/http.dart'; +import 'package:injectable/injectable.dart'; + +/// +/// Convenience class which handles timeout errors. +/// +@Injectable(as: BaseClient) +@Named("timeoutClient") +class TimeoutClient implements BaseClient { + static const Duration requestTimeout = Duration(seconds: 25); + + @override + Future send(BaseRequest request) async { + return getIt().send(request).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ); + } + + @override + void close() { + getIt().close(); + } + + @override + Future delete(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return _handle400Error( + await getIt() + .delete(url, headers: headers, body: body, encoding: encoding) + .timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future get(Uri url, {Map? headers}) async { + return _handle400Error( + await getIt().get(url, headers: headers).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future head(Uri url, {Map? headers}) async { + return _handle400Error( + await getIt().head(url, headers: headers).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future patch(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return _handle400Error( + await getIt() + .patch(url, headers: headers, body: body, encoding: encoding) + .timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future post(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return _handle400Error( + await getIt().post(url, headers: headers, body: body, encoding: encoding).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future put(Uri url, + {Map? headers, Object? body, Encoding? encoding}) async { + return _handle400Error( + await getIt().put(url, headers: headers, body: body, encoding: encoding).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ), + ); + } + + @override + Future read(Uri url, {Map? headers}) async { + return getIt().read(url, headers: headers).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ); + } + + @override + Future readBytes(Uri url, {Map? headers}) { + return getIt().readBytes(url, headers: headers).timeout( + requestTimeout, + onTimeout: () => Future.error(const ErrorMessage(ErrorCode.requestTimedOut)), + ); + } + + Response _handle400Error(Response response) { + if (response.statusCode == 400) { + // try to parse contained error message, otherwise return response + final JSON json = jsonDecode(response.body); + final Map errorMessages = {}; + //TODO: This could be simplified, look at error message format of paperless-ngx + for (final entry in json.entries) { + if (entry.value is List) { + errorMessages.putIfAbsent(entry.key, () => (entry.value as List).cast().first); + } else if (entry.value is String) { + errorMessages.putIfAbsent(entry.key, () => entry.value); + } else { + errorMessages.putIfAbsent(entry.key, () => entry.value.toString()); + } + } + throw errorMessages; + } + return response; + } +} diff --git a/lib/core/model/document_processing_status.dart b/lib/core/model/document_processing_status.dart new file mode 100644 index 0000000..7d8730a --- /dev/null +++ b/lib/core/model/document_processing_status.dart @@ -0,0 +1,46 @@ +enum ProcessingStatus { starting, working, success, error } + +enum ProcessingMessage { + new_file, + parsing_document, + generating_thumbnail, + parse_date, + save_document, + finished +} + +class DocumentProcessingStatus { + final int currentProgress; + final int? documentId; + final String filename; + final int maxProgress; + final ProcessingMessage message; + final ProcessingStatus status; + final String taskId; + final bool isApproximated; + + static const String UNKNOWN_TASK_ID = "NO_TASK_ID"; + + DocumentProcessingStatus({ + required this.currentProgress, + this.documentId, + required this.filename, + required this.maxProgress, + required this.message, + required this.status, + required this.taskId, + this.isApproximated = false, + }); + + factory DocumentProcessingStatus.fromJson(Map json) { + return DocumentProcessingStatus( + currentProgress: json['current_progress'], + documentId: json['documentId'], + filename: json['filename'], + maxProgress: json['max_progress'], + message: ProcessingMessage.values.byName(json['message']), + status: ProcessingStatus.values.byName(json['status']), + taskId: json['task_id'], + ); + } +} diff --git a/lib/core/model/error_message.dart b/lib/core/model/error_message.dart new file mode 100644 index 0000000..d602c7b --- /dev/null +++ b/lib/core/model/error_message.dart @@ -0,0 +1,50 @@ +class ErrorMessage implements Exception { + final ErrorCode code; + final StackTrace? stackTrace; + final int? httpStatusCode; + + const ErrorMessage(this.code, {this.stackTrace, this.httpStatusCode}); + + factory ErrorMessage.unknown() { + return const ErrorMessage(ErrorCode.unknown); + } + + @override + String toString() { + return "ErrorMessage(code: $code${stackTrace != null ? ', stackTrace: ${stackTrace.toString()}' : ''}${httpStatusCode != null ? ', httpStatusCode: $httpStatusCode' : ''})"; + } +} + +enum ErrorCode { + unknown, + authenticationFailed, + notAuthenticated, + documentUploadFailed, + documentUpdateFailed, + documentLoadFailed, + documentDeleteFailed, + documentBulkDeleteFailed, + documentPreviewFailed, + documentAsnQueryFailed, + tagCreateFailed, + tagLoadFailed, + documentTypeCreateFailed, + documentTypeLoadFailed, + correspondentCreateFailed, + correspondentLoadFailed, + scanRemoveFailed, + invalidClientCertificateConfiguration, + biometricsNotSupported, + biometricAuthenticationFailed, + deviceOffline, + serverUnreachable, + similarQueryError, + autocompleteQueryError, + storagePathLoadFailed, + storagePathCreateFailed, + loadSavedViewsError, + createSavedViewError, + deleteSavedViewError, + requestTimedOut, + storagePathAlreadyExists; +} diff --git a/lib/core/service/connectivity_status.service.dart b/lib/core/service/connectivity_status.service.dart new file mode 100644 index 0000000..704fd22 --- /dev/null +++ b/lib/core/service/connectivity_status.service.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:injectable/injectable.dart'; + +abstract class ConnectivityStatusService { + Future isConnectedToInternet(); + Future isServerReachable(String serverAddress); + Stream connectivityChanges(); +} + +@Injectable(as: ConnectivityStatusService) +class ConnectivityStatusServiceImpl implements ConnectivityStatusService { + final Connectivity connectivity; + + ConnectivityStatusServiceImpl(this.connectivity); + + @override + Stream connectivityChanges() { + return connectivity.onConnectivityChanged + .map(_hasActiveInternetConnection) + .asBroadcastStream(); + } + + @override + Future isConnectedToInternet() async { + return _hasActiveInternetConnection( + await (Connectivity().checkConnectivity())); + } + + @override + Future isServerReachable(String serverAddress) async { + try { + final result = await InternetAddress.lookup( + serverAddress.replaceAll(RegExp(r"https?://"), "")); + if (result.isNotEmpty && result.first.rawAddress.isNotEmpty) { + return true; + } else { + return false; + } + } on SocketException catch (_) { + return false; + } + } + + bool _hasActiveInternetConnection(ConnectivityResult conn) { + return conn == ConnectivityResult.mobile || + conn == ConnectivityResult.wifi || + conn == ConnectivityResult.ethernet; + } +} diff --git a/lib/core/service/status.service.dart b/lib/core/service/status.service.dart new file mode 100644 index 0000000..225c414 --- /dev/null +++ b/lib/core/service/status.service.dart @@ -0,0 +1,112 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_paperless_mobile/core/bloc/document_status_cubit.dart'; +import 'package:flutter_paperless_mobile/core/model/document_processing_status.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart'; +import 'package:flutter_paperless_mobile/features/login/model/authentication_information.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:injectable/injectable.dart'; +import 'package:web_socket_channel/io.dart'; + +abstract class StatusService { + Future startListeningBeforeDocumentUpload( + String httpUrl, AuthenticationInformation credentials, String documentFileName); +} + +@Singleton(as: StatusService) +@Named("webSocketStatusService") +class WebSocketStatusService implements StatusService { + late WebSocket? socket; + late IOWebSocketChannel? _channel; + + WebSocketStatusService(); + + @override + Future startListeningBeforeDocumentUpload( + String httpUrl, + AuthenticationInformation credentials, + String documentFileName, + ) async { + socket = await WebSocket.connect( + httpUrl.replaceFirst("http", "ws") + "/ws/status/", + customClient: getIt(), + headers: { + 'Authorization': 'Token ${credentials.token}', + }, + ).catchError((_) { + // Use long polling if connection could not be established + }); + + if (socket != null) { + socket!.where(isNotNull).listen((event) { + final status = DocumentProcessingStatus.fromJson(event); + getIt().updateStatus(status); + if (status.currentProgress == 100) { + socket!.close(); + } + }); + } + } +} + +@Injectable(as: StatusService) +@Named("longPollingStatusService") +class LongPollingStatusService implements StatusService { + static const maxRetries = 60; + + final BaseClient httpClient; + LongPollingStatusService(@Named("timeoutClient") this.httpClient); + + @override + Future startListeningBeforeDocumentUpload( + String httpUrl, + AuthenticationInformation credentials, + String documentFileName, + ) async { + final today = DateTime.now(); + bool consumptionFinished = false; + int retryCount = 0; + + getIt().updateStatus( + DocumentProcessingStatus( + currentProgress: 0, + filename: documentFileName, + maxProgress: 100, + message: ProcessingMessage.new_file, + status: ProcessingStatus.working, + taskId: DocumentProcessingStatus.UNKNOWN_TASK_ID, + documentId: null, + isApproximated: true, + ), + ); + + do { + final response = await httpClient.get( + Uri.parse('$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'), + ); + final data = PagedSearchResult.fromJson(jsonDecode(response.body), DocumentModel.fromJson); + if (data.count > 0) { + consumptionFinished = true; + final docId = data.results[0].id; + getIt().updateStatus( + DocumentProcessingStatus( + currentProgress: 100, + filename: documentFileName, + maxProgress: 100, + message: ProcessingMessage.finished, + status: ProcessingStatus.success, + taskId: DocumentProcessingStatus.UNKNOWN_TASK_ID, + documentId: docId, + isApproximated: true, + ), + ); + return; + } + sleep(const Duration(seconds: 1)); + } while (!consumptionFinished && retryCount < maxRetries); + } +} diff --git a/lib/core/store/local_vault.dart b/lib/core/store/local_vault.dart new file mode 100644 index 0000000..2cf51fa --- /dev/null +++ b/lib/core/store/local_vault.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'; +import 'package:flutter_paperless_mobile/features/login/model/authentication_information.dart'; +import 'package:flutter_paperless_mobile/features/login/model/client_certificate.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class LocalVault { + static const applicationSettingsKey = "applicationSettings"; + static const authenticationKey = "authentication"; + + final EncryptedSharedPreferences sharedPreferences; + + LocalVault(this.sharedPreferences); + + Future storeAuthenticationInformation( + AuthenticationInformation auth, + ) async { + await sharedPreferences.setString( + authenticationKey, + json.encode(auth.toJson()), + ); + } + + Future loadAuthenticationInformation() async { + if ((await sharedPreferences.getString(authenticationKey)).isEmpty) { + return null; + } + return AuthenticationInformation.fromJson( + json.decode(await sharedPreferences.getString(authenticationKey)), + ); + } + + Future loadCertificate() async { + return loadAuthenticationInformation().then((value) => value?.clientCertificate); + } + + Future storeApplicationSettings(ApplicationSettingsState settings) { + return sharedPreferences.setString(applicationSettingsKey, json.encode(settings.toJson())); + } + + Future loadApplicationSettings() async { + final settings = await sharedPreferences.getString(applicationSettingsKey); + if (settings.isEmpty) { + return null; + } + return ApplicationSettingsState.fromJson(json.decode(settings)); + } + + Future clear() { + return sharedPreferences.clear(); + } +} diff --git a/lib/core/type/json.dart b/lib/core/type/json.dart new file mode 100644 index 0000000..3f05d90 --- /dev/null +++ b/lib/core/type/json.dart @@ -0,0 +1 @@ +typedef JSON = Map; diff --git a/lib/core/util.dart b/lib/core/util.dart new file mode 100644 index 0000000..1acfe63 --- /dev/null +++ b/lib/core/util.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_paperless_mobile/core/logic/timeout_client.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:http/http.dart'; +import 'package:path_provider/path_provider.dart'; + +const requestTimeout = Duration(seconds: 5); + +Future getSingleResult( + String url, + T Function(JSON) fromJson, + ErrorCode errorCode, { + int minRequiredApiVersion = 1, +}) async { + final httpClient = getIt(instanceName: "timeoutClient"); + final response = await httpClient.get( + Uri.parse(url), + headers: {'accept': 'application/json; version=$minRequiredApiVersion'}, + ); + if (response.statusCode == 200) { + return fromJson(jsonDecode(utf8.decode(response.bodyBytes)) as JSON); + } + return Future.error(errorCode); +} + +Future> getCollection( + String url, + T Function(JSON) fromJson, + ErrorCode errorCode, { + int minRequiredApiVersion = 1, +}) async { + final httpClient = getIt(instanceName: "timeoutClient"); + final response = await httpClient.get( + Uri.parse(url), + headers: {'accept': 'application/json; version=$minRequiredApiVersion'}, + ); + if (response.statusCode == 200) { + final JSON body = jsonDecode(utf8.decode(response.bodyBytes)); + if (body.containsKey('count')) { + if (body['count'] == 0) { + return []; + } else { + return body['results'].cast().map((result) => fromJson(result)).toList(); + } + } + } + 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/core/widgets/coming_soon_placeholder.dart b/lib/core/widgets/coming_soon_placeholder.dart new file mode 100644 index 0000000..d8fda8a --- /dev/null +++ b/lib/core/widgets/coming_soon_placeholder.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class ComingSoon extends StatelessWidget { + const ComingSoon({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + "Coming Soon\u2122", + style: Theme.of(context).textTheme.titleLarge, + ), + ); + } +} diff --git a/lib/core/widgets/confirm_button.dart b/lib/core/widgets/confirm_button.dart new file mode 100644 index 0000000..12e1aa1 --- /dev/null +++ b/lib/core/widgets/confirm_button.dart @@ -0,0 +1,70 @@ +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class ElevatedConfirmationButton extends StatefulWidget { + factory ElevatedConfirmationButton.icon(BuildContext context, + {required void Function() onPressed, required Icon icon, required Widget label}) { + final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1; + final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!; + return ElevatedConfirmationButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [icon, SizedBox(width: gap), Flexible(child: label)], + ), + onPressed: onPressed, + ); + } + + const ElevatedConfirmationButton({ + Key? key, + this.color, + required this.onPressed, + required this.child, + this.confirmWidget = const Text("Confirm?"), + }) : super(key: key); + + final Color? color; + final void Function()? onPressed; + final Widget child; + final Widget confirmWidget; + @override + State createState() => _ElevatedConfirmationButtonState(); +} + +class _ElevatedConfirmationButtonState extends State { + bool _clickedOnce = false; + double? _originalWidth; + final GlobalKey _originalWidgetKey = GlobalKey(); + @override + Widget build(BuildContext context) { + if (!_clickedOnce) { + return ElevatedButton( + key: _originalWidgetKey, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(widget.color), + ), + onPressed: () { + _originalWidth = + (_originalWidgetKey.currentContext?.findRenderObject() as RenderBox).size.width; + setState(() => _clickedOnce = true); + }, + child: widget.child, + ); + } else { + return Builder(builder: (context) { + return SizedBox( + width: _originalWidth, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(widget.color), + ), + onPressed: widget.onPressed, + child: widget.confirmWidget, + ), + ); + }); + } + } +} diff --git a/lib/core/widgets/documents_list_loading_widget.dart b/lib/core/widgets/documents_list_loading_widget.dart new file mode 100644 index 0000000..46884a7 --- /dev/null +++ b/lib/core/widgets/documents_list_loading_widget.dart @@ -0,0 +1,86 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class DocumentsListLoadingWidget extends StatelessWidget { + static const tags = [" ", " ", " "]; + static const titleLengths = [double.infinity, 150.0, 200.0]; + static const correspondentLengths = [200.0, 300.0, 150.0]; + static const fontSize = 16.0; + + const DocumentsListLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: MediaQuery.of(context).size.height, + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Shimmer.fromColors( + baseColor: Theme.of(context).brightness == Brightness.light + ? Colors.grey[300]! + : Colors.grey[900]!, + highlightColor: Theme.of(context).brightness == Brightness.light + ? Colors.grey[100]! + : Colors.grey[600]!, + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final r = Random(index); + final tagCount = r.nextInt(tags.length + 1); + final correspondentLength = correspondentLengths[ + r.nextInt(correspondentLengths.length - 1)]; + final titleLength = + titleLengths[r.nextInt(titleLengths.length - 1)]; + return ListTile( + isThreeLine: true, + leading: Container( + color: Colors.white, + height: 50, + width: 50, + ), + title: Container( + padding: const EdgeInsets.symmetric(vertical: 2.0), + width: correspondentLength, + height: fontSize, + color: Colors.white, + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 2.0), + height: fontSize, + width: titleLength, + color: Colors.white, + ), + Wrap( + spacing: 2.0, + children: List.generate( + tagCount, + (index) => Chip( + label: Text(tags[r.nextInt(tags.length)]), + ), + ), + ), + ], + ), + ), + ); + }, + itemCount: 25, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/core/widgets/empty_state.dart b/lib/core/widgets/empty_state.dart new file mode 100644 index 0000000..2b179ed --- /dev/null +++ b/lib/core/widgets/empty_state.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class EmptyState extends StatelessWidget { + final String title; + final String subtitle; + final Widget? bottomChild; + + const EmptyState({ + Key? key, + required this.title, + required this.subtitle, + this.bottomChild, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: size.height / 3, + width: size.width / 3, + child: SvgPicture.asset("assets/images/empty-state.svg"), + ), + Column( + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Text( + subtitle, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + if (bottomChild != null) ...[bottomChild!] else ...[] + ], + ); + } +} diff --git a/lib/core/widgets/expandable_floating_action_button.dart b/lib/core/widgets/expandable_floating_action_button.dart new file mode 100644 index 0000000..3314a2e --- /dev/null +++ b/lib/core/widgets/expandable_floating_action_button.dart @@ -0,0 +1,215 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +@immutable +class ExpandableFloatingActionButton extends StatefulWidget { + const ExpandableFloatingActionButton({ + super.key, + this.initialOpen, + required this.distance, + required this.children, + }); + + final bool? initialOpen; + final double distance; + final List children; + + @override + State createState() => + _ExpandableFloatingActionButtonState(); +} + +class _ExpandableFloatingActionButtonState + extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _expandAnimation; + bool _open = false; + + @override + void initState() { + super.initState(); + _open = widget.initialOpen ?? false; + _controller = AnimationController( + value: _open ? 1.0 : 0.0, + duration: const Duration(milliseconds: 250), + vsync: this, + ); + _expandAnimation = CurvedAnimation( + curve: Curves.fastOutSlowIn, + reverseCurve: Curves.easeOutQuad, + parent: _controller, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _toggle() { + setState(() { + _open = !_open; + if (_open) { + _controller.forward(); + } else { + _controller.reverse(); + } + }); + } + + @override + Widget build(BuildContext context) { + return SizedBox.expand( + child: Stack( + alignment: Alignment.bottomRight, + clipBehavior: Clip.none, + children: [ + _buildTapToCloseFab(), + ..._buildExpandingActionButtons(), + _buildTapToOpenFab(), + ], + ), + ); + } + + Widget _buildTapToCloseFab() { + return SizedBox( + width: 56.0, + height: 56.0, + child: Center( + child: Material( + shape: const CircleBorder(), + clipBehavior: Clip.antiAlias, + elevation: 4.0, + child: InkWell( + onTap: _toggle, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.close, + color: Theme.of(context).primaryColor, + ), + ), + ), + ), + ), + ); + } + + List _buildExpandingActionButtons() { + final children = []; + final count = widget.children.length; + final step = 90.0 / (count - 1); + for (var i = 0, angleInDegrees = 0.0; + i < count; + i++, angleInDegrees += step) { + children.add( + _ExpandingActionButton( + directionInDegrees: angleInDegrees, + maxDistance: widget.distance, + progress: _expandAnimation, + child: widget.children[i], + ), + ); + } + return children; + } + + Widget _buildTapToOpenFab() { + return IgnorePointer( + ignoring: _open, + child: AnimatedContainer( + transformAlignment: Alignment.center, + transform: Matrix4.diagonal3Values( + _open ? 0.7 : 1.0, + _open ? 0.7 : 1.0, + 1.0, + ), + duration: const Duration(milliseconds: 250), + curve: const Interval(0.0, 0.5, curve: Curves.easeOut), + child: AnimatedOpacity( + opacity: _open ? 0.0 : 1.0, + curve: const Interval(0.25, 1.0, curve: Curves.easeInOut), + duration: const Duration(milliseconds: 250), + child: FloatingActionButton( + onPressed: _toggle, + child: const Icon(Icons.create), + ), + ), + ), + ); + } +} + +@immutable +class _ExpandingActionButton extends StatelessWidget { + const _ExpandingActionButton({ + required this.directionInDegrees, + required this.maxDistance, + required this.progress, + required this.child, + }); + + final double directionInDegrees; + final double maxDistance; + final Animation progress; + final Widget child; + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: progress, + builder: (context, child) { + final offset = Offset.fromDirection( + directionInDegrees * (math.pi / 180.0), + progress.value * maxDistance, + ); + return Positioned( + right: 4.0 + offset.dx, + bottom: 4.0 + offset.dy, + child: Transform.rotate( + angle: (1.0 - progress.value) * math.pi / 2, + child: child!, + ), + ); + }, + child: FadeTransition( + opacity: progress, + child: child, + ), + ); + } +} + +@immutable +class ExpandableActionButton extends StatelessWidget { + const ExpandableActionButton({ + super.key, + this.color, + this.onPressed, + required this.icon, + }); + + final VoidCallback? onPressed; + final Widget icon; + final Color? color; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 48, + width: 48, + child: ElevatedButton( + onPressed: onPressed, + child: icon, + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + backgroundColor: MaterialStateProperty.all(color), + ), + ), + ); + } +} diff --git a/lib/core/widgets/highlighted_text.dart b/lib/core/widgets/highlighted_text.dart new file mode 100644 index 0000000..e16b680 --- /dev/null +++ b/lib/core/widgets/highlighted_text.dart @@ -0,0 +1,125 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class HighlightedText extends StatelessWidget { + final String text; + final List highlights; + final Color? color; + final TextStyle? style; + final bool caseSensitive; + + final TextAlign textAlign; + final TextDirection? textDirection; + final TextOverflow overflow; + final double textScaleFactor; + final int? maxLines; + final StrutStyle? strutStyle; + final TextWidthBasis textWidthBasis; + final TextHeightBehavior? textHeightBehavior; + + const HighlightedText({ + super.key, + required this.text, + required this.highlights, + this.style, + this.color = Colors.yellowAccent, + this.caseSensitive = true, + this.textAlign = TextAlign.start, + this.textDirection = TextDirection.ltr, + this.overflow = TextOverflow.clip, + this.textScaleFactor = 1.0, + this.maxLines, + this.strutStyle, + this.textWidthBasis = TextWidthBasis.parent, + this.textHeightBehavior, + }); + + @override + Widget build(BuildContext context) { + if (text.isEmpty || highlights.isEmpty || highlights.contains('')) { + return SelectableText.rich( + _normalSpan(text, context), + key: key, + textAlign: textAlign, + textDirection: textDirection, + textScaleFactor: textScaleFactor, + maxLines: maxLines, + strutStyle: strutStyle, + textWidthBasis: textWidthBasis, + textHeightBehavior: textHeightBehavior, + style: TextStyle(overflow: overflow), + ); + } + + return SelectableText.rich( + TextSpan(children: _buildChildren(context)), + key: key, + textAlign: textAlign, + textDirection: textDirection, + textScaleFactor: textScaleFactor, + maxLines: maxLines, + strutStyle: strutStyle, + textWidthBasis: textWidthBasis, + textHeightBehavior: textHeightBehavior, + style: TextStyle(overflow: overflow), + ); + } + + List _buildChildren(BuildContext context) { + List _spans = []; + int _start = 0; + + String _text = caseSensitive ? text : text.toLowerCase(); + List _highlights = + caseSensitive ? highlights : highlights.map((e) => e.toLowerCase()).toList(); + + while (true) { + Map _highlightsMap = {}; //key (index), value (highlight). + + for (final h in _highlights) { + final idx = _text.indexOf(h, _start); + if (idx >= 0) { + _highlightsMap.putIfAbsent(_text.indexOf(h, _start), () => h); + } + } + + if (_highlightsMap.isNotEmpty) { + int _currentIndex = _highlightsMap.keys.reduce(min); + String _currentHighlight = text.substring( + _currentIndex, + _currentIndex + _highlightsMap[_currentIndex]!.length, + ); + + if (_currentIndex == _start) { + _spans.add(_highlightSpan(_currentHighlight)); + _start += _currentHighlight.length; + } else { + _spans.add(_normalSpan(text.substring(_start, _currentIndex), context)); + _spans.add(_highlightSpan(_currentHighlight)); + _start = _currentIndex + _currentHighlight.length; + } + } else { + _spans.add(_normalSpan(text.substring(_start, text.length), context)); + break; + } + } + return _spans; + } + + TextSpan _highlightSpan(String value) { + return TextSpan( + text: value, + style: style?.copyWith( + backgroundColor: color, + ), + ); + } + + TextSpan _normalSpan(String value, BuildContext context) { + return TextSpan( + text: value, + style: style ?? Theme.of(context).textTheme.bodyText2, + ); + } +} diff --git a/lib/core/widgets/offline_banner.dart b/lib/core/widgets/offline_banner.dart new file mode 100644 index 0000000..0836014 --- /dev/null +++ b/lib/core/widgets/offline_banner.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class OfflineBanner extends StatelessWidget with PreferredSizeWidget { + const OfflineBanner({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).disabledColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Icon( + Icons.cloud_off, + size: 24, + ), + ), + Text(S.of(context).genericMessageOfflineText), + ], + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(24); +} diff --git a/lib/core/widgets/offline_widget.dart b/lib/core/widgets/offline_widget.dart new file mode 100644 index 0000000..a721316 --- /dev/null +++ b/lib/core/widgets/offline_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class OfflineWidget extends StatelessWidget { + const OfflineWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.mood_bad, size: (Theme.of(context).iconTheme.size ?? 24) * 3), + Text( + S.of(context).offlineWidgetText, + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} diff --git a/lib/core/widgets/paperless_logo.dart b/lib/core/widgets/paperless_logo.dart new file mode 100644 index 0000000..dd359b7 --- /dev/null +++ b/lib/core/widgets/paperless_logo.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class PaperlessLogo extends StatelessWidget { + final double? height; + final double? width; + const PaperlessLogo({Key? key, this.height, this.width}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints( + maxHeight: height ?? Theme.of(context).iconTheme.size ?? 32, + maxWidth: width ?? Theme.of(context).iconTheme.size ?? 32, + ), + padding: const EdgeInsets.only(right: 8), + child: SvgPicture.asset( + "assets/logo/paperless_ng_logo_light.svg", + color: Theme.of(context).primaryColor, + ), + ); + } +} diff --git a/lib/di_initializer.dart b/lib/di_initializer.dart new file mode 100644 index 0000000..abed715 --- /dev/null +++ b/lib/di_initializer.dart @@ -0,0 +1,31 @@ +import 'dart:io'; + +import 'package:flutter_paperless_mobile/di_initializer.config.dart'; +import 'package:flutter_paperless_mobile/di_modules.dart'; +import 'package:flutter_paperless_mobile/features/login/model/client_certificate.dart'; +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; + +final getIt = GetIt.instance..allowReassignment; + +@InjectableInit( + initializerName: r'$initGetIt', // default + preferRelativeImports: true, // default + asExtension: false, // default +) +void configureDependencies() => $initGetIt(getIt); + +/// +/// Registers new security context, which will be used by the HttpClient, see [RegisterModule]. +/// +void registerSecurityContext(ClientCertificate? cert) { + var context = SecurityContext(); + if (cert != null) { + context = context + ..usePrivateKeyBytes(cert.bytes, password: cert.passphrase) + ..useCertificateChainBytes(cert.bytes, password: cert.passphrase) + ..setTrustedCertificatesBytes(cert.bytes, password: cert.passphrase); + } + getIt.unregister(); + getIt.registerSingleton(context); +} diff --git a/lib/di_modules.dart b/lib/di_modules.dart new file mode 100644 index 0000000..3fdcc2b --- /dev/null +++ b/lib/di_modules.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:flutter_paperless_mobile/core/interceptor/authentication.interceptor.dart'; +import 'package:flutter_paperless_mobile/core/interceptor/connection_state.interceptor.dart'; +import 'package:flutter_paperless_mobile/core/interceptor/language_header.interceptor.dart'; +import 'package:flutter_paperless_mobile/core/interceptor/response_conversion.interceptor.dart'; +import 'package:http/http.dart'; +import 'package:http/io_client.dart'; +import 'package:http_interceptor/http/http.dart'; +import 'package:injectable/injectable.dart'; +import 'package:local_auth/local_auth.dart'; + +@module +abstract class RegisterModule { + @singleton + LocalAuthentication get localAuthentication => LocalAuthentication(); + @singleton + EncryptedSharedPreferences get encryptedSharedPreferences => EncryptedSharedPreferences(); + @singleton + SecurityContext get securityContext => SecurityContext(); + @singleton + Connectivity get connectivity => Connectivity(); + + /// + /// Factory method creating an [HttpClient] with the currently registered [SecurityContext]. + /// + HttpClient getHttpClient(SecurityContext securityContext) => + HttpClient(context: securityContext)..connectionTimeout = const Duration(seconds: 10); + + /// + /// Factory method creating a [InterceptedClient] on top of the currently registered [HttpClient]. + /// + BaseClient getBaseClient( + AuthenticationInterceptor authInterceptor, + ResponseConversionInterceptor responseConversionInterceptor, + ConnectionStateInterceptor connectionStateInterceptor, + LanguageHeaderInterceptor languageHeaderInterceptor, + HttpClient client, + ) => + InterceptedClient.build( + interceptors: [ + authInterceptor, + responseConversionInterceptor, + connectionStateInterceptor, + languageHeaderInterceptor + ], + client: IOClient(client), + ); + + CacheManager getCacheManager(BaseClient client) => + CacheManager(Config('cacheKey', fileService: HttpFileService(httpClient: client))); +} diff --git a/lib/extensions/dart_extensions.dart b/lib/extensions/dart_extensions.dart new file mode 100644 index 0000000..f155504 --- /dev/null +++ b/lib/extensions/dart_extensions.dart @@ -0,0 +1,9 @@ +extension NullableMapKey on Map { + V? tryPutIfAbsent(K key, V? Function() ifAbsent) { + final value = ifAbsent(); + if (value == null) { + return null; + } + return putIfAbsent(key, () => value); + } +} diff --git a/lib/extensions/flutter_extensions.dart b/lib/extensions/flutter_extensions.dart new file mode 100644 index 0000000..51fb94d --- /dev/null +++ b/lib/extensions/flutter_extensions.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; + +extension WidgetPadding on Widget { + Widget padded([EdgeInsetsGeometry value = const EdgeInsets.all(8)]) { + return Padding( + padding: value, + child: this, + ); + } +} + +extension WidgetsPadding on List { + List padded([EdgeInsetsGeometry value = const EdgeInsets.all(8)]) { + return map((child) => Padding( + padding: value, + child: child, + )).toList(); + } +} diff --git a/lib/features/app_intro/application_intro_slideshow.dart b/lib/features/app_intro/application_intro_slideshow.dart new file mode 100644 index 0000000..f34eb8d --- /dev/null +++ b/lib/features/app_intro/application_intro_slideshow.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/app_intro/widgets/biometric_authentication_intro_slide.dart'; +import 'package:flutter_paperless_mobile/features/app_intro/widgets/configuration_done_intro_slide.dart'; +import 'package:flutter_paperless_mobile/features/app_intro/widgets/welcome_intro_slide.dart'; +import 'package:flutter_paperless_mobile/features/home/view/home_page.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:intro_slider/intro_slider.dart'; + +class ApplicationIntroSlideshow extends StatelessWidget { + const ApplicationIntroSlideshow({super.key}); + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: IntroSlider( + renderDoneBtn: TextButton( + child: Text("GO"), //TODO: INTL + onPressed: () { + Navigator.pop(context); + }, + ), + backgroundColorAllTabs: Theme.of(context).canvasColor, + onDonePress: () => Navigator.of(context) + .pushReplacement(MaterialPageRoute(builder: (context) => const HomePage())), + listCustomTabs: [ + const WelcomeIntroSlide(), + BlocProvider.value( + value: getIt(), + child: const BiometricAuthenticationIntroSlide(), + ), + const ConfigurationDoneIntroSlide(), + ].padded(const EdgeInsets.all(16.0)), + ), + ); + } +} diff --git a/lib/features/app_intro/widgets/biometric_authentication_intro_slide.dart b/lib/features/app_intro/widgets/biometric_authentication_intro_slide.dart new file mode 100644 index 0000000..06bf214 --- /dev/null +++ b/lib/features/app_intro/widgets/biometric_authentication_intro_slide.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/login/services/authentication.service.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class BiometricAuthenticationIntroSlide extends StatefulWidget { + const BiometricAuthenticationIntroSlide({ + Key? key, + }) : super(key: key); + + @override + State createState() => + _BiometricAuthenticationIntroSlideState(); +} + +class _BiometricAuthenticationIntroSlideState extends State { + @override + Widget build(BuildContext context) { + //TODO: INTL + return BlocBuilder( + builder: (context, settings) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Configure Biometric Authentication", + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + Text( + "It is highly recommended to additionally secure your local data. Do you want to enable biometric authentication?", + textAlign: TextAlign.center, + ), + Column( + children: [ + const Icon( + Icons.fingerprint, + size: 48, + ), + const SizedBox( + height: 32, + ), + Builder(builder: (context) { + if (settings.isLocalAuthenticationEnabled) { + return ElevatedButton.icon( + icon: Icon( + Icons.done, + color: Colors.green, + ), + label: Text("Enabled"), + onPressed: null, + ); + } + return ElevatedButton( + child: Text("Enable"), + onPressed: () { + final settings = BlocProvider.of(context).state; + getIt() + .authenticateLocalUser("Please authenticate to secure Paperless Mobile") + .then((isEnabled) { + if (!isEnabled) { + showSnackBar(context, + "Could not set up biometric authentication. Please try again or skip for now."); + return; + } + BlocProvider.of(context) + .setIsBiometricAuthenticationEnabled(true); + }); + }, + ); + }), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/app_intro/widgets/configuration_done_intro_slide.dart b/lib/features/app_intro/widgets/configuration_done_intro_slide.dart new file mode 100644 index 0000000..e5c0b95 --- /dev/null +++ b/lib/features/app_intro/widgets/configuration_done_intro_slide.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class ConfigurationDoneIntroSlide extends StatelessWidget { + const ConfigurationDoneIntroSlide({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + //TODO: INTL + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + "All set up!", + style: Theme.of(context).textTheme.titleLarge, + ), + Icon( + Icons.emoji_emotions_outlined, + size: 64, + ), + Text( + "You've successfully configured Paperless Mobile! Press 'GO' to get started managing your documents.", + textAlign: TextAlign.center, + ), + ], + ); + } +} diff --git a/lib/features/app_intro/widgets/welcome_intro_slide.dart b/lib/features/app_intro/widgets/welcome_intro_slide.dart new file mode 100644 index 0000000..5563a2f --- /dev/null +++ b/lib/features/app_intro/widgets/welcome_intro_slide.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; + +class WelcomeIntroSlide extends StatelessWidget { + const WelcomeIntroSlide({super.key}); + + @override + Widget build(BuildContext context) { + //TODO: INTL + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Welcome to Paperless Mobile!", + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + Text( + "Manage and add your documents on the go!", + textAlign: TextAlign.center, + ), + ], + ); + } +} diff --git a/lib/features/documents/bloc/documents_cubit.dart b/lib/features/documents/bloc/documents_cubit.dart new file mode 100644 index 0000000..184acd4 --- /dev/null +++ b/lib/features/documents/bloc/documents_cubit.dart @@ -0,0 +1,130 @@ +import 'dart:typed_data'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class DocumentsCubit extends Cubit { + final DocumentRepository documentRepository; + + DocumentsCubit(this.documentRepository) : super(DocumentsState.initial); + + Future addDocument( + Uint8List bytes, + String fileName, { + required String title, + required void Function(DocumentModel document) onConsumptionFinished, + int? documentType, + int? correspondent, + List? tags, + DateTime? createdAt, + }) async { + await documentRepository.create( + bytes, + fileName, + title: title, + documentType: documentType, + correspondent: correspondent, + tags: tags, + createdAt: createdAt, + ); + // documentRepository + // .waitForConsumptionFinished(fileName, title) + // .then((value) => onConsumptionFinished(value)); + } + + Future removeDocument(DocumentModel document) async { + await documentRepository.delete(document); + return await reloadDocuments(); + } + + Future bulkRemoveDocuments(List documents) async { + await documentRepository.bulkDelete(documents); + return await reloadDocuments(); + } + + Future updateDocument(DocumentModel document) async { + await documentRepository.update(document); + await reloadDocuments(); + } + + Future loadDocuments() async { + final result = await documentRepository.find(state.filter); + emit(DocumentsState( + isLoaded: true, + value: [...state.value, result], + filter: state.filter, + )); + } + + Future reloadDocuments() async { + if (state.currentPageNumber >= 5) { + return _bulkReloadDocuments(); + } + var newPages = []; + for (final page in state.value) { + final result = await documentRepository.find(state.filter.copyWith(page: page.pageKey)); + newPages.add(result); + } + emit(DocumentsState(isLoaded: true, value: newPages, filter: state.filter)); + } + + Future _bulkReloadDocuments() async { + final result = await documentRepository + .find(state.filter.copyWith(page: 1, pageSize: state.documents.length)); + emit(DocumentsState(isLoaded: true, value: [result], filter: state.filter)); + } + + Future loadMore() async { + if (state.isLastPageLoaded) { + return; + } + final newFilter = state.filter.copyWith(page: state.filter.page + 1); + final result = await documentRepository.find(newFilter); + emit(DocumentsState(isLoaded: true, value: [...state.value, result], filter: newFilter)); + } + + Future assignAsn(DocumentModel document) async { + if (document.archiveSerialNumber == null) { + final int asn = await documentRepository.findNextAsn(); + updateDocument(document.copyWith(archiveSerialNumber: asn)); + } + } + + /// + /// Update filter state and automatically reload documents. Always resets page to 1. + /// Use [DocumentsCubit.loadMore] to load more data. + Future updateFilter({ + DocumentFilter filter = DocumentFilter.initial, + }) async { + final result = await documentRepository.find(filter.copyWith(page: 1)); + emit(DocumentsState(filter: filter, value: [result], isLoaded: true)); + } + + void toggleDocumentSelection(DocumentModel model) { + if (state.selection.contains(model)) { + emit( + state.copyWith( + selection: state.selection.where((element) => element.id != model.id).toList(), + ), + ); + } else { + emit( + state.copyWith(selection: [...state.selection, model]), + ); + } + } + + void resetSelection() { + emit(state.copyWith(selection: [])); + } + + void reset() { + emit(DocumentsState.initial); + } +} diff --git a/lib/features/documents/bloc/documents_state.dart b/lib/features/documents/bloc/documents_state.dart new file mode 100644 index 0000000..e02bf96 --- /dev/null +++ b/lib/features/documents/bloc/documents_state.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart'; + +class DocumentsState extends Equatable { + final bool isLoaded; + final DocumentFilter filter; + final List value; + final List selection; + + const DocumentsState({ + required this.isLoaded, + required this.value, + required this.filter, + this.selection = const [], + }); + + static const DocumentsState initial = DocumentsState( + isLoaded: false, + value: [], + filter: DocumentFilter.initial, + selection: [], + ); + + int get currentPageNumber { + return filter.page; + } + + int? get nextPageNumber { + return isLastPageLoaded ? null : currentPageNumber + 1; + } + + int get count { + if (value.isEmpty) { + return 0; + } + return value.first.count; + } + + bool get isLastPageLoaded { + if (!isLoaded) { + return false; + } + if (value.isNotEmpty) { + return value.last.next == null; + } + return true; + } + + int inferPageCount({required int pageSize}) { + if (!isLoaded) { + return 100000; + } + if (value.isEmpty) { + return 0; + } + return value.first.inferPageCount(pageSize: pageSize); + } + + List get documents { + return value.fold([], (previousValue, element) => [...previousValue, ...element.results]); + } + + DocumentsState copyWith({ + bool overwrite = false, + bool? isLoaded, + List? value, + DocumentFilter? filter, + List? selection, + }) { + return DocumentsState( + isLoaded: isLoaded ?? this.isLoaded, + value: value ?? this.value, + filter: filter ?? this.filter, + selection: selection ?? this.selection, + ); + } + + @override + List get props => [isLoaded, filter, value, selection]; +} diff --git a/lib/features/documents/bloc/saved_view_cubit.dart b/lib/features/documents/bloc/saved_view_cubit.dart new file mode 100644 index 0000000..a3dd1bc --- /dev/null +++ b/lib/features/documents/bloc/saved_view_cubit.dart @@ -0,0 +1,50 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/saved_views_repository.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class SavedViewCubit extends Cubit { + SavedViewCubit() : super(SavedViewState(value: {})); + + void selectView(SavedView? view) { + emit(SavedViewState(value: state.value, selectedSavedViewId: view?.id)); + } + + Future add(SavedView view) async { + final savedView = await getIt().save(view); + emit( + SavedViewState( + value: {...state.value, savedView.id!: savedView}, + selectedSavedViewId: state.selectedSavedViewId, + ), + ); + return savedView; + } + + Future remove(SavedView view) async { + final id = await getIt().delete(view); + final newValue = {...state.value}; + newValue.removeWhere((key, value) => key == id); + emit( + SavedViewState( + value: newValue, + selectedSavedViewId: + view.id == state.selectedSavedViewId ? null : state.selectedSavedViewId, + ), + ); + return id; + } + + Future initialize() async { + final views = await getIt().getAll(); + final values = {for (var element in views) element.id!: element}; + emit(SavedViewState(value: values)); + } + + void resetSelection() { + emit(SavedViewState(value: state.value)); + } +} diff --git a/lib/features/documents/bloc/saved_view_state.dart b/lib/features/documents/bloc/saved_view_state.dart new file mode 100644 index 0000000..8f009f5 --- /dev/null +++ b/lib/features/documents/bloc/saved_view_state.dart @@ -0,0 +1,15 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; + +class SavedViewState with EquatableMixin { + final Map value; + final int? selectedSavedViewId; + + SavedViewState({ + required this.value, + this.selectedSavedViewId, + }); + + @override + List get props => [value, selectedSavedViewId]; +} diff --git a/lib/features/documents/model/bulk_edit.model.dart b/lib/features/documents/model/bulk_edit.model.dart new file mode 100644 index 0000000..9fb7d65 --- /dev/null +++ b/lib/features/documents/model/bulk_edit.model.dart @@ -0,0 +1,19 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; + +class BulkEditAction { + final List documents; + final String _method; + final Map parameters; + + BulkEditAction.delete(this.documents) + : _method = 'delete', + parameters = {}; + + JSON toJson() { + return { + 'documents': documents, + 'method': _method, + 'parameters': parameters, + }; + } +} diff --git a/lib/features/documents/model/document.model.dart b/lib/features/documents/model/document.model.dart new file mode 100644 index 0000000..8eae4f1 --- /dev/null +++ b/lib/features/documents/model/document.model.dart @@ -0,0 +1,148 @@ +// ignore_for_file: non_constant_identifier_names + +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/ids_query_parameter.dart'; + +class DocumentModel extends Equatable { + static const idKey = 'id'; + static const titleKey = "title"; + static const contentKey = "content"; + static const archivedFileNameKey = "archived_file_name"; + static const asnKey = "archive_serial_number"; + static const createdKey = "created"; + static const modifiedKey = "modified"; + static const addedKey = "added"; + static const correspondentKey = "correspondent"; + static const originalFileNameKey = 'original_file_name'; + static const documentTypeKey = "document_type"; + static const tagsKey = "tags"; + static const storagePathKey = "storage_path"; + + final int id; + final String title; + final String? content; + final List tags; + final int? documentType; + final int? correspondent; + final int? storagePath; + final DateTime created; + final DateTime modified; + final DateTime added; + final int? archiveSerialNumber; + final String originalFileName; + final String? archivedFileName; + + const DocumentModel({ + required this.id, + required this.title, + this.content, + this.tags = const [], + required this.documentType, + required this.correspondent, + required this.created, + required this.modified, + required this.added, + this.archiveSerialNumber, + required this.originalFileName, + this.archivedFileName, + this.storagePath, + }); + + DocumentModel.fromJson(JSON json) + : id = json[idKey], + title = json[titleKey], + content = json[contentKey], + created = DateTime.parse(json[createdKey]), + modified = DateTime.parse(json[modifiedKey]), + added = DateTime.parse(json[addedKey]), + archiveSerialNumber = json[asnKey], + originalFileName = json[originalFileNameKey], + archivedFileName = json[archivedFileNameKey], + tags = (json[tagsKey] as List).cast(), + correspondent = json[correspondentKey], + documentType = json[documentTypeKey], + storagePath = json[storagePathKey]; + + JSON toJson() { + return { + idKey: id, + titleKey: title, + asnKey: archiveSerialNumber, + archivedFileNameKey: archivedFileName, + contentKey: content, + correspondentKey: correspondent, + documentTypeKey: documentType, + createdKey: created.toUtc().toIso8601String(), + modifiedKey: modified.toUtc().toIso8601String(), + addedKey: added.toUtc().toIso8601String(), + originalFileNameKey: originalFileName, + tagsKey: tags, + storagePathKey: storagePath, + }; + } + + DocumentModel copyWith({ + String? title, + String? content, + IdsQueryParameter? tags, + IdQueryParameter? documentType, + IdQueryParameter? correspondent, + IdQueryParameter? storagePath, + DateTime? created, + DateTime? modified, + DateTime? added, + int? archiveSerialNumber, + String? originalFileName, + String? archivedFileName, + }) { + return DocumentModel( + id: id, + title: title ?? this.title, + content: content ?? this.content, + documentType: fromQuery(documentType, this.documentType), + correspondent: fromQuery(correspondent, this.correspondent), + storagePath: fromQuery(storagePath, this.storagePath), + tags: fromListQuery(tags, this.tags), + created: created ?? this.created, + modified: modified ?? this.modified, + added: added ?? this.added, + originalFileName: originalFileName ?? this.originalFileName, + archiveSerialNumber: archiveSerialNumber ?? this.archiveSerialNumber, + archivedFileName: archivedFileName ?? this.archivedFileName, + ); + } + + int? fromQuery(IdQueryParameter? query, int? previous) { + if (query == null) { + return previous; + } + return query.id; + } + + List fromListQuery(IdsQueryParameter? query, List previous) { + if (query == null) { + return previous; + } + return query.ids; + } + + @override + List get props => [ + id, + title, + content, + tags, + documentType, + storagePath, + correspondent, + created, + modified, + added, + archiveSerialNumber, + originalFileName, + archivedFileName, + storagePath + ]; +} diff --git a/lib/features/documents/model/document_filter.dart b/lib/features/documents/model/document_filter.dart new file mode 100644 index 0000000..5180d8b --- /dev/null +++ b/lib/features/documents/model/document_filter.dart @@ -0,0 +1,168 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/asn_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_order.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class DocumentFilter with EquatableMixin { + static const DocumentFilter initial = DocumentFilter(); + + static const DocumentFilter latestDocument = DocumentFilter( + sortField: SortField.added, + sortOrder: SortOrder.descending, + pageSize: 1, + page: 1, + ); + + final int pageSize; + final int page; + final DocumentTypeQuery documentType; + final CorrespondentQuery correspondent; + final StoragePathQuery storagePath; + final AsnQuery asn; + final TagsQuery tags; + final SortField sortField; + final SortOrder sortOrder; + final DateTime? addedDateAfter; + final DateTime? addedDateBefore; + final DateTime? createdDateAfter; + final DateTime? createdDateBefore; + final QueryType queryType; + final String? queryText; + + const DocumentFilter({ + this.createdDateAfter, + this.createdDateBefore, + this.documentType = const DocumentTypeQuery.unset(), + this.correspondent = const CorrespondentQuery.unset(), + this.storagePath = const StoragePathQuery.unset(), + this.asn = const AsnQuery.unset(), + this.tags = const TagsQuery.unset(), + this.sortField = SortField.created, + this.sortOrder = SortOrder.descending, + this.page = 1, + this.pageSize = 25, + this.addedDateAfter, + this.addedDateBefore, + this.queryType = QueryType.titleAndContent, + this.queryText, + }); + + String toQueryString() { + final StringBuffer sb = StringBuffer("page=$page&page_size=$pageSize"); + sb.write(documentType.toQueryParameter()); + sb.write(correspondent.toQueryParameter()); + sb.write(tags.toQueryParameter()); + sb.write(storagePath.toQueryParameter()); + sb.write(asn.toQueryParameter()); + + if (queryText?.isNotEmpty ?? false) { + sb.write("&${queryType.queryParam}=$queryText"); + } + + sb.write("&ordering=${sortOrder.queryString}${sortField.queryString}"); + + if (addedDateAfter != null) { + sb.write("&added__date__gt=${dateFormat.format(addedDateAfter!)}"); + } + + if (addedDateBefore != null) { + sb.write("&added__date__lt=${dateFormat.format(addedDateBefore!)}"); + } + + if (createdDateAfter != null) { + sb.write("&created__date__gt=${dateFormat.format(createdDateAfter!)}"); + } + + if (createdDateBefore != null) { + sb.write("&created__date__lt=${dateFormat.format(createdDateBefore!)}"); + } + + return sb.toString(); + } + + @override + String toString() { + return toQueryString(); + } + + DocumentFilter copyWith({ + int? pageSize, + int? page, + bool? onlyNoDocumentType, + DocumentTypeQuery? documentType, + CorrespondentQuery? correspondent, + StoragePathQuery? storagePath, + TagsQuery? tags, + SortField? sortField, + SortOrder? sortOrder, + DateTime? addedDateAfter, + DateTime? addedDateBefore, + DateTime? createdDateBefore, + DateTime? createdDateAfter, + QueryType? queryType, + String? queryText, + }) { + return DocumentFilter( + pageSize: pageSize ?? this.pageSize, + page: page ?? this.page, + documentType: documentType ?? this.documentType, + correspondent: correspondent ?? this.correspondent, + storagePath: storagePath ?? this.storagePath, + tags: tags ?? this.tags, + sortField: sortField ?? this.sortField, + sortOrder: sortOrder ?? this.sortOrder, + addedDateAfter: addedDateAfter ?? this.addedDateAfter, + addedDateBefore: addedDateBefore ?? this.addedDateBefore, + queryType: queryType ?? this.queryType, + queryText: queryText ?? this.queryText, + createdDateBefore: createdDateBefore ?? this.createdDateBefore, + createdDateAfter: createdDateAfter ?? this.createdDateAfter, + ); + } + + String? get titleOnlyMatchString { + if (queryType == QueryType.title) { + return queryText?.isEmpty ?? true ? null : queryText; + } + return null; + } + + String? get titleAndContentMatchString { + if (queryType == QueryType.titleAndContent) { + return queryText?.isEmpty ?? true ? null : queryText; + } + return null; + } + + String? get extendedMatchString { + if (queryType == QueryType.extended) { + return queryText?.isEmpty ?? true ? null : queryText; + } + return null; + } + + @override + List get props => [ + pageSize, + page, + documentType, + correspondent, + storagePath, + asn, + tags, + sortField, + sortOrder, + addedDateAfter, + addedDateBefore, + createdDateAfter, + createdDateBefore, + queryType, + queryText, + ]; +} diff --git a/lib/features/documents/model/document_meta_data.model.dart b/lib/features/documents/model/document_meta_data.model.dart new file mode 100644 index 0000000..02ef6be --- /dev/null +++ b/lib/features/documents/model/document_meta_data.model.dart @@ -0,0 +1,40 @@ +class DocumentMetaData { + String originalChecksum; + int originalSize; + String originalMimeType; + String mediaFilename; + bool hasArchiveVersion; + String? archiveChecksum; + int? archiveSize; + + DocumentMetaData({ + required this.originalChecksum, + required this.originalSize, + required this.originalMimeType, + required this.mediaFilename, + required this.hasArchiveVersion, + this.archiveChecksum, + this.archiveSize, + }); + + DocumentMetaData.fromJson(Map json) + : originalChecksum = json['original_checksum'], + originalSize = json['original_size'], + originalMimeType = json['original_mime_type'], + mediaFilename = json['media_filename'], + hasArchiveVersion = json['has_archive_version'], + archiveChecksum = json['archive_checksum'], + archiveSize = json['archive_size']; + + Map toJson() { + final data = {}; + data['original_checksum'] = originalChecksum; + data['original_size'] = originalSize; + data['original_mime_type'] = originalMimeType; + data['media_filename'] = mediaFilename; + data['has_archive_version'] = hasArchiveVersion; + data['archive_checksum'] = archiveChecksum; + data['archive_size'] = archiveSize; + return data; + } +} diff --git a/lib/features/documents/model/filter_rule.model.dart b/lib/features/documents/model/filter_rule.model.dart new file mode 100644 index 0000000..145717b --- /dev/null +++ b/lib/features/documents/model/filter_rule.model.dart @@ -0,0 +1,166 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class FilterRule with EquatableMixin { + static const int titleRule = 0; + static const int asnRule = 2; + static const int correspondentRule = 3; + static const int documentTypeRule = 4; + static const int tagRule = 6; + static const int createdBeforeRule = 8; + static const int createdAfterRule = 9; + static const int addedBeforeRule = 13; + static const int addedAfterRule = 14; + static const int titleAndContentRule = 19; + static const int extendedRule = 20; + static const int storagePathRule = 25; + // Currently unsupported view optiosn: + static const int _content = 1; + static const int _isInInbox = 5; + static const int _hasAnyTag = 7; + static const int _createdYearIs = 10; + static const int _createdMonthIs = 11; + static const int _createdDayIs = 12; + static const int _modifiedBefore = 15; + static const int _modifiedAfter = 16; + static const int _doesNotHaveTag = 17; + static const int _doesNotHaveAsn = 18; + static const int _moreLikeThis = 21; + static const int _hasTagsIn = 22; + static const int _asnGreaterThan = 23; + static const int _asnLessThan = 24; + + final int ruleType; + final String? value; + + FilterRule(this.ruleType, this.value); + + FilterRule.fromJson(JSON json) + : ruleType = json['rule_type'], + value = json['value']; + + JSON toJson() { + return { + 'rule_type': ruleType, + 'value': value, + }; + } + + DocumentFilter applyToFilter(final DocumentFilter filter) { + //TODO: Check in profiling mode if this is inefficient enough to cause stutters... + switch (ruleType) { + case titleRule: + return filter.copyWith(queryText: value, queryType: QueryType.title); + case documentTypeRule: + return filter.copyWith( + documentType: value == null + ? const DocumentTypeQuery.notAssigned() + : DocumentTypeQuery.fromId(int.parse(value!)), + ); + case correspondentRule: + return filter.copyWith( + correspondent: value == null + ? const CorrespondentQuery.notAssigned() + : CorrespondentQuery.fromId(int.parse(value!)), + ); + case storagePathRule: + return filter.copyWith( + storagePath: value == null + ? const StoragePathQuery.notAssigned() + : StoragePathQuery.fromId(int.parse(value!)), + ); + case tagRule: + return filter.copyWith( + tags: value == null + ? const TagsQuery.notAssigned() + : TagsQuery.fromIds([...filter.tags.ids, int.parse(value!)]), + ); + case createdBeforeRule: + return filter.copyWith(createdDateBefore: value == null ? null : DateTime.parse(value!)); + case createdAfterRule: + return filter.copyWith(createdDateAfter: value == null ? null : DateTime.parse(value!)); + case addedBeforeRule: + return filter.copyWith(addedDateBefore: value == null ? null : DateTime.parse(value!)); + case addedAfterRule: + return filter.copyWith(addedDateAfter: value == null ? null : DateTime.parse(value!)); + case titleAndContentRule: + return filter.copyWith(queryText: value, queryType: QueryType.titleAndContent); + case extendedRule: + return filter.copyWith(queryText: value, queryType: QueryType.extended); + //TODO: Add currently unused rules + default: + return filter; + } + } + + /// + /// Converts a [DocumentFilter] to a list of [FilterRule]s. + /// + static List fromFilter(final DocumentFilter filter) { + List filterRules = []; + if (filter.correspondent.onlyNotAssigned) { + filterRules.add(FilterRule(correspondentRule, null)); + } + if (filter.correspondent.isSet) { + filterRules.add(FilterRule(correspondentRule, filter.correspondent.id!.toString())); + } + if (filter.documentType.onlyNotAssigned) { + filterRules.add(FilterRule(documentTypeRule, null)); + } + if (filter.documentType.isSet) { + filterRules.add(FilterRule(documentTypeRule, filter.documentType.id!.toString())); + } + if (filter.storagePath.onlyNotAssigned) { + filterRules.add(FilterRule(storagePathRule, null)); + } + if (filter.storagePath.isSet) { + filterRules.add(FilterRule(storagePathRule, filter.storagePath.id!.toString())); + } + if (filter.tags.onlyNotAssigned) { + filterRules.add(FilterRule(tagRule, null)); + } + if (filter.tags.isSet) { + filterRules.addAll(filter.tags.ids.map((id) => FilterRule(tagRule, id.toString()))); + } + + if (filter.queryText != null) { + switch (filter.queryType) { + case QueryType.title: + filterRules.add(FilterRule(titleRule, filter.queryText!)); + break; + case QueryType.titleAndContent: + filterRules.add(FilterRule(titleAndContentRule, filter.queryText!)); + break; + case QueryType.extended: + filterRules.add(FilterRule(extendedRule, filter.queryText!)); + break; + case QueryType.asn: + filterRules.add(FilterRule(asnRule, filter.queryText!)); + break; + } + } + if (filter.createdDateAfter != null) { + filterRules.add(FilterRule(createdAfterRule, dateFormat.format(filter.createdDateAfter!))); + } + if (filter.createdDateBefore != null) { + filterRules.add(FilterRule(createdBeforeRule, dateFormat.format(filter.createdDateBefore!))); + } + if (filter.addedDateAfter != null) { + filterRules.add(FilterRule(addedAfterRule, dateFormat.format(filter.addedDateAfter!))); + } + if (filter.addedDateBefore != null) { + filterRules.add(FilterRule(addedBeforeRule, dateFormat.format(filter.addedDateBefore!))); + } + return filterRules; + } + + @override + List get props => [ruleType, value]; +} diff --git a/lib/features/documents/model/paged_search_result.dart b/lib/features/documents/model/paged_search_result.dart new file mode 100644 index 0000000..dfe625b --- /dev/null +++ b/lib/features/documents/model/paged_search_result.dart @@ -0,0 +1,84 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; + +const pageRegex = r".*page=(\d+).*"; + +class PagedSearchResult extends Equatable { + /// Total number of available items + final int count; + + /// Link to next page + final String? next; + + /// Link to previous page + final String? previous; + + /// Actual items + final List results; + + int get pageKey { + if (next != null) { + final matches = RegExp(pageRegex).allMatches(next!); + final group = matches.first.group(1)!; + final nextPageKey = int.parse(group); + return nextPageKey - 1; + } + if (previous != null) { + // This is only executed if it's the last page or there is no data. + final matches = RegExp(pageRegex).allMatches(previous!); + if (matches.isEmpty) { + //In case there is a match but a page is not explicitly set, the page is 1 per default. Therefore, if the previous page is 1, this page is 1+1=2 + return 2; + } + final group = matches.first.group(1)!; + final previousPageKey = int.parse(group); + return previousPageKey + 1; + } + return 1; + } + + const PagedSearchResult({ + required this.count, + required this.next, + required this.previous, + required this.results, + }); + + factory PagedSearchResult.fromJson(Map json, T Function(JSON) fromJson) { + return PagedSearchResult( + count: json['count'], + next: json['next'], + previous: json['previous'], + results: List.from(json['results']).map(fromJson).toList(), + ); + } + + PagedSearchResult copyWith({ + int? count, + String? next, + String? previous, + List? results, + }) { + return PagedSearchResult( + count: count ?? this.count, + next: next ?? this.next, + previous: previous ?? this.previous, + results: results ?? this.results, + ); + } + + /// + /// Returns the number of pages based on the given [pageSize]. The last page + /// might not exhaust its capacity. + /// + int inferPageCount({required int pageSize}) { + if (pageSize == 0) { + return 0; + } + return (count / pageSize).round() + 1; + } + + @override + List get props => [count, next, previous, results]; +} diff --git a/lib/features/documents/model/query_parameters/asn_query.dart b/lib/features/documents/model/query_parameters/asn_query.dart new file mode 100644 index 0000000..364bb8d --- /dev/null +++ b/lib/features/documents/model/query_parameters/asn_query.dart @@ -0,0 +1,10 @@ +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; + +class AsnQuery extends IdQueryParameter { + const AsnQuery.fromId(super.id) : super.fromId(); + const AsnQuery.unset() : super.unset(); + const AsnQuery.notAssigned() : super.notAssigned(); + + @override + String get queryParameterKey => 'archive_serial_number'; +} diff --git a/lib/features/documents/model/query_parameters/correspondent_query.dart b/lib/features/documents/model/query_parameters/correspondent_query.dart new file mode 100644 index 0000000..2704fc7 --- /dev/null +++ b/lib/features/documents/model/query_parameters/correspondent_query.dart @@ -0,0 +1,10 @@ +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; + +class CorrespondentQuery extends IdQueryParameter { + const CorrespondentQuery.fromId(super.id) : super.fromId(); + const CorrespondentQuery.unset() : super.unset(); + const CorrespondentQuery.notAssigned() : super.notAssigned(); + + @override + String get queryParameterKey => 'correspondent'; +} diff --git a/lib/features/documents/model/query_parameters/document_type_query.dart b/lib/features/documents/model/query_parameters/document_type_query.dart new file mode 100644 index 0000000..874324e --- /dev/null +++ b/lib/features/documents/model/query_parameters/document_type_query.dart @@ -0,0 +1,10 @@ +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; + +class DocumentTypeQuery extends IdQueryParameter { + const DocumentTypeQuery.fromId(super.id) : super.fromId(); + const DocumentTypeQuery.unset() : super.unset(); + const DocumentTypeQuery.notAssigned() : super.notAssigned(); + + @override + String get queryParameterKey => 'document_type'; +} diff --git a/lib/features/documents/model/query_parameters/id_query_parameter.dart b/lib/features/documents/model/query_parameters/id_query_parameter.dart new file mode 100644 index 0000000..2348b89 --- /dev/null +++ b/lib/features/documents/model/query_parameters/id_query_parameter.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; + +abstract class IdQueryParameter extends Equatable { + final bool _onlyNotAssigned; + final int? _id; + + const IdQueryParameter.notAssigned() + : _onlyNotAssigned = true, + _id = null; + + const IdQueryParameter.fromId(int? id) + : _onlyNotAssigned = false, + _id = id; + + const IdQueryParameter.unset() : this.fromId(null); + + bool get isUnset => _id == null && _onlyNotAssigned == false; + + bool get isSet => _id != null && _onlyNotAssigned == false; + + bool get onlyNotAssigned => _onlyNotAssigned; + + int? get id => _id; + + @protected + String get queryParameterKey; + + String toQueryParameter() { + if (onlyNotAssigned) { + return "&${queryParameterKey}__isnull=1"; + } + + return isUnset ? "" : "&${queryParameterKey}__id=$id"; + } + + @override + List get props => [_onlyNotAssigned, _id]; +} diff --git a/lib/features/documents/model/query_parameters/ids_query_parameter.dart b/lib/features/documents/model/query_parameters/ids_query_parameter.dart new file mode 100644 index 0000000..ae3e56c --- /dev/null +++ b/lib/features/documents/model/query_parameters/ids_query_parameter.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +abstract class IdsQueryParameter with EquatableMixin { + final List _ids; + final bool onlyNotAssigned; + + const IdsQueryParameter.fromIds(List ids) + : onlyNotAssigned = false, + _ids = ids; + + const IdsQueryParameter.notAssigned() + : onlyNotAssigned = true, + _ids = const []; + + const IdsQueryParameter.unset() + : onlyNotAssigned = false, + _ids = const []; + + bool get isUnset => _ids.isEmpty && onlyNotAssigned == false; + + bool get isSet => _ids.isNotEmpty && onlyNotAssigned == false; + + List get ids => _ids; + + String toQueryParameter(); + + @override + List get props => [onlyNotAssigned, _ids]; +} diff --git a/lib/features/documents/model/query_parameters/query_type.dart b/lib/features/documents/model/query_parameters/query_type.dart new file mode 100644 index 0000000..4216a89 --- /dev/null +++ b/lib/features/documents/model/query_parameters/query_type.dart @@ -0,0 +1,9 @@ +enum QueryType { + title('title__icontains'), + titleAndContent('title_content'), + extended('query'), + asn('asn'); + + final String queryParam; + const QueryType(this.queryParam); +} diff --git a/lib/features/documents/model/query_parameters/sort_field.dart b/lib/features/documents/model/query_parameters/sort_field.dart new file mode 100644 index 0000000..dfb9a8b --- /dev/null +++ b/lib/features/documents/model/query_parameters/sort_field.dart @@ -0,0 +1,18 @@ +enum SortField { + archiveSerialNumber("archive_serial_number"), + correspondentName("correspondent__name"), + title("title"), + documentType("documentType"), + created("created"), + added("added"), + modified("modified"); + + final String queryString; + + const SortField(this.queryString); + + @override + String toString() { + return name.toLowerCase(); + } +} diff --git a/lib/features/documents/model/query_parameters/sort_order.dart b/lib/features/documents/model/query_parameters/sort_order.dart new file mode 100644 index 0000000..3962b30 --- /dev/null +++ b/lib/features/documents/model/query_parameters/sort_order.dart @@ -0,0 +1,11 @@ +enum SortOrder { + ascending(""), + descending("-"); + + final String queryString; + const SortOrder(this.queryString); + + SortOrder toggle() { + return this == ascending ? descending : ascending; + } +} diff --git a/lib/features/documents/model/query_parameters/storage_path_query.dart b/lib/features/documents/model/query_parameters/storage_path_query.dart new file mode 100644 index 0000000..3f703a1 --- /dev/null +++ b/lib/features/documents/model/query_parameters/storage_path_query.dart @@ -0,0 +1,10 @@ +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; + +class StoragePathQuery extends IdQueryParameter { + const StoragePathQuery.fromId(super.id) : super.fromId(); + const StoragePathQuery.unset() : super.unset(); + const StoragePathQuery.notAssigned() : super.notAssigned(); + + @override + String get queryParameterKey => 'storage_path'; +} diff --git a/lib/features/documents/model/query_parameters/tags_query.dart b/lib/features/documents/model/query_parameters/tags_query.dart new file mode 100644 index 0000000..0ea7a95 --- /dev/null +++ b/lib/features/documents/model/query_parameters/tags_query.dart @@ -0,0 +1,15 @@ +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/ids_query_parameter.dart'; + +class TagsQuery extends IdsQueryParameter { + const TagsQuery.fromIds(super.ids) : super.fromIds(); + const TagsQuery.unset() : super.unset(); + const TagsQuery.notAssigned() : super.notAssigned(); + + @override + String toQueryParameter() { + if (onlyNotAssigned) { + return '&is_tagged=false'; + } + return isUnset ? "" : '&tags__id__all=${ids.join(',')}'; + } +} diff --git a/lib/features/documents/model/saved_view.model.dart b/lib/features/documents/model/saved_view.model.dart new file mode 100644 index 0000000..9069453 --- /dev/null +++ b/lib/features/documents/model/saved_view.model.dart @@ -0,0 +1,88 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/filter_rule.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_order.dart'; + +class SavedView with EquatableMixin { + final int? id; + final String name; + + final bool showOnDashboard; + final bool showInSidebar; + + final SortField sortField; + final bool sortReverse; + final List filterRules; + + SavedView({ + this.id, + required this.name, + required this.showOnDashboard, + required this.showInSidebar, + required this.sortField, + required this.sortReverse, + required this.filterRules, + }) { + filterRules.sort( + (a, b) => (a.ruleType.compareTo(b.ruleType) != 0 + ? a.ruleType.compareTo(b.ruleType) + : a.value?.compareTo(b.value ?? "") ?? -1), + ); + } + + @override + List get props => + [name, showOnDashboard, showInSidebar, sortField, sortReverse, filterRules]; + + SavedView.fromJson(JSON json) + : this( + id: json['id'], + name: json['name'], + showOnDashboard: json['show_on_dashboard'], + showInSidebar: json['show_in_sidebar'], + sortField: + SortField.values.where((order) => order.queryString == json['sort_field']).first, + sortReverse: json['sort_reverse'], + filterRules: + json['filter_rules'].cast().map(FilterRule.fromJson).toList(), + ); + + DocumentFilter toDocumentFilter() { + return filterRules.fold( + DocumentFilter( + sortOrder: sortReverse ? SortOrder.ascending : SortOrder.descending, + sortField: sortField, + ), + (filter, filterRule) => filterRule.applyToFilter(filter), + ); + } + + SavedView.fromDocumentFilter( + DocumentFilter filter, { + required String name, + required bool showInSidebar, + required bool showOnDashboard, + }) : this( + id: null, + name: name, + filterRules: FilterRule.fromFilter(filter), + sortField: filter.sortField, + showInSidebar: showInSidebar, + showOnDashboard: showOnDashboard, + sortReverse: filter.sortOrder == SortOrder.ascending, + ); + + JSON toJson() { + return { + 'id': id, + 'name': name, + 'show_on_dashboard': showOnDashboard, + 'show_in_sidebar': showInSidebar, + 'sort_reverse': sortReverse, + 'sort_field': sortField.queryString, + 'filter_rules': filterRules.map((rule) => rule.toJson()).toList(), + }; + } +} diff --git a/lib/features/documents/model/similar_document.model.dart b/lib/features/documents/model/similar_document.model.dart new file mode 100644 index 0000000..0679ee1 --- /dev/null +++ b/lib/features/documents/model/similar_document.model.dart @@ -0,0 +1,59 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; + +class SimilarDocumentModel extends DocumentModel { + final SearchHit searchHit; + + const SimilarDocumentModel({ + required super.id, + required super.title, + required super.documentType, + required super.correspondent, + required super.created, + required super.modified, + required super.added, + required super.originalFileName, + required this.searchHit, + super.archiveSerialNumber, + super.archivedFileName, + super.content, + super.storagePath, + super.tags, + }); + + @override + JSON toJson() { + final json = super.toJson(); + json['__search_hit__'] = searchHit.toJson(); + return json; + } + + SimilarDocumentModel.fromJson(JSON json) + : searchHit = SearchHit.fromJson(json), + super.fromJson(json); +} + +class SearchHit { + final double? score; + final String? highlights; + final int? rank; + + SearchHit({ + this.score, + required this.highlights, + required this.rank, + }); + + JSON toJson() { + return { + 'score': score, + 'highlights': highlights, + 'rank': rank, + }; + } + + SearchHit.fromJson(JSON json) + : score = json['score'], + highlights = json['highlights'], + rank = json['rank']; +} diff --git a/lib/features/documents/repository/document_repository.dart b/lib/features/documents/repository/document_repository.dart new file mode 100644 index 0000000..1a72abb --- /dev/null +++ b/lib/features/documents/repository/document_repository.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; + +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_meta_data.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/similar_document.model.dart'; + +abstract class DocumentRepository { + Future create( + Uint8List documentBytes, + String filename, { + required String title, + int? documentType, + int? correspondent, + List? tags, + DateTime? createdAt, + }); + Future update(DocumentModel doc); + Future findNextAsn(); + Future find(DocumentFilter filter); + Future> findSimilar(int docId); + Future delete(DocumentModel doc); + Future getMetaData(DocumentModel document); + Future> bulkDelete(List models); + Future getPreview(int docId); + String getThumbnailUrl(int docId); + Future waitForConsumptionFinished(String filename, String title); + Future download(DocumentModel document); + + Future> autocomplete(String query, [int limit = 10]); +} diff --git a/lib/features/documents/repository/document_repository_impl.dart b/lib/features/documents/repository/document_repository_impl.dart new file mode 100644 index 0000000..398ea7e --- /dev/null +++ b/lib/features/documents/repository/document_repository_impl.dart @@ -0,0 +1,275 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/store/local_vault.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/core/util.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/dart_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/bulk_edit.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_meta_data.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/paged_search_result.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_order.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/similar_document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:http/http.dart'; +import 'package:http/src/boundary_characters.dart'; //TODO: remove once there is either a paperless API update or there is a better solution... +import 'package:injectable/injectable.dart'; + +@Injectable(as: DocumentRepository) +class DocumentRepositoryImpl implements DocumentRepository { + //// + //final StatusService statusService; + final LocalVault localStorage; + final BaseClient httpClient; + + DocumentRepositoryImpl( + //this.statusService, + this.localStorage, + @Named("timeoutClient") this.httpClient, + ); + @override + Future create( + Uint8List documentBytes, + String filename, { + required String title, + int? documentType, + int? correspondent, + List? tags, + DateTime? createdAt, + }) async { + final auth = await localStorage.loadAuthenticationInformation(); + + if (auth == null) { + throw const ErrorMessage(ErrorCode.notAuthenticated); + } + + // The multipart request has to be generated from scratch as the http library does + // not allow the same key (tags) to be added multiple times. However, this is what the + // paperless api expects, i.e. one block for each tag. + final request = await getIt().postUrl( + Uri.parse("${auth.serverUrl}/api/documents/post_document/"), + ); + + final boundary = _boundaryString(); + + StringBuffer bodyBuffer = StringBuffer(); + + var fields = {}; + + fields.tryPutIfAbsent('title', () => title); + fields.tryPutIfAbsent('created', () => formatDateNullable(createdAt)); + fields.tryPutIfAbsent( + 'correspondent', () => correspondent == null ? null : json.encode(correspondent)); + fields.tryPutIfAbsent( + 'document_type', () => documentType == null ? null : json.encode(documentType)); + + for (final key in fields.keys) { + bodyBuffer.write(_buildMultipartField(key, fields[key]!, boundary)); + } + + for (final tag in tags ?? []) { + bodyBuffer.write(_buildMultipartField('tags', tag.toString(), boundary)); + } + + bodyBuffer.write("--$boundary" + '\r\nContent-Disposition: form-data; name="document"; filename="$filename"' + "\r\nContent-type: application/octet-stream" + "\r\n\r\n"); + + final closing = "\r\n--" + boundary + "--\r\n"; + + // Set headers + request.headers.set(HttpHeaders.contentTypeHeader, "multipart/form-data; boundary=" + boundary); + request.headers.set(HttpHeaders.contentLengthHeader, + "${bodyBuffer.length + closing.length + documentBytes.lengthInBytes}"); + request.headers.set(HttpHeaders.authorizationHeader, "Token ${auth.token}"); + + //Write fields to request + request.write(bodyBuffer.toString()); + //Stream file + await request.addStream(Stream.fromIterable(documentBytes.map((e) => [e]))); + // Write closing boundary to request + request.write(closing); + + final response = await request.close(); + + if (response.statusCode != 200) { + throw ErrorMessage(ErrorCode.documentUploadFailed, httpStatusCode: response.statusCode); + } + } + + String _buildMultipartField(String fieldName, String value, String boundary) { + return '--$boundary' + '\r\nContent-Disposition: form-data; name="$fieldName"' + '\r\nContent-type: text/plain' + '\r\n\r\n' + + value + + '\r\n'; + } + + String _boundaryString() { + Random _random = Random(); + var prefix = 'dart-http-boundary-'; + var list = List.generate(70 - prefix.length, + (index) => boundaryCharacters[_random.nextInt(boundaryCharacters.length)], + growable: false); + return '$prefix${String.fromCharCodes(list)}'; + } + + @override + Future update(DocumentModel doc) async { + final response = await httpClient.put(Uri.parse("/api/documents/${doc.id}/"), + body: json.encode(doc.toJson()), + headers: {"Content-Type": "application/json"}).timeout(requestTimeout); + if (response.statusCode == 200) { + return DocumentModel.fromJson(jsonDecode(response.body)); + } else { + throw const ErrorMessage(ErrorCode.documentUpdateFailed); + } + } + + @override + Future> find(DocumentFilter filter) async { + final filterParams = filter.toQueryString(); + final response = await httpClient.get( + Uri.parse("/api/documents/?$filterParams"), + ); + if (response.statusCode == 200) { + final searchResult = PagedSearchResult.fromJson( + jsonDecode(const Utf8Decoder().convert(response.body.codeUnits)), + DocumentModel.fromJson, + ); + return searchResult; + } else { + throw const ErrorMessage(ErrorCode.documentLoadFailed); + } + } + + @override + Future delete(DocumentModel doc) async { + final response = await httpClient.delete(Uri.parse("/api/documents/${doc.id}/")); + + if (response.statusCode == 204) { + return Future.value(doc.id); + } + throw const ErrorMessage(ErrorCode.documentDeleteFailed); + } + + @override + String getThumbnailUrl(int documentId) { + return "/api/documents/$documentId/thumb/"; + } + + String getPreviewUrl(int documentId) { + return "/api/documents/$documentId/preview/"; + } + + @override + Future getPreview(int documentId) async { + final response = await httpClient.get(Uri.parse(getPreviewUrl(documentId))); + if (response.statusCode == 200) { + return response.bodyBytes; + } + throw const ErrorMessage(ErrorCode.documentPreviewFailed); + } + + @override + Future findNextAsn() async { + const DocumentFilter asnQueryFilter = DocumentFilter( + sortField: SortField.archiveSerialNumber, + sortOrder: SortOrder.descending, + page: 1, + pageSize: 1, + ); + try { + final result = await find(asnQueryFilter); + return result.results + .map((e) => e.archiveSerialNumber) + .firstWhere((asn) => asn != null, orElse: () => 0)! + + 1; + } on ErrorMessage catch (_) { + throw const ErrorMessage(ErrorCode.documentAsnQueryFailed); + } + } + + @override + Future> bulkDelete(List documentModels) async { + final List ids = documentModels.map((e) => e.id).toList(); + final action = BulkEditAction.delete(ids); + final response = await httpClient.post( + Uri.parse("/api/documents/bulk_edit/"), + body: json.encode(action.toJson()), + headers: {'Content-Type': 'application/json'}, + ); + if (response.statusCode == 200) { + return ids; + } else { + throw const ErrorMessage(ErrorCode.documentBulkDeleteFailed); + } + } + + @override + Future waitForConsumptionFinished(String fileName, String title) async { + // Always wait 5 seconds, processing usually takes longer... + //await Future.delayed(const Duration(seconds: 5)); + PagedSearchResult results = await find(DocumentFilter.latestDocument); + + while ((results.results.isEmpty || + (results.results[0].originalFileName != fileName && results.results[0].title != title))) { + //TODO: maybe implement more intelligent retry logic or find workaround for websocket authentication... + await Future.delayed(const Duration(seconds: 2)); + results = await find(DocumentFilter.latestDocument); + } + try { + return results.results.first; + } on StateError { + throw const ErrorMessage(ErrorCode.documentUploadFailed); + } + } + + @override + Future download(DocumentModel document) async { + //TODO: Check if this works... + final response = await httpClient.get(Uri.parse("/api/documents/${document.id}/download/")); + return response.bodyBytes; + } + + @override + Future getMetaData(DocumentModel document) async { + final response = await httpClient.get(Uri.parse("/api/documents/${document.id}/metadata/")); + return DocumentMetaData.fromJson(jsonDecode(response.body)); + } + + @override + Future> autocomplete(String query, [int limit = 10]) async { + final response = + await httpClient.get(Uri.parse("/api/search/autocomplete/?query=$query&limit=$limit}")); + if (response.statusCode == 200) { + return json.decode(response.body) as List; + } + throw const ErrorMessage(ErrorCode.autocompleteQueryError); + } + + @override + Future> findSimilar(int docId) async { + final response = + await httpClient.get(Uri.parse("/api/documents/?more_like=$docId&pageSize=10")); + if (response.statusCode == 200) { + return PagedSearchResult.fromJson( + json.decode(response.body), + SimilarDocumentModel.fromJson, + ).results; + } + throw const ErrorMessage(ErrorCode.similarQueryError); + } +} diff --git a/lib/features/documents/repository/saved_views_repository.dart b/lib/features/documents/repository/saved_views_repository.dart new file mode 100644 index 0000000..4de40b3 --- /dev/null +++ b/lib/features/documents/repository/saved_views_repository.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/util.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:http/http.dart'; +import 'package:injectable/injectable.dart'; + +abstract class SavedViewsRepository { + Future> getAll(); + + Future save(SavedView view); + Future delete(SavedView view); +} + +@Injectable(as: SavedViewsRepository) +class SavedViewRepositoryImpl implements SavedViewsRepository { + final BaseClient httpClient; + + SavedViewRepositoryImpl(@Named("timeoutClient") this.httpClient); + + @override + Future> getAll() { + return getCollection( + "/api/saved_views/", + SavedView.fromJson, + ErrorCode.loadSavedViewsError, + ); + } + + @override + Future save(SavedView view) async { + final response = await httpClient.post( + Uri.parse("/api/saved_views/"), + body: jsonEncode(view.toJson()), + headers: {'Content-Type': 'application/json'}, + ); + if (response.statusCode == 201) { + return SavedView.fromJson(jsonDecode(response.body)); + } + throw ErrorMessage(ErrorCode.createSavedViewError, httpStatusCode: response.statusCode); + } + + @override + Future delete(SavedView view) async { + final response = await httpClient.delete(Uri.parse("/api/saved_views/${view.id}/")); + if (response.statusCode == 204) { + return view.id!; + } + throw ErrorMessage(ErrorCode.deleteSavedViewError, httpStatusCode: response.statusCode); + } +} diff --git a/lib/features/documents/view/pages/document_details_page.dart b/lib/features/documents/view/pages/document_details_page.dart new file mode 100644 index 0000000..424b289 --- /dev/null +++ b/lib/features/documents/view/pages/document_details_page.dart @@ -0,0 +1,418 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/widgets/highlighted_text.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_meta_data.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/pages/document_edit_page.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/pages/document_view.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/document_preview.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; + +class DocumentDetailsPage extends StatefulWidget { + final int documentId; + const DocumentDetailsPage({ + Key? key, + required this.documentId, + }) : super(key: key); + + @override + State createState() => _DocumentDetailsPageState(); +} + +class _DocumentDetailsPageState extends State { + static final DateFormat _detailedDateFormat = DateFormat("MMM d, yyyy HH:mm:ss"); + + bool _isDownloadPending = false; + bool _isAssignAsnPending = false; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + // buildWhen required because rebuild would happen after delete causing error. + buildWhen: (previous, current) { + return current.documents.where((element) => element.id == widget.documentId).isNotEmpty; + }, + builder: (context, state) { + final document = state.documents.where((doc) => doc.id == widget.documentId).first; + return SafeArea( + bottom: true, + child: DefaultTabController( + length: 3, + child: Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.edit), + onPressed: () => _onEdit(document), + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () => _onDelete(document), + ).padded(const EdgeInsets.symmetric(horizontal: 8.0)), + IconButton( + icon: const Icon(Icons.download), + onPressed: null, //() => _onDownload(document), //TODO: FIX + ), + IconButton( + icon: const Icon(Icons.open_in_new), + onPressed: () => _onOpen(document), + ).padded(const EdgeInsets.symmetric(horizontal: 8.0)), + ], + ), + ), + body: NestedScrollView( + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + SliverAppBar( + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors + .black, //TODO: check if there is a way to dynamically determine color... + ), + onPressed: () => Navigator.pop(context), + ), + floating: true, + pinned: true, + expandedHeight: 200.0, + flexibleSpace: DocumentPreview( + id: document.id, + fit: BoxFit.cover, + ), + bottom: ColoredTabBar( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + tabBar: TabBar( + tabs: [ + Tab( + child: Text( + S.of(context).documentDetailsPageTabOverviewLabel, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer), + ), + ), + Tab( + child: Text( + S.of(context).documentDetailsPageTabContentLabel, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer), + ), + ), + Tab( + child: Text( + S.of(context).documentDetailsPageTabMetaDataLabel, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimaryContainer), + ), + ), + ], + ), + ), + ), + ], + body: TabBarView( + children: [ + _buildDocumentOverview(document, state.filter.titleAndContentMatchString), + _buildDocumentContentView(document, state.filter.titleAndContentMatchString), + _buildDocumentMetaDataView(document), + ].padded(), + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildDocumentMetaDataView(DocumentModel document) { + return FutureBuilder( + future: getIt().getMetaData(document), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + final meta = snapshot.data!; + return ListView( + children: [ + _DetailsItem.text(_detailedDateFormat.format(document.modified), + label: S.of(context).documentModifiedPropertyLabel, context: context), + _separator(), + _DetailsItem.text(_detailedDateFormat.format(document.added), + label: S.of(context).documentAddedPropertyLabel, context: context), + _separator(), + _DetailsItem( + label: S.of(context).documentArchiveSerialNumberPropertyLongLabel, + content: document.archiveSerialNumber != null + ? Text(document.archiveSerialNumber.toString()) + : OutlinedButton( + child: Text(S.of(context).documentDetailsPageAssignAsnButtonLabel), + onPressed: () => BlocProvider.of(context).assignAsn(document), + ), + ), + _separator(), + _DetailsItem.text( + meta.mediaFilename, + context: context, + label: S.of(context).documentMetaDataMediaFilenamePropertyLabel, + ), + _separator(), + _DetailsItem.text( + meta.originalChecksum, + context: context, + label: S.of(context).documentMetaDataChecksumLabel, + ), + _separator(), + _DetailsItem.text(formatBytes(meta.originalSize, 2), + label: S.of(context).documentMetaDataOriginalFileSizeLabel, context: context), + _separator(), + _DetailsItem.text( + meta.originalMimeType, + label: S.of(context).documentMetaDataOriginalMimeTypeLabel, + context: context, + ), + _separator(), + ], + ); + }, + ); + } + + Widget _buildDocumentContentView(DocumentModel document, String? match) { + return SingleChildScrollView( + child: _DetailsItem( + content: HighlightedText( + text: document.content ?? "", + highlights: match == null ? [] : match.split(" "), + style: Theme.of(context).textTheme.bodyText2, + caseSensitive: false, + ), + label: S.of(context).documentDetailsPageTabContentLabel, + ), + ); + } + + Widget _buildDocumentOverview(DocumentModel document, String? match) { + return ListView( + children: [ + _DetailsItem( + content: HighlightedText( + text: document.title, + highlights: match?.split(" ") ?? [], + ), + label: S.of(context).documentTitlePropertyLabel, + ), + _separator(), + _DetailsItem.text( + DateFormat.yMMMd(Localizations.localeOf(context).toLanguageTag()) + .format(document.created), + context: context, + label: S.of(context).documentCreatedPropertyLabel, + ), + _separator(), + _DetailsItem( + content: DocumentTypeWidget( + documentTypeId: document.documentType, + afterSelected: () { + Navigator.pop(context); + }, + ), + label: S.of(context).documentDocumentTypePropertyLabel, + ), + _separator(), + _DetailsItem( + label: S.of(context).documentCorrespondentPropertyLabel, + content: CorrespondentWidget( + correspondentId: document.correspondent, + afterSelected: () { + Navigator.pop(context); + }, + ), + ), + _separator(), + _DetailsItem( + label: S.of(context).documentStoragePathPropertyLabel, + content: StoragePathWidget( + pathId: document.storagePath, + afterSelected: () { + Navigator.pop(context); + }, + ), + ), + _separator(), + _DetailsItem( + label: S.of(context).documentTagsPropertyLabel, + content: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: TagsWidget( + tagIds: document.tags, + ), + ), + ), + // _separator(), + // FutureBuilder>( + // future: getIt().findSimilar(document.id), + // builder: (context, snapshot) { + // if (!snapshot.hasData) { + // return CircularProgressIndicator(); + // } + // return ExpansionTile( + // tilePadding: const EdgeInsets.symmetric(horizontal: 8.0), + // title: Text( + // S.of(context).documentDetailsPageSimilarDocumentsLabel, + // style: + // Theme.of(context).textTheme.headline5?.copyWith(fontWeight: FontWeight.bold), + // ), + // children: snapshot.data! + // .map((e) => DocumentListItem( + // document: e, + // onTap: (doc) {}, + // isSelected: false, + // isAtLeastOneSelected: false)) + // .toList(), + // ); + // }), + ], + ); + } + + Widget _separator() { + return const SizedBox(height: 32.0); + } + + void _onEdit(DocumentModel document) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => LabelBlocProvider( + child: DocumentEditPage(document: document), + ), + maintainState: true, + ), + ); + } + + Future _onDownload(DocumentModel document) async { + setState(() { + _isDownloadPending = true; + }); + getIt().download(document).then((bytes) async { + //FIXME: logic currently flawed, some error somewhere but cannot look into directory... + final dir = await getApplicationDocumentsDirectory(); + final dirPath = dir.path + "/files/"; + var filePath = dirPath + document.originalFileName; + + if (File(filePath).existsSync()) { + final count = dir + .listSync() + .where((entity) => (entity.path.contains(document.originalFileName))) + .fold(0, (previous, element) => previous + 1); + final extSeperationIdx = filePath.lastIndexOf("."); + filePath = + filePath.replaceRange(extSeperationIdx, extSeperationIdx + 1, " (${count + 1})."); + } + Directory(dirPath).createSync(); + await File(filePath).writeAsBytes(bytes); + _isDownloadPending = false; + showSnackBar(context, "Document successfully downloaded to $filePath"); //TODO: INTL + }); + } + + Future _onDelete(DocumentModel document) async { + showDialog( + context: context, + builder: (context) => DeleteDocumentConfirmationDialog(document: document)).then((delete) { + if (delete ?? false) { + BlocProvider.of(context).removeDocument(document).then((value) { + Navigator.pop(context); + showSnackBar(context, S.of(context).documentDeleteSuccessMessage); + }).onError((error, _) { + showSnackBar(context, translateError(context, error.code)); + }); + } + }); + } + + Future _onOpen(DocumentModel document) async { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DocumentView(document: document), + ), + ); + } + + static String formatBytes(int bytes, int decimals) { + if (bytes <= 0) return "0 B"; + const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + var i = (log(bytes) / log(1024)).floor(); + return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i]; + } +} + +class _DetailsItem extends StatelessWidget { + final String label; + final Widget content; + const _DetailsItem({Key? key, required this.label, required this.content}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: Theme.of(context).textTheme.headline5?.copyWith(fontWeight: FontWeight.bold), + ), + content, + ], + ), + ); + } + + _DetailsItem.text( + String text, { + required this.label, + required BuildContext context, + }) : content = Text(text, style: Theme.of(context).textTheme.bodyText2); +} + +class ColoredTabBar extends Container implements PreferredSizeWidget { + ColoredTabBar({ + super.key, + required this.backgroundColor, + required this.tabBar, + }); + + final TabBar tabBar; + final Color backgroundColor; + @override + Size get preferredSize => tabBar.preferredSize; + + @override + Widget build(BuildContext context) => Container( + color: backgroundColor, + child: tabBar, + ); +} diff --git a/lib/features/documents/view/pages/document_edit_page.dart b/lib/features/documents/view/pages/document_edit_page.dart new file mode 100644 index 0000000..c07f6d5 --- /dev/null +++ b/lib/features/documents/view/pages/document_edit_page.dart @@ -0,0 +1,204 @@ +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:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/ids_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_form_field.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:image/image.dart'; +import 'package:intl/intl.dart'; + +class DocumentEditPage extends StatefulWidget { + final DocumentModel document; + const DocumentEditPage({Key? key, required this.document}) : super(key: key); + + @override + State createState() => _DocumentEditPageState(); +} + +class _DocumentEditPageState extends State { + static const fkTitle = "title"; + static const fkCorrespondent = "correspondent"; + static const fkTags = "tags"; + static const fkDocumentType = "documentType"; + static const fkCreatedDate = "createdAtDate"; + static const fkStoragePath = 'storagePath'; + + late Future documentBytes; + + final GlobalKey _formKey = GlobalKey(); + bool _isSubmitLoading = false; + + @override + void initState() { + documentBytes = getIt().getPreview(widget.document.id); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + if (_formKey.currentState?.saveAndValidate() ?? false) { + final values = _formKey.currentState!.value; + final updatedDocument = widget.document.copyWith( + title: values[fkTitle], + created: values[fkCreatedDate], + documentType: values[fkDocumentType] as IdQueryParameter, + correspondent: values[fkCorrespondent] as IdQueryParameter, + storagePath: values[fkStoragePath] as IdQueryParameter, + tags: values[fkTags] as IdsQueryParameter, + ); + setState(() { + _isSubmitLoading = true; + }); + await getIt().updateDocument(updatedDocument); + Navigator.pop(context); + showSnackBar(context, "Document successfully updated."); //TODO: INTL + } + }, + icon: const Icon(Icons.save), + label: Text(S.of(context).genericActionSaveLabel), + ), + appBar: AppBar( + title: Text(S.of(context).documentEditPageTitle), + bottom: _isSubmitLoading + ? const PreferredSize( + preferredSize: Size.fromHeight(4), + child: LinearProgressIndicator(), + ) + : null, + ), + extendBody: true, + body: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + top: 8, + left: 8, + right: 8, + ), + child: FormBuilder( + key: _formKey, + child: ListView(children: [ + _buildTitleFormField().padded(), + _buildCreatedAtFormField().padded(), + BlocBuilder>( + builder: (context, state) { + return LabelFormField( + notAssignedSelectable: false, + formBuilderState: _formKey.currentState, + labelCreationWidgetBuilder: (currentInput) => BlocProvider.value( + value: BlocProvider.of(context), + child: AddDocumentTypePage( + initialName: currentInput, + ), + ), + label: S.of(context).documentDocumentTypePropertyLabel, + initialValue: DocumentTypeQuery.fromId(widget.document.documentType), + state: state, + name: fkDocumentType, + queryParameterIdBuilder: DocumentTypeQuery.fromId, + queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned, + prefixIcon: const Icon(Icons.description_outlined), + ); + }, + ).padded(), + BlocBuilder>( + builder: (context, state) { + return LabelFormField( + notAssignedSelectable: false, + formBuilderState: _formKey.currentState, + labelCreationWidgetBuilder: (initialValue) => BlocProvider.value( + value: BlocProvider.of(context), + child: AddCorrespondentPage(initalValue: initialValue), + ), + label: S.of(context).documentCorrespondentPropertyLabel, + state: state, + initialValue: CorrespondentQuery.fromId(widget.document.correspondent), + name: fkCorrespondent, + queryParameterIdBuilder: CorrespondentQuery.fromId, + queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned, + prefixIcon: const Icon(Icons.person_outlined), + ); + }, + ).padded(), + BlocBuilder>( + builder: (context, state) { + return LabelFormField( + notAssignedSelectable: false, + formBuilderState: _formKey.currentState, + labelCreationWidgetBuilder: (initialValue) => BlocProvider.value( + value: BlocProvider.of(context), + child: AddStoragePathPage(initalValue: initialValue), + ), + label: S.of(context).documentStoragePathPropertyLabel, + state: state, + initialValue: StoragePathQuery.fromId(widget.document.storagePath), + name: fkStoragePath, + queryParameterIdBuilder: StoragePathQuery.fromId, + queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned, + prefixIcon: const Icon(Icons.folder_outlined), + ); + }, + ).padded(), + TagFormField( + initialValue: TagsQuery.fromIds(widget.document.tags), + name: fkTags, + ).padded(), + ]), + ), + ), + ); + } + + Widget _buildTitleFormField() { + return FormBuilderTextField( + name: fkTitle, + validator: FormBuilderValidators.required(), + decoration: InputDecoration( + label: Text(S.of(context).documentTitlePropertyLabel), + ), + initialValue: widget.document.title, + ); + } + + Widget _buildCreatedAtFormField() { + return FormBuilderDateTimePicker( + inputType: InputType.date, + name: fkCreatedDate, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.calendar_month_outlined), + label: Text(S.of(context).documentCreatedPropertyLabel), + ), + initialValue: widget.document.created, + format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format + initialEntryMode: DatePickerEntryMode.calendar, + ); + } +} diff --git a/lib/features/documents/view/pages/document_view.dart b/lib/features/documents/view/pages/document_view.dart new file mode 100644 index 0000000..274b0a7 --- /dev/null +++ b/lib/features/documents/view/pages/document_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:pdfx/pdfx.dart'; + +class DocumentView extends StatefulWidget { + final DocumentModel document; + + const DocumentView({ + Key? key, + required this.document, + }) : super(key: key); + + @override + State createState() => _DocumentViewState(); +} + +class _DocumentViewState extends State { + late PdfController _pdfController; + + @override + void initState() { + super.initState(); + _pdfController = PdfController( + document: PdfDocument.openData( + getIt().getPreview(widget.document.id), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context).documentPreviewPageTitle), + ), + body: PdfView( + builders: PdfViewBuilders( + options: const DefaultBuilderOptions(), + pageLoaderBuilder: (context) => const Center( + child: CircularProgressIndicator(), + ), + ), + controller: _pdfController, + ), + ); + } +} diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart new file mode 100644 index 0000000..6263f03 --- /dev/null +++ b/lib/features/documents/view/pages/documents_page.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/connectivity_cubit.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/widgets/offline_banner.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/pages/document_details_page.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/documents_empty_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/grid/document_grid.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/list/document_list.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/selection/documents_page_app_bar.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/sort_documents_button.dart'; +import 'package:flutter_paperless_mobile/features/home/view/widget/info_drawer.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; + +class DocumentsPage extends StatefulWidget { + const DocumentsPage({Key? key}) : super(key: key); + + @override + State createState() => _DocumentsPageState(); +} + +class _DocumentsPageState extends State { + final PagingController _pagingController = + PagingController( + firstPageKey: 1, + ); + + final PanelController _panelController = PanelController(); + ViewType _viewType = ViewType.list; + + @override + void initState() { + super.initState(); + final documentsCubit = BlocProvider.of(context); + if (!documentsCubit.state.isLoaded) { + documentsCubit.loadDocuments().onError( + (error, stackTrace) => showSnackBar( + context, + translateError(context, error.code), + ), + ); + } + _pagingController.addPageRequestListener(_loadNewPage); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + Future _loadNewPage(int pageKey) async { + final documentsCubit = BlocProvider.of(context); + final pageCount = + documentsCubit.state.inferPageCount(pageSize: documentsCubit.state.filter.pageSize); + if (pageCount <= pageKey + 1) { + _pagingController.nextPageKey = null; + } + documentsCubit.loadMore(); + } + + void _onSelected(DocumentModel model) { + BlocProvider.of(context).toggleDocumentSelection(model); + } + + Future _onRefresh() { + final documentsCubit = BlocProvider.of(context); + return documentsCubit + .updateFilter(filter: documentsCubit.state.filter.copyWith(page: 1)) + .onError((error, _) { + showSnackBar(context, translateError(context, error.code)); + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (_panelController.isPanelOpen) { + FocusScope.of(context).unfocus(); + _panelController.close(); + return false; + } + final docBloc = BlocProvider.of(context); + if (docBloc.state.selection.isNotEmpty) { + docBloc.resetSelection(); + return false; + } + return true; + }, + child: BlocConsumer( + listenWhen: (previous, current) => + previous != ConnectivityState.connected && current == ConnectivityState.connected, + listener: (context, state) { + BlocProvider.of(context).loadDocuments(); + }, + builder: (context, connectivityState) { + return Scaffold( + drawer: BlocProvider.value( + value: BlocProvider.of(context), + child: const InfoDrawer(), + ), + resizeToAvoidBottomInset: true, + appBar: connectivityState == ConnectivityState.connected ? null : const OfflineBanner(), + body: SlidingUpPanel( + backdropEnabled: true, + parallaxEnabled: true, + parallaxOffset: .5, + controller: _panelController, + defaultPanelState: PanelState.CLOSED, + minHeight: 48, + maxHeight: MediaQuery.of(context).size.height - + kBottomNavigationBarHeight - + 2 * kToolbarHeight, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + body: _buildBody(connectivityState), + color: Theme.of(context).scaffoldBackgroundColor, + panelBuilder: (scrollController) => DocumentFilterPanel( + panelController: _panelController, + scrollController: scrollController, + ), + ), + ); + }, + ), + ); + } + + Widget _buildBody(ConnectivityState connectivityState) { + return BlocBuilder( + builder: (context, state) { + // Some ugly tricks to make it work with bloc, update pageController + _pagingController.value = PagingState( + itemList: state.documents, + nextPageKey: state.nextPageNumber, + ); + + late Widget child; + switch (_viewType) { + case ViewType.list: + child = DocumentListView( + onTap: _openDocumentDetails, + state: state, + onSelected: _onSelected, + pagingController: _pagingController, + hasInternetConnection: connectivityState == ConnectivityState.connected, + ); + break; + case ViewType.grid: + child = DocumentGridView( + onTap: _openDocumentDetails, + state: state, + onSelected: _onSelected, + pagingController: _pagingController, + hasInternetConnection: connectivityState == ConnectivityState.connected); + break; + } + + if (state.isLoaded && state.documents.isEmpty) { + child = SliverToBoxAdapter( + child: DocumentsEmptyState( + state: state, + ), + ); + } + + return RefreshIndicator( + onRefresh: _onRefresh, + child: Container( + padding: const EdgeInsets.only( + bottom: 142, + ), // Prevents panel from hiding scrollable content + child: CustomScrollView( + slivers: [ + DocumentsPageAppBar( + actions: [ + const SortDocumentsButton(), + IconButton( + icon: Icon( + _viewType == ViewType.grid ? Icons.list : Icons.grid_view, + ), + onPressed: () => setState(() => _viewType = _viewType.toggle()), + ), + ], + ), + child + ], + ), + ), + ); + }, + ); + } + + void _openDocumentDetails(DocumentModel model) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + ], + child: DocumentDetailsPage( + documentId: model.id, + ), + ), + ), + ); + } +} + +enum ViewType { + grid, + list; + + ViewType toggle() { + return this == grid ? list : grid; + } +} diff --git a/lib/features/documents/view/widgets/delete_document_confirmation_dialog.dart b/lib/features/documents/view/widgets/delete_document_confirmation_dialog.dart new file mode 100644 index 0000000..b1b5c23 --- /dev/null +++ b/lib/features/documents/view/widgets/delete_document_confirmation_dialog.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class DeleteDocumentConfirmationDialog extends StatelessWidget { + final DocumentModel document; + const DeleteDocumentConfirmationDialog({super.key, required this.document}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(S.of(context).documentsPageSelectionBulkDeleteDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).documentsPageSelectionBulkDeleteDialogWarningTextOne, + ), + const SizedBox(height: 16), + Text( + document.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 16), + Text(S.of(context).documentsPageSelectionBulkDeleteDialogContinueText), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(S.of(context).genericActionCancelLabel), + ), + TextButton( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error), + ), + onPressed: () { + Navigator.pop(context, true); + }, + child: Text(S.of(context).genericActionDeleteLabel), + ), + ], + ); + } +} diff --git a/lib/features/documents/view/widgets/document_preview.dart b/lib/features/documents/view/widgets/document_preview.dart new file mode 100644 index 0000000..16f2cd4 --- /dev/null +++ b/lib/features/documents/view/widgets/document_preview.dart @@ -0,0 +1,45 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:shimmer/shimmer.dart'; + +class DocumentPreview extends StatelessWidget { + final int id; + final BoxFit fit; + final Alignment alignment; + final double borderRadius; + + const DocumentPreview({ + Key? key, + required this.id, + this.fit = BoxFit.cover, + this.alignment = Alignment.center, + this.borderRadius = 8.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return + // Hero( + // tag: "document_$id",child: + ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: CachedNetworkImage( + fit: fit, + alignment: Alignment.topCenter, + cacheKey: "thumb_$id", + imageUrl: getIt().getThumbnailUrl(id), + errorWidget: (ctxt, msg, __) => Text(msg), + placeholder: (context, value) => Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: const SizedBox(height: 100, width: 100), + ), + cacheManager: getIt(), + ), + // ), + ); + } +} diff --git a/lib/features/documents/view/widgets/documents_empty_state.dart b/lib/features/documents/view/widgets/documents_empty_state.dart new file mode 100644 index 0000000..5bf6c84 --- /dev/null +++ b/lib/features/documents/view/widgets/documents_empty_state.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/widgets/empty_state.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class DocumentsEmptyState extends StatelessWidget { + final DocumentsState state; + const DocumentsEmptyState({ + Key? key, + required this.state, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: EmptyState( + title: S.of(context).documentsPageEmptyStateOopsText, + subtitle: S.of(context).documentsPageEmptyStateNothingHereText, + bottomChild: state.filter != DocumentFilter.initial + ? ElevatedButton( + onPressed: () async { + await BlocProvider.of(context).updateFilter(); + BlocProvider.of(context).resetSelection(); + }, + child: Text( + S.of(context).documentsFilterPageResetFilterLabel, + ), + ).padded() + : null, + ), + ); + } +} diff --git a/lib/features/documents/view/widgets/grid/document_grid.dart b/lib/features/documents/view/widgets/grid/document_grid.dart new file mode 100644 index 0000000..34580ba --- /dev/null +++ b/lib/features/documents/view/widgets/grid/document_grid.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/core/widgets/documents_list_loading_widget.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/grid/document_grid_item.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + +class DocumentGridView extends StatelessWidget { + final void Function(DocumentModel model) onTap; + final void Function(DocumentModel) onSelected; + final PagingController pagingController; + final DocumentsState state; + final bool hasInternetConnection; + + const DocumentGridView({ + super.key, + required this.onTap, + required this.pagingController, + required this.state, + required this.onSelected, + required this.hasInternetConnection, + }); + @override + Widget build(BuildContext context) { + return PagedSliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + childAspectRatio: 1 / 2, + ), + pagingController: pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return DocumentGridItem( + document: item, + onTap: onTap, + isSelected: state.selection.contains(item), + onSelected: onSelected, + isAtLeastOneSelected: state.selection.isNotEmpty, + ); + }, + noItemsFoundIndicatorBuilder: (context) => + const DocumentsListLoadingWidget(), //TODO: Replace with grid loading widget + ), + ); + } +} diff --git a/lib/features/documents/view/widgets/grid/document_grid_item.dart b/lib/features/documents/view/widgets/grid/document_grid_item.dart new file mode 100644 index 0000000..7824f33 --- /dev/null +++ b/lib/features/documents/view/widgets/grid/document_grid_item.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/document_preview.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; +import 'package:intl/intl.dart'; + +class DocumentGridItem extends StatelessWidget { + final DocumentModel document; + final bool isSelected; + final void Function(DocumentModel) onTap; + final void Function(DocumentModel) onSelected; + final bool isAtLeastOneSelected; + + const DocumentGridItem({ + Key? key, + required this.document, + required this.onTap, + required this.onSelected, + required this.isSelected, + required this.isAtLeastOneSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _onTap, + onLongPress: () => onSelected(document), + child: AbsorbPointer( + absorbing: isAtLeastOneSelected, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 1.0, + color: isSelected + ? Theme.of(context).colorScheme.inversePrimary + : Theme.of(context).cardColor, + child: Column( + children: [ + AspectRatio( + aspectRatio: 1, + child: DocumentPreview( + id: document.id, + borderRadius: 12.0, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CorrespondentWidget(correspondentId: document.correspondent), + DocumentTypeWidget(documentTypeId: document.documentType), + Text( + document.title, + maxLines: document.tags.isEmpty ? 3 : 2, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleMedium, + ), + const Spacer(), + TagsWidget( + tagIds: document.tags, + isMultiLine: false, + ), + Text(DateFormat.yMMMd(Intl.getCurrentLocale()).format(document.created)), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + void _onTap() { + if (isAtLeastOneSelected || isSelected) { + onSelected(document); + } else { + onTap(document); + } + } +} diff --git a/lib/features/documents/view/widgets/list/document_list.dart b/lib/features/documents/view/widgets/list/document_list.dart new file mode 100644 index 0000000..8a11491 --- /dev/null +++ b/lib/features/documents/view/widgets/list/document_list.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/core/widgets/documents_list_loading_widget.dart'; +import 'package:flutter_paperless_mobile/core/widgets/offline_widget.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/list/document_list_item.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + +class DocumentListView extends StatelessWidget { + final void Function(DocumentModel model) onTap; + final void Function(DocumentModel) onSelected; + final PagingController pagingController; + final DocumentsState state; + final bool hasInternetConnection; + + const DocumentListView({ + super.key, + required this.onTap, + required this.pagingController, + required this.state, + required this.onSelected, + required this.hasInternetConnection, + }); + @override + Widget build(BuildContext context) { + return PagedSliverList( + pagingController: pagingController, + builderDelegate: PagedChildBuilderDelegate( + animateTransitions: true, + itemBuilder: (context, item, index) { + return DocumentListItem( + document: item, + onTap: onTap, + isSelected: state.selection.contains(item), + onSelected: onSelected, + isAtLeastOneSelected: state.selection.isNotEmpty, + ); + }, + noItemsFoundIndicatorBuilder: (context) => + hasInternetConnection ? const DocumentsListLoadingWidget() : const OfflineWidget(), + ), + ); + } +} diff --git a/lib/features/documents/view/widgets/list/document_list_item.dart b/lib/features/documents/view/widgets/list/document_list_item.dart new file mode 100644 index 0000000..d382c26 --- /dev/null +++ b/lib/features/documents/view/widgets/list/document_list_item.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/document_preview.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; + +class DocumentListItem extends StatelessWidget { + static const a4AspectRatio = 1 / 1.4142; + final DocumentModel document; + final bool isSelected; + final void Function(DocumentModel) onTap; + final void Function(DocumentModel)? onSelected; + final bool isAtLeastOneSelected; + + const DocumentListItem({ + Key? key, + required this.document, + required this.onTap, + this.onSelected, + required this.isSelected, + required this.isAtLeastOneSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + child: ListTile( + dense: true, + selected: isSelected, + onTap: () => _onTap(), + selectedTileColor: Theme.of(context).colorScheme.inversePrimary, + onLongPress: () => onSelected?.call(document), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + AbsorbPointer( + absorbing: isAtLeastOneSelected, + child: CorrespondentWidget( + correspondentId: document.correspondent, + afterSelected: () {}, + ), + ), + ], + ), + Text( + document.title, + overflow: TextOverflow.ellipsis, + maxLines: document.tags.isEmpty ? 2 : 1, + ), + ], + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: AbsorbPointer( + absorbing: isAtLeastOneSelected, + child: TagsWidget( + tagIds: document.tags, + isMultiLine: false, + ), + ), + ), + isThreeLine: document.tags.isNotEmpty, + leading: AspectRatio( + aspectRatio: a4AspectRatio, + child: GestureDetector( + child: DocumentPreview( + id: document.id, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), + ), + ), + contentPadding: const EdgeInsets.all(8.0), + ), + ); + } + + void _onTap() { + if (isAtLeastOneSelected || isSelected) { + onSelected?.call(document); + } else { + onTap(document); + } + } +} diff --git a/lib/features/documents/view/widgets/order_by_dropdown.dart b/lib/features/documents/view/widgets/order_by_dropdown.dart new file mode 100644 index 0000000..7732f76 --- /dev/null +++ b/lib/features/documents/view/widgets/order_by_dropdown.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; + +class OrderByDropdown extends StatefulWidget { + static const fkOrderBy = "orderBy"; + const OrderByDropdown({super.key}); + + @override + State createState() => _OrderByDropdownState(); +} + +class _OrderByDropdownState extends State { + @override + Widget build(BuildContext context) { + return FormBuilderDropdown( + name: OrderByDropdown.fkOrderBy, + items: const [], + ); + } +} diff --git a/lib/features/documents/view/widgets/search/document_filter_panel.dart b/lib/features/documents/view/widgets/search/document_filter_panel.dart new file mode 100644 index 0000000..46884c7 --- /dev/null +++ b/lib/features/documents/view/widgets/search/document_filter_panel.dart @@ -0,0 +1,530 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_form_field.dart'; +import 'package:flutter_paperless_mobile/features/scan/view/document_upload_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:intl/intl.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; + +enum DateRangeSelection { before, after } + +class DocumentFilterPanel extends StatefulWidget { + final PanelController panelController; + final ScrollController scrollController; + + const DocumentFilterPanel({ + Key? key, + required this.panelController, + required this.scrollController, + }) : super(key: key); + + @override + State createState() => _DocumentFilterPanelState(); +} + +class _DocumentFilterPanelState extends State { + static const fkCorrespondent = DocumentModel.correspondentKey; + static const fkDocumentType = DocumentModel.documentTypeKey; + static const fkStoragePath = DocumentModel.storagePathKey; + static const fkQuery = "query"; + static const fkCreatedAt = DocumentModel.createdKey; + static const fkAddedAt = DocumentModel.addedKey; + + static const _sortFields = [ + SortField.created, + SortField.added, + SortField.modified, + SortField.title, + SortField.correspondentName, + SortField.documentType, + SortField.archiveSerialNumber + ]; + + final _formKey = GlobalKey(); + bool _isQueryLoading = false; + + DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) { + if (start == null && end == null) { + return null; + } + if (start != null && end != null) { + return DateTimeRange(start: start, end: end); + } + assert(start != null || end != null); + final singleDate = (start ?? end)!; + return DateTimeRange(start: singleDate, end: singleDate); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + // Set initial values, otherwise they would not automatically update. + _patchFromFilter(state.filter); + }, + builder: (context, state) { + return FormBuilder( + key: _formKey, + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + _buildDragLine(), + Align( + alignment: Alignment.topRight, + child: TextButton.icon( + icon: const Icon(Icons.refresh), + label: Text(S.of(context).documentsFilterPageResetFilterLabel), + onPressed: () => _resetFilter(context), + ), + ), + ], + ), + const SizedBox( + height: 8.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).documentsFilterPageTitle, + style: Theme.of(context).textTheme.titleLarge, + ), + TextButton( + onPressed: _onApplyFilter, + child: Text(S.of(context).documentsFilterPageApplyFilterLabel), + ), + ], + ).padded(), + Expanded( + child: ListView( + controller: widget.scrollController, + children: [ + const SizedBox( + height: 16.0, + ), + Align( + alignment: Alignment.centerLeft, + child: Text(S.of(context).documentsFilterPageSearchLabel), + ).padded(), + _buildQueryFormField(state), + _buildSortByChipsList(context, state), + Align( + alignment: Alignment.centerLeft, + child: Text(S.of(context).documentsFilterPageAdvancedLabel), + ).padded(), + _buildCreatedDateRangePickerFormField(state).padded(), + _buildAddedDateRangePickerFormField(state).padded(), + _buildCorrespondentFormField(state).padded(), + _buildDocumentTypeFormField(state).padded(), + _buildStoragePathFormField(state).padded(), + TagFormField( + name: DocumentModel.tagsKey, + initialValue: state.filter.tags, + ).padded(), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } + + void _resetFilter(BuildContext context) async { + FocusScope.of(context).unfocus(); + await BlocProvider.of(context).updateFilter(); + BlocProvider.of(context).resetSelection(); + if (!widget.panelController.isPanelClosed) { + widget.panelController.close(); + } + } + + Widget _buildDocumentTypeFormField(DocumentsState docState) { + return BlocBuilder>( + builder: (context, state) { + return LabelFormField( + formBuilderState: _formKey.currentState, + name: fkDocumentType, + state: state, + label: S.of(context).documentDocumentTypePropertyLabel, + initialValue: docState.filter.documentType, + queryParameterIdBuilder: DocumentTypeQuery.fromId, + queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned, + prefixIcon: const Icon(Icons.description_outlined), + ); + }, + ); + } + + Widget _buildStoragePathFormField(DocumentsState docState) { + return BlocBuilder>( + builder: (context, state) { + return LabelFormField( + formBuilderState: _formKey.currentState, + name: fkStoragePath, + state: state, + label: S.of(context).documentStoragePathPropertyLabel, + initialValue: docState.filter.storagePath, + queryParameterIdBuilder: StoragePathQuery.fromId, + queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned, + prefixIcon: const Icon(Icons.folder_outlined), + ); + }, + ); + } + + Widget _buildQueryFormField(DocumentsState state) { + final queryType = _formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ?? + QueryType.titleAndContent; + late String label; + switch (queryType) { + case QueryType.title: + label = S.of(context).documentsFilterPageQueryOptionsTitleLabel; + break; + case QueryType.titleAndContent: + label = S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel; + break; + case QueryType.extended: + label = S.of(context).documentsFilterPageQueryOptionsExtendedLabel; + break; + } + + return FormBuilderTextField( + name: fkQuery, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search_outlined), + labelText: label, + suffixIcon: QueryTypeFormField( + initialValue: state.filter.queryType, + afterSelected: (queryType) => setState(() {}), + ), + ), + initialValue: state.filter.queryText, + ).padded(); + } + + Widget _buildDateRangePickerHelper(DocumentsState state, String formFieldKey) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ActionChip( + label: Text( + S.of(context).documentsFilterPageDateRangeLastSevenDaysLabel, + ), + onPressed: () { + _formKey.currentState?.fields[formFieldKey]?.didChange( + DateTimeRange( + start: DateUtils.addDaysToDate(DateTime.now(), -7), + end: DateTime.now(), + ), + ); + }, + ).padded(const EdgeInsets.only(right: 8.0)), + ActionChip( + label: Text( + S.of(context).documentsFilterPageDateRangeLastMonthLabel, + ), + onPressed: () { + final now = DateTime.now(); + final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -1); + _formKey.currentState?.fields[formFieldKey]?.didChange( + DateTimeRange( + start: DateTime(firstDayOfLastMonth.year, firstDayOfLastMonth.month, now.day), + end: DateTime.now(), + ), + ); + }, + ).padded(const EdgeInsets.only(right: 8.0)), + ActionChip( + label: Text( + S.of(context).documentsFilterPageDateRangeLastThreeMonthsLabel, + ), + onPressed: () { + final now = DateTime.now(); + final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -3); + _formKey.currentState?.fields[formFieldKey]?.didChange( + DateTimeRange( + start: DateTime( + firstDayOfLastMonth.year, + firstDayOfLastMonth.month, + now.day, + ), + end: DateTime.now(), + ), + ); + }, + ).padded(const EdgeInsets.only(right: 8.0)), + ActionChip( + label: Text( + S.of(context).documentsFilterPageDateRangeLastYearLabel, + ), + onPressed: () { + final now = DateTime.now(); + final firstDayOfLastMonth = DateUtils.addMonthsToMonthDate(now, -12); + _formKey.currentState?.fields[formFieldKey]?.didChange( + DateTimeRange( + start: DateTime( + firstDayOfLastMonth.year, + firstDayOfLastMonth.month, + now.day, + ), + end: DateTime.now(), + ), + ); + }, + ), + ], + ), + ); + } + + Widget _buildCorrespondentFormField(DocumentsState docState) { + return BlocBuilder>( + builder: (context, state) { + return LabelFormField( + formBuilderState: _formKey.currentState, + name: fkCorrespondent, + state: state, + label: S.of(context).documentCorrespondentPropertyLabel, + initialValue: docState.filter.correspondent, + queryParameterIdBuilder: CorrespondentQuery.fromId, + queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned, + prefixIcon: const Icon(Icons.person_outline), + ); + }, + ); + } + + Widget _buildCreatedDateRangePickerFormField(DocumentsState state) { + return Column( + children: [ + FormBuilderDateRangePicker( + initialValue: _dateTimeRangeOfNullable( + state.filter.createdDateAfter, + state.filter.createdDateBefore, + ), + pickerBuilder: (context, child) { + return Theme( + data: ThemeData.light().copyWith( + primaryColor: Theme.of(context).primaryColor, + colorScheme: Theme.of(context).colorScheme, + buttonTheme: Theme.of(context).buttonTheme, + ), + child: child!, + ); + }, + format: DateFormat.yMMMd(Localizations.localeOf(context).toString()), + fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel, + fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel, + firstDate: DateTime.fromMicrosecondsSinceEpoch(0), + lastDate: DateTime.now(), + name: fkCreatedAt, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.calendar_month_outlined), + labelText: S.of(context).documentCreatedPropertyLabel, + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () => _formKey.currentState?.fields[fkCreatedAt]?.didChange(null), + ), + ), + ), + _buildDateRangePickerHelper(state, fkCreatedAt), + ], + ); + } + + Widget _buildAddedDateRangePickerFormField(DocumentsState state) { + return Column( + children: [ + FormBuilderDateRangePicker( + initialValue: _dateTimeRangeOfNullable( + state.filter.addedDateAfter, + state.filter.addedDateBefore, + ), + pickerBuilder: (context, child) { + return Theme( + data: ThemeData.light().copyWith( + primaryColor: Theme.of(context).primaryColor, + colorScheme: Theme.of(context).colorScheme, + buttonTheme: Theme.of(context).buttonTheme, + ), + child: child!, + ); + }, + format: DateFormat.yMMMd(Localizations.localeOf(context).toString()), + fieldStartLabelText: S.of(context).documentsFilterPageDateRangeFieldStartLabel, + fieldEndLabelText: S.of(context).documentsFilterPageDateRangeFieldEndLabel, + firstDate: DateTime.fromMicrosecondsSinceEpoch(0), + lastDate: DateTime.now(), + name: fkAddedAt, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.calendar_month_outlined), + labelText: S.of(context).documentAddedPropertyLabel, + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () => _formKey.currentState?.fields[fkAddedAt]?.didChange(null), + ), + ), + ), + _buildDateRangePickerHelper(state, fkAddedAt), + ], + ); + } + + Widget _buildDragLine() { + return Container( + width: 48, + height: 5, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + ); + } + + Widget _buildSortByChipsList(BuildContext context, DocumentsState state) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).documentsPageOrderByLabel, + ), + SizedBox( + height: kToolbarHeight, + child: ListView.separated( + itemCount: _sortFields.length, + scrollDirection: Axis.horizontal, + separatorBuilder: (context, index) => const SizedBox( + width: 8.0, + ), + itemBuilder: (context, index) => + _buildActionChip(_sortFields[index], state.filter.sortField, context), + ), + ), + ], + ), + ); + } + + Widget _buildActionChip( + SortField sortField, SortField? currentlySelectedOrder, BuildContext context) { + String text; + switch (sortField) { + case SortField.archiveSerialNumber: + text = S.of(context).documentArchiveSerialNumberPropertyShortLabel; + break; + case SortField.correspondentName: + text = S.of(context).documentCorrespondentPropertyLabel; + break; + case SortField.title: + text = S.of(context).documentTitlePropertyLabel; + break; + case SortField.documentType: + text = S.of(context).documentDocumentTypePropertyLabel; + break; + case SortField.created: + text = S.of(context).documentCreatedPropertyLabel; + break; + case SortField.added: + text = S.of(context).documentAddedPropertyLabel; + break; + case SortField.modified: + text = S.of(context).documentModifiedPropertyLabel; + break; + } + + final docBloc = BlocProvider.of(context); + return ActionChip( + label: Text(text), + avatar: currentlySelectedOrder == sortField + ? const Icon( + Icons.done, + color: Colors.green, + ) + : null, + onPressed: () => + docBloc.updateFilter(filter: docBloc.state.filter.copyWith(sortField: sortField)), + ); + } + + void _onApplyFilter() { + setState(() => _isQueryLoading = true); + _formKey.currentState?.save(); + if (_formKey.currentState?.validate() ?? false) { + final v = _formKey.currentState!.value; + final docCubit = BlocProvider.of(context); + DocumentFilter newFilter = docCubit.state.filter.copyWith( + createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end, + createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start, + correspondent: v[fkCorrespondent] as CorrespondentQuery?, + documentType: v[fkDocumentType] as DocumentTypeQuery?, + storagePath: v[fkStoragePath] as StoragePathQuery?, + tags: v[DocumentModel.tagsKey] as TagsQuery?, + page: 1, + queryText: v[fkQuery] as String?, + addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end, + addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start, + queryType: v[QueryTypeFormField.fkQueryType] as QueryType, + ); + BlocProvider.of(context).updateFilter(filter: newFilter).then((value) { + BlocProvider.of(context).resetSelection(); + FocusScope.of(context).unfocus(); + widget.panelController.close(); + setState(() => _isQueryLoading = false); + }); + } + } + + void _patchFromFilter(DocumentFilter f) { + _formKey.currentState?.patchValue({ + fkCorrespondent: f.correspondent, + fkDocumentType: f.documentType, + fkQuery: f.queryText, + fkStoragePath: f.storagePath, + DocumentModel.tagsKey: f.tags, + DocumentModel.titleKey: f.queryText, + QueryTypeFormField.fkQueryType: f.queryType, + fkCreatedAt: _dateTimeRangeOfNullable( + f.createdDateAfter, + f.createdDateBefore, + ), + fkAddedAt: _dateTimeRangeOfNullable( + f.addedDateAfter, + f.addedDateBefore, + ), + }); + } +} diff --git a/lib/features/documents/view/widgets/search/query_type_form_field.dart b/lib/features/documents/view/widgets/search/query_type_form_field.dart new file mode 100644 index 0000000..10b2316 --- /dev/null +++ b/lib/features/documents/view/widgets/search/query_type_form_field.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/query_type.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class QueryTypeFormField extends StatelessWidget { + static const fkQueryType = 'queryType'; + final QueryType? initialValue; + final void Function(QueryType)? afterSelected; + const QueryTypeFormField({ + super.key, + this.initialValue, + this.afterSelected, + }); + + @override + Widget build(BuildContext context) { + return FormBuilderField( + builder: (field) => PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + child: ListTile( + title: Text(S.of(context).documentsFilterPageQueryOptionsTitleAndContentLabel), + ), + value: QueryType.titleAndContent, + ), + PopupMenuItem( + child: ListTile( + title: Text(S.of(context).documentsFilterPageQueryOptionsTitleLabel), + ), + value: QueryType.title, + ), + PopupMenuItem( + child: ListTile( + title: Text(S.of(context).documentsFilterPageQueryOptionsExtendedLabel), + ), + value: QueryType.extended, + ), + //TODO: Add support for ASN queries + ], + onSelected: (selection) { + field.didChange(selection); + afterSelected?.call(selection); + }, + child: const Icon(Icons.more_vert), + ), + initialValue: initialValue, + name: QueryTypeFormField.fkQueryType, + ); + } +} diff --git a/lib/features/documents/view/widgets/selection/add_saved_view_page.dart b/lib/features/documents/view/widgets/selection/add_saved_view_page.dart new file mode 100644 index 0000000..30cc377 --- /dev/null +++ b/lib/features/documents/view/widgets/selection/add_saved_view_page.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class AddSavedViewPage extends StatefulWidget { + final DocumentFilter currentFilter; + const AddSavedViewPage({super.key, required this.currentFilter}); + + @override + State createState() => _AddSavedViewPageState(); +} + +class _AddSavedViewPageState extends State { + static const fkName = 'name'; + static const fkShowOnDashboard = 'show_on_dashboard'; + static const fkShowInSidebar = 'show_in_sidebar'; + + final GlobalKey _formKey = GlobalKey(); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context).savedViewCreateNewLabel), + actions: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Tooltip( + child: const Icon(Icons.info_outline), + message: S.of(context).savedViewCreateTooltipText, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton.extended( + icon: const Icon(Icons.add), + onPressed: () => _onCreate(context), + label: Text(S.of(context).genericActionCreateLabel), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: FormBuilder( + key: _formKey, + child: ListView( + children: [ + FormBuilderTextField( + name: fkName, + validator: FormBuilderValidators.required(), //TODO: INTL + decoration: InputDecoration( + label: Text(S.of(context).savedViewNameLabel), + ), + ), + FormBuilderCheckbox( + name: fkShowOnDashboard, + initialValue: false, + title: Text(S.of(context).savedViewShowOnDashboardLabel), + ), + FormBuilderCheckbox( + name: fkShowInSidebar, + initialValue: false, + title: Text(S.of(context).savedViewShowInSidebarLabel), + ), + ], + ), + ), + ), + ); + } + + void _onCreate(BuildContext context) { + if (_formKey.currentState?.saveAndValidate() ?? false) { + Navigator.pop( + context, + SavedView.fromDocumentFilter( + widget.currentFilter, + name: _formKey.currentState?.value[fkName] as String, + showOnDashboard: _formKey.currentState?.value[fkShowOnDashboard] as bool, + showInSidebar: _formKey.currentState?.value[fkShowInSidebar] as bool, + ), + ); + } + } +} diff --git a/lib/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart b/lib/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart new file mode 100644 index 0000000..086a7fa --- /dev/null +++ b/lib/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class BulkDeleteConfirmationDialog extends StatelessWidget { + static const _bulletPoint = "\u2022"; + final DocumentsState state; + const BulkDeleteConfirmationDialog({Key? key, required this.state}) + : super(key: key); + + @override + Widget build(BuildContext context) { + assert(state.selection.isNotEmpty); + return AlertDialog( + title: Text(S.of(context).documentsPageSelectionBulkDeleteDialogTitle), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + //TODO: use plurals, didn't use because of crash... investigate later. + state.selection.length == 1 + ? S + .of(context) + .documentsPageSelectionBulkDeleteDialogWarningTextOne + : S + .of(context) + .documentsPageSelectionBulkDeleteDialogWarningTextMany, + ), + const SizedBox(height: 16), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 150), + child: ListView( + shrinkWrap: true, + children: state.selection.map(_buildBulletPoint).toList(), + ), + ), + const SizedBox(height: 16), + Text( + S.of(context).documentsPageSelectionBulkDeleteDialogContinueText), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(S.of(context).genericActionCancelLabel), + ), + TextButton( + style: ButtonStyle( + foregroundColor: + MaterialStateProperty.all(Theme.of(context).colorScheme.error), + ), + onPressed: () { + Navigator.pop(context, true); + }, + child: Text(S.of(context).genericActionDeleteLabel), + ), + ], + ); + } + + Widget _buildBulletPoint(DocumentModel doc) { + return Text( + "\t$_bulletPoint ${doc.title}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w700, + ), + ); + } +} diff --git a/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart b/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart new file mode 100644 index 0000000..3b5a78f --- /dev/null +++ b/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class ConfirmDeleteSavedViewDialog extends StatelessWidget { + const ConfirmDeleteSavedViewDialog({ + Key? key, + required this.view, + }) : super(key: key); + + final SavedView view; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text( + "Delete view " + view.name + "?", + softWrap: true, + ), + content: Text("Do you really want to delete this view?"), + actions: [ + TextButton( + child: Text(S.of(context).genericActionCancelLabel), + onPressed: () => Navigator.pop(context, false), + ), + TextButton( + child: Text( + S.of(context).genericActionDeleteLabel, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + onPressed: () => Navigator.pop(context, true), + ), + ], + ); + } +} diff --git a/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart b/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart new file mode 100644 index 0000000..6fe6725 --- /dev/null +++ b/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/selection/saved_view_selection_widget.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class DocumentsPageAppBar extends StatefulWidget with PreferredSizeWidget { + final List actions; + + const DocumentsPageAppBar({ + super.key, + this.actions = const [], + }); + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + @override + State createState() => _DocumentsPageAppBarState(); +} + +class _DocumentsPageAppBarState extends State { + static const _flexibleAreaHeight = kToolbarHeight + 48.0; + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, documentsState) { + if (documentsState.selection.isNotEmpty) { + return SliverAppBar( + snap: true, + floating: true, + pinned: true, + expandedHeight: kToolbarHeight, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => BlocProvider.of(context).resetSelection(), + ), + title: + Text('${documentsState.selection.length} ${S.of(context).documentsSelectedText}'), + actions: [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () => _onDelete(context, documentsState), + ), + ], + ); + } else { + return SliverAppBar( + expandedHeight: kToolbarHeight + _flexibleAreaHeight, + pinned: true, + flexibleSpace: const FlexibleSpaceBar( + background: Padding( + padding: EdgeInsets.all(8.0), + child: SavedViewSelectionWidget(height: _flexibleAreaHeight), + ), + ), + title: BlocBuilder( + builder: (context, state) { + return Text( + '${S.of(context).documentsPageTitle} (${_formatDocumentCount(state.count)})', + ); + }, + ), + actions: widget.actions, + ); + } + }, + ); + } + + void _onDelete(BuildContext context, DocumentsState documentsState) async { + final shouldDelete = await showDialog( + context: context, + builder: (context) => BulkDeleteConfirmationDialog(state: documentsState), + ); + if (shouldDelete ?? false) { + BlocProvider.of(context) + .bulkRemoveDocuments(documentsState.selection) + .then((_) => showSnackBar(context, S.of(context).documentsPageBulkDeleteSuccessfulText)) + .onError( + (error, _) => showSnackBar(context, translateError(context, error.code))); + } + } + + String _formatDocumentCount(int count) { + return count > 99 ? "99+" : count.toString(); + } +} diff --git a/lib/features/documents/view/widgets/selection/saved_view_selection_widget.dart b/lib/features/documents/view/widgets/selection/saved_view_selection_widget.dart new file mode 100644 index 0000000..2172d94 --- /dev/null +++ b/lib/features/documents/view/widgets/selection/saved_view_selection_widget.dart @@ -0,0 +1,117 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/selection/add_saved_view_page.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class SavedViewSelectionWidget extends StatelessWidget { + const SavedViewSelectionWidget({ + Key? key, + required this.height, + }) : super(key: key); + + final double height; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BlocBuilder( + builder: (context, state) { + if (state.value.isEmpty) { + return Text(S.of(context).savedViewsEmptyStateText); + } + return SizedBox( + height: 48.0, + child: ListView.separated( + itemCount: state.value.length, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + final view = state.value.values.elementAt(index); + return GestureDetector( + onLongPress: () => _onDelete(context, view), + child: FilterChip( + label: Text(state.value.values.toList()[index].name), + selected: view.id == state.selectedSavedViewId, + onSelected: (isSelected) => _onSelected(isSelected, context, view), + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox( + width: 8.0, + ), + ), + ); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).savedViewsLabel, + style: Theme.of(context).textTheme.titleSmall, + ), + TextButton.icon( + icon: const Icon(Icons.add), + onPressed: () => _onCreatePressed(context), + label: Text(S.of(context).savedViewCreateNewLabel), + ), + ], + ), + ], + ); + } + + void _onCreatePressed(BuildContext context) async { + final newView = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AddSavedViewPage(currentFilter: getIt().state.filter), + ), + ); + if (newView != null) { + try { + BlocProvider.of(context).add(newView); + } on ErrorMessage catch (error) { + showError(context, error); + } + } + } + + void _onSelected(bool isSelected, BuildContext context, SavedView view) { + if (isSelected) { + BlocProvider.of(context).updateFilter(filter: view.toDocumentFilter()); + BlocProvider.of(context).selectView(view); + } else { + BlocProvider.of(context).updateFilter(); + BlocProvider.of(context).selectView(null); + } + } + + void _onDelete(BuildContext context, SavedView view) async { + { + final delete = await showDialog( + context: context, + builder: (context) => ConfirmDeleteSavedViewDialog(view: view), + ) ?? + false; + if (delete) { + try { + BlocProvider.of(context).remove(view); + } on ErrorMessage catch (error) { + showError(context, error); + } + } + } + } +} diff --git a/lib/features/documents/view/widgets/sort_documents_button.dart b/lib/features/documents/view/widgets/sort_documents_button.dart new file mode 100644 index 0000000..97c2e73 --- /dev/null +++ b/lib/features/documents/view/widgets/sort_documents_button.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/sort_order.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class SortDocumentsButton extends StatefulWidget { + const SortDocumentsButton({ + Key? key, + }) : super(key: key); + + @override + State createState() => _SortDocumentsButtonState(); +} + +class _SortDocumentsButtonState extends State { + bool _isLoading = false; + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + Widget child; + if (_isLoading) { + child = const FittedBox( + fit: BoxFit.scaleDown, + child: RefreshProgressIndicator( + strokeWidth: 4.0, + backgroundColor: Colors.transparent, + ), + ); + } else { + final bool isAscending = state.filter.sortOrder == SortOrder.ascending; + child = IconButton( + icon: FaIcon( + isAscending ? FontAwesomeIcons.arrowDownAZ : FontAwesomeIcons.arrowUpZA, + ), + onPressed: () async { + setState(() => _isLoading = true); + BlocProvider.of(context) + .updateFilter( + filter: state.filter.copyWith(sortOrder: state.filter.sortOrder.toggle())) + .whenComplete(() => setState(() => _isLoading = false)); + }, + ); + } + return SizedBox( + height: Theme.of(context).iconTheme.size, + width: Theme.of(context).iconTheme.size, + child: child, + ); + }, + ); + } +} diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart new file mode 100644 index 0000000..4a818a5 --- /dev/null +++ b/lib/features/home/view/home_page.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/connectivity_cubit.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/saved_view.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/pages/documents_page.dart'; +import 'package:flutter_paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart'; +import 'package:flutter_paperless_mobile/features/home/view/widget/info_drawer.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/labels_page.dart'; +import 'package:flutter_paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; +import 'package:flutter_paperless_mobile/features/scan/view/scanner_page.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentIndex = 0; + + @override + void initState() { + super.initState(); + initializeLabelData(context); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + //Only re-initialize data if the connectivity changed from not connected to connected + listenWhen: (previous, current) => + previous != ConnectivityState.connected && current == ConnectivityState.connected, + listener: (context, state) { + initializeLabelData(context); + }, + child: Scaffold( + key: rootScaffoldKey, + bottomNavigationBar: BottomNavBar( + selectedIndex: _currentIndex, + onNavigationChanged: (index) => setState(() => _currentIndex = index), + ), + drawer: const InfoDrawer(), + body: [ + MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + ], + child: const DocumentsPage(), + ), + BlocProvider.value( + value: getIt(), + child: const ScannerPage(), + ), + const LabelsPage(), + ][_currentIndex], + ), + ); + } + + initializeLabelData(BuildContext context) { + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + } +} diff --git a/lib/features/home/view/widget/bottom_navigation_bar.dart b/lib/features/home/view/widget/bottom_navigation_bar.dart new file mode 100644 index 0000000..ed15ec8 --- /dev/null +++ b/lib/features/home/view/widget/bottom_navigation_bar.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class BottomNavBar extends StatelessWidget { + final int selectedIndex; + final void Function(int) onNavigationChanged; + + const BottomNavBar( + {Key? key, + required this.selectedIndex, + required this.onNavigationChanged}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return NavigationBar( + elevation: 4.0, + onDestinationSelected: onNavigationChanged, + selectedIndex: selectedIndex, + destinations: [ + NavigationDestination( + icon: const Icon(Icons.description), + label: S.of(context).bottomNavDocumentsPageLabel, + ), + NavigationDestination( + icon: const Icon(Icons.document_scanner), + label: S.of(context).bottomNavScannerPageLabel, + ), + NavigationDestination( + icon: const Icon(Icons.sell), + label: S.of(context).bottomNavLabelsPageLabel, + ), + ], + ); + } +} diff --git a/lib/features/home/view/widget/info_drawer.dart b/lib/features/home/view/widget/info_drawer.dart new file mode 100644 index 0000000..74ecdd3 --- /dev/null +++ b/lib/features/home/view/widget/info_drawer.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/settings_page.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:flutter_paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; + +class InfoDrawer extends StatelessWidget { + const InfoDrawer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + children: [ + DrawerHeader( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset( + "assets/logos/paperless_logo_white.png", + height: 32, + width: 32, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ).padded(const EdgeInsets.only(right: 8.0)), + Text( + S.of(context).appTitleText, + style: Theme.of(context) + .textTheme + .headline5! + .copyWith(color: Theme.of(context).colorScheme.onPrimaryContainer), + ), + ], + ), + Align( + alignment: Alignment.bottomRight, + child: BlocBuilder( + builder: (context, state) { + return Text( + state.authentication?.serverUrl.replaceAll(RegExp(r'https?://'), "") ?? "", + textAlign: TextAlign.end, + style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer), + ); + }, + ), + ), + ], + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + ), + ), + ListTile( + leading: const Icon(Icons.settings), + title: Text( + S.of(context).appDrawerSettingsLabel, + ), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: getIt(), + child: const SettingsPage(), + ), + ), + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.bug_report), + title: Text(S.of(context).appDrawerReportBugLabel), + onTap: () { + launchUrlString("https://github.com/astubenbord/paperless-mobile/issues/new"); + }, + ), + const Divider(), + AboutListTile( + icon: const Icon(Icons.info), + applicationIcon: const ImageIcon(AssetImage("assets/logos/paperless_logo_green.png")), + applicationName: "Paperless Mobile", + applicationVersion: kPackageInfo.version + "+" + kPackageInfo.buildNumber, + aboutBoxChildren: [ + Text('${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'), + ], + child: Text(S.of(context).appDrawerAboutLabel), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.logout), + title: Text(S.of(context).appDrawerLogoutLabel), + onTap: () { + // Clear all bloc data + BlocProvider.of(context).logout(); + getIt().reset(); + getIt().reset(); + getIt().reset(); + getIt().reset(); + getIt().reset(); + }, + ), + const Divider(), + ], + ), + ); + } +} diff --git a/lib/features/labels/correspondent/bloc/correspondents_cubit.dart b/lib/features/labels/correspondent/bloc/correspondents_cubit.dart new file mode 100644 index 0000000..c4a175d --- /dev/null +++ b/lib/features/labels/correspondent/bloc/correspondents_cubit.dart @@ -0,0 +1,22 @@ +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class CorrespondentCubit extends LabelCubit { + CorrespondentCubit(super.metaDataService); + + @override + Future initialize() async { + return labelRepository.getCorrespondents().then(loadFrom); + } + + @override + Future save(Correspondent item) => labelRepository.saveCorrespondent(item); + + @override + Future update(Correspondent item) => labelRepository.updateCorrespondent(item); + + @override + Future delete(Correspondent item) => labelRepository.deleteCorrespondent(item); +} diff --git a/lib/features/labels/correspondent/model/correspondent.model.dart b/lib/features/labels/correspondent/model/correspondent.model.dart new file mode 100644 index 0000000..4e382a9 --- /dev/null +++ b/lib/features/labels/correspondent/model/correspondent.model.dart @@ -0,0 +1,65 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/dart_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; + +class Correspondent extends Label { + static const lastCorrespondenceKey = 'last_correspondence'; + + late DateTime? lastCorrespondence; + + Correspondent({ + required super.id, + required super.name, + super.slug, + super.match, + super.matchingAlgorithm, + super.isInsensitive, + super.documentCount, + this.lastCorrespondence, + }); + + Correspondent.fromJson(JSON json) + : lastCorrespondence = + DateTime.tryParse(json[lastCorrespondenceKey] ?? ''), + super.fromJson(json); + + @override + String toString() { + return name; + } + + @override + void addSpecificFieldsToJson(JSON json) { + json.tryPutIfAbsent( + lastCorrespondenceKey, + () => lastCorrespondence?.toIso8601String(), + ); + } + + @override + Correspondent copyWith({ + int? id, + String? name, + String? slug, + String? match, + MatchingAlgorithm? matchingAlgorithm, + bool? isInsensitive, + int? documentCount, + DateTime? lastCorrespondence, + }) { + return Correspondent( + id: id ?? this.id, + name: name ?? this.name, + documentCount: documentCount ?? documentCount, + isInsensitive: isInsensitive ?? isInsensitive, + lastCorrespondence: lastCorrespondence ?? this.lastCorrespondence, + match: match ?? this.match, + matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm, + slug: slug ?? this.slug, + ); + } + + @override + String get queryEndpoint => 'correspondents'; +} diff --git a/lib/features/labels/correspondent/view/pages/add_correspondent_page.dart b/lib/features/labels/correspondent/view/pages/add_correspondent_page.dart new file mode 100644 index 0000000..c992d02 --- /dev/null +++ b/lib/features/labels/correspondent/view/pages/add_correspondent_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/add_label_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class AddCorrespondentPage extends StatelessWidget { + final String? initalValue; + const AddCorrespondentPage({Key? key, this.initalValue}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AddLabelPage( + addLabelStr: S.of(context).addCorrespondentPageTitle, + fromJson: Correspondent.fromJson, + cubit: BlocProvider.of(context), + initialName: initalValue, + ); + } +} diff --git a/lib/features/labels/correspondent/view/pages/edit_correspondent_page.dart b/lib/features/labels/correspondent/view/pages/edit_correspondent_page.dart new file mode 100644 index 0000000..4b24e8f --- /dev/null +++ b/lib/features/labels/correspondent/view/pages/edit_correspondent_page.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/edit_label_page.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class EditCorrespondentPage extends StatelessWidget { + final Correspondent correspondent; + const EditCorrespondentPage({super.key, required this.correspondent}); + + @override + Widget build(BuildContext context) { + return EditLabelPage( + label: correspondent, + onSubmit: BlocProvider.of(context).replace, + onDelete: (correspondent) => _onDelete(correspondent, context), + fromJson: Correspondent.fromJson, + ); + } + + Future _onDelete(Correspondent correspondent, BuildContext context) async { + try { + await BlocProvider.of(context).remove(correspondent); + final cubit = BlocProvider.of(context); + if (cubit.state.filter.correspondent.id == correspondent.id) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(correspondent: const CorrespondentQuery.unset()), + ); + } + } on ErrorMessage catch (e) { + showSnackBar(context, translateError(context, e.code)); + } finally { + Navigator.pop(context); + } + } +} diff --git a/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart b/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart new file mode 100644 index 0000000..4c89f88 --- /dev/null +++ b/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; + +class CorrespondentWidget extends StatelessWidget { + final int? correspondentId; + final void Function()? afterSelected; + final Color? textColor; + final bool isClickable; + + const CorrespondentWidget({ + Key? key, + required this.correspondentId, + this.afterSelected, + this.textColor, + this.isClickable = true, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AbsorbPointer( + absorbing: !isClickable, + child: BlocBuilder>( + builder: (context, state) { + return GestureDetector( + onTap: () => _addCorrespondentToFilter(context), + child: Text( + (state[correspondentId]?.name) ?? "-", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: textColor ?? Theme.of(context).colorScheme.primary, + ), + ), + ); + }, + ), + ); + } + + void _addCorrespondentToFilter(BuildContext context) { + final cubit = getIt(); + if (cubit.state.filter.correspondent.id == correspondentId) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(correspondent: const CorrespondentQuery.unset())); + } else { + cubit.updateFilter( + filter: cubit.state.filter.copyWith( + correspondent: CorrespondentQuery.fromId(correspondentId), + ), + ); + } + afterSelected?.call(); + } +} diff --git a/lib/features/labels/document_type/bloc/document_type_cubit.dart b/lib/features/labels/document_type/bloc/document_type_cubit.dart new file mode 100644 index 0000000..659dd31 --- /dev/null +++ b/lib/features/labels/document_type/bloc/document_type_cubit.dart @@ -0,0 +1,22 @@ +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class DocumentTypeCubit extends LabelCubit { + DocumentTypeCubit(super.metaDataService); + + @override + Future initialize() async { + return labelRepository.getDocumentTypes().then(loadFrom); + } + + @override + Future save(DocumentType item) => labelRepository.saveDocumentType(item); + + @override + Future update(DocumentType item) => labelRepository.updateDocumentType(item); + + @override + Future delete(DocumentType item) => labelRepository.deleteDocumentType(item); +} diff --git a/lib/features/labels/document_type/model/document_type.model.dart b/lib/features/labels/document_type/model/document_type.model.dart new file mode 100644 index 0000000..b3371fe --- /dev/null +++ b/lib/features/labels/document_type/model/document_type.model.dart @@ -0,0 +1,44 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; + +class DocumentType extends Label { + DocumentType({ + required super.id, + required super.name, + super.slug, + super.match, + super.matchingAlgorithm, + super.isInsensitive, + super.documentCount, + }); + + DocumentType.fromJson(JSON json) : super.fromJson(json); + + @override + void addSpecificFieldsToJson(JSON json) {} + + @override + String get queryEndpoint => 'document_types'; + + @override + DocumentType copyWith({ + int? id, + String? name, + String? match, + MatchingAlgorithm? matchingAlgorithm, + bool? isInsensitive, + int? documentCount, + String? slug, + }) { + return DocumentType( + id: id ?? this.id, + name: name ?? this.name, + match: match ?? this.match, + matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm, + isInsensitive: isInsensitive ?? this.isInsensitive, + documentCount: documentCount ?? this.documentCount, + slug: slug ?? this.slug, + ); + } +} diff --git a/lib/features/labels/document_type/model/matching_algorithm.dart b/lib/features/labels/document_type/model/matching_algorithm.dart new file mode 100644 index 0000000..ac4b486 --- /dev/null +++ b/lib/features/labels/document_type/model/matching_algorithm.dart @@ -0,0 +1,22 @@ +enum MatchingAlgorithm { + anyWord(1, "Any: Match one of the following words"), + allWords(2, "All: Match all of the following words"), + exactMatch(3, "Exact: Match the following string"), + regex(4, "Regex: Match the regular expression"), + similarWord(5, "Similar: Match a similar word"), + auto(6, "Auto: Learn automatic assignment"); + + final int value; + final String name; + + const MatchingAlgorithm(this.value, this.name); + + static MatchingAlgorithm fromInt(int? value) { + return MatchingAlgorithm.values + .where((element) => element.value == value) + .firstWhere( + (element) => true, + orElse: () => MatchingAlgorithm.anyWord, + ); + } +} diff --git a/lib/features/labels/document_type/view/pages/add_document_type_page.dart b/lib/features/labels/document_type/view/pages/add_document_type_page.dart new file mode 100644 index 0000000..6e4b220 --- /dev/null +++ b/lib/features/labels/document_type/view/pages/add_document_type_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/add_label_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class AddDocumentTypePage extends StatelessWidget { + final String? initialName; + const AddDocumentTypePage({Key? key, this.initialName}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AddLabelPage( + addLabelStr: S.of(context).addDocumentTypePageTitle, + fromJson: DocumentType.fromJson, + cubit: BlocProvider.of(context), + initialName: initialName, + ); + } +} diff --git a/lib/features/labels/document_type/view/pages/edit_document_type_page.dart b/lib/features/labels/document_type/view/pages/edit_document_type_page.dart new file mode 100644 index 0000000..f941fce --- /dev/null +++ b/lib/features/labels/document_type/view/pages/edit_document_type_page.dart @@ -0,0 +1,41 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/edit_label_page.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class EditDocumentTypePage extends StatelessWidget { + final DocumentType documentType; + const EditDocumentTypePage({super.key, required this.documentType}); + + @override + Widget build(BuildContext context) { + return EditLabelPage( + label: documentType, + onSubmit: BlocProvider.of(context).replace, + onDelete: (docType) => _onDelete(docType, context), + fromJson: DocumentType.fromJson, + ); + } + + Future _onDelete(DocumentType docType, BuildContext context) async { + try { + await BlocProvider.of(context).remove(docType); + final cubit = BlocProvider.of(context); + if (cubit.state.filter.documentType.id == docType.id) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(documentType: const DocumentTypeQuery.unset()), + ); + } + } on ErrorMessage catch (e) { + showSnackBar(context, translateError(context, e.code)); + } finally { + Navigator.pop(context); + } + } +} diff --git a/lib/features/labels/document_type/view/widgets/document_type_widget.dart b/lib/features/labels/document_type/view/widgets/document_type_widget.dart new file mode 100644 index 0000000..66acb35 --- /dev/null +++ b/lib/features/labels/document_type/view/widgets/document_type_widget.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; + +class DocumentTypeWidget extends StatelessWidget { + final int? documentTypeId; + final void Function()? afterSelected; + const DocumentTypeWidget({ + Key? key, + required this.documentTypeId, + this.afterSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _addDocumentTypeToFilter, + child: BlocBuilder>( + builder: (context, state) { + return Text( + state[documentTypeId]?.toString() ?? "-", + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(color: Theme.of(context).colorScheme.primary), + ); + }, + ), + ); + } + + void _addDocumentTypeToFilter() { + final cubit = getIt(); + if (cubit.state.filter.documentType.id == documentTypeId) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(documentType: const DocumentTypeQuery.unset())); + } else { + cubit.updateFilter( + filter: cubit.state.filter.copyWith( + documentType: DocumentTypeQuery.fromId(documentTypeId), + ), + ); + } + if (afterSelected != null) { + afterSelected?.call(); + } + } +} diff --git a/lib/features/labels/model/label.model.dart b/lib/features/labels/model/label.model.dart new file mode 100644 index 0000000..e30b4e8 --- /dev/null +++ b/lib/features/labels/model/label.model.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/dart_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; + +abstract class Label with EquatableMixin implements Comparable { + static const idKey = "id"; + static const nameKey = "name"; + static const slugKey = "slug"; + static const matchKey = "match"; + static const matchingAlgorithmKey = "matching_algorithm"; + static const isInsensitiveKey = "is_insensitive"; + static const documentCountKey = "document_count"; + + String get queryEndpoint; + + final int? id; + final String name; + final String? slug; + final String? match; + final MatchingAlgorithm? matchingAlgorithm; + final bool? isInsensitive; + final int? documentCount; + + const Label({ + required this.id, + required this.name, + this.match, + this.matchingAlgorithm, + this.isInsensitive, + this.documentCount, + this.slug, + }); + + Label.fromJson(JSON json) + : id = json[idKey], + name = json[nameKey], + slug = json[slugKey], + match = json[matchKey], + matchingAlgorithm = + MatchingAlgorithm.fromInt(json[matchingAlgorithmKey]), + isInsensitive = json[isInsensitiveKey], + documentCount = json[documentCountKey]; + + JSON toJson() { + JSON json = {}; + json.tryPutIfAbsent(idKey, () => id); + json.tryPutIfAbsent(nameKey, () => name); + json.tryPutIfAbsent(slugKey, () => slug); + json.tryPutIfAbsent(matchKey, () => match); + json.tryPutIfAbsent(matchingAlgorithmKey, () => matchingAlgorithm?.value); + json.tryPutIfAbsent(isInsensitiveKey, () => isInsensitive); + json.tryPutIfAbsent(documentCountKey, () => documentCount); + addSpecificFieldsToJson(json); + return json; + } + + void addSpecificFieldsToJson(JSON json); + + Label copyWith({ + int? id, + String? name, + String? match, + MatchingAlgorithm? matchingAlgorithm, + bool? isInsensitive, + int? documentCount, + String? slug, + }); + + @override + String toString() { + return name; + } + + @override + int compareTo(dynamic other) { + return toString().toLowerCase().compareTo(other.toString().toLowerCase()); + } + + @override + List get props => [id]; +} diff --git a/lib/features/labels/repository/label.repository.dart b/lib/features/labels/repository/label.repository.dart new file mode 100644 index 0000000..b327625 --- /dev/null +++ b/lib/features/labels/repository/label.repository.dart @@ -0,0 +1,246 @@ +import 'dart:convert'; + +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/util.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/repository/label_repository.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:http/http.dart'; +import 'package:injectable/injectable.dart'; + +@Singleton(as: LabelRepository) +class LabelRepositoryImpl implements LabelRepository { + final BaseClient httpClient; + + LabelRepositoryImpl(@Named("timeoutClient") this.httpClient); + + @override + Future getCorrespondent(int id) async { + return getSingleResult( + "/api/correspondents/$id/", + Correspondent.fromJson, + ErrorCode.correspondentLoadFailed, + ); + } + + @override + Future getTag(int id) async { + return getSingleResult("/api/tags/$id/", Tag.fromJson, ErrorCode.tagLoadFailed); + } + + @override + Future> getTags({List? ids}) async { + final results = await getCollection( + "/api/tags/?page=1&page_size=100000", + Tag.fromJson, + ErrorCode.tagLoadFailed, + minRequiredApiVersion: 2, + ); + return results.where((element) => ids?.contains(element.id) ?? true).toList(); + } + + @override + Future getDocumentType(int id) async { + return getSingleResult( + "/api/document_types/$id/", + DocumentType.fromJson, + ErrorCode.documentTypeLoadFailed, + ); + } + + @override + Future> getCorrespondents() { + return getCollection( + "/api/correspondents/?page=1&page_size=100000", + Correspondent.fromJson, + ErrorCode.correspondentLoadFailed, + ); + } + + @override + Future> getDocumentTypes() { + return getCollection( + "/api/document_types/?page=1&page_size=100000", + DocumentType.fromJson, + ErrorCode.documentTypeLoadFailed, + ); + } + + @override + Future saveCorrespondent(Correspondent correspondent) async { + final response = await httpClient.post( + Uri.parse('/api/correspondents/'), + body: json.encode(correspondent.toJson()), + headers: {"Content-Type": "application/json"}, + ); + if (response.statusCode == 201) { + return Correspondent.fromJson(json.decode(response.body)); + } + throw ErrorMessage(ErrorCode.correspondentCreateFailed, httpStatusCode: response.statusCode); + } + + @override + Future saveDocumentType(DocumentType type) async { + final response = await httpClient.post( + Uri.parse('/api/document_types/'), + body: json.encode(type.toJson()), + headers: {"Content-Type": "application/json"}, + ); + if (response.statusCode == 201) { + return DocumentType.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.documentTypeCreateFailed); + } + + @override + Future saveTag(Tag tag) async { + final body = json.encode(tag.toJson()); + final response = await httpClient.post(Uri.parse('/api/tags/'), body: body, headers: { + "Content-Type": "application/json", + "Accept": "application/json; version=2", + }); + if (response.statusCode == 201) { + return Tag.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.tagCreateFailed); + } + + @override + Future getStatistics() async { + final response = await httpClient.get(Uri.parse('/api/statistics/')); + if (response.statusCode == 200) { + return json.decode(response.body)['documents_total']; + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future deleteCorrespondent(Correspondent correspondent) async { + assert(correspondent.id != null); + final response = await httpClient.delete(Uri.parse('/api/correspondents/${correspondent.id}/')); + if (response.statusCode == 204) { + return correspondent.id!; + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future deleteDocumentType(DocumentType documentType) async { + assert(documentType.id != null); + final response = await httpClient.delete(Uri.parse('/api/document_types/${documentType.id}/')); + if (response.statusCode == 204) { + return documentType.id!; + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future deleteTag(Tag tag) async { + assert(tag.id != null); + final response = await httpClient.delete(Uri.parse('/api/tags/${tag.id}/')); + if (response.statusCode == 204) { + return tag.id!; + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future updateCorrespondent(Correspondent correspondent) async { + assert(correspondent.id != null); + final response = await httpClient.put( + Uri.parse('/api/correspondents/${correspondent.id}/'), + headers: {"Content-Type": "application/json"}, + body: json.encode(correspondent.toJson()), + ); + if (response.statusCode == 200) { + return Correspondent.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future updateDocumentType(DocumentType documentType) async { + assert(documentType.id != null); + final response = await httpClient.put( + Uri.parse('/api/document_types/${documentType.id}/'), + headers: {"Content-Type": "application/json"}, + body: json.encode(documentType.toJson()), + ); + if (response.statusCode == 200) { + return DocumentType.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future updateTag(Tag tag) async { + assert(tag.id != null); + final response = await httpClient.put( + Uri.parse('/api/tags/${tag.id}/'), + headers: { + "Accept": "application/json; version=2", + "Content-Type": "application/json", + }, + body: json.encode(tag.toJson()), + ); + if (response.statusCode == 200) { + return Tag.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future deleteStoragePath(StoragePath path) async { + assert(path.id != null); + final response = await httpClient.delete(Uri.parse('/api/storage_paths/${path.id}/')); + if (response.statusCode == 204) { + return path.id!; + } + throw const ErrorMessage(ErrorCode.unknown); + } + + @override + Future getStoragePath(int id) { + return getSingleResult("/api/storage_paths/?page=1&page_size=100000", StoragePath.fromJson, + ErrorCode.storagePathLoadFailed); + } + + @override + Future> getStoragePaths() { + return getCollection( + "/api/storage_paths/?page=1&page_size=100000", + StoragePath.fromJson, + ErrorCode.storagePathLoadFailed, + ); + } + + @override + Future saveStoragePath(StoragePath path) async { + final response = await httpClient.post( + Uri.parse('/api/storage_paths/'), + body: json.encode(path.toJson()), + headers: {"Content-Type": "application/json"}, + ); + if (response.statusCode == 201) { + return StoragePath.fromJson(json.decode(response.body)); + } + throw ErrorMessage(ErrorCode.storagePathCreateFailed, httpStatusCode: response.statusCode); + } + + @override + Future updateStoragePath(StoragePath path) async { + assert(path.id != null); + final response = await httpClient.put( + Uri.parse('/api/storage_paths/${path.id}/'), + headers: {"Content-Type": "application/json"}, + body: json.encode(path.toJson()), + ); + if (response.statusCode == 200) { + return StoragePath.fromJson(json.decode(response.body)); + } + throw const ErrorMessage(ErrorCode.unknown); + } +} diff --git a/lib/features/labels/repository/label_repository.dart b/lib/features/labels/repository/label_repository.dart new file mode 100644 index 0000000..3034683 --- /dev/null +++ b/lib/features/labels/repository/label_repository.dart @@ -0,0 +1,32 @@ +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; + +abstract class LabelRepository { + Future getCorrespondent(int id); + Future> getCorrespondents(); + Future saveCorrespondent(Correspondent correspondent); + Future updateCorrespondent(Correspondent correspondent); + Future deleteCorrespondent(Correspondent correspondent); + + Future getTag(int id); + Future> getTags({List? ids}); + Future saveTag(Tag tag); + Future updateTag(Tag tag); + Future deleteTag(Tag tag); + + Future getDocumentType(int id); + Future> getDocumentTypes(); + Future saveDocumentType(DocumentType type); + Future updateDocumentType(DocumentType documentType); + Future deleteDocumentType(DocumentType documentType); + + Future getStoragePath(int id); + Future> getStoragePaths(); + Future saveStoragePath(StoragePath path); + Future updateStoragePath(StoragePath path); + Future deleteStoragePath(StoragePath path); + + Future getStatistics(); +} diff --git a/lib/features/labels/storage_path/bloc/storage_path_cubit.dart b/lib/features/labels/storage_path/bloc/storage_path_cubit.dart new file mode 100644 index 0000000..e199663 --- /dev/null +++ b/lib/features/labels/storage_path/bloc/storage_path_cubit.dart @@ -0,0 +1,23 @@ +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class StoragePathCubit extends LabelCubit { + StoragePathCubit(super.metaDataService); + + @override + Future initialize() async { + return labelRepository.getStoragePaths().then(loadFrom); + } + + @override + Future save(StoragePath item) => labelRepository.saveStoragePath(item); + + @override + Future update(StoragePath item) => labelRepository.updateStoragePath(item); + + @override + Future delete(StoragePath item) => labelRepository.deleteStoragePath(item); +} diff --git a/lib/features/labels/storage_path/model/storage_path.model.dart b/lib/features/labels/storage_path/model/storage_path.model.dart new file mode 100644 index 0000000..22ee605 --- /dev/null +++ b/lib/features/labels/storage_path/model/storage_path.model.dart @@ -0,0 +1,64 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/dart_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; + +class StoragePath extends Label { + static const pathKey = 'path'; + + late String? path; + + StoragePath({ + required super.id, + required super.name, + super.slug, + super.match, + super.matchingAlgorithm, + super.isInsensitive, + super.documentCount, + required this.path, + }); + + StoragePath.fromJson(JSON json) + : path = json[pathKey], + super.fromJson(json); + + @override + String toString() { + return name; + } + + @override + void addSpecificFieldsToJson(JSON json) { + json.tryPutIfAbsent( + pathKey, + () => path, + ); + } + + @override + StoragePath copyWith({ + int? id, + String? name, + String? slug, + String? match, + MatchingAlgorithm? matchingAlgorithm, + bool? isInsensitive, + int? documentCount, + String? path, + }) { + return StoragePath( + id: id ?? this.id, + name: name ?? this.name, + documentCount: documentCount ?? documentCount, + isInsensitive: isInsensitive ?? isInsensitive, + path: path ?? this.path, + match: match ?? this.match, + matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm, + slug: slug ?? this.slug, + ); + } + + @override + String get queryEndpoint => 'storage_paths'; +} diff --git a/lib/features/labels/storage_path/view/pages/add_storage_path_page.dart b/lib/features/labels/storage_path/view/pages/add_storage_path_page.dart new file mode 100644 index 0000000..9274980 --- /dev/null +++ b/lib/features/labels/storage_path/view/pages/add_storage_path_page.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/add_label_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class AddStoragePathPage extends StatelessWidget { + final String? initalValue; + const AddStoragePathPage({Key? key, this.initalValue}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AddLabelPage( + addLabelStr: S.of(context).addStoragePathPageTitle, + fromJson: StoragePath.fromJson, + cubit: BlocProvider.of(context), + initialName: initalValue, + additionalFields: const [ + StoragePathAutofillFormBuilderField(name: StoragePath.pathKey), + SizedBox(height: 120.0), + ], + ); + } +} diff --git a/lib/features/labels/storage_path/view/pages/edit_storage_path_page.dart b/lib/features/labels/storage_path/view/pages/edit_storage_path_page.dart new file mode 100644 index 0000000..0424d12 --- /dev/null +++ b/lib/features/labels/storage_path/view/pages/edit_storage_path_page.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/edit_label_page.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class EditStoragePathPage extends StatelessWidget { + final StoragePath storagePath; + const EditStoragePathPage({super.key, required this.storagePath}); + + @override + Widget build(BuildContext context) { + return EditLabelPage( + label: storagePath, + onSubmit: BlocProvider.of(context).replace, + onDelete: (correspondent) => _onDelete(correspondent, context), + fromJson: StoragePath.fromJson, + additionalFields: [ + StoragePathAutofillFormBuilderField( + name: StoragePath.pathKey, + initialValue: storagePath.path, + ), + const SizedBox(height: 120.0), + ], + ); + } + + Future _onDelete(StoragePath path, BuildContext context) async { + try { + await BlocProvider.of(context).remove(path); + final cubit = BlocProvider.of(context); + if (cubit.state.filter.storagePath.id == path.id) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(storagePath: const StoragePathQuery.unset())); + } + } on ErrorMessage catch (e) { + showSnackBar(context, translateError(context, e.code)); + } finally { + Navigator.pop(context); + } + } +} diff --git a/lib/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart b/lib/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart new file mode 100644 index 0000000..ec3e8ba --- /dev/null +++ b/lib/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class StoragePathAutofillFormBuilderField extends StatefulWidget { + final String name; + final String? initialValue; + const StoragePathAutofillFormBuilderField({ + super.key, + required this.name, + this.initialValue, + }); + + @override + State createState() => + _StoragePathAutofillFormBuilderFieldState(); +} + +class _StoragePathAutofillFormBuilderFieldState extends State { + late final TextEditingController _textEditingController; + + late String _exampleOutput; + late bool _showClearIcon; + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController.fromValue( + TextEditingValue(text: widget.initialValue ?? ''), + )..addListener(() { + setState(() { + _showClearIcon = _textEditingController.text.isNotEmpty; + }); + }); + _exampleOutput = _buildExampleOutput(widget.initialValue ?? ''); + _showClearIcon = widget.initialValue?.isNotEmpty ?? false; + } + + @override + Widget build(BuildContext context) { + return FormBuilderField( + name: widget.name, + initialValue: widget.initialValue ?? '', + builder: (field) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _textEditingController, + validator: FormBuilderValidators.required(), //TODO: INTL + decoration: InputDecoration( + label: Text(S.of(context).documentStoragePathPropertyLabel), + suffixIcon: _showClearIcon + ? IconButton( + icon: const Icon(Icons.clear), + onPressed: () => _resetfield(field), + ) + : null), + onChanged: field.didChange, + ), + const SizedBox(height: 8.0), + Text( + "Select to autofill path variable", + style: Theme.of(context).textTheme.caption, + ), + Wrap( + alignment: WrapAlignment.start, + spacing: 8.0, + children: [ + InputChip( + label: Text(S.of(context).documentArchiveSerialNumberPropertyLongLabel), + onPressed: () => _addParameterToInput("{asn}", field), + ), + InputChip( + label: Text(S.of(context).documentCorrespondentPropertyLabel), + onPressed: () => _addParameterToInput("{correspondent}", field), + ), + InputChip( + label: Text(S.of(context).documentDocumentTypePropertyLabel), + onPressed: () => _addParameterToInput("{document_type}", field), + ), + InputChip( + label: Text(S.of(context).documentTagsPropertyLabel), + onPressed: () => _addParameterToInput("{tag_list}", field), + ), + InputChip( + label: Text(S.of(context).documentTitlePropertyLabel), + onPressed: () => _addParameterToInput("{title}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel), + onPressed: () => _addParameterToInput("{created}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterYearLabel})"), + onPressed: () => _addParameterToInput("{created_year}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterMonthLabel})"), + onPressed: () => _addParameterToInput("{created_month}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterDayLabel})"), + onPressed: () => _addParameterToInput("{created_day}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel), + onPressed: () => _addParameterToInput("{added}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterYearLabel})"), + onPressed: () => _addParameterToInput("{added_year}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterMonthLabel})"), + onPressed: () => _addParameterToInput("{added_month}", field), + ), + InputChip( + label: Text(S.of(context).documentCreatedPropertyLabel + + " (${S.of(context).storagePathParameterDayLabel})"), + onPressed: () => _addParameterToInput("{added_day}", field), + ), + ], + ) + ], + ), + ); + } + + void _addParameterToInput(String param, FormFieldState field) { + final text = (field.value ?? "") + param; + field.didChange(text); + _textEditingController.text = text; + } + + String _buildExampleOutput(String input) { + return input + .replaceAll("{asn}", "1234") + .replaceAll("{correspondent}", "My Bank") + .replaceAll("{document_type}", "Invoice") + .replaceAll("{tag_list}", "TODO,University,Work") + .replaceAll("{created}", "2020-02-10") + .replaceAll("{created_year}", "2020") + .replaceAll("{created_month}", "02") + .replaceAll("{created_day}", "10") + .replaceAll("{added}", "2029-12-24") + .replaceAll("{added_year}", "2029") + .replaceAll("{added_month}", "12") + .replaceAll("{added_day}", "24"); + } + + void _resetfield(FormFieldState field) { + field.didChange(""); + _textEditingController.clear(); + } +} diff --git a/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart b/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart new file mode 100644 index 0000000..8ebdec7 --- /dev/null +++ b/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; + +class StoragePathWidget extends StatelessWidget { + final int? pathId; + final void Function()? afterSelected; + final Color? textColor; + final bool isClickable; + + const StoragePathWidget({ + Key? key, + required this.pathId, + this.afterSelected, + this.textColor, + this.isClickable = true, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AbsorbPointer( + absorbing: !isClickable, + child: BlocBuilder>( + builder: (context, state) { + return GestureDetector( + onTap: () => _addStoragePathToFilter(context), + child: Text( + (state[pathId]?.name) ?? "-", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyText2?.copyWith( + color: textColor ?? Theme.of(context).colorScheme.primary, + ), + ), + ); + }, + ), + ); + } + + void _addStoragePathToFilter(BuildContext context) { + final cubit = getIt(); + if (cubit.state.filter.correspondent.id == pathId) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith(storagePath: const StoragePathQuery.unset())); + } else { + cubit.updateFilter( + filter: cubit.state.filter.copyWith( + storagePath: StoragePathQuery.fromId(pathId), + ), + ); + } + afterSelected?.call(); + } +} diff --git a/lib/features/labels/tags/bloc/tags_cubit.dart b/lib/features/labels/tags/bloc/tags_cubit.dart new file mode 100644 index 0000000..d9588c7 --- /dev/null +++ b/lib/features/labels/tags/bloc/tags_cubit.dart @@ -0,0 +1,22 @@ +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class TagCubit extends LabelCubit { + TagCubit(super.metaDataService); + + @override + Future initialize() async { + return labelRepository.getTags().then(loadFrom); + } + + @override + Future save(Tag item) => labelRepository.saveTag(item); + + @override + Future update(Tag item) => labelRepository.updateTag(item); + + @override + Future delete(Tag item) => labelRepository.deleteTag(item); +} diff --git a/lib/features/labels/tags/model/tag.model.dart b/lib/features/labels/tags/model/tag.model.dart new file mode 100644 index 0000000..4447706 --- /dev/null +++ b/lib/features/labels/tags/model/tag.model.dart @@ -0,0 +1,96 @@ +import 'dart:developer'; +import 'dart:ui'; + +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/dart_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; + +class Tag extends Label { + static const colorKey = 'color'; + static const isInboxTagKey = 'is_inbox_tag'; + static const textColorKey = 'text_color'; + + final Color? color; + final Color? textColor; + final bool? isInboxTag; + + Tag({ + required super.id, + required super.name, + super.documentCount, + super.isInsensitive, + super.match, + super.matchingAlgorithm, + super.slug, + this.color, + this.textColor, + this.isInboxTag, + }); + + 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), + super.fromJson(json); + + @override + String toString() { + return name; + } + + @override + void addSpecificFieldsToJson(JSON json) { + json.tryPutIfAbsent(colorKey, () => _toHex(color)); + json.tryPutIfAbsent(isInboxTagKey, () => isInboxTag); + } + + @override + Tag copyWith({ + int? id, + String? name, + String? match, + MatchingAlgorithm? matchingAlgorithm, + bool? isInsensitive, + int? documentCount, + String? slug, + Color? color, + Color? textColor, + bool? isInboxTag, + }) { + return Tag( + id: id ?? this.id, + name: name ?? this.name, + match: match ?? this.match, + matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm, + isInsensitive: isInsensitive ?? this.isInsensitive, + documentCount: documentCount ?? this.documentCount, + slug: slug ?? this.slug, + color: color ?? this.color, + textColor: textColor ?? this.textColor, + isInboxTag: isInboxTag ?? this.isInboxTag, + ); + } + + @override + String get queryEndpoint => 'tags'; +} + +/// +/// Taken from [FormBuilderColorPicker]. +/// +String? _toHex(Color? color) { + if (color == null) { + return null; + } + String val = '#${(color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toLowerCase()}'; + log("Color in Tag#_toHex is $val"); + return val; +} + +int? _colorStringToInt(String? color) { + if (color == null) return null; + return int.tryParse(color.replaceAll("#", "ff"), radix: 16); +} diff --git a/lib/features/labels/tags/view/pages/add_tag_page.dart b/lib/features/labels/tags/view/pages/add_tag_page.dart new file mode 100644 index 0000000..0e800db --- /dev/null +++ b/lib/features/labels/tags/view/pages/add_tag_page.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/add_label_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; + +class AddTagPage extends StatelessWidget { + const AddTagPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AddLabelPage( + addLabelStr: S.of(context).addTagPageTitle, + fromJson: Tag.fromJson, + cubit: BlocProvider.of(context), + additionalFields: [ + FormBuilderColorPickerField( + name: Tag.colorKey, + valueTransformer: (color) => "#${color?.value.toRadixString(16)}", + decoration: InputDecoration( + label: Text(S.of(context).tagColorPropertyLabel), + ), + colorPickerType: ColorPickerType.materialPicker, + ), + FormBuilderCheckbox( + name: Tag.isInboxTagKey, + title: Text(S.of(context).tagInboxTagPropertyLabel), + ), + ], + ); + } +} diff --git a/lib/features/labels/tags/view/pages/edit_tag_page.dart b/lib/features/labels/tags/view/pages/edit_tag_page.dart new file mode 100644 index 0000000..3ff592f --- /dev/null +++ b/lib/features/labels/tags/view/pages/edit_tag_page.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/pages/edit_label_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; + +class EditTagPage extends StatelessWidget { + final Tag tag; + + const EditTagPage({super.key, required this.tag}); + + @override + Widget build(BuildContext context) { + return EditLabelPage( + label: tag, + onSubmit: BlocProvider.of(context).replace, + onDelete: (tag) => _onDelete(tag, context), + fromJson: Tag.fromJson, + additionalFields: [ + FormBuilderColorPickerField( + initialValue: tag.color, + name: Tag.colorKey, + decoration: InputDecoration( + label: Text(S.of(context).tagColorPropertyLabel), + ), + colorPickerType: ColorPickerType.blockPicker, + ), + FormBuilderCheckbox( + initialValue: tag.isInboxTag, + name: Tag.isInboxTagKey, + title: Text(S.of(context).tagInboxTagPropertyLabel), + ), + ], + ); + } + + Future _onDelete(Tag tag, BuildContext context) async { + try { + await BlocProvider.of(context).remove(tag); + final cubit = BlocProvider.of(context); + final currentFilter = cubit.state.filter; + late DocumentFilter updatedFilter = currentFilter; + if (currentFilter.tags.ids.contains(tag.id)) { + updatedFilter = currentFilter.copyWith( + tags: TagsQuery.fromIds( + currentFilter.tags.ids.where((tagId) => tagId != tag.id).toList())); + } + cubit.updateFilter(filter: updatedFilter); + } on ErrorMessage catch (error) { + showError(context, error); + } + } +} diff --git a/lib/features/labels/tags/view/widgets/form_builder_tag_selection_field.dart b/lib/features/labels/tags/view/widgets/form_builder_tag_selection_field.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/features/labels/tags/view/widgets/form_builder_tag_selection_field.dart @@ -0,0 +1 @@ + diff --git a/lib/features/labels/tags/view/widgets/tag_widget.dart b/lib/features/labels/tags/view/widgets/tag_widget.dart new file mode 100644 index 0000000..bb07c87 --- /dev/null +++ b/lib/features/labels/tags/view/widgets/tag_widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; + +class TagWidget extends StatelessWidget { + final Tag tag; + final void Function()? afterTagTapped; + const TagWidget({super.key, required this.tag, required this.afterTagTapped}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 4.0), + child: BlocBuilder( + builder: (context, state) { + return FilterChip( + selected: state.filter.tags.ids.contains(tag.id), + selectedColor: tag.color, + onSelected: (_) => _addTagToFilter(context), + visualDensity: const VisualDensity(vertical: -2), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + label: Text( + tag.name, + style: TextStyle(color: tag.textColor), + ), + backgroundColor: tag.color, + side: BorderSide.none, + ); + }, + ), + ); + } + + void _addTagToFilter(BuildContext context) { + final cubit = BlocProvider.of(context); + if (cubit.state.filter.tags.ids.contains(tag.id)) { + cubit.updateFilter( + filter: cubit.state.filter.copyWith( + tags: TagsQuery.fromIds(cubit.state.filter.tags.ids.where((id) => id != tag.id).toList()), + ), + ); + } else { + cubit.updateFilter( + filter: cubit.state.filter + .copyWith(tags: TagsQuery.fromIds([...cubit.state.filter.tags.ids, tag.id!])), + ); + } + if (afterTagTapped != null) { + afterTagTapped!(); + } + } +} diff --git a/lib/features/labels/tags/view/widgets/tags_form_field.dart b/lib/features/labels/tags/view/widgets/tags_form_field.dart new file mode 100644 index 0000000..99e61c0 --- /dev/null +++ b/lib/features/labels/tags/view/widgets/tags_form_field.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class TagFormField extends StatefulWidget { + final TagsQuery? initialValue; + final String name; + + const TagFormField({ + super.key, + required this.name, + this.initialValue, + }); + + @override + State createState() => _TagFormFieldState(); +} + +class _TagFormFieldState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder>( + builder: (context, tagState) { + return FormBuilderField( + builder: (field) { + final sortedTags = tagState.values.toList() + ..sort( + (a, b) => a.name.compareTo(b.name), + ); + //TODO: this is either not correctly resetting on filter reset or (when adding UniqueKey to FormField or ChipsInput) unmounts widget. + // return ChipsInput( + // chipBuilder: (context, state, data) => Chip( + // onDeleted: () => state.deleteChip(data), + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + // backgroundColor: Color(tagState[data]!.color ?? Colors.white.value), + // label: Text( + // tagState[data]!.name, + // style: TextStyle(color: Color(tagState[data]!.textColor ?? Colors.black.value)), + // ), + // ), + // suggestionBuilder: (context, state, data) => ListTile( + // title: Text(tagState[data]!.name), + // textColor: Color(tagState[data]!.textColor!), + // tileColor: Color(tagState[data]!.color!), + // onTap: () => state.selectSuggestion(data), + // ), + // findSuggestions: (query) => tagState.values + // .where((element) => element.name.toLowerCase().startsWith(query.toLowerCase())) + // .map((e) => e.id!) + // .toList(), + // onChanged: (tags) => field.didChange(tags), + // initialValue: field.value!, + // ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).documentTagsPropertyLabel, + ), + Wrap( + children: sortedTags + .map((tag) => FilterChip( + label: Text( + tag.name, + style: TextStyle( + color: tag.textColor, + ), + ), + selectedColor: tag.color, + selected: field.value?.ids.contains(tag.id) ?? false, + onSelected: (isSelected) { + List ids = [...field.value?.ids ?? []]; + if (isSelected) { + ids.add(tag.id!); + } else { + ids.remove(tag.id); + } + field.didChange(TagsQuery.fromIds(ids)); + }, + backgroundColor: tag.color, + )) + .toList() + .padded(const EdgeInsets.only(right: 4.0)), + ), + ], + ); + }, + initialValue: widget.initialValue ?? const TagsQuery.unset(), + name: widget.name, + ); + }, + ); + } +} diff --git a/lib/features/labels/tags/view/widgets/tags_widget.dart b/lib/features/labels/tags/view/widgets/tags_widget.dart new file mode 100644 index 0000000..eb2a563 --- /dev/null +++ b/lib/features/labels/tags/view/widgets/tags_widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; + +class TagsWidget extends StatefulWidget { + final List tagIds; + final bool isMultiLine; + final void Function()? afterTagTapped; + + const TagsWidget({ + Key? key, + required this.tagIds, + this.afterTagTapped, + this.isMultiLine = true, + }) : super(key: key); + + @override + State createState() => _TagsWidgetState(); +} + +class _TagsWidgetState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder>( + builder: (context, state) { + final children = widget.tagIds + .where((id) => state.containsKey(id)) + .map( + (id) => TagWidget( + tag: state[id]!, + afterTagTapped: widget.afterTagTapped, + ), + ) + .toList(); + if (widget.isMultiLine) { + return Wrap( + runAlignment: WrapAlignment.start, + children: children, + runSpacing: 8, + spacing: 4, + ); + } else { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: children, + ), + ); + } + }, + ); + } +} diff --git a/lib/features/labels/view/pages/add_label_page.dart b/lib/features/labels/view/pages/add_label_page.dart new file mode 100644 index 0000000..ed6b2dd --- /dev/null +++ b/lib/features/labels/view/pages/add_label_page.dart @@ -0,0 +1,114 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class AddLabelPage extends StatefulWidget { + final String? initialName; + final String addLabelStr; + final T Function(Map json) fromJson; + final LabelCubit cubit; + final List additionalFields; + + const AddLabelPage({ + Key? key, + this.initialName, + required this.addLabelStr, + required this.fromJson, + required this.cubit, + this.additionalFields = const [], + }) : super(key: key); + + @override + State createState() => _AddLabelPageState(); +} + +class _AddLabelPageState extends State> { + final _formKey = GlobalKey(); + Map _errors = {}; + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text(widget.addLabelStr), + ), + floatingActionButton: FloatingActionButton.extended( + icon: const Icon(Icons.add), + label: Text(S.of(context).genericActionCreateLabel), + onPressed: _onSubmit, + ), + body: FormBuilder( + key: _formKey, + child: ListView( + children: [ + FormBuilderTextField( + autovalidateMode: AutovalidateMode.onUserInteraction, + name: Label.nameKey, + decoration: InputDecoration( + labelText: S.of(context).labelNamePropertyLabel, + errorText: _errors[Label.nameKey], + ), + initialValue: widget.initialName, + validator: FormBuilderValidators.required(), + onChanged: (val) => setState(() => _errors = {}), + ), + FormBuilderTextField( + autovalidateMode: AutovalidateMode.onUserInteraction, + name: Label.matchKey, + decoration: InputDecoration( + labelText: S.of(context).labelMatchPropertyLabel, + ), + onChanged: (val) => setState(() => _errors = {}), + ), + FormBuilderDropdown( + name: Label.matchingAlgorithmKey, + initialValue: MatchingAlgorithm.anyWord.value, + decoration: InputDecoration( + labelText: S.of(context).labelMatchingAlgorithmPropertyLabel, + errorText: _errors[Label.matchingAlgorithmKey], + ), + onChanged: (val) => setState(() => _errors = {}), + items: MatchingAlgorithm.values + .map((algo) => DropdownMenuItem( + child: Text(algo.name), //TODO: INTL + value: algo.value)) + .toList(), + ), + FormBuilderCheckbox( + name: Label.isInsensitiveKey, + initialValue: true, + title: Text(S.of(context).labelIsInsensivitePropertyLabel), + ), + ...widget.additionalFields, + ].padded(), + ), + ), + ); + } + + void _onSubmit() async { + log("IsValid? ${_formKey.currentState?.isValid}"); + if (_formKey.currentState?.saveAndValidate() ?? false) { + try { + final label = await widget.cubit.add(widget.fromJson(_formKey.currentState!.value)); + Navigator.pop(context, label); + } on ErrorMessage catch (e) { + showSnackBar(context, translateError(context, e.code)); + } on Map catch (json) { + setState(() => _errors = json); + } + } + } +} diff --git a/lib/features/labels/view/pages/edit_label_page.dart b/lib/features/labels/view/pages/edit_label_page.dart new file mode 100644 index 0000000..30a3fa8 --- /dev/null +++ b/lib/features/labels/view/pages/edit_label_page.dart @@ -0,0 +1,124 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/matching_algorithm.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class EditLabelPage extends StatefulWidget { + final T label; + final Future Function(T) onSubmit; + final Future Function(T) onDelete; + final T Function(JSON) fromJson; + final List additionalFields; + + const EditLabelPage({ + Key? key, + required this.label, + required this.fromJson, + required this.onSubmit, + required this.onDelete, + this.additionalFields = const [], + }) : super(key: key); + + @override + State createState() => _EditLabelPageState(); +} + +class _EditLabelPageState extends State> { + final _formKey = GlobalKey(); + + Map _errors = {}; + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text(S.of(context).genericActionEditLabel), + actions: [ + IconButton( + onPressed: () => widget.onDelete(widget.label), + icon: const Icon(Icons.delete), + ), + ], + ), + floatingActionButton: FloatingActionButton.extended( + icon: const Icon(Icons.add), + label: Text(S.of(context).genericActionUpdateLabel), + onPressed: _onSubmit, + ), + body: FormBuilder( + key: _formKey, + child: ListView( + children: [ + FormBuilderTextField( + name: Label.nameKey, + decoration: InputDecoration( + labelText: S.of(context).labelNamePropertyLabel, + errorText: _errors[Label.nameKey], + ), + validator: FormBuilderValidators.required(), + initialValue: widget.label.name, + onChanged: (val) => setState(() => _errors = {}), + ), + FormBuilderTextField( + name: Label.matchKey, + decoration: InputDecoration( + labelText: S.of(context).labelMatchPropertyLabel, + errorText: _errors[Label.matchKey], + ), + initialValue: widget.label.match, + onChanged: (val) => setState(() => _errors = {}), + ), + FormBuilderDropdown( + name: Label.matchingAlgorithmKey, + initialValue: + widget.label.matchingAlgorithm?.value ?? MatchingAlgorithm.allWords.value, + decoration: InputDecoration( + labelText: S.of(context).labelMatchingAlgorithmPropertyLabel, + errorText: _errors[Label.matchingAlgorithmKey], + ), + onChanged: (val) => setState(() => _errors = {}), + items: MatchingAlgorithm.values + .map( + (algo) => DropdownMenuItem( + child: Text(algo.name), //TODO: INTL + value: algo.value, + ), + ) + .toList(), + ), + FormBuilderCheckbox( + name: Label.isInsensitiveKey, + initialValue: widget.label.isInsensitive, + title: Text(S.of(context).labelIsInsensivitePropertyLabel), + ), + ...widget.additionalFields, + ].padded(), + ), + ), + ); + } + + void _onSubmit() async { + if (_formKey.currentState?.saveAndValidate() ?? false) { + try { + final mergedJson = {...widget.label.toJson(), ..._formKey.currentState!.value}; + await widget.onSubmit(widget.fromJson(mergedJson)); + Navigator.pop(context); + } on ErrorMessage catch (e) { + showSnackBar(context, translateError(context, e.code)); + } on Map catch (errorMessages) { + setState(() => _errors = errorMessages); + } + } + } +} diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart new file mode 100644 index 0000000..7ddade5 --- /dev/null +++ b/lib/features/labels/view/pages/labels_page.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/pages/edit_correspondent_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/home/view/widget/info_drawer.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/pages/edit_document_type_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/storage_path/view/pages/edit_storage_path_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/pages/edit_tag_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_item.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_tab_view.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class LabelsPage extends StatefulWidget { + const LabelsPage({Key? key}) : super(key: key); + + @override + State createState() => _LabelsPageState(); +} + +class _LabelsPageState extends State with SingleTickerProviderStateMixin { + late final TabController _tabController; + int _currentIndex = 0; + + @override + void initState() { + super.initState(); + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + BlocProvider.of(context).initialize(); + + _tabController = TabController(length: 4, vsync: this) + ..addListener(() => setState(() => _currentIndex = _tabController.index)); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 3, + child: Scaffold( + drawer: const InfoDrawer(), + appBar: AppBar( + title: Text( + [ + S.of(context).labelsPageCorrespondentsTitleText, + S.of(context).labelsPageDocumentTypesTitleText, + S.of(context).labelsPageTagsTitleText, + S.of(context).labelsPageStoragePathTitleText + ][_currentIndex], + ), + actions: [ + IconButton( + onPressed: _onAddPressed, + icon: const Icon(Icons.add), + ) + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight), + child: ColoredBox( + color: Theme.of(context).bottomAppBarColor, + child: TabBar( + indicatorColor: Theme.of(context).colorScheme.primary, + controller: _tabController, + tabs: [ + Tab( + icon: Icon( + Icons.person_outline, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + Tab( + icon: Icon( + Icons.description_outlined, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + Tab( + icon: Icon( + Icons.label_outline, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + Tab( + icon: Icon( + Icons.folder_open, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ) + ], + ), + ), + ), + ), + body: TabBarView( + controller: _tabController, + children: [ + LabelTabView( + cubit: BlocProvider.of(context), + filterBuilder: (label) => DocumentFilter( + correspondent: CorrespondentQuery.fromId(label.id), + pageSize: label.documentCount ?? 0, + ), + onOpenEditPage: _openEditCorrespondentPage, + ), + LabelTabView( + cubit: BlocProvider.of(context), + filterBuilder: (label) => DocumentFilter( + documentType: DocumentTypeQuery.fromId(label.id), + pageSize: label.documentCount ?? 0, + ), + onOpenEditPage: _openEditDocumentTypePage, + ), + LabelTabView( + cubit: BlocProvider.of(context), + filterBuilder: (label) => DocumentFilter( + tags: TagsQuery.fromIds([label.id!]), + pageSize: label.documentCount ?? 0, + ), + onOpenEditPage: _openEditTagPage, + leadingBuilder: (t) => CircleAvatar(backgroundColor: t.color), + ), + LabelTabView( + cubit: BlocProvider.of(context), + onOpenEditPage: _openEditStoragePathPage, + filterBuilder: (label) => DocumentFilter( + storagePath: StoragePathQuery.fromId(label.id), + pageSize: label.documentCount ?? 0, + ), + contentBuilder: (path) => Text(path.path ?? ""), + ), + ], + ), + ), + ); + } + + void _openEditCorrespondentPage(Correspondent correspondent) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: BlocProvider.of(context)), + ], + child: EditCorrespondentPage(correspondent: correspondent), + ), + ), + ); + } + + void _openEditDocumentTypePage(DocumentType docType) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: BlocProvider.of(context)), + ], + child: EditDocumentTypePage(documentType: docType), + ), + ), + ); + } + + void _openEditTagPage(Tag tag) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: BlocProvider.of(context)), + ], + child: EditTagPage(tag: tag), + ), + ), + ); + } + + void _openEditStoragePathPage(StoragePath path) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: BlocProvider.of(context)), + ], + child: EditStoragePathPage(storagePath: path), + ), + ), + ); + } + + void _onAddPressed() { + Navigator.push(context, MaterialPageRoute( + builder: (context) { + late final Widget page; + switch (_currentIndex) { + case 0: + page = const AddCorrespondentPage(); + break; + case 1: + page = const AddDocumentTypePage(); + break; + case 2: + page = const AddTagPage(); + break; + case 3: + page = const AddStoragePathPage(); + } + return LabelBlocProvider(child: page); + }, + )); + } +} diff --git a/lib/features/labels/view/widgets/label_form_field.dart b/lib/features/labels/view/widgets/label_form_field.dart new file mode 100644 index 0000000..0b6095b --- /dev/null +++ b/lib/features/labels/view/widgets/label_form_field.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_extra_fields/form_builder_extra_fields.dart'; + +/// +/// Form field allowing to select labels (i.e. correspondent, documentType) +/// [T] is the label (model) type, [R] is the return type. +/// +class LabelFormField extends StatefulWidget { + final Widget prefixIcon; + final Map state; + final FormBuilderState? formBuilderState; + final IdQueryParameter? initialValue; + final String name; + final String label; + final FormFieldValidator? validator; + final Widget Function(String)? labelCreationWidgetBuilder; + final R Function() queryParameterNotAssignedBuilder; + final R Function(int? id) queryParameterIdBuilder; + final bool notAssignedSelectable; + + const LabelFormField({ + Key? key, + required this.name, + required this.state, + this.validator, + this.initialValue, + required this.label, + this.labelCreationWidgetBuilder, + required this.queryParameterNotAssignedBuilder, + required this.queryParameterIdBuilder, + required this.formBuilderState, + required this.prefixIcon, + this.notAssignedSelectable = true, + }) : super(key: key); + + @override + State> createState() => _LabelFormFieldState(); +} + +class _LabelFormFieldState + extends State> { + bool _showCreationSuffixIcon = false; + late bool _showClearSuffixIcon; + + late final TextEditingController _textEditingController; + + @override + void initState() { + super.initState(); + _showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id); + _textEditingController = + TextEditingController(text: widget.state[widget.initialValue?.id]?.name ?? '') + ..addListener(() { + setState(() { + _showCreationSuffixIcon = widget.state.values + .where((item) => item.name.toLowerCase().startsWith( + _textEditingController.text.toLowerCase(), + )) + .isEmpty; + }); + setState(() => _showClearSuffixIcon = _textEditingController.text.isNotEmpty); + }); + } + + @override + Widget build(BuildContext context) { + return FormBuilderTypeAhead( + initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null), + name: widget.name, + itemBuilder: (context, suggestion) => ListTile( + title: Text(widget.state[suggestion.id]?.name ?? S.of(context).labelNotAssignedText), + ), + suggestionsCallback: (pattern) { + final List suggestions = widget.state.keys + .where((item) => + widget.state[item]!.name.toLowerCase().startsWith(pattern.toLowerCase()) || + pattern.isEmpty) + .map((id) => widget.queryParameterIdBuilder(id)) + .toList(); + if (widget.notAssignedSelectable) { + suggestions.insert(0, widget.queryParameterNotAssignedBuilder()); + } + return suggestions; + }, + onChanged: (value) { + setState(() => _showClearSuffixIcon = value?.isSet ?? false); + }, + controller: _textEditingController, + decoration: InputDecoration( + prefixIcon: widget.prefixIcon, + label: Text(widget.label), + hintText: _getLocalizedHint(context), + suffixIcon: _buildSuffixIcon(context), + ), + selectionToTextTransformer: (suggestion) { + if (suggestion == widget.queryParameterNotAssignedBuilder()) { + return S.of(context).labelNotAssignedText; + } + return widget.state[suggestion.id]?.name ?? ""; + }, + direction: AxisDirection.up, + onSuggestionSelected: (suggestion) => + widget.formBuilderState?.fields[widget.name]?.didChange(suggestion as R), + ); + } + + Widget? _buildSuffixIcon(BuildContext context) { + if (_showCreationSuffixIcon && widget.labelCreationWidgetBuilder != null) { + return IconButton( + onPressed: () => Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => + widget.labelCreationWidgetBuilder!(_textEditingController.text))) + .then((value) { + if (value != null) { + // If new label has been created, set form field value and text of this form field and unfocus keyboard (we assume user is done). + widget.formBuilderState?.fields[widget.name] + ?.didChange(widget.queryParameterIdBuilder(value.id)); + _textEditingController.text = value.name; + FocusScope.of(context).unfocus(); + } else { + _reset(); + } + }), + icon: const Icon( + Icons.new_label, + ), + ); + } + if (_showClearSuffixIcon) { + return IconButton( + icon: const Icon(Icons.clear), + onPressed: _reset, + ); + } + return null; + } + + void _reset() { + widget.formBuilderState?.fields[widget.name]?.didChange(widget.queryParameterIdBuilder(null)); + _textEditingController.clear(); + } + + String _getLocalizedHint(BuildContext context) { + if (T == Correspondent) { + return S.of(context).correspondentFormFieldSearchHintText; + } else if (T == DocumentType) { + return S.of(context).documentTypeFormFieldSearchHintText; + } else { + return S + .of(context) + .tagFormFieldSearchHintText; //TODO: Update tag form field once there is multi selection support. + } + } +} diff --git a/lib/features/labels/view/widgets/label_item.dart b/lib/features/labels/view/widgets/label_item.dart new file mode 100644 index 0000000..3d4f97a --- /dev/null +++ b/lib/features/labels/view/widgets/label_item.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/linked_documents_preview.dart'; + +class LabelItem extends StatelessWidget { + final T label; + final String name; + final Widget content; + final void Function(T) onOpenEditPage; + final DocumentFilter Function(T) filterBuilder; + final Widget? leading; + + const LabelItem({ + super.key, + required this.name, + required this.content, + required this.onOpenEditPage, + required this.filterBuilder, + this.leading, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(name), + subtitle: content, + leading: leading, + onTap: () => onOpenEditPage(label), + trailing: _buildDocumentCountWidget(context), + ); + } + + Widget _buildDocumentCountWidget(BuildContext context) { + return TextButton.icon( + label: const Icon(Icons.link), + icon: Text(_formatDocumentCount(label.documentCount)), + onPressed: (label.documentCount ?? 0) == 0 + ? null + : () { + final filter = filterBuilder(label); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LabelBlocProvider( + child: BlocProvider( + create: (context) => + DocumentsCubit(getIt())..updateFilter(filter: filter), + child: LinkedDocumentsPreview(filter: filter), + ), + ), + ), + ); + }, + ); + } + + String _formatDocumentCount(int? count) { + if ((count ?? 0) > 99) { + return "99+"; + } + return (count ?? 0).toString().padLeft(3); + } +} diff --git a/lib/features/labels/view/widgets/label_list_tile.dart b/lib/features/labels/view/widgets/label_list_tile.dart new file mode 100644 index 0000000..459fb4e --- /dev/null +++ b/lib/features/labels/view/widgets/label_list_tile.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/model/tag.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/linked_documents_preview.dart'; + +class LabelListTile extends StatelessWidget { + final T label; + final DocumentFilter Function(Label) filterBuilder; + final void Function() onOpenEditPage; + + const LabelListTile( + this.label, { + super.key, + required this.filterBuilder, + required this.onOpenEditPage, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: (label is Tag) + ? CircleAvatar( + backgroundColor: (label as Tag).color, + ) + : null, + title: Text(label.name), + onTap: onOpenEditPage, + trailing: _buildDocumentCountWidget(context), + subtitle: Text( + (label.match?.isEmpty ?? true) ? "-" : label.match!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + } + + Widget _buildDocumentCountWidget(BuildContext context) { + return TextButton.icon( + label: const Icon(Icons.link), + icon: Text(_formatDocumentCount(label.documentCount)), + onPressed: (label.documentCount ?? 0) == 0 + ? null + : () { + final filter = filterBuilder(label); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LabelBlocProvider( + child: BlocProvider( + create: (context) => + DocumentsCubit(getIt())..updateFilter(filter: filter), + child: LinkedDocumentsPreview(filter: filter), + ), + ), + ), + ); + }, + ); + } + + String _formatDocumentCount(int? count) { + if ((count ?? 0) > 99) { + return "99+"; + } + return (count ?? 0).toString().padLeft(3); + } +} diff --git a/lib/features/labels/view/widgets/label_tab_view.dart b/lib/features/labels/view/widgets/label_tab_view.dart new file mode 100644 index 0000000..9f31444 --- /dev/null +++ b/lib/features/labels/view/widgets/label_tab_view.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/labels/model/label.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_item.dart'; + +class LabelTabView extends StatelessWidget { + final LabelCubit cubit; + final DocumentFilter Function(Label) filterBuilder; + final void Function(T) onOpenEditPage; + + /// Displayed as the subtitle of the [ListTile] + final Widget Function(T)? contentBuilder; + + /// Displayed as the leading widget of the [ListTile] + final Widget Function(T)? leadingBuilder; + + const LabelTabView({ + super.key, + required this.cubit, + required this.filterBuilder, + this.contentBuilder, + this.leadingBuilder, + required this.onOpenEditPage, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder>, Map>( + bloc: cubit, + builder: (context, state) { + final labels = state.values.toList()..sort(); + return RefreshIndicator( + onRefresh: cubit.initialize, + child: ListView( + children: labels + .map((l) => LabelItem( + name: l.name, + content: contentBuilder?.call(l) ?? Text(l.match ?? '-'), + onOpenEditPage: onOpenEditPage, + filterBuilder: filterBuilder, + leading: leadingBuilder?.call(l), + label: l, + )) + .toList(), + ), + ); + }, + ); + } +} diff --git a/lib/features/labels/view/widgets/linked_documents_preview.dart b/lib/features/labels/view/widgets/linked_documents_preview.dart new file mode 100644 index 0000000..c55a54b --- /dev/null +++ b/lib/features/labels/view/widgets/linked_documents_preview.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_state.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document_filter.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/pages/document_details_page.dart'; +import 'package:flutter_paperless_mobile/features/documents/view/widgets/list/document_list.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + +class LinkedDocumentsPreview extends StatefulWidget { + final DocumentFilter filter; + + const LinkedDocumentsPreview({super.key, required this.filter}); + + @override + State createState() => _LinkedDocumentsPreviewState(); +} + +class _LinkedDocumentsPreviewState extends State { + final PagingController _pagingController = PagingController(firstPageKey: 1); + + @override + void initState() { + super.initState(); + _pagingController.nextPageKey = null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context).linkedDocumentsPageTitle), + ), + body: BlocBuilder( + builder: (context, state) { + _pagingController.itemList = state.documents; + return CustomScrollView( + slivers: [ + DocumentListView( + onTap: (doc) { + Navigator.push( + context, + MaterialPageRoute( + builder: (ctxt) => LabelBlocProvider( + child: BlocProvider.value( + value: BlocProvider.of(context), + child: DocumentDetailsPage(documentId: doc.id)), + ), + ), + ); + }, + pagingController: _pagingController, + state: state, + onSelected: BlocProvider.of(context).toggleDocumentSelection, + hasInternetConnection: true, + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/features/login/bloc/authentication_cubit.dart b/lib/features/login/bloc/authentication_cubit.dart new file mode 100644 index 0000000..4cc93be --- /dev/null +++ b/lib/features/login/bloc/authentication_cubit.dart @@ -0,0 +1,131 @@ +import 'dart:io'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/store/local_vault.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/login/model/authentication_information.dart'; +import 'package:flutter_paperless_mobile/features/login/model/client_certificate.dart'; +import 'package:flutter_paperless_mobile/features/login/model/user_credentials.model.dart'; +import 'package:flutter_paperless_mobile/features/login/services/authentication.service.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:injectable/injectable.dart'; + +const authenticationKey = "authentication"; + +@singleton +class AuthenticationCubit extends Cubit { + final LocalVault localStore; + final AuthenticationService authenticationService; + + AuthenticationCubit(this.localStore, this.authenticationService) + : super(AuthenticationState.initial); + + Future initialize() { + return restoreSessionState(); + } + + Future login({ + required UserCredentials credentials, + required String serverUrl, + ClientCertificate? clientCertificate, + }) async { + assert(credentials.username != null && credentials.password != null); + try { + registerSecurityContext(clientCertificate); + } on TlsException catch (_) { + throw const ErrorMessage(ErrorCode.invalidClientCertificateConfiguration); + } + emit( + AuthenticationState( + isAuthenticated: false, + wasLoginStored: false, + authentication: AuthenticationInformation( + username: credentials.username!, + password: credentials.password!, + serverUrl: serverUrl, + token: "", + clientCertificate: clientCertificate, + ), + ), + ); + final token = await authenticationService.login( + username: credentials.username!, + password: credentials.password!, + serverUrl: serverUrl, + ); + final auth = AuthenticationInformation( + username: credentials.username!, + password: credentials.password!, + token: token, + serverUrl: serverUrl, + clientCertificate: clientCertificate, + ); + + await localStore.storeAuthenticationInformation(auth); + + emit(AuthenticationState( + isAuthenticated: true, + wasLoginStored: false, + authentication: auth, + )); + } + + Future restoreSessionState() async { + final storedAuth = await localStore.loadAuthenticationInformation(); + final appSettings = + await localStore.loadApplicationSettings() ?? ApplicationSettingsState.defaultSettings; + + if (storedAuth == null || !storedAuth.isValid) { + emit(AuthenticationState(isAuthenticated: false, wasLoginStored: false)); + } else { + if (!appSettings.isLocalAuthenticationEnabled || + await authenticationService.authenticateLocalUser("Authenticate to log back in")) { + registerSecurityContext(storedAuth.clientCertificate); + emit( + AuthenticationState( + isAuthenticated: true, + wasLoginStored: true, + authentication: storedAuth, + ), + ); + } else { + emit(AuthenticationState(isAuthenticated: false, wasLoginStored: true)); + } + } + } + + Future logout() async { + await localStore.clear(); + emit(AuthenticationState.initial); + } +} + +class AuthenticationState { + final bool wasLoginStored; + final bool isAuthenticated; + final AuthenticationInformation? authentication; + + static final AuthenticationState initial = AuthenticationState( + wasLoginStored: false, + isAuthenticated: false, + ); + + AuthenticationState({ + required this.isAuthenticated, + required this.wasLoginStored, + this.authentication, + }); + + AuthenticationState copyWith({ + bool? wasLoginStored, + bool? isAuthenticated, + AuthenticationInformation? authentication, + }) { + return AuthenticationState( + isAuthenticated: isAuthenticated ?? this.isAuthenticated, + wasLoginStored: wasLoginStored ?? this.wasLoginStored, + authentication: authentication ?? this.authentication, + ); + } +} diff --git a/lib/features/login/bloc/local_authentication_cubit.dart b/lib/features/login/bloc/local_authentication_cubit.dart new file mode 100644 index 0000000..d6d5ecb --- /dev/null +++ b/lib/features/login/bloc/local_authentication_cubit.dart @@ -0,0 +1,24 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:local_auth/local_auth.dart'; + +class LocalAuthenticationCubit extends Cubit { + LocalAuthenticationCubit() : super(LocalAuthenticationState(false)); + + Future authorize(String localizedMessage) async { + final isAuthenticationSuccessful = await getIt() + .authenticate(localizedReason: localizedMessage); + if (isAuthenticationSuccessful) { + emit(LocalAuthenticationState(true)); + } else { + throw const ErrorMessage(ErrorCode.biometricAuthenticationFailed); + } + } +} + +class LocalAuthenticationState { + final bool isAuthorized; + + LocalAuthenticationState(this.isAuthorized); +} diff --git a/lib/features/login/model/authentication_information.dart b/lib/features/login/model/authentication_information.dart new file mode 100644 index 0000000..5bbb4be --- /dev/null +++ b/lib/features/login/model/authentication_information.dart @@ -0,0 +1,66 @@ +import 'package:flutter_paperless_mobile/core/type/json.dart'; +import 'package:flutter_paperless_mobile/features/login/model/client_certificate.dart'; + +class AuthenticationInformation { + static const usernameKey = 'username'; + static const passwordKey = 'password'; + static const tokenKey = 'token'; + static const serverUrlKey = 'serverUrl'; + static const clientCertificateKey = 'clientCertificate'; + + final String username; + final String password; + final String token; + final String serverUrl; + final ClientCertificate? clientCertificate; + + AuthenticationInformation({ + required this.username, + required this.password, + required this.token, + required this.serverUrl, + this.clientCertificate, + }); + + AuthenticationInformation.fromJson(JSON json) + : username = json[usernameKey], + password = json[passwordKey], + token = json[tokenKey], + serverUrl = json[serverUrlKey], + clientCertificate = json[clientCertificateKey] != null + ? ClientCertificate.fromJson(json[clientCertificateKey]) + : null; + + JSON toJson() { + return { + usernameKey: username, + passwordKey: password, + tokenKey: token, + serverUrlKey: serverUrl, + clientCertificateKey: clientCertificate?.toJson(), + }; + } + + bool get isValid { + return serverUrl.isNotEmpty && token.isNotEmpty; + } + + AuthenticationInformation copyWith({ + String? username, + String? password, + String? token, + String? serverUrl, + ClientCertificate? clientCertificate, + bool removeClientCertificate = false, + bool? isLocalAuthenticationEnabled, + }) { + return AuthenticationInformation( + username: username ?? this.username, + password: password ?? this.password, + token: token ?? this.token, + serverUrl: serverUrl ?? this.serverUrl, + clientCertificate: clientCertificate ?? + (removeClientCertificate ? null : this.clientCertificate), + ); + } +} diff --git a/lib/features/login/model/client_certificate.dart b/lib/features/login/model/client_certificate.dart new file mode 100644 index 0000000..be1c457 --- /dev/null +++ b/lib/features/login/model/client_certificate.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter_paperless_mobile/core/type/json.dart'; + +class ClientCertificate { + static const bytesKey = 'bytes'; + static const passphraseKey = 'passphrase'; + + final Uint8List bytes; + final String? passphrase; + + ClientCertificate({required this.bytes, this.passphrase}); + + static ClientCertificate? nullable(Uint8List? bytes, {String? passphrase}) { + if (bytes != null) { + return ClientCertificate(bytes: bytes, passphrase: passphrase); + } + return null; + } + + JSON toJson() { + return { + bytesKey: base64Encode(bytes), + passphraseKey: passphrase, + }; + } + + ClientCertificate.fromJson(JSON json) + : bytes = base64Decode(json[bytesKey]), + passphrase = json[passphraseKey]; + + ClientCertificate copyWith({Uint8List? bytes, String? passphrase}) { + return ClientCertificate( + bytes: bytes ?? this.bytes, + passphrase: passphrase ?? this.passphrase, + ); + } +} diff --git a/lib/features/login/model/user_credentials.model.dart b/lib/features/login/model/user_credentials.model.dart new file mode 100644 index 0000000..b69e3ce --- /dev/null +++ b/lib/features/login/model/user_credentials.model.dart @@ -0,0 +1,13 @@ +class UserCredentials { + final String? username; + final String? password; + + UserCredentials({this.username, this.password}); + + UserCredentials copyWith({String? username, String? password}) { + return UserCredentials( + username: username ?? this.username, + password: password ?? this.password, + ); + } +} diff --git a/lib/features/login/services/authentication.service.dart b/lib/features/login/services/authentication.service.dart new file mode 100644 index 0000000..c2ac4a5 --- /dev/null +++ b/lib/features/login/services/authentication.service.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/core/store/local_vault.dart'; +import 'package:http/http.dart'; +import 'package:injectable/injectable.dart'; +import 'package:local_auth/local_auth.dart'; + +@singleton +class AuthenticationService { + final BaseClient httpClient; + final LocalVault localStore; + final LocalAuthentication localAuthentication; + + AuthenticationService( + this.localStore, + this.localAuthentication, + @Named("timeoutClient") this.httpClient, + ); + + /// + /// Returns the authentication token. + /// + Future login({ + required String username, + required String password, + required String serverUrl, + }) async { + final response = await httpClient.post( + Uri.parse("/api/token/"), + body: {"username": username, "password": password}, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return data['token']; + } else if (response.statusCode == 400 && + response.body.toLowerCase().contains("no required certificate was sent")) { + throw const ErrorMessage(ErrorCode.invalidClientCertificateConfiguration); + } else { + throw const ErrorMessage(ErrorCode.authenticationFailed); + } + } + + Future authenticateLocalUser(String localizedReason) async { + if (await localAuthentication.isDeviceSupported()) { + return await localAuthentication.authenticate( + localizedReason: localizedReason, + options: const AuthenticationOptions( + stickyAuth: true, + biometricOnly: true, + useErrorDialogs: true, + ), + ); + } + return false; + } +} diff --git a/lib/features/login/view/login_page.dart b/lib/features/login/view/login_page.dart new file mode 100644 index 0000000..be3eec5 --- /dev/null +++ b/lib/features/login/view/login_page.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:flutter_paperless_mobile/features/login/view/widgets/client_certificate_form_field.dart'; +import 'package:flutter_paperless_mobile/features/login/view/widgets/server_address_form_field.dart'; +import 'package:flutter_paperless_mobile/features/login/view/widgets/user_credentials_form_field.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({Key? key}) : super(key: key); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final _formKey = GlobalKey(); + + bool _isLoginLoading = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + title: Text(S.of(context).loginPageTitle), + bottom: _isLoginLoading + ? const PreferredSize( + preferredSize: Size(double.infinity, 4), + child: LinearProgressIndicator(), + ) + : null, + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: FormBuilder( + key: _formKey, + child: ListView( + children: [ + const ServerAddressFormField().padded(), + const UserCredentialsFormField(), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + S.of(context).loginPageAdvancedLabel, + style: Theme.of(context).textTheme.bodyText1, + ).padded(), + ), + ), + const ClientCertificateFormField(), + LayoutBuilder(builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: constraints.maxWidth, + child: _buildLoginButton(), + ), + ); + }), + ], + ), + ), + ), + ); + } + + Widget _buildLoginButton() { + return ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStatePropertyAll(Theme.of(context).colorScheme.primaryContainer), + elevation: const MaterialStatePropertyAll(0), + ), + onPressed: _login, + child: Text( + S.of(context).loginPageLoginButtonLabel, + ), + ); + } + + void _login() async { + FocusScope.of(context).unfocus(); + if (_formKey.currentState?.saveAndValidate() ?? false) { + setState(() => _isLoginLoading = true); + final form = _formKey.currentState?.value; + getIt() + .login( + credentials: form?[UserCredentialsFormField.fkCredentials], + serverUrl: form?[ServerAddressFormField.fkServerAddress], + clientCertificate: form?[ClientCertificateFormField.fkClientCertificate], + ) //TODO: Move Intro slider route push here! + .onError( + (error, _) => showError(context, error), + ) + .whenComplete(() => setState(() => _isLoginLoading = false)); + } + } +} diff --git a/lib/features/login/view/widgets/client_certificate_form_field.dart b/lib/features/login/view/widgets/client_certificate_form_field.dart new file mode 100644 index 0000000..1d82956 --- /dev/null +++ b/lib/features/login/view/widgets/client_certificate_form_field.dart @@ -0,0 +1,116 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/login/model/client_certificate.dart'; +import 'package:flutter_paperless_mobile/features/login/view/widgets/password_text_field.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class ClientCertificateFormField extends StatefulWidget { + static const fkClientCertificate = 'clientCertificate'; + const ClientCertificateFormField({Key? key}) : super(key: key); + + @override + State createState() => _ClientCertificateFormFieldState(); +} + +class _ClientCertificateFormFieldState extends State { + File? _selectedFile; + @override + Widget build(BuildContext context) { + return FormBuilderField( + initialValue: null, + validator: (value) { + if (value == null) { + return null; + } + assert(_selectedFile != null); + if (_selectedFile?.path.split(".").last != 'pfx') { + return S.of(context).loginPageClientCertificateSettingInvalidFileFormatValidationText; + } + return null; + }, + builder: (field) { + return ExpansionTile( + title: Text(S.of(context).loginPageClientCertificateSettingLabel), + subtitle: Text(S.of(context).loginPageClientCertificateSettingDescriptionText), + children: [ + InputDecorator( + decoration: InputDecoration( + errorText: field.errorText, + border: InputBorder.none, + ), + child: Column( + children: [ + ListTile( + leading: ElevatedButton( + onPressed: () => _onSelectFile(field), + child: Text(S.of(context).genericActionSelectText), + ), + title: _buildSelectedFileText(field), + trailing: AbsorbPointer( + absorbing: field.value == null, + child: _selectedFile != null + ? IconButton( + icon: const Icon(Icons.close), + onPressed: () => setState(() { + _selectedFile = null; + field.didChange(null); + }), + ) + : null, + ), + ), + if (_selectedFile != null) ...[ + ObscuredInputTextFormField( + initialValue: field.value?.passphrase, + onChanged: (value) => field.didChange( + field.value?.copyWith(passphrase: value), + ), + label: S.of(context).loginPageClientCertificatePassphraseLabel, + ).padded(), + ] else + ...[] + ], + ), + ), + ], + ); + }, + name: ClientCertificateFormField.fkClientCertificate, + ); + } + + Future _onSelectFile(FormFieldState field) async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result != null && result.files.single.path != null) { + File file = File(result.files.single.path!); + setState(() { + _selectedFile = file; + }); + final changedValue = field.value?.copyWith(bytes: file.readAsBytesSync()) ?? + ClientCertificate(bytes: file.readAsBytesSync()); + field.didChange(changedValue); + } + } + + Widget _buildSelectedFileText(FormFieldState field) { + if (field.value == null) { + assert(_selectedFile == null); + return Text( + S.of(context).loginPageClientCertificateSettingSelectFileText, + style: TextStyle(color: Theme.of(context).hintColor), + ); + } else { + assert(_selectedFile != null); + return Text( + _selectedFile!.path.split("/").last, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + ), + ); + } + } +} diff --git a/lib/features/login/view/widgets/password_text_field.dart b/lib/features/login/view/widgets/password_text_field.dart new file mode 100644 index 0000000..0fd9ee8 --- /dev/null +++ b/lib/features/login/view/widgets/password_text_field.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +class ObscuredInputTextFormField extends StatefulWidget { + final String? initialValue; + final String label; + final void Function(String?) onChanged; + final FormFieldValidator? validator; + + const ObscuredInputTextFormField({ + super.key, + required this.onChanged, + required this.label, + this.validator, + this.initialValue, + }); + + @override + State createState() => _ObscuredInputTextFormFieldState(); +} + +class _ObscuredInputTextFormFieldState extends State { + bool _showPassword = false; + final FocusNode _passwordFocusNode = FocusNode(); + + @override + void dispose() { + _passwordFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TextFormField( + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: widget.validator, + initialValue: widget.initialValue, + focusNode: _passwordFocusNode, + obscureText: !_showPassword, + autocorrect: false, + onChanged: widget.onChanged, + autofillHints: const [AutofillHints.password], + decoration: InputDecoration( + label: Text(widget.label), + suffixIcon: IconButton( + icon: Icon(_showPassword ? Icons.visibility_off : Icons.visibility), + onPressed: () => setState(() { + _showPassword = !_showPassword; + }), + ), + ), + ); + } +} diff --git a/lib/features/login/view/widgets/server_address_form_field.dart b/lib/features/login/view/widgets/server_address_form_field.dart new file mode 100644 index 0000000..6177867 --- /dev/null +++ b/lib/features/login/view/widgets/server_address_form_field.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/core/service/connectivity_status.service.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class ServerAddressFormField extends StatefulWidget { + static const String fkServerAddress = "serverAddress"; + const ServerAddressFormField({ + Key? key, + }) : super(key: key); + + @override + State createState() => _ServerAddressFormFieldState(); +} + +class _ServerAddressFormFieldState extends State { + ReachabilityStatus _reachabilityStatus = ReachabilityStatus.undefined; + + @override + Widget build(BuildContext context) { + return FormBuilderTextField( + name: ServerAddressFormField.fkServerAddress, + validator: FormBuilderValidators.required( + errorText: S.of(context).loginPageServerUrlValidatorMessageText, + ), + decoration: InputDecoration( + suffixIcon: _buildIsReachableIcon(), + hintText: "http://192.168.1.50:8000", + labelText: S.of(context).loginPageServerUrlFieldLabel, + ), + onSubmitted: _updateIsAddressReachableStatus, + ); + } + + Widget? _buildIsReachableIcon() { + switch (_reachabilityStatus) { + case ReachabilityStatus.reachable: + return const Icon( + Icons.done, + color: Colors.green, + ); + case ReachabilityStatus.notReachable: + return Icon( + Icons.close, + color: Theme.of(context).colorScheme.error, + ); + case ReachabilityStatus.testing: + return const RefreshProgressIndicator(); + case ReachabilityStatus.undefined: + return null; + } + } + + void _updateIsAddressReachableStatus(String? address) async { + if (address == null || address.isEmpty) { + setState(() { + _reachabilityStatus = ReachabilityStatus.undefined; + }); + return; + } + //https://stackoverflow.com/questions/49648022/check-whether-there-is-an-internet-connection-available-on-flutter-app + setState(() => _reachabilityStatus = ReachabilityStatus.testing); + final isReachable = await getIt().isServerReachable(address); + if (isReachable) { + setState(() => _reachabilityStatus = ReachabilityStatus.reachable); + } else { + setState(() => _reachabilityStatus = ReachabilityStatus.notReachable); + } + } +} + +enum ReachabilityStatus { reachable, notReachable, testing, undefined } diff --git a/lib/features/login/view/widgets/user_credentials_form_field.dart b/lib/features/login/view/widgets/user_credentials_form_field.dart new file mode 100644 index 0000000..d799e5f --- /dev/null +++ b/lib/features/login/view/widgets/user_credentials_form_field.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/login/model/user_credentials.model.dart'; +import 'package:flutter_paperless_mobile/features/login/view/widgets/password_text_field.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; + +class UserCredentialsFormField extends StatefulWidget { + static const fkCredentials = 'credentials'; + const UserCredentialsFormField({Key? key}) : super(key: key); + + @override + State createState() => + _UserCredentialsFormFieldState(); +} + +class _UserCredentialsFormFieldState extends State { + @override + Widget build(BuildContext context) { + return FormBuilderField( + name: UserCredentialsFormField.fkCredentials, + builder: (field) => AutofillGroup( + child: Column( + children: [ + TextFormField( + textCapitalization: TextCapitalization.words, + autovalidateMode: AutovalidateMode.onUserInteraction, + // USERNAME + autocorrect: false, + onChanged: (username) => field.didChange( + field.value?.copyWith(username: username) ?? + UserCredentials(username: username), + ), + validator: FormBuilderValidators.required( + errorText: S.of(context).loginPageUsernameValidatorMessageText, + ), + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + label: Text(S.of(context).loginPageUsernameLabel), + ), + ), + ObscuredInputTextFormField( + label: S.of(context).loginPagePasswordFieldLabel, + onChanged: (password) => field.didChange( + field.value?.copyWith(password: password) ?? + UserCredentials(password: password), + ), + validator: FormBuilderValidators.required( + errorText: S.of(context).loginPagePasswordValidatorMessageText, + ), + ), + ].map((child) => child.padded()).toList(), + ), + ), + ); + } +} + +/** + * AutofillGroup( + child: Column( + children: [ + FormBuilderTextField( + name: fkUsername, + focusNode: _focusNodes[fkUsername], + onSubmitted: (_) { + FocusScope.of(context).requestFocus(_focusNodes[fkPassword]); + }, + validator: FormBuilderValidators.required( + errorText: S.of(context).loginPageUsernameValidatorMessageText, + ), + autofillHints: const [AutofillHints.username], + decoration: InputDecoration( + labelText: S.of(context).loginPageUsernameLabel, + ), + ).padded(), + FormBuilderTextField( + name: fkPassword, + focusNode: _focusNodes[fkPassword], + onSubmitted: (_) { + FocusScope.of(context).unfocus(); + }, + autofillHints: const [AutofillHints.password], + validator: FormBuilderValidators.required( + errorText: S.of(context).loginPagePasswordValidatorMessageText, + ), + obscureText: true, + decoration: InputDecoration( + labelText: S.of(context).loginPagePasswordFieldLabel, + ), + ).padded(), + ], + ), + ); + */ diff --git a/lib/features/scan/bloc/document_scanner_cubit.dart b/lib/features/scan/bloc/document_scanner_cubit.dart new file mode 100644 index 0000000..d4b65a3 --- /dev/null +++ b/lib/features/scan/bloc/document_scanner_cubit.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class DocumentScannerCubit extends Cubit> { + static List initialState = []; + + DocumentScannerCubit() : super(initialState); + + void addScan(File file) => emit([...state, file]); + + void removeScan(int fileIndex) { + try { + state[fileIndex].deleteSync(); + final scans = [...state]; + scans.removeAt(fileIndex); + emit(scans); + } catch (_) { + addError(const ErrorMessage(ErrorCode.scanRemoveFailed)); + } + } + + void reset() { + for (final doc in state) { + doc.deleteSync(); + } + imageCache.clear(); + emit(initialState); + } +} diff --git a/lib/features/scan/logic/services/decode.isolate.dart b/lib/features/scan/logic/services/decode.isolate.dart new file mode 100644 index 0000000..91196be --- /dev/null +++ b/lib/features/scan/logic/services/decode.isolate.dart @@ -0,0 +1,44 @@ +import 'dart:io'; +import 'dart:isolate'; + +import 'package:image/image.dart' as im; + +typedef ImageOperationCallback = im.Image Function(im.Image); + +class DecodeParam { + final File file; + final SendPort sendPort; + final im.Image Function(im.Image) imageOperation; + DecodeParam(this.file, this.sendPort, this.imageOperation); +} + +void decodeIsolate(DecodeParam param) { + // Read an image from file (webp in this case). + // decodeImage will identify the format of the image and use the appropriate + // decoder. + var image = im.decodeImage(param.file.readAsBytesSync())!; + // Resize the image to a 120x? thumbnail (maintaining the aspect ratio). + var processed = param.imageOperation(image); + param.sendPort.send(processed); +} + +// Decode and process an image file in a separate thread (isolate) to avoid +// stalling the main UI thread. +Future processImage( + File file, + ImageOperationCallback imageOperation, +) async { + var receivePort = ReceivePort(); + + await Isolate.spawn( + decodeIsolate, + DecodeParam( + file, + receivePort.sendPort, + imageOperation, + )); + + var image = await receivePort.first as im.Image; + + return file.writeAsBytes(im.encodePng(image)); +} diff --git a/lib/features/scan/view/document_upload_page.dart b/lib/features/scan/view/document_upload_page.dart new file mode 100644 index 0000000..340997f --- /dev/null +++ b/lib/features/scan/view/document_upload_page.dart @@ -0,0 +1,224 @@ +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:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/document.model.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/documents/model/query_parameters/ids_query_parameter.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/model/document_type.model.dart'; +import 'package:flutter_paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart'; +import 'package:flutter_paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart'; +import 'package:flutter_paperless_mobile/features/labels/view/widgets/label_form_field.dart'; +import 'package:flutter_paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart'; + +class DocumentUploadPage extends StatefulWidget { + final Uint8List pdfBytes; + const DocumentUploadPage({ + Key? key, + required this.pdfBytes, + }) : super(key: key); + + @override + State createState() => _DocumentUploadPageState(); +} + +class _DocumentUploadPageState extends State { + static const fkFileName = "fileName"; + + static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss"); + final GlobalKey _formKey = GlobalKey(); + + Map _errors = {}; + bool _isUploadLoading = false; + + @override + void initState() { + super.initState(); + initializeDateFormatting(); //TODO: INTL (has to do with intl below) + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + title: Text(S.of(context).documentsUploadPageTitle), + bottom: _isUploadLoading + ? const PreferredSize( + child: LinearProgressIndicator(), preferredSize: Size.fromHeight(4.0)) + : null, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: _onSubmit, + label: Text(S.of(context).genericActionUploadLabel), + icon: const Icon(Icons.upload), + ), + body: SingleChildScrollView( + child: FormBuilder( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FormBuilderTextField( + autovalidateMode: AutovalidateMode.always, + name: DocumentModel.titleKey, + initialValue: "scan_${fileNameDateFormat.format(DateTime.now())}", + validator: FormBuilderValidators.required(), + decoration: InputDecoration( + labelText: S.of(context).documentTitlePropertyLabel, + suffixIcon: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + _formKey.currentState?.fields[DocumentModel.titleKey]?.didChange(""); + _formKey.currentState?.fields[fkFileName]?.didChange(".pdf"); + }, + ), + errorText: _errors[DocumentModel.titleKey], + ), + onChanged: (value) { + final String? transformedValue = value?.replaceAll(RegExp(r"[\W_]"), "_"); + _formKey.currentState?.fields[fkFileName] + ?.didChange("${transformedValue ?? ''}.pdf"); + }, + ), + FormBuilderTextField( + autovalidateMode: AutovalidateMode.always, + readOnly: true, + enabled: false, + name: fkFileName, + decoration: InputDecoration( + labelText: S.of(context).documentUploadFileNameLabel, + ), + initialValue: "scan_${fileNameDateFormat.format(DateTime.now())}.pdf", + ), + FormBuilderDateTimePicker( + autovalidateMode: AutovalidateMode.always, + format: DateFormat("dd. MMMM yyyy"), //TODO: INTL + inputType: InputType.date, + name: DocumentModel.createdKey, + initialValue: null, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.calendar_month_outlined), + labelText: S.of(context).documentCreatedPropertyLabel + " *", + ), + ), + BlocBuilder>( + bloc: getIt(), //TODO: Use provider + builder: (context, state) { + return LabelFormField( + notAssignedSelectable: false, + formBuilderState: _formKey.currentState, + labelCreationWidgetBuilder: (initialValue) => BlocProvider.value( + value: BlocProvider.of(context), + child: AddDocumentTypePage(initialName: initialValue), + ), + label: S.of(context).documentDocumentTypePropertyLabel + " *", + name: DocumentModel.documentTypeKey, + state: state, + queryParameterIdBuilder: DocumentTypeQuery.fromId, + queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned, + prefixIcon: const Icon(Icons.description_outlined), + ); + }, + ), + BlocBuilder>( + bloc: getIt(), //TODO: Use provider + builder: (context, state) { + return LabelFormField( + notAssignedSelectable: false, + formBuilderState: _formKey.currentState, + labelCreationWidgetBuilder: (initialValue) => BlocProvider.value( + value: BlocProvider.of(context), + child: AddCorrespondentPage(initalValue: initialValue), + ), + label: S.of(context).documentCorrespondentPropertyLabel + " *", + name: DocumentModel.correspondentKey, + state: state, + queryParameterIdBuilder: CorrespondentQuery.fromId, + queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned, + prefixIcon: const Icon(Icons.person_outline), + ); + }, + ), + const TagFormField( + name: DocumentModel.tagsKey, + //Label: "Tags" + " *", + ), + Text( + "* " + S.of(context).uploadPageAutomaticallInferredFieldsHintText, + style: Theme.of(context).textTheme.caption, + ), + ].padded(), + ), + ), + ), + ); + } + + void _onSubmit() async { + _formKey.currentState?.save(); + if (_formKey.currentState?.validate() ?? false) { + try { + setState(() { + _isUploadLoading = true; + }); + await BlocProvider.of(context).addDocument( + widget.pdfBytes, + _formKey.currentState?.value[fkFileName], + onConsumptionFinished: (document) { + ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar( + SnackBar( + action: SnackBarAction( + onPressed: () { + getIt().reloadDocuments(); + }, + label: S.of(context).documentUploadProcessingSuccessfulReloadActionText, + ), + content: Text(S.of(context).documentUploadProcessingSuccessfulText), + ), + ); + }, + title: _formKey.currentState?.value[DocumentModel.titleKey], + documentType: + (_formKey.currentState?.value[DocumentModel.documentTypeKey] as IdQueryParameter).id, + correspondent: + (_formKey.currentState?.value[DocumentModel.correspondentKey] as IdQueryParameter).id, + tags: (_formKey.currentState?.value[DocumentModel.tagsKey] as IdsQueryParameter).ids, + createdAt: (_formKey.currentState?.value[DocumentModel.createdKey] as DateTime?), + ); + setState(() { + _isUploadLoading = false; + }); + getIt().reset(); + Navigator.pop(context); + showSnackBar(context, S.of(context).documentUploadSuccessText); + } on ErrorMessage catch (error) { + showError(context, error); + } on Map catch (errorMessages) { + setState(() => _errors = errorMessages); + } catch (other) { + showSnackBar(context, other.toString()); + } finally { + setState(() { + _isUploadLoading = true; + }); + } + } + } +} diff --git a/lib/features/scan/view/scanner_page.dart b/lib/features/scan/view/scanner_page.dart new file mode 100644 index 0000000..17a4afa --- /dev/null +++ b/lib/features/scan/view/scanner_page.dart @@ -0,0 +1,184 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:edge_detection/edge_detection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:flutter_paperless_mobile/features/home/view/widget/info_drawer.dart'; +import 'package:flutter_paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; +import 'package:flutter_paperless_mobile/features/scan/view/document_upload_page.dart'; +import 'package:flutter_paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:permission_handler/permission_handler.dart'; + +class ScannerPage extends StatefulWidget { + const ScannerPage({Key? key}) : super(key: key); + + @override + State createState() => _ScannerPageState(); +} + +class _ScannerPageState extends State with SingleTickerProviderStateMixin { + late final AnimationController _fabPulsingController; + late final Animation _animation; + @override + void initState() { + super.initState(); + _fabPulsingController = AnimationController(vsync: this, duration: const Duration(seconds: 1)) + ..repeat(reverse: true); + _animation = Tween(begin: 1.0, end: 1.2).animate(_fabPulsingController) + ..addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _fabPulsingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: const InfoDrawer(), + floatingActionButton: BlocBuilder>( + builder: (context, state) { + final fab = FloatingActionButton( + onPressed: () => _openDocumentScanner(context), + child: const Icon(Icons.add_a_photo_outlined), + ); + if (state.isEmpty) { + return Transform.scale( + child: fab, + scale: _animation.value, + ); + } + return fab; + }, + ), + appBar: _buildAppBar(context), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: _buildBody(), + ), + ); + } + + AppBar _buildAppBar(BuildContext context) { + return AppBar( + title: Text(S.of(context).documentScannerPageTitle), + actions: [ + BlocBuilder>( + builder: (context, state) { + return IconButton( + onPressed: state.isEmpty ? null : () => _reset(context), + icon: const Icon(Icons.delete_sweep), + tooltip: S.of(context).documentScannerPageResetButtonTooltipText, + ); + }, + ), + BlocBuilder>( + builder: (context, state) { + return IconButton( + onPressed: state.isEmpty ? null : () => _export(context), + icon: const Icon(Icons.done), + tooltip: S.of(context).documentScannerPageUploadButtonTooltip, + ); + }, + ), + ], + ); + } + + void _openDocumentScanner(BuildContext context) async { + await _requestCameraPermissions(); + final imagePath = await EdgeDetection.detectEdge; + if (imagePath == null) { + return; + } + final file = File(imagePath); + BlocProvider.of(context).addScan(file); + } + + void _export(BuildContext context) async { + final pw.Document doc = pw.Document(); + + for (var element in BlocProvider.of(context).state) { + final img = pw.MemoryImage(element.readAsBytesSync()); + doc.addPage( + pw.Page( + pageFormat: PdfPageFormat(img.width!.toDouble(), img.height!.toDouble()), + build: (context) => pw.Image(img), + ), + ); + } + final bytes = await doc.save(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: getIt(), + child: LabelBlocProvider( + child: DocumentUploadPage( + pdfBytes: bytes, + ), + ), + ), + ), + ); + } + + Widget _buildBody() { + return BlocBuilder>( + builder: (context, scans) { + if (scans.isNotEmpty) { + return _buildImageGrid(scans); + } + return Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text( + S.of(context).documentScannerPageEmptyStateText, + textAlign: TextAlign.center, + ), + ), + ); + }, + ); + } + + Widget _buildImageGrid(List scans) { + return GridView.builder( + itemCount: scans.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1 / sqrt(2), + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemBuilder: (context, index) { + return GridImageItemWidget( + file: scans[index], + onDelete: () => BlocProvider.of(context).removeScan(index), + index: index, + totalNumberOfFiles: scans.length, + ); + }); + } + + void _reset(BuildContext context) { + BlocProvider.of(context).reset(); + } + + Future _requestCameraPermissions() async { + final hasPermission = await Permission.camera.isGranted; + if (!hasPermission) { + Permission.camera.request(); + } + } +} diff --git a/lib/features/scan/view/widgets/grid_image_item_widget.dart b/lib/features/scan/view/widgets/grid_image_item_widget.dart new file mode 100644 index 0000000..ec69378 --- /dev/null +++ b/lib/features/scan/view/widgets/grid_image_item_widget.dart @@ -0,0 +1,106 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; + +typedef DeleteCallback = void Function(); +typedef OnImageOperation = void Function(File); + +class GridImageItemWidget extends StatefulWidget { + final File file; + final DeleteCallback onDelete; + //final OnImageOperation onImageOperation; + + final int index; + final int totalNumberOfFiles; + + const GridImageItemWidget({ + Key? key, + required this.file, + required this.onDelete, + required this.index, + required this.totalNumberOfFiles, + //required this.onImageOperation, + }) : super(key: key); + + @override + State createState() => _GridImageItemWidgetState(); +} + +class _GridImageItemWidgetState extends State { + bool isProcessing = false; + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => _showImage(context), + child: _buildImageItem(context), + ); + } + + Card _buildImageItem(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Stack( + children: [ + Align(alignment: Alignment.bottomCenter, child: _buildNumbering()), + Align( + alignment: Alignment.topRight, + child: IconButton( + onPressed: widget.onDelete, + icon: const Icon(Icons.close), + ), + ), + isProcessing + ? _buildIsProcessing() + : Align( + alignment: Alignment.center, + child: AspectRatio( + aspectRatio: 4 / 3, + child: Image.file( + widget.file, + fit: BoxFit.contain, + ), + ), + ), + ], + ), + ), + ); + } + + Center _buildIsProcessing() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + CircularProgressIndicator(), + Text( + "Processing transformation...", + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + void _showImage(BuildContext context) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar( + title: _buildNumbering(prefix: "Image"), + ), + body: PhotoView(imageProvider: FileImage(widget.file)), + ), + ), + ); + } + + Widget _buildNumbering({String? prefix}) { + return Text( + "${prefix ?? ""} ${widget.index + 1}/${widget.totalNumberOfFiles}", + ); + } +} diff --git a/lib/features/scan/view/widgets/scanner.dart b/lib/features/scan/view/widgets/scanner.dart new file mode 100644 index 0000000..18612c9 --- /dev/null +++ b/lib/features/scan/view/widgets/scanner.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +typedef OnImageScannedCallback = void Function(File); + +class ScannerWidget extends StatefulWidget { + final OnImageScannedCallback onImageScannedCallback; + const ScannerWidget({ + Key? key, + required this.onImageScannedCallback, + }) : super(key: key); + + @override + _ScannerWidgetState createState() => _ScannerWidgetState(); +} + +class _ScannerWidgetState extends State { + List documents = List.empty(growable: true); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("Scan document")), + body: FutureBuilder( + future: Permission.camera.request(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.data!.isGranted) { + return Container(); + } + return const Center( + child: Text("No camera permissions, please enable in settings!"), + ); + }), + ); + } +} diff --git a/lib/features/scan/view/widgets/upload_dialog.dart b/lib/features/scan/view/widgets/upload_dialog.dart new file mode 100644 index 0000000..aafb278 --- /dev/null +++ b/lib/features/scan/view/widgets/upload_dialog.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class UploadDialog extends StatefulWidget { + const UploadDialog({ + Key? key, + }) : super(key: key); + + @override + State createState() => _UploadDialogState(); +} + +class _UploadDialogState extends State { + late TextEditingController _controller; + final _formKey = GlobalKey(); + + @override + void initState() { + final DateFormat format = DateFormat("yyyy_MM_dd_hh_mm_ss"); + final today = format.format(DateTime.now()); + _controller = TextEditingController.fromValue(TextEditingValue(text: "Scan_$today.pdf")); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Upload to paperless-ng"), + content: Form( + key: _formKey, + child: TextFormField( + controller: _controller, + validator: (text) { + if (text == null || text.isEmpty) { + return "Filename must be specified!"; + } + return null; + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + TextButton( + onPressed: () { + if (!_formKey.currentState!.validate()) { + return; + } + var txt = _controller.text; + if (!txt.endsWith(".pdf")) { + txt += ".pdf"; + } + Navigator.of(context).pop(txt); + }, + child: const Text("Upload"), + ), + ], + ); + } +} diff --git a/lib/features/settings/bloc/application_settings_cubit.dart b/lib/features/settings/bloc/application_settings_cubit.dart new file mode 100644 index 0000000..f6ddfb9 --- /dev/null +++ b/lib/features/settings/bloc/application_settings_cubit.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/core/store/local_vault.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class ApplicationSettingsCubit extends Cubit { + final LocalVault localVault; + + ApplicationSettingsCubit(this.localVault) : super(ApplicationSettingsState.defaultSettings); + + Future initialize() async { + final settings = + (await localVault.loadApplicationSettings()) ?? ApplicationSettingsState.defaultSettings; + emit(settings); + } + + Future setLocale(String? localeSubtag) async { + final updatedSettings = state.copyWith(preferredLocaleSubtag: localeSubtag); + _updateSettings(updatedSettings); + } + + Future setIsBiometricAuthenticationEnabled(bool isEnabled) async { + final updatedSettings = state.copyWith(isLocalAuthenticationEnabled: isEnabled); + _updateSettings(updatedSettings); + } + + Future _updateSettings(ApplicationSettingsState settings) async { + await localVault.storeApplicationSettings(settings); + emit(settings); + } + + Future setThemeMode(ThemeMode? selectedMode) async { + final updatedSettings = state.copyWith(preferredThemeMode: selectedMode); + _updateSettings(updatedSettings); + } +} diff --git a/lib/features/settings/model/application_settings_state.dart b/lib/features/settings/model/application_settings_state.dart new file mode 100644 index 0000000..a85996e --- /dev/null +++ b/lib/features/settings/model/application_settings_state.dart @@ -0,0 +1,61 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/core/type/json.dart'; + +/// +/// State holding the current application settings such as selected language, theme mode and more. +/// +/// +class ApplicationSettingsState { + static final defaultSettings = ApplicationSettingsState( + isLocalAuthenticationEnabled: false, + preferredLocaleSubtag: Platform.localeName.split('_').first, + preferredThemeMode: ThemeMode.system, + ); + + static const isLocalAuthenticationEnabledKey = "isLocalAuthenticationEnabled"; + static const preferredLocaleSubtagKey = "localeSubtag"; + static const preferredThemeModeKey = "preferredThemeModeKey"; + + final bool isLocalAuthenticationEnabled; + final String preferredLocaleSubtag; + + final ThemeMode preferredThemeMode; + + ApplicationSettingsState({ + required this.preferredLocaleSubtag, + required this.preferredThemeMode, + required this.isLocalAuthenticationEnabled, + }); + + JSON toJson() { + return { + isLocalAuthenticationEnabledKey: isLocalAuthenticationEnabled, + preferredLocaleSubtagKey: preferredLocaleSubtag, + preferredThemeModeKey: preferredThemeMode.index, + }; + } + + ApplicationSettingsState.fromJson(JSON json) + : isLocalAuthenticationEnabled = + json[isLocalAuthenticationEnabledKey] ?? defaultSettings.isLocalAuthenticationEnabled, + preferredLocaleSubtag = + json[preferredLocaleSubtagKey] ?? Platform.localeName.split("_").first, + preferredThemeMode = json[preferredThemeModeKey] != null + ? ThemeMode.values[(json[preferredThemeModeKey])] + : defaultSettings.preferredThemeMode; + + ApplicationSettingsState copyWith({ + bool? isLocalAuthenticationEnabled, + String? preferredLocaleSubtag, + ThemeMode? preferredThemeMode, + }) { + return ApplicationSettingsState( + isLocalAuthenticationEnabled: + isLocalAuthenticationEnabled ?? this.isLocalAuthenticationEnabled, + preferredLocaleSubtag: preferredLocaleSubtag ?? this.preferredLocaleSubtag, + preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode, + ); + } +} diff --git a/lib/features/settings/view/pages/application_settings_page.dart b/lib/features/settings/view/pages/application_settings_page.dart new file mode 100644 index 0000000..14dc0c2 --- /dev/null +++ b/lib/features/settings/view/pages/application_settings_page.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/widgets/language_selection_setting.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/widgets/theme_mode_setting.dart'; + +class ApplicationSettingsPage extends StatelessWidget { + const ApplicationSettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Application"), + ), + body: ListView( + children: const [ + LanguageSelectionSetting(), + ThemeModeSetting(), + ], + ), + ); + } +} diff --git a/lib/features/settings/view/pages/security_settings_page.dart b/lib/features/settings/view/pages/security_settings_page.dart new file mode 100644 index 0000000..2e81cc9 --- /dev/null +++ b/lib/features/settings/view/pages/security_settings_page.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class SecuritySettingsPage extends StatelessWidget { + const SecuritySettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(S.of(context).settingsPageSecuritySettingsLabel)), + body: ListView( + children: const [ + BiometricAuthenticationSetting(), + ], + ), + ); + } +} diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart new file mode 100644 index 0000000..7aefd50 --- /dev/null +++ b/lib/features/settings/view/settings_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/pages/application_settings_page.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/pages/security_settings_page.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context).appDrawerSettingsLabel), + ), + body: ListView( + children: [ + ListTile( + title: Text(S.of(context).settingsPageApplicationSettingsLabel), + subtitle: Text(S.of(context).settingsPageApplicationSettingsDescriptionText), + onTap: () => _goto(const ApplicationSettingsPage()), + ), + ListTile( + title: Text(S.of(context).settingsPageSecuritySettingsLabel), + subtitle: Text(S.of(context).settingsPageSecuritySettingsDescriptionText), + onTap: () => _goto(const SecuritySettingsPage()), + ), + ], + ), + ); + } + + void _goto(Widget page) { + Navigator.push( + context, + MaterialPageRoute( + builder: (ctxt) => BlocProvider.value( + value: BlocProvider.of(context), child: page), + maintainState: true, + ), + ); + } +} diff --git a/lib/features/settings/view/widgets/biometric_authentication_setting.dart b/lib/features/settings/view/widgets/biometric_authentication_setting.dart new file mode 100644 index 0000000..445cabc --- /dev/null +++ b/lib/features/settings/view/widgets/biometric_authentication_setting.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/login/services/authentication.service.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class BiometricAuthenticationSetting extends StatelessWidget { + const BiometricAuthenticationSetting({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, settings) { + return SwitchListTile( + value: settings.isLocalAuthenticationEnabled, + title: Text(S.of(context).appSettingsBiometricAuthenticationLabel), + subtitle: Text(S.of(context).appSettingsBiometricAuthenticationDescriptionText), + onChanged: (val) async { + final settingsBloc = BlocProvider.of(context); + final String localizedReason = val + ? S.of(context).appSettingsEnableBiometricAuthenticationReasonText + : S.of(context).appSettingsDisableBiometricAuthenticationReasonText; + final changeValue = + await getIt().authenticateLocalUser(localizedReason); + if (changeValue) { + settingsBloc.setIsBiometricAuthenticationEnabled(val); + } + }, + ); + }, + ); + } +} diff --git a/lib/features/settings/view/widgets/language_selection_setting.dart b/lib/features/settings/view/widgets/language_selection_setting.dart new file mode 100644 index 0000000..ae643d9 --- /dev/null +++ b/lib/features/settings/view/widgets/language_selection_setting.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class LanguageSelectionSetting extends StatefulWidget { + const LanguageSelectionSetting({super.key}); + + @override + State createState() => _LanguageSelectionSettingState(); +} + +class _LanguageSelectionSettingState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, settings) { + return ListTile( + title: Text(S.of(context).settingsPageLanguageSettingLabel), + subtitle: Text(_mapSubtagToLanguage(settings.preferredLocaleSubtag)), + onTap: () => showDialog( + context: context, + builder: (_) => RadioSettingsDialog( + title: Text(S.of(context).settingsPageLanguageSettingLabel), + options: [ + RadioOption( + value: 'en', + label: _mapSubtagToLanguage('en'), + ), + RadioOption( + value: 'de', + label: _mapSubtagToLanguage('de'), + ), + ], + initialValue: + BlocProvider.of(context).state.preferredLocaleSubtag, + ), + ).then((value) => BlocProvider.of(context).setLocale(value)), + ); + }, + ); + } + + _mapSubtagToLanguage(String subtag) { + switch (subtag) { + case 'en': + return "English"; + case 'de': + return "Deutsch"; + default: + return "English"; + } + } +} diff --git a/lib/features/settings/view/widgets/radio_settings_dialog.dart b/lib/features/settings/view/widgets/radio_settings_dialog.dart new file mode 100644 index 0000000..bc41d2a --- /dev/null +++ b/lib/features/settings/view/widgets/radio_settings_dialog.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class RadioSettingsDialog extends StatefulWidget { + final List> options; + final T initialValue; + final Widget? title; + final Widget? confirmButton; + final Widget? cancelButton; + + const RadioSettingsDialog({ + super.key, + required this.options, + required this.initialValue, + this.title, + this.confirmButton, + this.cancelButton, + }); + + @override + State> createState() => _RadioSettingsDialogState(); +} + +class _RadioSettingsDialogState extends State> { + late T _groupValue; + + @override + void initState() { + super.initState(); + _groupValue = widget.initialValue; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + actions: [ + widget.confirmButton ?? + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(S.of(context).genericActionCancelLabel)), + widget.confirmButton ?? + TextButton( + onPressed: () => Navigator.pop(context, _groupValue), + child: Text(S.of(context).genericActionOkLabel)), + ], + title: widget.title, + content: Column( + mainAxisSize: MainAxisSize.min, + children: widget.options.map(_buildOptionListTile).toList(), + ), + ); + } + + Widget _buildOptionListTile(RadioOption option) => RadioListTile( + groupValue: _groupValue, + onChanged: (value) => setState(() => _groupValue = value!), + value: option.value, + title: Text(option.label), + ); +} + +class RadioOption { + final T value; + final String label; + + RadioOption({required this.value, required this.label}); +} diff --git a/lib/features/settings/view/widgets/theme_mode_setting.dart b/lib/features/settings/view/widgets/theme_mode_setting.dart new file mode 100644 index 0000000..1b8552a --- /dev/null +++ b/lib/features/settings/view/widgets/theme_mode_setting.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:flutter_paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; + +class ThemeModeSetting extends StatelessWidget { + const ThemeModeSetting({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, settings) { + return ListTile( + title: Text(S.of(context).settingsPageAppearanceSettingTitle), + subtitle: Text(_mapThemeModeToLocalizedString(settings.preferredThemeMode, context)), + onTap: () => showDialog( + context: context, + builder: (_) => RadioSettingsDialog( + options: [ + RadioOption( + value: ThemeMode.system, + label: S.of(context).settingsPageAppearanceSettingSystemThemeLabel, + ), + RadioOption( + value: ThemeMode.light, + label: S.of(context).settingsPageAppearanceSettingLightThemeLabel, + ), + RadioOption( + value: ThemeMode.dark, + label: S.of(context).settingsPageAppearanceSettingDarkThemeLabel, + ) + ], + initialValue: + BlocProvider.of(context).state.preferredThemeMode, + title: Text(S.of(context).settingsPageAppearanceSettingTitle), + ), + ).then((value) { + return BlocProvider.of(context).setThemeMode(value); + }), + ); + }, + ); + } + + String _mapThemeModeToLocalizedString(ThemeMode theme, BuildContext context) { + switch (theme) { + case ThemeMode.system: + return S.of(context).settingsThemeModeSystemLabel; + case ThemeMode.light: + return S.of(context).settingsThemeModeLightLabel; + case ThemeMode.dark: + return S.of(context).settingsThemeModeDarkLabel; + } + } +} diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb new file mode 100644 index 0000000..948c24c --- /dev/null +++ b/lib/l10n/intl_de.arb @@ -0,0 +1,171 @@ +{ + "@@locale": "de", + "documentTitlePropertyLabel": "Titel", + "documentCreatedPropertyLabel": "Erstellt Am", + "documentAddedPropertyLabel": "Hinzugefügt Am", + "documentModifiedPropertyLabel": "Geändert Am", + "documentDocumentTypePropertyLabel": "Dokumenttyp", + "documentCorrespondentPropertyLabel": "Korrespondent", + "documentStoragePathPropertyLabel": "Speicherpfad", + "documentTagsPropertyLabel": "Tags", + "documentArchiveSerialNumberPropertyLongLabel": "Archiv-Seriennummer", + "documentArchiveSerialNumberPropertyShortLabel": "ASN", + "appTitleText": "Paperless Mobile", + "bottomNavDocumentsPageLabel": "Dokumente", + "bottomNavScannerPageLabel": "Scanner", + "bottomNavLabelsPageLabel": "Kennzeichnungen", + "documentsPageTitle": "Dokumente", + "documentsFilterPageTitle": "Dokumente Filtern", + "documentsFilterPageAdvancedLabel": "Erweitert", + "documentsFilterPageDateRangeLastSevenDaysLabel": "Letzte 7 Tage", + "documentsFilterPageDateRangeLastMonthLabel": "Letzter Monat", + "documentsFilterPageDateRangeLastThreeMonthsLabel": "Letzten 3 Monate", + "documentsFilterPageDateRangeLastYearLabel": "Letztes Jahr", + "documentsFilterPageApplyFilterLabel": "Anwenden", + "documentsFilterPageResetFilterLabel": "Zurücksetzen", + "documentsFilterPageQueryOptionsTitleLabel": "Titel", + "documentsFilterPageQueryOptionsTitleAndContentLabel": "Titel & Inhalt", + "documentsFilterPageQueryOptionsExtendedLabel": "Erweitert", + "documentsFilterPageQueryOptionsAsnLabel": "ASN", + "documentsFilterPageDateRangeFieldStartLabel": "Von", + "documentsFilterPageDateRangeFieldEndLabel": "Bis", + "genericActionOkLabel": "Ok", + "genericActionCancelLabel": "Abbrechen", + "genericActionDeleteLabel": "Löschen", + "genericActionEditLabel": "Bearbeiten", + "genericActionSaveLabel": "Speichern", + "genericActionSelectText": "Auswählen", + "genericActionCreateLabel": "Erstellen", + "genericActionUploadLabel": "Hochladen", + "genericActionUpdateLabel": "Aktualisieren", + "appDrawerSettingsLabel": "Einstellungen", + "appDrawerAboutLabel": "Über", + "appDrawerAboutInfoLoadingText": "Lade Anwendungsinformationen...", + "appDrawerReportBugLabel": "Einen Fehler melden", + "appDrawerLogoutLabel": "Verbindung trennen", + "loginPageLoginButtonLabel": "Verbinden", + "loginPageTitle": "Mit Paperless verbinden", + "loginPageAdvancedLabel": "Erweiterte Einstellungen", + "loginPageServerUrlValidatorMessageText": "Server-Addresse darf nicht leer sein.", + "loginPageServerUrlFieldLabel": "Server-Adresse", + "loginPageUsernameValidatorMessageText": "Nutzername darf nicht leer sein.", + "loginPageUsernameLabel": "Nutzername", + "loginPagePasswordValidatorMessageText": "Passwort darf nicht leer sein.", + "loginPagePasswordFieldLabel": "Passwort", + "loginPageClientCertificatePassphraseLabel": "Passphrase", + "loginPageIncorrectOrMissingCertificatePassphraseErrorMessageText": "Falsche oder fehlende Zertifikatspassphrase.", + "documentDetailsPageTabOverviewLabel": "Übersicht", + "documentDetailsPageTabContentLabel": "Inhalt", + "documentDetailsPageTabMetaDataLabel": "Metadaten", + "documentsPageEmptyStateOopsText": "Ups.", + "documentsPageEmptyStateNothingHereText": "Es scheint nichts hier zu sein...", + "errorMessageUnknonwnError": "Ein unbekannter Fehler ist aufgetreten.", + "errorMessageAuthenticationFailed": "Authentifizierung fehlgeschlagen, bitte versuche es erneut.", + "errorMessageNotAuthenticated": "User is not authenticated.", + "errorMessageDocumentUploadFailed": "Dokument konnte nicht hochgeladen werden, bitte versuche es erneut.", + "errorMessageDocumentUpdateFailed": "Dokument konnte nicht aktualisiert werden, bitte versuche es erneut.", + "errorMessageDocumentDeleteFailed": "Dokument konnte nicht gelöscht werden, bitte versuche es erneut.", + "errorMessageDocumentPreviewFailed": "Vorschau konnte nicht geladen werden.", + "errorMessageDocumentAsnQueryFailed": "Archiv-Seriennummer konnte nicht zugewiesen werden.", + "errorMessageTagCreateFailed": "Tag konnte nicht erstellt werden, bitte versuche es erneut.", + "errorMessageTagLoadFailed": "Tags konnten nicht geladen werden.", + "errorMessageDocumentTypeCreateFailed": "Dokumenttyp konnte nicht erstellt werden, bitte versuche es erneut.", + "errorMessageDocumentTypeLoadFailed": "Dokumenttypen konnten nicht geladen werden, bitte versuche es erneut.", + "errorMessageCorrespondentCreateFailed": "Korrespondent konnte nicht erstellt werden, bitte versuche es erneut.", + "errorMessageCorrespondentLoadFailed": "Korrespondenten konnten nicht geladen werden.", + "errorMessageScanRemoveFailed": "Beim Löschen der Aufnahmen ist ein Fehler aufgetreten.", + "errorMessageInvalidClientCertificateConfiguration": "Ungültiges Zertifikat oder fehlende Passphrase, bitte versuche es erneut.", + "errorMessageDocumentLoadFailed": "Dokumente konnten nicht geladen werden, bitte versuche es erneut.", + "documentsPageSelectionBulkDeleteDialogTitle": "Löschen bestätigen", + "documentsPageSelectionBulkDeleteDialogWarningTextOne": "Sind Sie sicher, dass sie folgendes Dokument löschen wollen?", + "documentsPageSelectionBulkDeleteDialogWarningTextMany": "Sind Sie sicher, dass sie folgende Dokumente löschen wollen?", + "documentsPageSelectionBulkDeleteDialogContinueText": "Diese Aktion ist unwiderruflich. Möchten Sie trotzdem fortfahren?", + "documentPreviewPageTitle": "Vorschau", + "appSettingsBiometricAuthenticationLabel": "Biometrische Authentifizierung aktivieren", + "appSettingsEnableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu aktivieren.", + "appSettingsDisableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu deaktivieren.", + "errorMessageBulkDeleteDocumentsFailed": "Es ist ein Fehler beim massenhaften Löschen der Dokumente aufgetreten.", + "errorMessageBiotmetricsNotSupported": "Biometrische Authentifizierung wird von diesem Gerät nicht unterstützt.", + "errorMessageBiometricAuthenticationFailed": "Biometrische Authentifizierung fehlgeschlagen.", + "errorMessageDeviceOffline": "Daten konnten nicht geladen werden: Eine Verbindung zum Internet konnte nicht hergestellt werden.", + "errorMessageServerUnreachable": "Es konnte keine Verbindung zu Deinem Paperless Server hergestellt werden, ist die Instanz in Betrieb?", + "aboutDialogDevelopedByText": "Entwickelt von", + "documentsPageBulkDeleteSuccessfulText": "Das massenhafte Löschen der Dokumente war erfolgreich.", + "documentDeleteSuccessMessage": "Das Dokument wurde erfolgreich gelöscht.", + "documentsSelectedText": "ausgewählt", + "labelsPageCorrespondentsTitleText": "Korrespondenten", + "labelsPageDocumentTypesTitleText": "Dokumenttypen", + "labelsPageTagsTitleText": "Tags", + "tagColorPropertyLabel": "Farbe", + "tagInboxTagPropertyLabel": "Posteingangs-Tag", + "documentScannerPageEmptyStateText": "Es wurden noch keine Dokumente gescannt.", + "documentScannerPageTitle": "Scanner", + "documentScannerPageResetButtonTooltipText": "Alle scans löschen", + "documentScannerPageUploadButtonTooltip": "Dokument hochladen", + "addTagPageTitle": "Neuer Tag", + "addCorrespondentPageTitle": "Neuer Korrespondent", + "addDocumentTypePageTitle": "Neuer Dokumententyp", + "labelNamePropertyLabel": "Name", + "labelMatchPropertyLabel": "Zuweisungsmuster", + "labelMatchingAlgorithmPropertyLabel": "Zuweisungsalgorithmus", + "labelIsInsensivitePropertyLabel": "Groß-/Kleinschreibung irrelevant", + "linkedDocumentsPageTitle": "Referenzierte Dokumente", + "documentsUploadPageTitle": "Dokument vorbereiten", + "documentUploadFileNameLabel": "Dateiname", + "documentUploadSuccessText": "Das Dokument wurde erfolgreich hochgeladen. Verarbeite...", + "documentUploadProcessingSuccessfulText": "Das Dokument wurde erfolgreich verarbeitet.", + "documentUploadProcessingSuccessfulReloadActionText": "Neu laden", + "labelNotAssignedText": "Nicht zugewiesen", + "correspondentFormFieldSearchHintText": "Beginne zu tippen...", + "documentTypeFormFieldSearchHintText": "Beginne zu tippen...", + "tagFormFieldSearchHintText": "Beginne zu tippen...", + "documentsPageOrderByLabel": "Sortiere nach", + "documentDetailsPageAssignAsnButtonLabel": "Zuweisen", + "documentMetaDataMediaFilenamePropertyLabel": "Media-Dateiname", + "documentMetaDataChecksumLabel": "MD5-Prüfsumme Original", + "documentMetaDataOriginalFileSizeLabel": "Dateigröße Original", + "documentMetaDataOriginalMimeTypeLabel": "MIME-Typ Original", + "loginPageClientCertificateSettingLabel": "Client Zertifikat", + "loginPageClientCertificateSettingDescriptionText": "Konfiguriere Mutual TLS Authentifizierung", + "loginPageClientCertificateSettingInvalidFileFormatValidationText": "Ungültiges Zertifikatsformat, nur .pfx ist erlaubt.", + "loginPageClientCertificateSettingSelectFileText": "Datei auswählen...", + "uploadPageAutomaticallInferredFieldsHintText": "Wenn Werte für diese Felder angegeben werden, wird Paperless nicht automatisch einen Wert zuweisen. Wenn diese Felder automatisch von Paperless erkannt werden sollen, sollten die Felder leer bleiben.", + "settingsPageLanguageSettingLabel": "Sprache", + "settingsPageApplicationSettingsLabel": "Anwendung", + "settingsPageSecuritySettingsLabel": "Sicherheit", + "settingsPageApplicationSettingsDescriptionText": "Sprache und Aussehen", + "settingsPageSecuritySettingsDescriptionText": "Biometrische Authentifizierung", + "settingsPageAppearanceSettingTitle": "Aussehen", + "settingsPageAppearanceSettingSystemThemeLabel": "Benutze Sytemeinstellung", + "settingsPageAppearanceSettingLightThemeLabel": "Heller Modus", + "settingsPageAppearanceSettingDarkThemeLabel": "Dunkler Modus", + "appSettingsBiometricAuthenticationDescriptionText": "Authentifizierung beim Start der Anwendung", + "settingsThemeModeSystemLabel": "System", + "settingsThemeModeLightLabel": "Hell", + "settingsThemeModeDarkLabel": "Dunkel", + "genericMessageOfflineText": "Du bist offline. Überprüfe deine Verbindung.", + "documentDetailsPageSimilarDocumentsLabel": "Similar Documents", + "offlineWidgetText": "Es konte keine Verbindung zum Internet hergestellt werden.", + "labelsPageStoragePathTitleText": "Speicherpfade", + "addStoragePathPageTitle": "Neuer Speicherpfad", + "savedViewCreateNewLabel": "Neue Ansicht", + "savedViewNameLabel": "Name", + "savedViewShowOnDashboardLabel": "Auf Startseite zeigen", + "savedViewShowInSidebarLabel": "In Seitenleiste zeigen", + "savedViewsLabel": "Gespeicherte Ansichten", + "savedViewsEmptyStateText": "Lege Ansichten an, um Dokumente schneller zu finden.", + "savedViewCreateTooltipText": "Erstellt eine neue Ansicht basierend auf den aktuellen Filterkriterien.", + "documentsFilterPageSearchLabel": "Suche", + "documentEditPageTitle": "Dokument Bearbeiten", + "storagePathParameterDayLabel": "Tag", + "storagePathParameterYearLabel": "Jahr", + "storagePathParameterMonthLabel": "Monat", + "errorMessageSimilarQueryError": "Ähnliche Dokumente konnten nicht geladen werden.", + "errorMessageAutocompleteQueryError": "Beim automatischen Vervollständigen ist ein Fehler aufgetreten.", + "errorMessageStoragePathLoadFailed": "Speicherpfade konnten nicht geladen werden.", + "errorMessageStoragePathCreateFailed": "Speicherpfad konnte nicht erstellt werden, bitte versuche es erneut.", + "errorMessageLoadSavedViewsError": "Gespeicherte Ansichten konnten nicht geladen werden.", + "errorMessageCreateSavedViewError": "Gespeicherte Ansicht konnte nicht erstellt werden, bitte versuche es erneut.", + "errorMessageDeleteSavedViewError": "Gespeicherte Ansicht konnte nicht geklöscht werden, bitte versuche es erneut.", + "errorMessageRequestTimedOut": "Bei der Anfrage an den Server kam es zu einer Zeitüberschreitung." +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..bd253dd --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,172 @@ +{ + "@@locale": "en", + "documentTitlePropertyLabel": "Title", + "documentCreatedPropertyLabel": "Created At", + "documentAddedPropertyLabel": "Added At", + "documentModifiedPropertyLabel": "Modified At", + "documentDocumentTypePropertyLabel": "Document Type", + "documentCorrespondentPropertyLabel": "Correspondent", + "documentStoragePathPropertyLabel": "Storage Path", + "documentTagsPropertyLabel": "Tags", + "documentArchiveSerialNumberPropertyLongLabel": "Archive Serial Number", + "documentArchiveSerialNumberPropertyShortLabel": "ASN", + "appTitleText": "Paperless Mobile", + "bottomNavDocumentsPageLabel": "Documents", + "bottomNavScannerPageLabel": "Scanner", + "bottomNavLabelsPageLabel": "Labels", + "documentsPageTitle": "Documents", + "documentsFilterPageTitle": "Filter Documents", + "documentsFilterPageAdvancedLabel": "Advanced", + "documentsFilterPageDateRangeLastSevenDaysLabel": "Last 7 Days", + "documentsFilterPageDateRangeLastMonthLabel": "Last Month", + "documentsFilterPageDateRangeLastThreeMonthsLabel": "Last 3 Months", + "documentsFilterPageDateRangeLastYearLabel": "Last Year", + "documentsFilterPageApplyFilterLabel": "Apply", + "documentsFilterPageResetFilterLabel": "Reset", + "documentsFilterPageQueryOptionsTitleLabel": "Title", + "documentsFilterPageQueryOptionsTitleAndContentLabel": "Title & Content", + "documentsFilterPageQueryOptionsExtendedLabel": "Extended", + "documentsFilterPageQueryOptionsAsnLabel": "ASN", + "documentsFilterPageDateRangeFieldStartLabel": "From", + "documentsFilterPageDateRangeFieldEndLabel": "To", + "genericActionOkLabel": "Ok", + "genericActionCancelLabel": "Cancel", + "genericActionSelectText": "Select", + "genericActionDeleteLabel": "Delete", + "genericActionEditLabel": "Edit", + "genericActionSaveLabel": "Save", + "genericActionCreateLabel": "Create", + "genericActionUploadLabel": "Upload", + "genericActionUpdateLabel": "Update", + "appDrawerSettingsLabel": "Settings", + "appDrawerAboutLabel": "About", + "appDrawerAboutInfoLoadingText": "Retrieving application information...", + "appDrawerReportBugLabel": "Report a Bug", + "appDrawerLogoutLabel": "Disconnect", + "loginPageLoginButtonLabel": "Connect", + "loginPageTitle": "Connect to Paperless", + "loginPageAdvancedLabel": "Advanced Settings", + "loginPageServerUrlValidatorMessageText": "Server address must not be empty.", + "loginPageServerUrlFieldLabel": "Server Address", + "loginPageUsernameValidatorMessageText": "Username must not be empty.", + "loginPageUsernameLabel": "Username", + "loginPagePasswordValidatorMessageText": "Password must not be empty.", + "loginPagePasswordFieldLabel": "Password", + "loginPageClientCertificatePassphraseLabel": "Passphrase", + "loginPageIncorrectOrMissingCertificatePassphraseErrorMessageText": "Incorrect or missing certificate passphrase.", + "documentDetailsPageTabOverviewLabel": "Overview", + "documentDetailsPageTabContentLabel": "Content", + "documentDetailsPageTabMetaDataLabel": "Meta Data", + "documentsPageEmptyStateOopsText": "Oops.", + "documentsPageEmptyStateNothingHereText": "There seems to be nothing here...", + "errorMessageUnknonwnError": "An unknown error occurred.", + "errorMessageAuthenticationFailed": "Authentication failed, please try again.", + "errorMessageNotAuthenticated": "User is not authenticated.", + "errorMessageDocumentUploadFailed": "Could not upload document, please try again.", + "errorMessageDocumentUpdateFailed": "Could not update document, please try again.", + "errorMessageDocumentDeleteFailed": "Could not delete document, please try again.", + "errorMessageDocumentPreviewFailed": "Could not load document preview.", + "errorMessageDocumentAsnQueryFailed": "Could not assign archive serial number.", + "errorMessageTagCreateFailed": "Could not create tag, please try again.", + "errorMessageTagLoadFailed": "Could not load tags.", + "errorMessageDocumentTypeCreateFailed": "Could not create document, please try again.", + "errorMessageDocumentTypeLoadFailed": "Could not load document types, please try again.", + "errorMessageCorrespondentCreateFailed": "Could not create correspondent, please try again.", + "errorMessageCorrespondentLoadFailed": "Could not load correspondents.", + "errorMessageScanRemoveFailed": "An error occurred removing the scans.", + "errorMessageInvalidClientCertificateConfiguration": "Invalid certificate or missing passphrase, please try again", + "errorMessageDocumentLoadFailed": "Could not load documents, please try again.", + "documentsPageSelectionBulkDeleteDialogTitle": "Confirm Deletion", + "documentsPageSelectionBulkDeleteDialogWarningTextOne": "Are you sure you want to delete the following document?", + "documentsPageSelectionBulkDeleteDialogWarningTextMany": "Are you sure you want to delete the following documents?", + "documentsPageSelectionBulkDeleteDialogContinueText": "This action is irreversible. Do you wish to proceed anyway?", + "documentPreviewPageTitle": "Preview", + "appSettingsEnableBiometricAuthenticationReasonText": "Authenticate to enable biometric authentication", + "appSettingsDisableBiometricAuthenticationReasonText": "Authenticate to disable biometric authentication", + "errorMessageBulkDeleteDocumentsFailed": "Could not bulk delete documents.", + "errorMessageBiotmetricsNotSupported": "Biometric authentication not supported on this device.", + "errorMessageBiometricAuthenticationFailed": "Biometric authentication failed.", + "errorMessageDeviceOffline": "Could not fetch data: You are not connected to the internet.", + "errorMessageServerUnreachable": "Could not reach your Paperless server, is it up and running?", + "aboutDialogDevelopedByText": "Developed by", + "documentsPageBulkDeleteSuccessfulText": "Documents successfully deleted.", + "documentDeleteSuccessMessage": "Document successfully deleted.", + "documentsSelectedText": "selected", + "labelsPageCorrespondentsTitleText": "Correspondents", + "labelsPageDocumentTypesTitleText": "Document Types", + "labelsPageTagsTitleText": "Tags", + "tagColorPropertyLabel": "Color", + "tagInboxTagPropertyLabel": "Inbox-Tag", + "documentScannerPageEmptyStateText": "No documents scanned yet.", + "documentScannerPageTitle": "Scan", + "documentScannerPageResetButtonTooltipText": "Delete all scans", + "documentScannerPageUploadButtonTooltip": "Upload to Paperless", + "addTagPageTitle": "New Tag", + "addCorrespondentPageTitle": "New Correspondent", + "addDocumentTypePageTitle": "New Document Type", + "labelNamePropertyLabel": "Name", + "labelMatchPropertyLabel": "Match", + "labelMatchingAlgorithmPropertyLabel": "Matching Algorithm", + "labelIsInsensivitePropertyLabel": "Case Irrelevant", + "linkedDocumentsPageTitle": "Linked Documents", + "documentsUploadPageTitle": "Prepare document", + "documentUploadFileNameLabel": "File Name", + "documentUploadSuccessText": "Document successfully uploaded, processing...", + "documentUploadProcessingSuccessfulText": "Document successfully processed.", + "documentUploadProcessingSuccessfulReloadActionText": "Reload", + "labelNotAssignedText": "Not assigned", + "correspondentFormFieldSearchHintText": "Start typing...", + "documentTypeFormFieldSearchHintText": "Start typing...", + "tagFormFieldSearchHintText": "Filter tags...", + "documentsPageOrderByLabel": "Order By", + "documentDetailsPageAssignAsnButtonLabel": "Assign", + "documentMetaDataMediaFilenamePropertyLabel": "Media Filename", + "documentMetaDataChecksumLabel": "Original MD5-Checksum", + "documentMetaDataOriginalFileSizeLabel": "Original File Size", + "documentMetaDataOriginalMimeTypeLabel": "Original MIME-Type", + "loginPageClientCertificateSettingLabel": "Client Certificate", + "loginPageClientCertificateSettingDescriptionText": "Configure Mutual TLS Authentication", + "loginPageClientCertificateSettingInvalidFileFormatValidationText": "Invalid certificate format, only .pfx is allowed", + "loginPageClientCertificateSettingSelectFileText": "Select file...", + "uploadPageAutomaticallInferredFieldsHintText": "If you specify values for these fields, your paperless instance will not automatically derive a value. If you want these values to be automatically populated by your server, leave the fields blank.", + "settingsPageLanguageSettingLabel": "Language", + "settingsPageApplicationSettingsLabel": "Application", + "settingsPageSecuritySettingsLabel": "Security", + "appSettingsBiometricAuthenticationLabel": "Biometric authentication", + "settingsPageApplicationSettingsDescriptionText": "Language and visual appearance", + "settingsPageSecuritySettingsDescriptionText": "Biometric authentication", + "settingsPageAppearanceSettingTitle": "Appearance", + "settingsPageAppearanceSettingDescriptionText": "Choose your light or dark theme preference", + "settingsPageAppearanceSettingSystemThemeLabel": "Use system theme", + "settingsPageAppearanceSettingLightThemeLabel": "Light Theme", + "settingsPageAppearanceSettingDarkThemeLabel": "Dark Theme", + "appSettingsBiometricAuthenticationDescriptionText": "Authenticate on app start", + "settingsThemeModeSystemLabel": "System", + "settingsThemeModeLightLabel": "Light", + "settingsThemeModeDarkLabel": "Dark", + "genericMessageOfflineText": "You're offline. Check your connection.", + "documentDetailsPageSimilarDocumentsLabel": "Similar Documents", + "offlineWidgetText": "An internet connection could not be established.", + "labelsPageStoragePathTitleText": "Storage Paths", + "addStoragePathPageTitle": "New Storage Path", + "savedViewCreateNewLabel": "New View", + "savedViewNameLabel": "Name", + "savedViewShowOnDashboardLabel": "Show on dashboard", + "savedViewShowInSidebarLabel": "Show in sidebar", + "savedViewsLabel": "Saved Views", + "savedViewsEmptyStateText": "Create views to quickly filter your documents.", + "savedViewCreateTooltipText": "Creates a new view based on the current filter criteria.", + "documentsFilterPageSearchLabel": "Search", + "documentEditPageTitle": "Edit Document", + "storagePathParameterDayLabel": "day", + "storagePathParameterYearLabel": "year", + "storagePathParameterMonthLabel": "month", + "errorMessageSimilarQueryError": "Could not load similar documents.", + "errorMessageAutocompleteQueryError": "An error ocurred while trying to autocomplete your query.", + "errorMessageStoragePathLoadFailed": "Could not load storage paths.", + "errorMessageStoragePathCreateFailed": "Could not create storage path, please try again.", + "errorMessageLoadSavedViewsError": "Could not load saved views.", + "errorMessageCreateSavedViewError": "Could not create saved view, please try again.", + "errorMessageDeleteSavedViewError": "Could not delete saved view, please try again", + "errorMessageRequestTimedOut": "The request to the server timed out." +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..6118d55 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,156 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:flutter_paperless_mobile/core/bloc/connectivity_cubit.dart'; +import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:flutter_paperless_mobile/core/global/http_self_signed_certificate_override.dart'; +import 'package:flutter_paperless_mobile/di_initializer.dart'; +import 'package:flutter_paperless_mobile/features/app_intro/application_intro_slideshow.dart'; +import 'package:flutter_paperless_mobile/features/home/view/home_page.dart'; +import 'package:flutter_paperless_mobile/features/login/bloc/authentication_cubit.dart'; +import 'package:flutter_paperless_mobile/features/login/view/login_page.dart'; +import 'package:flutter_paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; +import 'package:flutter_paperless_mobile/features/settings/model/application_settings_state.dart'; +import 'package:flutter_paperless_mobile/generated/l10n.dart'; +import 'package:flutter_paperless_mobile/util.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/intl_standalone.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +void main() async { + final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + Intl.systemLocale = await findSystemLocale(); + + // Required for client certificates + HttpOverrides.global = X509HttpOverrides(); + + configureDependencies(); + + kPackageInfo = await PackageInfo.fromPlatform(); + // Load application settings and stored authentication data + getIt().initialize(); + await getIt().initialize(); + await getIt().initialize(); + // Ogaylesgo + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: getIt()), + BlocProvider.value(value: getIt()), + ], + child: BlocBuilder( + bloc: getIt(), + builder: (context, settings) { + return MaterialApp( + debugShowCheckedModeBanner: true, + title: "Paperless Mobile", + theme: ThemeData( + brightness: Brightness.light, + useMaterial3: true, + colorSchemeSeed: Colors.lightGreen, + appBarTheme: const AppBarTheme( + scrolledUnderElevation: 0.0, + ), + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), + chipTheme: ChipThemeData( + backgroundColor: Colors.lightGreen[50], + ), + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + useMaterial3: true, + colorSchemeSeed: Colors.lightGreen, + //primarySwatch: Colors.green, + appBarTheme: const AppBarTheme( + scrolledUnderElevation: 0.0, + ), + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), + chipTheme: ChipThemeData( + backgroundColor: Colors.green[900], + ), + ), + themeMode: settings.preferredThemeMode, + supportedLocales: const [ + Locale('en'), // Default if system locale is not available + Locale('de'), + ], + locale: Locale.fromSubtags(languageCode: settings.preferredLocaleSubtag), + localizationsDelegates: const [ + S.delegate, + FormBuilderLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + home: const AuthenticationWrapper(), + ); + }, + ), + ); + } +} + +class AuthenticationWrapper extends StatefulWidget { + const AuthenticationWrapper({Key? key}) : super(key: key); + + @override + State createState() => _AuthenticationWrapperState(); +} + +class _AuthenticationWrapperState extends State { + @override + void initState() { + FlutterNativeSplash.remove(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: BlocConsumer( + listener: (context, authState) { + final bool showIntroSlider = authState.isAuthenticated && !authState.wasLoginStored; + if (showIntroSlider) { + 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(); + } + }, + ), + ); + } +} diff --git a/lib/util.dart b/lib/util.dart new file mode 100644 index 0000000..92af1a6 --- /dev/null +++ b/lib/util.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_paperless_mobile/core/logic/error_code_localization_mapper.dart'; +import 'package:flutter_paperless_mobile/core/model/error_message.dart'; +import 'package:intl/intl.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; + +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 showError(BuildContext context, ErrorMessage error) { + showSnackBar(context, translateError(context, error.code)); +} + +bool isNotNull(dynamic value) { + return value != null; +} + +String formatDate(DateTime date) { + return dateFormat.format(date); +} + +String? formatDateNullable(DateTime? date) { + if (date == null) return null; + return dateFormat.format(date); +} + +Future writeToFile(Uint8List data) async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = tempDir.path; + var filePath = tempPath + '/file_01.tmp'; // file_01.tmp is dump file, can be anything + return (await File(filePath).writeAsBytes(data)).path; +} + +void setKeyNullable(Map data, String key, dynamic value) { + if (value != null) { + data[key] = value is String ? value : json.encode(value); + } +} + +String formatLocalDate(BuildContext context, DateTime dateTime) { + final tag = Localizations.maybeLocaleOf(context)?.toLanguageTag(); + return DateFormat.yMMMd(tag).format(dateTime); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..02cb707 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1504 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "47.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + badges: + dependency: "direct main" + description: + name: badges + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + barcode: + dependency: transitive + description: + name: barcode + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.0" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "9.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.11" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.3.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.9" + connectivity_plus_linux: + dependency: transitive + description: + name: connectivity_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + connectivity_plus_macos: + dependency: transitive + description: + name: connectivity_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + connectivity_plus_web: + dependency: transitive + description: + name: connectivity_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.5" + connectivity_plus_windows: + dependency: transitive + description: + name: connectivity_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.2" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + dbus: + dependency: transitive + description: + name: dbus + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.8" + dependency_validator: + dependency: "direct dev" + description: + name: dependency_validator + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.2" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.3" + device_info_plus_linux: + dependency: transitive + description: + name: device_info_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + device_info_plus_macos: + dependency: transitive + description: + name: device_info_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + device_info_plus_web: + dependency: transitive + description: + name: device_info_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + device_info_plus_windows: + dependency: transitive + description: + name: device_info_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" + dropdown_search: + dependency: transitive + description: + name: dropdown_search + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.3" + edge_detection: + dependency: "direct main" + description: + name: edge_detection + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.9" + encrypt: + dependency: transitive + description: + name: encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.1" + encrypted_shared_preferences: + dependency: "direct main" + description: + name: encrypted_shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + extension: + dependency: transitive + description: + name: extension + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + file_picker: + dependency: "direct main" + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.1" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + flutter_chips_input: + dependency: "direct main" + description: + name: flutter_chips_input + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_colorpicker: + dependency: transitive + description: + name: flutter_colorpicker + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + flutter_datetime_picker_bdaya: + dependency: transitive + description: + name: flutter_datetime_picker_bdaya + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_form_builder: + dependency: "direct main" + description: + name: flutter_form_builder + url: "https://pub.dartlang.org" + source: hosted + version: "7.5.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + url: "https://pub.dartlang.org" + source: hosted + version: "5.4.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.11" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + flutter_rating_bar: + dependency: transitive + description: + name: flutter_rating_bar + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_touch_spin: + dependency: transitive + description: + name: flutter_touch_spin + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_typeahead: + dependency: transitive + description: + name: flutter_typeahead + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "10.1.0" + form_builder_extra_fields: + dependency: "direct main" + description: + path: "." + ref: main + resolved-ref: "33ba0a4407086275ac4357badc631be550fb3bcc" + url: "https://github.com/flutter-form-builder-ecosystem/form_builder_extra_fields.git" + source: git + version: "8.4.0" + form_builder_validators: + dependency: "direct main" + description: + name: form_builder_validators + url: "https://pub.dartlang.org" + source: hosted + version: "8.3.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_interceptor: + dependency: "direct main" + description: + name: http_interceptor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0-beta.5" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + image: + dependency: "direct main" + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + infinite_scroll_pagination: + dependency: "direct main" + description: + name: infinite_scroll_pagination + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + injectable: + dependency: "direct main" + description: + name: injectable + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.4" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + intro_slider: + dependency: "direct main" + description: + name: intro_slider + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + local_auth: + dependency: "direct main" + description: + name: local_auth + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.13" + local_auth_ios: + dependency: transitive + description: + name: local_auth_ios + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.2" + mocktail: + dependency: transitive + description: + name: mocktail + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.3+1" + package_info_plus_linux: + dependency: transitive + description: + name: package_info_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_info_plus_macos: + dependency: transitive + description: + name: package_info_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + package_info_plus_web: + dependency: transitive + description: + name: package_info_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_info_plus_windows: + dependency: transitive + description: + name: package_info_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.10" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + pdf: + dependency: "direct main" + description: + name: pdf + url: "https://pub.dartlang.org" + source: hosted + version: "3.8.1" + pdfx: + dependency: "direct main" + description: + name: pdfx + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.2+1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + photo_view: + dependency: "direct main" + description: + name: photo_view + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + qr: + dependency: transitive + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.4" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.12" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + signature: + dependency: transitive + description: + name: signature + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + sliding_up_panel: + dependency: "direct main" + description: + name: sliding_up_panel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+1" + sliver_tools: + dependency: transitive + description: + name: sliver_tools + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.7" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1+1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+2" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.21.4" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.16" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + universal_platform: + dependency: transitive + description: + name: universal_platform + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: "direct main" + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..399ee4b --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,143 @@ +name: flutter_paperless_mobile +description: + Application to conveniently scan and share documents with a paperless-ng + server. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.1 + +environment: + sdk: ">=2.17.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + get_it: ^7.2.0 + injectable: ^1.5.3 + encrypted_shared_preferences: ^3.0.0 + permission_handler: ^9.2.0 + pdf: ^3.8.1 + pdfx: ^2.3.0 + edge_detection: ^1.0.9 + path_provider: ^2.0.10 + image: ^3.1.3 + photo_view: ^0.14.0 + intl: ^0.17.0 + flutter_svg: ^1.0.3 + url_launcher: ^6.1.2 + file_picker: ^3.0.4 + web_socket_channel: ^2.2.0 + http: ^0.13.4 + http_interceptor: ^2.0.0-beta.5 + flutter_cache_manager: ^3.3.0 + cached_network_image: ^3.2.1 + shimmer: ^2.0.0 + flutter_bloc: ^8.1.1 + equatable: ^2.0.3 + flutter_form_builder: ^7.5.0 + form_builder_extra_fields: + git: + url: https://github.com/flutter-form-builder-ecosystem/form_builder_extra_fields.git + ref: main + form_builder_validators: ^8.3.0 + infinite_scroll_pagination: ^3.2.0 + flutter_chips_input: ^2.0.0 + sliding_up_panel: ^2.0.0+1 + package_info_plus: ^1.4.3+1 + font_awesome_flutter: ^10.1.0 + badges: ^2.0.3 + local_auth: ^2.1.2 + intro_slider: ^4.2.0 + connectivity_plus: ^2.3.9 + + flutter_native_splash: ^2.2.11 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + injectable_generator: ^1.5.3 + mockito: ^5.3.2 + bloc_test: ^9.1.0 + dependency_validator: ^3.0.0 + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec +# The following section is specific to Flutter. +flutter: + generate: true + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + assets: + - assets/logos/ + - assets/images/ + - test/fixtures/ + - test/fixtures/documents/ + - test/fixtures/correspondents/ + - test/fixtures/tags/ + - test/fixtures/preview/ + - test/fixtures/document_types/ + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages +flutter_intl: + enabled: true + +flutter_native_splash: + image: assets/logos/paperless_logo_green.png + color: "#f9f9f9" + + image_dark: assets/logos/paperless_logo_white.png + color_dark: "#181818" diff --git a/test/fixtures/correspondents/correspondents.json b/test/fixtures/correspondents/correspondents.json new file mode 100644 index 0000000..25ce5b4 --- /dev/null +++ b/test/fixtures/correspondents/correspondents.json @@ -0,0 +1,28 @@ +[{ + "id": 1, + "slug": "correspondent1", + "name": "Correspondent 1", + "match": "correspondent1", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 3 + }, + { + "id": 2, + "slug": "correspondent2", + "name": "Correspondent 2", + "match": "correspondent2", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 3 + }, + { + "id": 3, + "slug": "correspondent3", + "name": "Correspondent 3", + "match": "correspondent3", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 3 + } +] \ No newline at end of file diff --git a/test/fixtures/document_types/document_types.json b/test/fixtures/document_types/document_types.json new file mode 100644 index 0000000..3acc24e --- /dev/null +++ b/test/fixtures/document_types/document_types.json @@ -0,0 +1,47 @@ +[ + { + "id": 1, + "slug": "rechnung", + "name": "Rechnung", + "match": "rechnung", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 5 + }, + { + "id": 2, + "slug": "vertrag", + "name": "Vertrag", + "match": "vertrag", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 1 + }, + { + "id": 3, + "slug": "finanzunterlagen", + "name": "Finanzunterlagen", + "match": "finanz", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 2 + }, + { + "id": 4, + "slug": "lieferschein", + "name": "Lieferschein", + "match": "lieferschein", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 1 + }, + { + "id": 5, + "slug": "paper", + "name": "Paper", + "match": "paper", + "matching_algorithm": 1, + "is_insensitive": true, + "document_count": 1 + } +] \ No newline at end of file diff --git a/test/fixtures/documents/documents.json b/test/fixtures/documents/documents.json new file mode 100644 index 0000000..e40d671 --- /dev/null +++ b/test/fixtures/documents/documents.json @@ -0,0 +1,161 @@ +[{ + "id": 1, + "correspondent": 1, + "document_type": 1, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 2, + "correspondent": 1, + "document_type": 1, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 3, + "correspondent": 1, + "document_type": 1, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 4, + "correspondent": 1, + "document_type": 1, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 5, + "correspondent": 1, + "document_type": 1, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 6, + "correspondent": 1, + "document_type": 2, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 7, + "correspondent": 1, + "document_type": 3, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 8, + "correspondent": 1, + "document_type": 4, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 9, + "correspondent": 1, + "document_type": 5, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + }, + { + "id": 10, + "correspondent": 1, + "document_type": 3, + "storage_path": null, + "title": "Rechnung 1 title", + "content": "Rechnung 1 content", + "tags": [], + "created": "2020-01-15T00:00:00Z", + "created_date": "2020-01-15", + "modified": "2020-01-17T00:00:00Z", + "added": "2020-01-16T17:25:00Z", + "archive_serial_number": null, + "original_file_name": "document_1.pdf", + "archived_file_name": "document_1.pdf" + } +] \ No newline at end of file diff --git a/test/fixtures/preview/example_document_preview.png b/test/fixtures/preview/example_document_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..29997f87a1e10ccd219e3f6b27b62140f3611bea GIT binary patch literal 70927 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