Merge branch 'main' into download_to_public
79
README.md
@@ -79,20 +79,34 @@ To get a local copy up and running follow these simple steps.
|
|||||||
* Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions)
|
* Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions)
|
||||||
|
|
||||||
### Install dependencies and generate files
|
### Install dependencies and generate files
|
||||||
|
1. First, clone the repository:
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/astubenbord/paperless-mobile.git
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now either run the `install_dependencies.sh` script at the root of the project, which will automatically install dependencies and generate code for both the app and subpackages, or you can manually run the following steps:
|
||||||
|
|
||||||
1. Clone the repo
|
#### Inside the `packages/paperless_api/` folder:
|
||||||
```sh
|
|
||||||
git clone https://github.com/astubenbord/paperless-mobile.git
|
2. Install the dependencies for `paperless_api`
|
||||||
```
|
|
||||||
2. Install the dependencies
|
|
||||||
```sh
|
```sh
|
||||||
flutter pub get
|
flutter pub get
|
||||||
```
|
```
|
||||||
3. Build generated files (for json_serializable etc.)
|
3. Build generated files for `paperless_api`
|
||||||
|
```sh
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inside the project's root folder
|
||||||
|
4. Install the dependencies for the app
|
||||||
|
```sh
|
||||||
|
flutter pub get
|
||||||
|
```
|
||||||
|
5. Build generated files for the app
|
||||||
```sh
|
```sh
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
4. Generate the localization files
|
6. Generate the localization files for the app
|
||||||
```sh
|
```sh
|
||||||
flutter pub run intl_utils:generate
|
flutter pub run intl_utils:generate
|
||||||
```
|
```
|
||||||
@@ -117,14 +131,18 @@ buildTypes {
|
|||||||
```
|
```
|
||||||
2. Build the app with release profile (here for android):
|
2. Build the app with release profile (here for android):
|
||||||
```sh
|
```sh
|
||||||
flutter build apk --split-per-abi
|
flutter build apk
|
||||||
```
|
```
|
||||||
(the --release flag is implicit for the build command)
|
The --release flag is implicit for the build command. You can also run this command with --split-per-abi, which will generate three separate (smaller) binaries.
|
||||||
|
|
||||||
3. Install the app to your device
|
3. Install the app to your device (when omitting the `--split-per-abi` flag)
|
||||||
```sh
|
```sh
|
||||||
flutter install
|
flutter install
|
||||||
```
|
```
|
||||||
|
or when you built with `--split-per-abi`
|
||||||
|
```sh
|
||||||
|
flutter install --use-application-binary=build/pp/outputs/flutter-apk/<apk_file_name>.apk
|
||||||
|
```
|
||||||
|
|
||||||
## Languages and Translations
|
## Languages and Translations
|
||||||
If you want to contribute to translate the app into your language, create a new <a href="https://github.com/astubenbord/paperless-mobile/discussions/categories/languages-and-translations">Discussion</a> and you will be invited to the <a href="https://localizely.com/">Localizely</a> project.
|
If you want to contribute to translate the app into your language, create a new <a href="https://github.com/astubenbord/paperless-mobile/discussions/categories/languages-and-translations">Discussion</a> and you will be invited to the <a href="https://localizely.com/">Localizely</a> project.
|
||||||
@@ -138,17 +156,13 @@ This project is registered as an open source project in Localizely, which offers
|
|||||||
|
|
||||||
<!-- ROADMAP -->
|
<!-- ROADMAP -->
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [x] Add download functionality (implemented, but flutter cannot download to useful directories except app directory)
|
- [ ] Fully custom document scanner optimized for common white A4 documents and optimized for the use with Paperless
|
||||||
- [x] Add document share support
|
|
||||||
- [x] Improvements to UX (e.g. form fields show clear button while empty)
|
|
||||||
- [ ] Add more languages
|
- [ ] Add more languages
|
||||||
- [ ] Support for IOS
|
- [ ] Support for IOS and publish to AppStore
|
||||||
- [ ] Automatic releases and CI/CD with fastlane
|
- [ ] 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)
|
- [ ] 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).
|
See the [open issues](https://github.com/astubenbord/paperless-mobile/issues) for a full list of issues and [open feature requests](https://github.com/astubenbord/paperless-mobile/discussions/categories/feature-requests) for requested features.
|
||||||
|
|
||||||
<!-- CONTRIBUTING -->
|
<!-- CONTRIBUTING -->
|
||||||
## Contributing
|
## Contributing
|
||||||
@@ -173,35 +187,6 @@ I do this in my free time, so if you like the project, consider buying me a coff
|
|||||||
|
|
||||||
[](https://www.buymeacoffee.com/astubenbord)
|
[](https://www.buymeacoffee.com/astubenbord)
|
||||||
|
|
||||||
|
|
||||||
<!-- USAGE EXAMPLES -->
|
|
||||||
## Screenshots
|
|
||||||
Here are some impressions from the app!
|
|
||||||
|
|
||||||
#### Login Page
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198392006-f3badfb3-17c7-4b46-91c7-595c93b146b7.png" width=200/> <img src="https://user-images.githubusercontent.com/79228196/198392041-1ef5de1e-7d26-47f6-bdfb-f5ac831ddb30.png" width=200/>
|
|
||||||
|
|
||||||
#### Documents Overview (List)
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198392750-a0e4c0b1-9c1c-4346-980a-64d1cc192a99.png" width=200> <img src="https://user-images.githubusercontent.com/79228196/198392767-995536e4-5737-476a-9e78-34c37fac9c60.png" width=200>
|
|
||||||
|
|
||||||
#### Documents Overview (Grid)
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198393000-83a32969-c0d8-4f81-bb20-8afc79057d62.png" width=200> <img src="https://user-images.githubusercontent.com/79228196/198393018-2f1d02fc-a410-45d8-a022-32c0ae377717.png" width=200>
|
|
||||||
|
|
||||||
#### Document Filter/Search (More filters below!)
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198393168-60aa5114-85a8-4def-9ca9-5374e0b92aef.png" width=200> <img src="https://user-images.githubusercontent.com/79228196/198393173-db38e99e-f408-4a31-bc6a-fcce91a2a900.png" width=200>
|
|
||||||
|
|
||||||
#### Document Details
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198393856-6b11dbdc-77ce-44e8-a69c-b0a2536cd38b.png" width=200> <img src="https://user-images.githubusercontent.com/79228196/198393867-39e2148e-53a7-4fc9-8b6d-2ab038dfea64.png" width=200>
|
|
||||||
|
|
||||||
#### Edit Document
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198393926-1adc3fe8-6981-4b20-854e-6d17611a1d7a.png" width=200><img src="https://user-images.githubusercontent.com/79228196/198393931-c3b214db-e96e-4da4-8327-9c4779c2c64a.png" width=200>
|
|
||||||
|
|
||||||
#### Scan
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198394782-0955a57b-90c6-4c42-946c-ecf5f94bf704.png" width=200><img src="https://user-images.githubusercontent.com/79228196/198394796-cc7a5bb3-81b4-4010-9444-33440eb9aef7.png" width=200>
|
|
||||||
|
|
||||||
#### Upload
|
|
||||||
<img src="https://user-images.githubusercontent.com/79228196/198394876-7438dcfe-d901-4ac8-8e7f-0eba7c72a5d7.png" width=200><img src="https://user-images.githubusercontent.com/79228196/198394883-2721211b-17dc-405b-9ee9-2ca943e630fa.png" width=200>
|
|
||||||
|
|
||||||
<!-- MARKDOWN LINKS & IMAGES -->
|
<!-- MARKDOWN LINKS & IMAGES -->
|
||||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||||
[contributors-shield]: https://img.shields.io/github/contributors/astubenbord/paperless-mobile.svg?style=for-the-badge
|
[contributors-shield]: https://img.shields.io/github/contributors/astubenbord/paperless-mobile.svg?style=for-the-badge
|
||||||
@@ -229,4 +214,4 @@ Made with [contrib.rocks](https://contrib.rocks).
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
#### Suggestions are not selectable in any of the label form fields
|
#### Suggestions are not selectable in any of the label form fields
|
||||||
This is a known issue and it has to do with accessibility features of Android. Password managers such as Bitwarden often caused this issue to occur. Luckily, this can be resolved by turning off the accessibility features in these apps.
|
This is a known issue and it has to do with accessibility features of Android. Password managers such as Bitwarden often caused this issue. Luckily, this can be resolved by turning off the accessibility features in these apps. This could also be observed with apps that are allowed to display over other apps, such as emulations of the dynamic island on android.
|
||||||
|
|||||||
15
fastlane/metadata/full_description.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
With this app you can conveniently add, manage or simply find documents stored in your paperless server without any compromises.
|
||||||
|
|
||||||
|
🚀 Features
|
||||||
|
✔️ View your documents at a glance, in a compact list or a more detailed grid view
|
||||||
|
✔️ Add, delete or edit your documents
|
||||||
|
✔️ Share, download and preview PDF files
|
||||||
|
✔️ Manage and assign correspondents, document types, tags and storage paths
|
||||||
|
✔️ Scan and upload documents to paperless with preset correspondent, document type, tags and creation date
|
||||||
|
✔️ Upload existing documents from other apps via Paperless Mobile
|
||||||
|
✔️ See all new documents in a dedicated inbox
|
||||||
|
✔️ Search for documents using a wide range of filter criteria
|
||||||
|
✔️ Secure your data with biometric authentication across sessions
|
||||||
|
✔️ Support for TLS mutual authentication (client certificates)
|
||||||
|
✔️ Modern, intuitive UI built according to the Material Design 3 specification
|
||||||
|
✔️ Available in English and German language (more to come!)
|
||||||
BIN
fastlane/metadata/images/featureGraphic.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/1.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/2.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/3.png
Normal file
|
After Width: | Height: | Size: 249 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/4.png
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/5.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/6.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/7.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
fastlane/metadata/images/phoneScreenshots/8.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
1
fastlane/metadata/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
An (almost) fully fledged Paperless mobile client.
|
||||||
1
fastlane/metadata/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Paperless Mobile
|
||||||
8
install_dependencies.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
pushd packages/paperless_api
|
||||||
|
flutter pub get
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
popd
|
||||||
|
flutter pub get
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
flutter pub run intl_utils:generate
|
||||||
11
ios/Podfile
@@ -37,5 +37,16 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
## dart: PermissionGroup.camera
|
||||||
|
'PERMISSION_CAMERA=1',
|
||||||
|
]
|
||||||
|
|
||||||
|
end
|
||||||
|
# End of the permission_handler configuration
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -198,6 +198,6 @@ SPEC CHECKSUMS:
|
|||||||
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||||
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
|
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
|
||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
PODFILE CHECKSUM: 7daa35cc908d9fba025075df27cb57a1ba1ebf13
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.11.3
|
||||||
|
|||||||
@@ -1,73 +1,73 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Paperless Mobile</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>paperless_mobile</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<string>ShareMedia</string>
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>Editor</string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>ShareMedia</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict/>
|
|
||||||
</array>
|
</array>
|
||||||
|
</dict>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<dict/>
|
||||||
<string>To upload photos, please allow permission to access your photo library.</string>
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<string>Why is my app authenticating using face id?</string>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<true/>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<key>CFBundleDisplayName</key>
|
<string>Allow this app to use FaceID to secure your login credentials.</string>
|
||||||
<string>Paperless Mobile</string>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<key>CFBundleExecutable</key>
|
<string>Allow this app to access your photo library to upload images from this device.</string>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<key>CFBundleIdentifier</key>
|
<true/>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<key>UILaunchStoryboardName</key>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<string>LaunchScreen</string>
|
||||||
<string>6.0</string>
|
<key>UIMainStoryboardFile</key>
|
||||||
<key>CFBundleName</key>
|
<string>Main</string>
|
||||||
<string>paperless_mobile</string>
|
<key>UIStatusBarHidden</key>
|
||||||
<key>CFBundlePackageType</key>
|
<false/>
|
||||||
<string>APPL</string>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<key>CFBundleShortVersionString</key>
|
<array>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<key>CFBundleSignature</key>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>????</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<key>CFBundleVersion</key>
|
</array>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<array>
|
||||||
<true/>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>LaunchScreen</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<string>Main</string>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<array>
|
<false/>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>Allow this app access to your camera to scan documents.</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<key>UISupportsDocumentBrowser</key>
|
||||||
</array>
|
<true/>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
|
||||||
<key>UIStatusBarHidden</key>
|
|
||||||
<false/>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>UISupportsDocumentBrowser</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
|
||||||
|
|
||||||
extension AddressableHydratedStorage on Storage {
|
extension AddressableHydratedStorage on Storage {
|
||||||
ApplicationSettingsState get settings {
|
ApplicationSettingsState get settings {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_mobile/constants.dart';
|
import 'package:paperless_mobile/constants.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:url_launcher/link.dart';
|
import 'package:url_launcher/link.dart';
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:open_filex/open_filex.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
part 'document_details_state.dart';
|
part 'document_details_state.dart';
|
||||||
|
|
||||||
@@ -73,6 +75,24 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
emit(state.copyWith(document: document));
|
emit(state.copyWith(document: document));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> shareDocument() async {
|
||||||
|
final documentBytes = await _api.download(state.document);
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
final String path = "${dir.path}/${state.document.originalFileName}";
|
||||||
|
await File(path).writeAsBytes(documentBytes);
|
||||||
|
Share.shareXFiles(
|
||||||
|
[
|
||||||
|
XFile(
|
||||||
|
path,
|
||||||
|
name: state.document.originalFileName,
|
||||||
|
mimeType: "application/pdf",
|
||||||
|
lastModified: state.document.modified,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
subject: state.document.title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
for (final element in _subscriptions) {
|
for (final element in _subscriptions) {
|
||||||
@@ -4,31 +4,27 @@ import 'package:badges/badges.dart' as b;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:open_filex/open_filex.dart';
|
import 'package:open_filex/open_filex.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
|
||||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_edit_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
|
||||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
|
||||||
|
|
||||||
//TODO: Refactor this into several widgets
|
//TODO: Refactor this into several widgets
|
||||||
class DocumentDetailsPage extends StatefulWidget {
|
class DocumentDetailsPage extends StatefulWidget {
|
||||||
@@ -49,7 +45,7 @@ class DocumentDetailsPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||||
late Future<DocumentMetaData> _metaData;
|
late Future<DocumentMetaData> _metaData;
|
||||||
|
static const double _itemPadding = 24;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -70,7 +66,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
length: 4,
|
length: 4,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
floatingActionButton: widget.allowEdit ? _buildAppBar() : null,
|
floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
|
||||||
bottomNavigationBar: _buildBottomAppBar(),
|
bottomNavigationBar: _buildBottomAppBar(),
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
@@ -96,27 +92,30 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).documentDetailsPageTabOverviewLabel,
|
S.of(context).documentDetailsPageTabOverviewLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onPrimaryContainer),
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).documentDetailsPageTabContentLabel,
|
S.of(context).documentDetailsPageTabContentLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onPrimaryContainer),
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).documentDetailsPageTabMetaDataLabel,
|
S.of(context).documentDetailsPageTabMetaDataLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onPrimaryContainer),
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
@@ -146,20 +145,26 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
children: [
|
children: [
|
||||||
_buildDocumentOverview(
|
DocumentOverviewWidget(
|
||||||
state.document,
|
document: state.document,
|
||||||
|
itemSpacing: _itemPadding,
|
||||||
|
queryString: widget.titleAndContentQueryString,
|
||||||
),
|
),
|
||||||
_buildDocumentContentView(
|
DocumentContentWidget(
|
||||||
state.document,
|
isFullContentLoaded: state.isFullContentLoaded,
|
||||||
state,
|
document: state.document,
|
||||||
|
fullContent: state.fullContent,
|
||||||
|
queryString: widget.titleAndContentQueryString,
|
||||||
),
|
),
|
||||||
_buildDocumentMetaDataView(
|
DocumentMetaDataWidget(
|
||||||
state.document,
|
document: state.document,
|
||||||
|
itemSpacing: _itemPadding,
|
||||||
|
metaData: _metaData,
|
||||||
),
|
),
|
||||||
const SimilarDocumentsView(),
|
const SimilarDocumentsView(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).paddedSymmetrically(horizontal: 8);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -168,7 +173,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState> _buildAppBar() {
|
Widget _buildEditButton() {
|
||||||
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _filteredSuggestions =
|
final _filteredSuggestions =
|
||||||
@@ -176,7 +181,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, connectivityState) {
|
builder: (context, connectivityState) {
|
||||||
if (!connectivityState.isConnected) {
|
if (!connectivityState.isConnected) {
|
||||||
return Container();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return b.Badge(
|
return b.Badge(
|
||||||
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
||||||
@@ -244,8 +249,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
tooltip: S.of(context).documentDetailsPageShareTooltip,
|
tooltip: S.of(context).documentDetailsPageShareTooltip,
|
||||||
icon: const Icon(Icons.share),
|
icon: const Icon(Icons.share),
|
||||||
onPressed:
|
onPressed: isConnected
|
||||||
isConnected ? () => _onShare(state.document) : null,
|
? () =>
|
||||||
|
context.read<DocumentDetailsCubit>().shareDocument()
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -265,7 +272,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
builder: (_) => MultiBlocProvider(
|
builder: (_) => MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: EditDocumentCubit(
|
value: DocumentEditCubit(
|
||||||
document,
|
document,
|
||||||
documentsApi: context.read(),
|
documentsApi: context.read(),
|
||||||
correspondentRepository: context.read(),
|
correspondentRepository: context.read(),
|
||||||
@@ -279,7 +286,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
value: cubit,
|
value: cubit,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: BlocListener<EditDocumentCubit, EditDocumentState>(
|
child: BlocListener<DocumentEditCubit, DocumentEditState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.document != current.document,
|
previous.document != current.document,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
@@ -317,203 +324,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (!state.isConnected) {
|
|
||||||
return const Center(
|
|
||||||
child: OfflineWidget(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return FutureBuilder<DocumentMetaData>(
|
|
||||||
future: _metaData,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
final meta = snapshot.data!;
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
_DetailsItem.text(DateFormat().format(document.modified),
|
|
||||||
label: S.of(context).documentModifiedPropertyLabel,
|
|
||||||
context: context)
|
|
||||||
.paddedOnly(bottom: 16),
|
|
||||||
_DetailsItem.text(DateFormat().format(document.added),
|
|
||||||
label: S.of(context).documentAddedPropertyLabel,
|
|
||||||
context: context)
|
|
||||||
.paddedSymmetrically(vertical: 16),
|
|
||||||
_DetailsItem(
|
|
||||||
label: S
|
|
||||||
.of(context)
|
|
||||||
.documentArchiveSerialNumberPropertyLongLabel,
|
|
||||||
content: document.archiveSerialNumber != null
|
|
||||||
? Text(document.archiveSerialNumber.toString())
|
|
||||||
: TextButton.icon(
|
|
||||||
icon: const Icon(Icons.archive_outlined),
|
|
||||||
label: Text(S
|
|
||||||
.of(context)
|
|
||||||
.documentDetailsPageAssignAsnButtonLabel),
|
|
||||||
onPressed: widget.allowEdit
|
|
||||||
? () => _assignAsn(document)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
_DetailsItem.text(
|
|
||||||
meta.mediaFilename,
|
|
||||||
context: context,
|
|
||||||
label:
|
|
||||||
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
_DetailsItem.text(
|
|
||||||
meta.originalChecksum,
|
|
||||||
context: context,
|
|
||||||
label: S.of(context).documentMetaDataChecksumLabel,
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
_DetailsItem.text(formatBytes(meta.originalSize, 2),
|
|
||||||
label:
|
|
||||||
S.of(context).documentMetaDataOriginalFileSizeLabel,
|
|
||||||
context: context)
|
|
||||||
.paddedSymmetrically(vertical: 16),
|
|
||||||
_DetailsItem.text(
|
|
||||||
meta.originalMimeType,
|
|
||||||
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
|
|
||||||
context: context,
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _assignAsn(DocumentModel document) async {
|
|
||||||
try {
|
|
||||||
await context.read<DocumentDetailsCubit>().assignAsn(document);
|
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDocumentContentView(
|
|
||||||
DocumentModel document,
|
|
||||||
DocumentDetailsState state,
|
|
||||||
) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
HighlightedText(
|
|
||||||
text: (state.isFullContentLoaded
|
|
||||||
? state.fullContent
|
|
||||||
: document.content) ??
|
|
||||||
"",
|
|
||||||
highlights: widget.titleAndContentQueryString != null
|
|
||||||
? widget.titleAndContentQueryString!.split(" ")
|
|
||||||
: [],
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
caseSensitive: false,
|
|
||||||
),
|
|
||||||
if (!state.isFullContentLoaded && (document.content ?? '').isNotEmpty)
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: TextButton(
|
|
||||||
child:
|
|
||||||
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
|
|
||||||
onPressed: () {
|
|
||||||
context.read<DocumentDetailsCubit>().loadFullContent();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(8).paddedOnly(top: 14),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDocumentOverview(DocumentModel document) {
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
_DetailsItem(
|
|
||||||
label: S.of(context).documentTitlePropertyLabel,
|
|
||||||
content: HighlightedText(
|
|
||||||
text: document.title,
|
|
||||||
highlights: widget.titleAndContentQueryString?.split(" ") ?? [],
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
).paddedOnly(bottom: 16),
|
|
||||||
_DetailsItem.text(
|
|
||||||
DateFormat.yMMMMd().format(document.created),
|
|
||||||
context: context,
|
|
||||||
label: S.of(context).documentCreatedPropertyLabel,
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
Visibility(
|
|
||||||
visible: document.documentType != null,
|
|
||||||
child: _DetailsItem(
|
|
||||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
|
||||||
content: LabelText<DocumentType>(
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
id: document.documentType,
|
|
||||||
),
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: document.correspondent != null,
|
|
||||||
child: _DetailsItem(
|
|
||||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
|
||||||
content: LabelText<Correspondent>(
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
id: document.correspondent,
|
|
||||||
),
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: document.storagePath != null,
|
|
||||||
child: _DetailsItem(
|
|
||||||
label: S.of(context).documentStoragePathPropertyLabel,
|
|
||||||
content: StoragePathWidget(
|
|
||||||
pathId: document.storagePath,
|
|
||||||
),
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: document.tags.isNotEmpty,
|
|
||||||
child: _DetailsItem(
|
|
||||||
label: S.of(context).documentTagsPropertyLabel,
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: TagsWidget(
|
|
||||||
isClickable: widget.isLabelClickable,
|
|
||||||
tagIds: document.tags,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).paddedSymmetrically(vertical: 16),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Downloads file to temporary directory, from which it can then be shared.
|
|
||||||
///
|
|
||||||
Future<void> _onShare(DocumentModel document) async {
|
|
||||||
Uint8List documentBytes =
|
|
||||||
await context.read<PaperlessDocumentsApi>().download(document);
|
|
||||||
final dir = await getTemporaryDirectory();
|
|
||||||
final String path = "${dir.path}/${document.originalFileName}";
|
|
||||||
await File(path).writeAsBytes(documentBytes);
|
|
||||||
Share.shareXFiles(
|
|
||||||
[
|
|
||||||
XFile(
|
|
||||||
path,
|
|
||||||
name: document.originalFileName,
|
|
||||||
mimeType: "application/pdf",
|
|
||||||
lastModified: document.modified,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
subject: document.title,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDelete(DocumentModel document) async {
|
void _onDelete(DocumentModel document) async {
|
||||||
final delete = await showDialog(
|
final delete = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -546,42 +356,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.bodySmall,
|
|
||||||
),
|
|
||||||
content,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_DetailsItem.text(
|
|
||||||
String text, {
|
|
||||||
required this.label,
|
|
||||||
required BuildContext context,
|
|
||||||
}) : content = Text(
|
|
||||||
text,
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ColoredTabBar extends Container implements PreferredSizeWidget {
|
class ColoredTabBar extends Container implements PreferredSizeWidget {
|
||||||
ColoredTabBar({
|
ColoredTabBar({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
34
lib/features/document_details/view/widgets/details_item.dart
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
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 Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
content,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailsItem.text(
|
||||||
|
String text, {
|
||||||
|
required this.label,
|
||||||
|
required BuildContext context,
|
||||||
|
}) : content = Text(
|
||||||
|
text,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
|
class DocumentContentWidget extends StatelessWidget {
|
||||||
|
final bool isFullContentLoaded;
|
||||||
|
final String? fullContent;
|
||||||
|
final String? queryString;
|
||||||
|
final DocumentModel document;
|
||||||
|
const DocumentContentWidget({
|
||||||
|
super.key,
|
||||||
|
required this.isFullContentLoaded,
|
||||||
|
this.fullContent,
|
||||||
|
required this.document,
|
||||||
|
this.queryString,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
HighlightedText(
|
||||||
|
text: (isFullContentLoaded ? fullContent : document.content) ?? "",
|
||||||
|
highlights: queryString != null ? queryString!.split(" ") : [],
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
caseSensitive: false,
|
||||||
|
),
|
||||||
|
if (!isFullContentLoaded && (document.content ?? '').isNotEmpty)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: TextButton(
|
||||||
|
child:
|
||||||
|
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<DocumentDetailsCubit>().loadFullContent();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
||||||
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
|
||||||
|
class DocumentMetaDataWidget extends StatelessWidget {
|
||||||
|
final Future<DocumentMetaData> metaData;
|
||||||
|
final DocumentModel document;
|
||||||
|
final double itemSpacing;
|
||||||
|
const DocumentMetaDataWidget({
|
||||||
|
super.key,
|
||||||
|
required this.metaData,
|
||||||
|
required this.document,
|
||||||
|
required this.itemSpacing,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (!state.isConnected) {
|
||||||
|
return const Center(
|
||||||
|
child: OfflineWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return FutureBuilder<DocumentMetaData>(
|
||||||
|
future: metaData,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
final meta = snapshot.data!;
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
DetailsItem(
|
||||||
|
label: S
|
||||||
|
.of(context)
|
||||||
|
.documentArchiveSerialNumberPropertyLongLabel,
|
||||||
|
content: document.archiveSerialNumber != null
|
||||||
|
? Text(document.archiveSerialNumber.toString())
|
||||||
|
: TextButton.icon(
|
||||||
|
icon: const Icon(Icons.archive_outlined),
|
||||||
|
label: Text(S
|
||||||
|
.of(context)
|
||||||
|
.documentDetailsPageAssignAsnButtonLabel),
|
||||||
|
onPressed: () => _assignAsn(context),
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(DateFormat().format(document.modified),
|
||||||
|
label: S.of(context).documentModifiedPropertyLabel,
|
||||||
|
context: context)
|
||||||
|
.paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(DateFormat().format(document.added),
|
||||||
|
label: S.of(context).documentAddedPropertyLabel,
|
||||||
|
context: context)
|
||||||
|
.paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(
|
||||||
|
meta.mediaFilename,
|
||||||
|
context: context,
|
||||||
|
label:
|
||||||
|
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(
|
||||||
|
meta.originalChecksum,
|
||||||
|
context: context,
|
||||||
|
label: S.of(context).documentMetaDataChecksumLabel,
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(formatBytes(meta.originalSize, 2),
|
||||||
|
label:
|
||||||
|
S.of(context).documentMetaDataOriginalFileSizeLabel,
|
||||||
|
context: context)
|
||||||
|
.paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(
|
||||||
|
meta.originalMimeType,
|
||||||
|
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
|
||||||
|
context: context,
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _assignAsn(BuildContext context) async {
|
||||||
|
try {
|
||||||
|
await context.read<DocumentDetailsCubit>().assignAsn(document);
|
||||||
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
|
class DocumentOverviewWidget extends StatelessWidget {
|
||||||
|
final DocumentModel document;
|
||||||
|
final String? queryString;
|
||||||
|
final double itemSpacing;
|
||||||
|
const DocumentOverviewWidget({
|
||||||
|
super.key,
|
||||||
|
required this.document,
|
||||||
|
this.queryString,
|
||||||
|
required this.itemSpacing,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
DetailsItem(
|
||||||
|
label: S.of(context).documentTitlePropertyLabel,
|
||||||
|
content: HighlightedText(
|
||||||
|
text: document.title,
|
||||||
|
highlights: queryString?.split(" ") ?? [],
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
DetailsItem.text(
|
||||||
|
DateFormat.yMMMMd().format(document.created),
|
||||||
|
context: context,
|
||||||
|
label: S.of(context).documentCreatedPropertyLabel,
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
Visibility(
|
||||||
|
visible: document.documentType != null,
|
||||||
|
child: DetailsItem(
|
||||||
|
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||||
|
content: LabelText<DocumentType>(
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
id: document.documentType,
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: document.correspondent != null,
|
||||||
|
child: DetailsItem(
|
||||||
|
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||||
|
content: LabelText<Correspondent>(
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
id: document.correspondent,
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: document.storagePath != null,
|
||||||
|
child: DetailsItem(
|
||||||
|
label: S.of(context).documentStoragePathPropertyLabel,
|
||||||
|
content: StoragePathWidget(
|
||||||
|
pathId: document.storagePath,
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: document.tags.isNotEmpty,
|
||||||
|
child: DetailsItem(
|
||||||
|
label: S.of(context).documentTagsPropertyLabel,
|
||||||
|
content: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: TagsWidget(
|
||||||
|
isClickable: false,
|
||||||
|
tagIds: document.tags,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).paddedOnly(bottom: itemSpacing),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
|
||||||
|
|
||||||
part 'edit_document_state.dart';
|
part 'document_edit_state.dart';
|
||||||
|
|
||||||
class EditDocumentCubit extends Cubit<EditDocumentState> {
|
class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||||
final DocumentModel _initialDocument;
|
final DocumentModel _initialDocument;
|
||||||
final PaperlessDocumentsApi _docsApi;
|
final PaperlessDocumentsApi _docsApi;
|
||||||
|
|
||||||
@@ -24,7 +20,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
final LabelRepository<Tag> _tagRepository;
|
final LabelRepository<Tag> _tagRepository;
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
EditDocumentCubit(
|
DocumentEditCubit(
|
||||||
DocumentModel document, {
|
DocumentModel document, {
|
||||||
required PaperlessDocumentsApi documentsApi,
|
required PaperlessDocumentsApi documentsApi,
|
||||||
required LabelRepository<Correspondent> correspondentRepository,
|
required LabelRepository<Correspondent> correspondentRepository,
|
||||||
@@ -40,7 +36,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
_tagRepository = tagRepository,
|
_tagRepository = tagRepository,
|
||||||
_notifier = notifier,
|
_notifier = notifier,
|
||||||
super(
|
super(
|
||||||
EditDocumentState(
|
DocumentEditState(
|
||||||
document: document,
|
document: document,
|
||||||
correspondents: correspondentRepository.current?.values ?? {},
|
correspondents: correspondentRepository.current?.values ?? {},
|
||||||
documentTypes: documentTypeRepository.current?.values ?? {},
|
documentTypes: documentTypeRepository.current?.values ?? {},
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
part of 'edit_document_cubit.dart';
|
part of 'document_edit_cubit.dart';
|
||||||
|
|
||||||
class EditDocumentState extends Equatable {
|
class DocumentEditState extends Equatable {
|
||||||
final DocumentModel document;
|
final DocumentModel document;
|
||||||
|
|
||||||
final Map<int, Correspondent> correspondents;
|
final Map<int, Correspondent> correspondents;
|
||||||
@@ -8,7 +8,7 @@ class EditDocumentState extends Equatable {
|
|||||||
final Map<int, StoragePath> storagePaths;
|
final Map<int, StoragePath> storagePaths;
|
||||||
final Map<int, Tag> tags;
|
final Map<int, Tag> tags;
|
||||||
|
|
||||||
const EditDocumentState({
|
const DocumentEditState({
|
||||||
required this.correspondents,
|
required this.correspondents,
|
||||||
required this.documentTypes,
|
required this.documentTypes,
|
||||||
required this.storagePaths,
|
required this.storagePaths,
|
||||||
@@ -25,14 +25,14 @@ class EditDocumentState extends Equatable {
|
|||||||
document,
|
document,
|
||||||
];
|
];
|
||||||
|
|
||||||
EditDocumentState copyWith({
|
DocumentEditState copyWith({
|
||||||
Map<int, Correspondent>? correspondents,
|
Map<int, Correspondent>? correspondents,
|
||||||
Map<int, DocumentType>? documentTypes,
|
Map<int, DocumentType>? documentTypes,
|
||||||
Map<int, StoragePath>? storagePaths,
|
Map<int, StoragePath>? storagePaths,
|
||||||
Map<int, Tag>? tags,
|
Map<int, Tag>? tags,
|
||||||
DocumentModel? document,
|
DocumentModel? document,
|
||||||
}) {
|
}) {
|
||||||
return EditDocumentState(
|
return DocumentEditState(
|
||||||
document: document ?? this.document,
|
document: document ?? this.document,
|
||||||
correspondents: correspondents ?? this.correspondents,
|
correspondents: correspondents ?? this.correspondents,
|
||||||
documentTypes: documentTypes ?? this.documentTypes,
|
documentTypes: documentTypes ?? this.documentTypes,
|
||||||
@@ -13,7 +13,7 @@ import 'package:paperless_mobile/core/repository/state/impl/document_type_reposi
|
|||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.dart';
|
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||||
@@ -51,12 +51,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_filteredSuggestions = widget.suggestions
|
_filteredSuggestions = widget.suggestions
|
||||||
.documentDifference(context.read<EditDocumentCubit>().state.document);
|
.documentDifference(context.read<DocumentEditCubit>().state.document);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<EditDocumentCubit, EditDocumentState>(
|
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -252,7 +252,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
_isSubmitLoading = true;
|
_isSubmitLoading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await context.read<EditDocumentCubit>().updateDocument(mergedDocument);
|
await context.read<DocumentEditCubit>().updateDocument(mergedDocument);
|
||||||
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
@@ -19,8 +19,8 @@ import 'package:paperless_mobile/features/document_search/view/document_search_p
|
|||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/scan/view/widgets/scanned_image_item.dart';
|
import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart';
|
||||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -2,11 +2,17 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
|
|
||||||
|
part 'document_search_state.dart';
|
||||||
|
|
||||||
|
part 'document_search_cubit.g.dart';
|
||||||
|
|
||||||
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
||||||
with PagedDocumentsMixin {
|
with DocumentPagingBlocMixin {
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
part of 'document_search_cubit.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
|
||||||
|
|
||||||
part 'document_search_state.g.dart';
|
|
||||||
|
|
||||||
enum SearchView {
|
enum SearchView {
|
||||||
suggestions,
|
suggestions,
|
||||||
@@ -11,7 +6,7 @@ enum SearchView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable(ignoreUnannotated: true)
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
class DocumentSearchState extends PagedDocumentsState {
|
class DocumentSearchState extends DocumentPagingState {
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final List<String> searchHistory;
|
final List<String> searchHistory;
|
||||||
final SearchView view;
|
final SearchView view;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
|
|
||||||
|
part 'documents_state.dart';
|
||||||
|
part 'documents_cubit.g.dart';
|
||||||
|
|
||||||
class DocumentsCubit extends HydratedCubit<DocumentsState>
|
class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||||
with PagedDocumentsMixin {
|
with DocumentPagingBlocMixin {
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
@@ -94,4 +99,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
notifier.unsubscribe(this);
|
notifier.unsubscribe(this);
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setViewType(ViewType viewType) {
|
||||||
|
emit(state.copyWith(viewType: viewType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
part of 'documents_cubit.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
|
||||||
|
|
||||||
class DocumentsState extends PagedDocumentsState {
|
@JsonSerializable()
|
||||||
@JsonKey(includeFromJson: true, includeToJson: false)
|
class DocumentsState extends DocumentPagingState {
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
final List<DocumentModel> selection;
|
final List<DocumentModel> selection;
|
||||||
|
|
||||||
|
final ViewType viewType;
|
||||||
|
|
||||||
const DocumentsState({
|
const DocumentsState({
|
||||||
this.selection = const [],
|
this.selection = const [],
|
||||||
|
this.viewType = ViewType.list,
|
||||||
super.value = const [],
|
super.value = const [],
|
||||||
super.filter = const DocumentFilter(),
|
super.filter = const DocumentFilter(),
|
||||||
super.hasLoaded = false,
|
super.hasLoaded = false,
|
||||||
@@ -22,6 +24,7 @@ class DocumentsState extends PagedDocumentsState {
|
|||||||
List<PagedSearchResult<DocumentModel>>? value,
|
List<PagedSearchResult<DocumentModel>>? value,
|
||||||
DocumentFilter? filter,
|
DocumentFilter? filter,
|
||||||
List<DocumentModel>? selection,
|
List<DocumentModel>? selection,
|
||||||
|
ViewType? viewType,
|
||||||
}) {
|
}) {
|
||||||
return DocumentsState(
|
return DocumentsState(
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
@@ -29,38 +32,22 @@ class DocumentsState extends PagedDocumentsState {
|
|||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
filter: filter ?? this.filter,
|
filter: filter ?? this.filter,
|
||||||
selection: selection ?? this.selection,
|
selection: selection ?? this.selection,
|
||||||
|
viewType: viewType ?? this.viewType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DocumentsStateFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
selection,
|
selection,
|
||||||
|
viewType,
|
||||||
...super.props,
|
...super.props,
|
||||||
];
|
];
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = {
|
|
||||||
'hasLoaded': hasLoaded,
|
|
||||||
'isLoading': isLoading,
|
|
||||||
'filter': filter.toJson(),
|
|
||||||
'value':
|
|
||||||
value.map((e) => e.toJson(DocumentModelJsonConverter())).toList(),
|
|
||||||
};
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory DocumentsState.fromJson(Map<String, dynamic> json) {
|
|
||||||
return DocumentsState(
|
|
||||||
hasLoaded: json['hasLoaded'],
|
|
||||||
isLoading: json['isLoading'],
|
|
||||||
value: (json['value'] as List<dynamic>)
|
|
||||||
.map((e) =>
|
|
||||||
PagedSearchResult.fromJsonT(e, DocumentModelJsonConverter()))
|
|
||||||
.toList(),
|
|
||||||
filter: DocumentFilter.fromJson(json['filter']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DocumentsState copyWithPaged({
|
DocumentsState copyWithPaged({
|
||||||
bool? hasLoaded,
|
bool? hasLoaded,
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:badges/badges.dart' as b;
|
import 'package:badges/badges.dart' as b;
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
@@ -9,21 +6,18 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/labels_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
||||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|
||||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
@@ -263,14 +257,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
),
|
),
|
||||||
_buildViewActions(),
|
_buildViewActions(),
|
||||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
// Not required anymore since saved views are now handled separately
|
|
||||||
// buildWhen: (previous, current) =>
|
|
||||||
// !const ListEquality().equals(
|
|
||||||
// previous.documents,
|
|
||||||
// current.documents,
|
|
||||||
// ) ||
|
|
||||||
// previous.selectedIds !=
|
|
||||||
// current.selectedIds,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.hasLoaded &&
|
if (state.hasLoaded &&
|
||||||
state.documents.isEmpty) {
|
state.documents.isEmpty) {
|
||||||
@@ -285,34 +271,27 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return BlocBuilder<
|
|
||||||
ApplicationSettingsCubit,
|
return SliverAdaptiveDocumentsView(
|
||||||
ApplicationSettingsState>(
|
viewType: state.viewType,
|
||||||
builder: (context, settings) {
|
onTap: _openDetails,
|
||||||
return SliverAdaptiveDocumentsView(
|
onSelected: context
|
||||||
viewType:
|
.read<DocumentsCubit>()
|
||||||
settings.preferredViewType,
|
.toggleDocumentSelection,
|
||||||
onTap: _openDetails,
|
hasInternetConnection:
|
||||||
onSelected: context
|
connectivityState.isConnected,
|
||||||
.read<DocumentsCubit>()
|
onTagSelected: _addTagToFilter,
|
||||||
.toggleDocumentSelection,
|
onCorrespondentSelected:
|
||||||
hasInternetConnection:
|
_addCorrespondentToFilter,
|
||||||
connectivityState.isConnected,
|
onDocumentTypeSelected:
|
||||||
onTagSelected: _addTagToFilter,
|
_addDocumentTypeToFilter,
|
||||||
onCorrespondentSelected:
|
onStoragePathSelected:
|
||||||
_addCorrespondentToFilter,
|
_addStoragePathToFilter,
|
||||||
onDocumentTypeSelected:
|
documents: state.documents,
|
||||||
_addDocumentTypeToFilter,
|
hasLoaded: state.hasLoaded,
|
||||||
onStoragePathSelected:
|
isLabelClickable: true,
|
||||||
_addStoragePathToFilter,
|
isLoading: state.isLoading,
|
||||||
documents: state.documents,
|
selectedDocumentIds: state.selectedIds,
|
||||||
hasLoaded: state.hasLoaded,
|
|
||||||
isLabelClickable: true,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
selectedDocumentIds:
|
|
||||||
state.selectedIds,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -360,18 +339,11 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const SortDocumentsButton(),
|
const SortDocumentsButton(),
|
||||||
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return IconButton(
|
return ViewTypeSelectionWidget(
|
||||||
icon: Icon(
|
viewType: state.viewType,
|
||||||
state.preferredViewType == ViewType.list
|
onChanged: context.read<DocumentsCubit>().setViewType,
|
||||||
? Icons.grid_view_rounded
|
|
||||||
: Icons.list,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<ApplicationSettingsCubit>().setViewType(
|
|
||||||
state.preferredViewType.toggle(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class DocumentsEmptyState extends StatelessWidget {
|
class DocumentsEmptyState extends StatelessWidget {
|
||||||
final PagedDocumentsState state;
|
final DocumentPagingState state;
|
||||||
final VoidCallback? onReset;
|
final VoidCallback? onReset;
|
||||||
const DocumentsEmptyState({
|
const DocumentsEmptyState({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/text_query_form_field.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
enum DateRangeSelection { before, after }
|
enum DateRangeSelection { before, after }
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_type_ahead.dart';
|
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_type_ahead.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
|
||||||
|
/// Meant to be used with blocbuilder.
|
||||||
|
class ViewTypeSelectionWidget extends StatelessWidget {
|
||||||
|
final ViewType viewType;
|
||||||
|
final void Function(ViewType type) onChanged;
|
||||||
|
|
||||||
|
const ViewTypeSelectionWidget({
|
||||||
|
super.key,
|
||||||
|
required this.viewType,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final next = viewType.toggle();
|
||||||
|
final icon = next == ViewType.grid ? Icons.grid_view_rounded : Icons.list;
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(icon),
|
||||||
|
onPressed: () {
|
||||||
|
onChanged(next);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class SortDocumentsButton extends StatelessWidget {
|
class SortDocumentsButton extends StatelessWidget {
|
||||||
const SortDocumentsButton({
|
const SortDocumentsButton({
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|
||||||
|
|
||||||
class ViewActions extends StatelessWidget {
|
|
||||||
const ViewActions({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const SortDocumentsButton(),
|
|
||||||
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
|
||||||
builder: (context, settings) {
|
|
||||||
final cubit = context.read<ApplicationSettingsCubit>();
|
|
||||||
switch (settings.preferredViewType) {
|
|
||||||
case ViewType.grid:
|
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(Icons.list),
|
|
||||||
onPressed: () =>
|
|
||||||
cubit.setViewType(settings.preferredViewType.toggle()),
|
|
||||||
);
|
|
||||||
case ViewType.list:
|
|
||||||
return IconButton(
|
|
||||||
icon: const Icon(Icons.grid_view_rounded),
|
|
||||||
onPressed: () =>
|
|
||||||
cubit.setViewType(settings.preferredViewType.toggle()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart';
|
part 'edit_label_state.dart';
|
||||||
|
|
||||||
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
||||||
final LabelRepository<T> _repository;
|
final LabelRepository<T> _repository;
|
||||||
@@ -13,7 +14,7 @@ class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
|||||||
|
|
||||||
EditLabelCubit(LabelRepository<T> repository)
|
EditLabelCubit(LabelRepository<T> repository)
|
||||||
: _repository = repository,
|
: _repository = repository,
|
||||||
super(const EditLabelInitial()) {
|
super(EditLabelState<T>(labels: repository.current?.values ?? {})) {
|
||||||
_subscription = repository.values.listen(
|
_subscription = repository.values.listen(
|
||||||
(event) => emit(EditLabelState(labels: event?.values ?? {})),
|
(event) => emit(EditLabelState(labels: event?.values ?? {})),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
part of 'edit_label_cubit.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class EditLabelState<T> extends Equatable {
|
class EditLabelState<T> extends Equatable {
|
||||||
final Map<int, T> labels;
|
final Map<int, T> labels;
|
||||||
|
|
||||||
const EditLabelState({required this.labels});
|
const EditLabelState({this.labels = const {}});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [labels];
|
List<Object> get props => [labels];
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditLabelInitial<T> extends EditLabelState<T> {
|
|
||||||
const EditLabelInitial() : super(labels: const {});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -10,25 +12,21 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
|
|||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_scan/view/scanner_page.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/route_description.dart';
|
import 'package:paperless_mobile/features/home/view/route_description.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
|
||||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
||||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -45,14 +43,16 @@ class HomePage extends StatefulWidget {
|
|||||||
_HomePageState createState() => _HomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
|
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
|
||||||
late final InboxCubit _inboxCubit;
|
late final InboxCubit _inboxCubit;
|
||||||
|
late Timer _inboxTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_initializeData(context);
|
_initializeData(context);
|
||||||
_inboxCubit = InboxCubit(
|
_inboxCubit = InboxCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
@@ -62,12 +62,43 @@ class _HomePageState extends State<HomePage> {
|
|||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
);
|
);
|
||||||
|
_listenToInboxChanges();
|
||||||
context.read<ConnectivityCubit>().reload();
|
context.read<ConnectivityCubit>().reload();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
_listenForReceivedFiles();
|
_listenForReceivedFiles();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _listenToInboxChanges() {
|
||||||
|
_inboxCubit.refreshItemsInInboxCount();
|
||||||
|
_inboxTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
|
||||||
|
if (!mounted) {
|
||||||
|
timer.cancel();
|
||||||
|
} else {
|
||||||
|
_inboxCubit.refreshItemsInInboxCount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed && !_inboxTimer.isActive) {
|
||||||
|
log('App is now in foreground, start polling for statistics.');
|
||||||
|
_listenToInboxChanges();
|
||||||
|
} else if (state != AppLifecycleState.resumed) {
|
||||||
|
log('App is now in background, stop polling for statistics.');
|
||||||
|
_inboxTimer.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_inboxTimer.cancel();
|
||||||
|
_inboxCubit.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void _listenForReceivedFiles() async {
|
void _listenForReceivedFiles() async {
|
||||||
if (ShareIntentQueue.instance.hasUnhandledFiles) {
|
if (ShareIntentQueue.instance.hasUnhandledFiles) {
|
||||||
await _handleReceivedFile(ShareIntentQueue.instance.pop()!);
|
await _handleReceivedFile(ShareIntentQueue.instance.pop()!);
|
||||||
@@ -156,12 +187,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_inboxCubit.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final destinations = [
|
final destinations = [
|
||||||
@@ -247,8 +272,18 @@ class _HomePageState extends State<HomePage> {
|
|||||||
],
|
],
|
||||||
child: const LabelsPage(),
|
child: const LabelsPage(),
|
||||||
),
|
),
|
||||||
BlocProvider.value(
|
MultiBlocProvider(
|
||||||
value: _inboxCubit,
|
providers: [
|
||||||
|
// We need to manually downcast the inboxcubit to the
|
||||||
|
// mixed-in DocumentPagingBlocMixin to use the
|
||||||
|
// DocumentPagingViewMixin in the inbox.
|
||||||
|
BlocProvider<DocumentPagingBlocMixin>.value(
|
||||||
|
value: _inboxCubit,
|
||||||
|
),
|
||||||
|
BlocProvider<InboxCubit>.value(
|
||||||
|
value: _inboxCubit,
|
||||||
|
),
|
||||||
|
],
|
||||||
child: const InboxPage(),
|
child: const InboxPage(),
|
||||||
),
|
),
|
||||||
// const SettingsPage(),
|
// const SettingsPage(),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
// import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||||
// import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
// import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||||
// import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
// import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
// import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
// import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
// import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
// import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||||
// import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
// import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||||
|
|||||||
@@ -3,13 +3,9 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/cubit/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
|
|
||||||
class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
part 'inbox_cubit.g.dart';
|
||||||
|
part 'inbox_state.dart';
|
||||||
|
|
||||||
|
class InboxCubit extends HydratedCubit<InboxState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
final LabelRepository<Tag> _tagsRepository;
|
final LabelRepository<Tag> _tagsRepository;
|
||||||
final LabelRepository<Correspondent> _correspondentRepository;
|
final LabelRepository<Correspondent> _correspondentRepository;
|
||||||
final LabelRepository<DocumentType> _documentTypeRepository;
|
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||||
@@ -82,13 +86,6 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
|
|
||||||
refreshItemsInInboxCount(false);
|
refreshItemsInInboxCount(false);
|
||||||
loadInbox();
|
loadInbox();
|
||||||
|
|
||||||
Timer.periodic(const Duration(seconds: 5), (timer) {
|
|
||||||
if (isClosed) {
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
refreshItemsInInboxCount();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
void refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
part of 'inbox_cubit.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
|
||||||
|
|
||||||
part 'inbox_state.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(ignoreUnannotated: true)
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
class InboxState extends PagedDocumentsState {
|
class InboxState extends DocumentPagingState {
|
||||||
final Iterable<int> inboxTags;
|
final Iterable<int> inboxTags;
|
||||||
|
|
||||||
final Map<int, Tag> availableTags;
|
final Map<int, Tag> availableTags;
|
||||||
@@ -9,10 +9,10 @@ import 'package:paperless_mobile/features/documents/view/widgets/documents_list_
|
|||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
|
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
|
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
@@ -24,35 +24,15 @@ class InboxPage extends StatefulWidget {
|
|||||||
State<InboxPage> createState() => _InboxPageState();
|
State<InboxPage> createState() => _InboxPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InboxPageState extends State<InboxPage> {
|
class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin {
|
||||||
final ScrollController _scrollController = ScrollController();
|
@override
|
||||||
|
final pagingScrollController = ScrollController();
|
||||||
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
context.read<InboxCubit>().loadInbox();
|
context.read<InboxCubit>().loadInbox();
|
||||||
_scrollController.addListener(_listenForLoadNewData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.removeListener(_listenForLoadNewData);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenForLoadNewData() {
|
|
||||||
final currState = context.read<InboxCubit>().state;
|
|
||||||
if (_scrollController.offset >=
|
|
||||||
_scrollController.position.maxScrollExtent * 0.75 &&
|
|
||||||
!currState.isLoading &&
|
|
||||||
!currState.isLastPageLoaded) {
|
|
||||||
try {
|
|
||||||
context.read<InboxCubit>().loadMore();
|
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -144,7 +124,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
physics: state.documents.isEmpty
|
physics: state.documents.isEmpty
|
||||||
? const NeverScrollableScrollPhysics()
|
? const NeverScrollableScrollPhysics()
|
||||||
: const AlwaysScrollableScrollPhysics(),
|
: const AlwaysScrollableScrollPhysics(),
|
||||||
controller: _scrollController,
|
controller: pagingScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
hintText: S.of(context).documentSearchSearchDocuments,
|
hintText: S.of(context).documentSearchSearchDocuments,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class InboxEmptyWidget extends StatelessWidget {
|
class InboxEmptyWidget extends StatelessWidget {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
|||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/correspondent_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
|
||||||
|
|
||||||
class CorrespondentWidget extends StatelessWidget {
|
class CorrespondentWidget extends StatelessWidget {
|
||||||
final int? correspondentId;
|
final int? correspondentId;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import 'dart:async';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
|
part 'label_state.dart';
|
||||||
|
|
||||||
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||||
final LabelRepository<T> _repository;
|
final LabelRepository<T> _repository;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
part of 'label_cubit.dart';
|
||||||
|
|
||||||
class LabelState<T extends Label> {
|
class LabelState<T extends Label> {
|
||||||
LabelState.initial() : this(isLoaded: false, labels: {});
|
LabelState.initial() : this(isLoaded: false, labels: {});
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class CorrespondentBlocProvider extends StatelessWidget {
|
class CorrespondentBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class DocumentTypeBlocProvider extends StatelessWidget {
|
class DocumentTypeBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@@ -6,7 +6,7 @@ import 'package:paperless_mobile/core/repository/state/impl/correspondent_reposi
|
|||||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class LabelsBlocProvider extends StatelessWidget {
|
class LabelsBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class StoragePathBlocProvider extends StatelessWidget {
|
class StoragePathBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
|
|
||||||
class TagBlocProvider extends StatelessWidget {
|
class TagBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
|
||||||
|
|
||||||
class DocumentTypeWidget extends StatelessWidget {
|
class DocumentTypeWidget extends StatelessWidget {
|
||||||
final int? documentTypeId;
|
final int? documentTypeId;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/storage_path_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
|
||||||
|
|
||||||
class StoragePathWidget extends StatelessWidget {
|
class StoragePathWidget extends StatelessWidget {
|
||||||
final int? pathId;
|
final int? pathId;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/tag_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
|
||||||
|
|
||||||
class TagsWidget extends StatelessWidget {
|
class TagsWidget extends StatelessWidget {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_corresponden
|
|||||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
||||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
|
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/linked_documents/view/pages/linked_documents_page.dart';
|
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
|
||||||
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
||||||
|
|
||||||
class LabelItem<T extends Label> extends StatelessWidget {
|
class LabelItem<T extends Label> extends StatelessWidget {
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
|
||||||
|
|
||||||
class LabelText<T extends Label> extends StatelessWidget {
|
class LabelText<T extends Label> extends StatelessWidget {
|
||||||
final int? id;
|
final int? id;
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
|
||||||
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
|
||||||
|
|
||||||
class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState>
|
|
||||||
with PagedDocumentsMixin {
|
|
||||||
@override
|
|
||||||
final PaperlessDocumentsApi api;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final DocumentChangedNotifier notifier;
|
|
||||||
|
|
||||||
LinkedDocumentsCubit(
|
|
||||||
DocumentFilter filter,
|
|
||||||
this.api,
|
|
||||||
this.notifier,
|
|
||||||
) : super(const LinkedDocumentsState()) {
|
|
||||||
updateFilter(filter: filter);
|
|
||||||
notifier.subscribe(
|
|
||||||
this,
|
|
||||||
onUpdated: replace,
|
|
||||||
onDeleted: remove,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> update(DocumentModel document) async {
|
|
||||||
final updated = await api.update(document);
|
|
||||||
if (!state.filter.matches(updated)) {
|
|
||||||
remove(document);
|
|
||||||
} else {
|
|
||||||
replace(document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
part 'linked_documents_state.dart';
|
||||||
|
|
||||||
|
part 'linked_documents_cubit.g.dart';
|
||||||
|
|
||||||
|
class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
|
@override
|
||||||
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
|
LinkedDocumentsCubit(
|
||||||
|
DocumentFilter filter,
|
||||||
|
this.api,
|
||||||
|
this.notifier,
|
||||||
|
) : super(const LinkedDocumentsState()) {
|
||||||
|
updateFilter(filter: filter);
|
||||||
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onUpdated: replace,
|
||||||
|
onDeleted: remove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> update(DocumentModel document) async {
|
||||||
|
final updated = await api.update(document);
|
||||||
|
if (!state.filter.matches(updated)) {
|
||||||
|
remove(document);
|
||||||
|
} else {
|
||||||
|
replace(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setViewType(ViewType type) {
|
||||||
|
emit(state.copyWith(viewType: type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LinkedDocumentsState? fromJson(Map<String, dynamic> json) {
|
||||||
|
return LinkedDocumentsState.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic>? toJson(LinkedDocumentsState state) {
|
||||||
|
return state.toJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
part of 'linked_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
|
||||||
|
|
||||||
class LinkedDocumentsState extends PagedDocumentsState {
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
|
class LinkedDocumentsState extends DocumentPagingState {
|
||||||
|
@JsonKey()
|
||||||
|
final ViewType viewType;
|
||||||
const LinkedDocumentsState({
|
const LinkedDocumentsState({
|
||||||
|
this.viewType = ViewType.list,
|
||||||
super.filter,
|
super.filter,
|
||||||
super.isLoading,
|
super.isLoading,
|
||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
@@ -14,12 +17,14 @@ class LinkedDocumentsState extends PagedDocumentsState {
|
|||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
bool? hasLoaded,
|
bool? hasLoaded,
|
||||||
List<PagedSearchResult<DocumentModel>>? value,
|
List<PagedSearchResult<DocumentModel>>? value,
|
||||||
|
ViewType? viewType,
|
||||||
}) {
|
}) {
|
||||||
return LinkedDocumentsState(
|
return LinkedDocumentsState(
|
||||||
filter: filter ?? this.filter,
|
filter: filter ?? this.filter,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
|
viewType: viewType ?? this.viewType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +45,12 @@ class LinkedDocumentsState extends PagedDocumentsState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
filter,
|
viewType,
|
||||||
isLoading,
|
...super.props,
|
||||||
hasLoaded,
|
|
||||||
value,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
factory LinkedDocumentsState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LinkedDocumentsStateFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$LinkedDocumentsStateToJson(this);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||||
|
|
||||||
|
class LinkedDocumentsPage extends StatefulWidget {
|
||||||
|
const LinkedDocumentsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
||||||
|
with DocumentPagingViewMixin {
|
||||||
|
@override
|
||||||
|
final pagingScrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(S.of(context).linkedDocumentsPageTitle),
|
||||||
|
actions: [
|
||||||
|
BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return ViewTypeSelectionWidget(
|
||||||
|
viewType: state.viewType,
|
||||||
|
onChanged: context.read<LinkedDocumentsCubit>().setViewType,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
|
builder: (context, connectivity) {
|
||||||
|
return CustomScrollView(
|
||||||
|
controller: pagingScrollController,
|
||||||
|
slivers: [
|
||||||
|
SliverAdaptiveDocumentsView(
|
||||||
|
viewType: state.viewType,
|
||||||
|
documents: state.documents,
|
||||||
|
hasInternetConnection: connectivity.isConnected,
|
||||||
|
isLabelClickable: false,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
hasLoaded: state.hasLoaded,
|
||||||
|
onTap: (document) {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
DocumentDetailsRoute.routeName,
|
||||||
|
arguments: DocumentDetailsRouteArguments(
|
||||||
|
document: document,
|
||||||
|
isLabelClickable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
|
||||||
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
|
||||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
|
||||||
|
|
||||||
class LinkedDocumentsPage extends StatefulWidget {
|
|
||||||
const LinkedDocumentsPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
|
||||||
final _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scrollController.addListener(_listenForLoadNewData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenForLoadNewData() async {
|
|
||||||
final currState = context.read<LinkedDocumentsCubit>().state;
|
|
||||||
if (_scrollController.offset >=
|
|
||||||
_scrollController.position.maxScrollExtent * 0.75 &&
|
|
||||||
!currState.isLoading &&
|
|
||||||
!currState.isLastPageLoaded) {
|
|
||||||
try {
|
|
||||||
await context.read<LinkedDocumentsCubit>().loadMore();
|
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(S.of(context).linkedDocumentsPageTitle),
|
|
||||||
),
|
|
||||||
body: BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
|
||||||
builder: (context, connectivity) {
|
|
||||||
return DefaultAdaptiveDocumentsView(
|
|
||||||
scrollController: _scrollController,
|
|
||||||
documents: state.documents,
|
|
||||||
hasInternetConnection: connectivity.isConnected,
|
|
||||||
isLabelClickable: false,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
hasLoaded: state.hasLoaded,
|
|
||||||
onTap: (document) {
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
DocumentDetailsRoute.routeName,
|
|
||||||
arguments: DocumentDetailsRouteArguments(
|
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/user_credentials.model.dart';
|
import 'package:paperless_mobile/features/login/model/user_credentials.model.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
|
part 'authentication_state.dart';
|
||||||
|
part 'authentication_cubit.g.dart';
|
||||||
|
|
||||||
class AuthenticationCubit extends Cubit<AuthenticationState>
|
class AuthenticationCubit extends Cubit<AuthenticationState>
|
||||||
with HydratedMixin<AuthenticationState> {
|
with HydratedMixin<AuthenticationState> {
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
part of 'authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
|
||||||
|
|
||||||
part 'authentication_state.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class AuthenticationState {
|
class AuthenticationState {
|
||||||
final bool wasLoginStored;
|
final bool wasLoginStored;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
final bool? wasLocalAuthenticationSuccessful;
|
final bool? wasLocalAuthenticationSuccessful;
|
||||||
final AuthenticationInformation? authentication;
|
final AuthenticationInformation? authentication;
|
||||||
|
|
||||||
@@ -3,17 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:paperless_mobile/constants.dart';
|
|
||||||
|
|
||||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
|
||||||
import 'widgets/login_pages/server_login_page.dart';
|
import 'widgets/login_pages/server_login_page.dart';
|
||||||
|
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({Key? key}) : super(key: key);
|
const LoginPage({Key? key}) : super(key: key);
|
||||||
|
|||||||
@@ -63,25 +63,48 @@ class _ClientCertificateFormFieldState
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
Row(
|
||||||
leading: ElevatedButton(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
onPressed: () => _onSelectFile(field),
|
children: [
|
||||||
child: Text(S.of(context).genericActionSelectText),
|
Row(
|
||||||
),
|
children: [
|
||||||
title: _buildSelectedFileText(field),
|
ElevatedButton(
|
||||||
trailing: AbsorbPointer(
|
onPressed: () => _onSelectFile(field),
|
||||||
absorbing: field.value == null,
|
child:
|
||||||
child: _selectedFile != null
|
Text(S.of(context).genericActionSelectText),
|
||||||
? IconButton(
|
),
|
||||||
icon: const Icon(Icons.close),
|
_buildSelectedFileText(field).paddedOnly(left: 8),
|
||||||
onPressed: () => setState(() {
|
],
|
||||||
_selectedFile = null;
|
),
|
||||||
field.didChange(null);
|
if (_selectedFile != null)
|
||||||
}),
|
IconButton(
|
||||||
)
|
icon: const Icon(Icons.close),
|
||||||
: null,
|
onPressed: () => setState(() {
|
||||||
),
|
_selectedFile = null;
|
||||||
),
|
field.didChange(null);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).padded(8),
|
||||||
|
// 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) ...[
|
if (_selectedFile != null) ...[
|
||||||
ObscuredInputTextFormField(
|
ObscuredInputTextFormField(
|
||||||
key: const ValueKey('login-client-cert-passphrase'),
|
key: const ValueKey('login-client-cert-passphrase'),
|
||||||
@@ -127,7 +150,9 @@ class _ClientCertificateFormFieldState
|
|||||||
assert(_selectedFile == null);
|
assert(_selectedFile == null);
|
||||||
return Text(
|
return Text(
|
||||||
S.of(context).loginPageClientCertificateSettingSelectFileText,
|
S.of(context).loginPageClientCertificateSettingSelectFileText,
|
||||||
style: TextStyle(color: Theme.of(context).hintColor),
|
style: Theme.of(context).textTheme.labelMedium?.apply(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
assert(_selectedFile != null);
|
assert(_selectedFile != null);
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
|
|
||||||
import 'model/paged_documents_state.dart';
|
import 'paged_documents_state.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Mixin which can be used on cubits that handle documents.
|
/// Mixin which can be used on cubits that handle documents.
|
||||||
/// This implements all paging and filtering logic.
|
/// This implements all paging and filtering logic.
|
||||||
///
|
///
|
||||||
mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||||
on BlocBase<State> {
|
on BlocBase<State> {
|
||||||
PaperlessDocumentsApi get api;
|
PaperlessDocumentsApi get api;
|
||||||
DocumentChangedNotifier get notifier;
|
DocumentChangedNotifier get notifier;
|
||||||
@@ -5,13 +5,13 @@ import 'package:paperless_api/paperless_api.dart';
|
|||||||
/// Base state for all blocs/cubits using a paged view of documents.
|
/// Base state for all blocs/cubits using a paged view of documents.
|
||||||
/// [T] is the return type of the API call.
|
/// [T] is the return type of the API call.
|
||||||
///
|
///
|
||||||
abstract class PagedDocumentsState extends Equatable {
|
abstract class DocumentPagingState extends Equatable {
|
||||||
final bool hasLoaded;
|
final bool hasLoaded;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final List<PagedSearchResult<DocumentModel>> value;
|
final List<PagedSearchResult<DocumentModel>> value;
|
||||||
final DocumentFilter filter;
|
final DocumentFilter filter;
|
||||||
|
|
||||||
const PagedDocumentsState({
|
const DocumentPagingState({
|
||||||
this.value = const [],
|
this.value = const [],
|
||||||
this.hasLoaded = false,
|
this.hasLoaded = false,
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
|
|
||||||
|
mixin DocumentPagingViewMixin<T extends StatefulWidget> on State<T> {
|
||||||
|
ScrollController get pagingScrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
pagingScrollController.addListener(shouldLoadMoreDocumentsListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
pagingScrollController.removeListener(shouldLoadMoreDocumentsListener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentPagingBlocMixin get _bloc => context.read<DocumentPagingBlocMixin>();
|
||||||
|
|
||||||
|
void shouldLoadMoreDocumentsListener() async {
|
||||||
|
if (shouldLoadMoreDocuments) {
|
||||||
|
try {
|
||||||
|
await _bloc.loadMore();
|
||||||
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get shouldLoadMoreDocuments {
|
||||||
|
final currState = _bloc.state;
|
||||||
|
return pagingScrollController.position.maxScrollExtent != 0 &&
|
||||||
|
!currState.isLoading &&
|
||||||
|
!currState.isLastPageLoaded &&
|
||||||
|
pagingScrollController.offset >=
|
||||||
|
pagingScrollController.position.maxScrollExtent * 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
|
||||||
|
part 'saved_view_state.dart';
|
||||||
|
|
||||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||||
final SavedViewRepository _repository;
|
final SavedViewRepository _repository;
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
|
||||||
|
|
||||||
part 'saved_view_details_state.dart';
|
|
||||||
|
|
||||||
class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
|
||||||
with PagedDocumentsMixin {
|
|
||||||
@override
|
|
||||||
final PaperlessDocumentsApi api;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final DocumentChangedNotifier notifier;
|
|
||||||
|
|
||||||
final SavedView savedView;
|
|
||||||
SavedViewDetailsCubit(
|
|
||||||
this.api,
|
|
||||||
this.notifier, {
|
|
||||||
required this.savedView,
|
|
||||||
}) : super(const SavedViewDetailsState()) {
|
|
||||||
notifier.subscribe(
|
|
||||||
this,
|
|
||||||
onDeleted: remove,
|
|
||||||
onUpdated: replace,
|
|
||||||
);
|
|
||||||
updateFilter(filter: savedView.toDocumentFilter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
part of 'saved_view_cubit.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
|
|
||||||
class SavedViewState with EquatableMixin {
|
class SavedViewState extends Equatable {
|
||||||
final bool hasLoaded;
|
final bool hasLoaded;
|
||||||
final Map<int, SavedView> value;
|
final Map<int, SavedView> value;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_form.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
|
||||||
|
|
||||||
class AddSavedViewPage extends StatefulWidget {
|
class AddSavedViewPage extends StatefulWidget {
|
||||||
final DocumentFilter currentFilter;
|
final DocumentFilter currentFilter;
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_page.dart';
|
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class SavedViewList extends StatelessWidget {
|
class SavedViewList extends StatelessWidget {
|
||||||
@@ -48,7 +45,7 @@ class SavedViewList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: SavedViewPage(
|
child: SavedViewDetailsPage(
|
||||||
onDelete: savedViewCubit.remove,
|
onDelete: savedViewCubit.remove,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/view_actions.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
|
||||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
|
||||||
|
|
||||||
class SavedViewPage extends StatefulWidget {
|
|
||||||
final Future<void> Function(SavedView savedView) onDelete;
|
|
||||||
const SavedViewPage({
|
|
||||||
super.key,
|
|
||||||
required this.onDelete,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SavedViewPage> createState() => _SavedViewPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SavedViewPageState extends State<SavedViewPage> {
|
|
||||||
final _scrollController = ScrollController();
|
|
||||||
ViewType _viewType = ViewType.list;
|
|
||||||
SavedView get _savedView => context.read<SavedViewDetailsCubit>().savedView;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_scrollController.addListener(_listenForLoadNewData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenForLoadNewData() async {
|
|
||||||
final currState = context.read<SavedViewDetailsCubit>().state;
|
|
||||||
if (_scrollController.offset >=
|
|
||||||
_scrollController.position.maxScrollExtent * 0.7 &&
|
|
||||||
!currState.isLoading &&
|
|
||||||
!currState.isLastPageLoaded) {
|
|
||||||
try {
|
|
||||||
await context.read<SavedViewDetailsCubit>().loadMore();
|
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Text(_savedView.name);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () async {
|
|
||||||
final shouldDelete = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) =>
|
|
||||||
ConfirmDeleteSavedViewDialog(view: _savedView),
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
if (shouldDelete) {
|
|
||||||
await widget.onDelete(_savedView);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_viewType == ViewType.list ? Icons.grid_view_rounded : Icons.list,
|
|
||||||
),
|
|
||||||
onPressed: () => setState(() => _viewType = _viewType.toggle()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.hasLoaded && state.documents.isEmpty) {
|
|
||||||
return DocumentsEmptyState(state: state);
|
|
||||||
}
|
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
|
||||||
builder: (context, connectivity) {
|
|
||||||
return CustomScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
slivers: [
|
|
||||||
SliverAdaptiveDocumentsView(
|
|
||||||
documents: state.documents,
|
|
||||||
hasInternetConnection: connectivity.isConnected,
|
|
||||||
isLabelClickable: false,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
hasLoaded: state.hasLoaded,
|
|
||||||
onTap: _onOpenDocumentDetails,
|
|
||||||
viewType: _viewType,
|
|
||||||
),
|
|
||||||
if (state.hasLoaded && state.isLoading)
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onOpenDocumentDetails(DocumentModel document) {
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
DocumentDetailsRoute.routeName,
|
|
||||||
arguments: DocumentDetailsRouteArguments(
|
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
// import 'dart:math';
|
|
||||||
|
|
||||||
// import 'package:flutter/material.dart';
|
|
||||||
// import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
// import 'package:paperless_api/paperless_api.dart';
|
|
||||||
// import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|
||||||
// import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
// import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
// import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
// import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
|
||||||
// import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
|
||||||
// import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
|
||||||
// import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
|
||||||
// import 'package:paperless_mobile/generated/l10n.dart';
|
|
||||||
// import 'package:paperless_mobile/helpers/message_helpers.dart';
|
|
||||||
// import 'package:paperless_mobile/constants.dart';
|
|
||||||
// import 'package:shimmer/shimmer.dart';
|
|
||||||
|
|
||||||
// class SavedViewSelectionWidget extends StatelessWidget {
|
|
||||||
// final DocumentFilter currentFilter;
|
|
||||||
// const SavedViewSelectionWidget({
|
|
||||||
// Key? key,
|
|
||||||
// required this.height,
|
|
||||||
// required this.enabled,
|
|
||||||
// required this.currentFilter,
|
|
||||||
// }) : super(key: key);
|
|
||||||
|
|
||||||
// final double height;
|
|
||||||
// final bool enabled;
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
|
||||||
// builder: (context, connectivityState) {
|
|
||||||
// final hasInternetConnection = connectivityState.isConnected;
|
|
||||||
// return SizedBox(
|
|
||||||
// height: height,
|
|
||||||
// child: Column(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// mainAxisSize: MainAxisSize.min,
|
|
||||||
// children: [
|
|
||||||
// BlocBuilder<SavedViewCubit, SavedViewState>(
|
|
||||||
// builder: (context, state) {
|
|
||||||
// if (!state.hasLoaded) {
|
|
||||||
// return _buildLoadingWidget(context);
|
|
||||||
// }
|
|
||||||
// if (state.value.isEmpty) {
|
|
||||||
// return Text(S.of(context).savedViewsEmptyStateText);
|
|
||||||
// }
|
|
||||||
// return SizedBox(
|
|
||||||
// height: 38,
|
|
||||||
// child: ListView.separated(
|
|
||||||
// itemCount: state.value.length,
|
|
||||||
// scrollDirection: Axis.horizontal,
|
|
||||||
// itemBuilder: (context, index) {
|
|
||||||
// final view = state.value.values.elementAt(index);
|
|
||||||
// return GestureDetector(
|
|
||||||
// onLongPress: hasInternetConnection
|
|
||||||
// ? () => _onDelete(context, view)
|
|
||||||
// : null,
|
|
||||||
// child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
||||||
// builder: (context, docState) {
|
|
||||||
// final view = state.value.values.toList()[index];
|
|
||||||
// return FilterChip(
|
|
||||||
// label: Text(
|
|
||||||
// view.name,
|
|
||||||
// ),
|
|
||||||
// selected:
|
|
||||||
// view.id == docState.selectedSavedViewId,
|
|
||||||
// onSelected: enabled && hasInternetConnection
|
|
||||||
// ? (isSelected) =>
|
|
||||||
// _onSelected(isSelected, context, view)
|
|
||||||
// : null,
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// separatorBuilder: (context, index) => const SizedBox(
|
|
||||||
// width: 4.0,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// BlocBuilder<SavedViewCubit, SavedViewState>(
|
|
||||||
// builder: (context, state) {
|
|
||||||
// return Row(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
// children: [
|
|
||||||
// Text(
|
|
||||||
// S.of(context).savedViewsLabel,
|
|
||||||
// style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
// ),
|
|
||||||
// BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
||||||
// buildWhen: (previous, current) =>
|
|
||||||
// previous.filter != current.filter,
|
|
||||||
// builder: (context, docState) {
|
|
||||||
// return TextButton.icon(
|
|
||||||
// icon: const Icon(Icons.add),
|
|
||||||
// onPressed: (enabled &&
|
|
||||||
// state.hasLoaded &&
|
|
||||||
// hasInternetConnection)
|
|
||||||
// ? () =>
|
|
||||||
// _onCreatePressed(context, docState.filter)
|
|
||||||
// : null,
|
|
||||||
// label: Text(S.of(context).savedViewCreateNewLabel),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ).padded(),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Widget _buildLoadingWidget(BuildContext context) {
|
|
||||||
// return SizedBox(
|
|
||||||
// height: 38,
|
|
||||||
// width: MediaQuery.of(context).size.width,
|
|
||||||
// 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(
|
|
||||||
// scrollDirection: Axis.horizontal,
|
|
||||||
// physics: const NeverScrollableScrollPhysics(),
|
|
||||||
// children: [
|
|
||||||
// FilterChip(
|
|
||||||
// label: const SizedBox(width: 32),
|
|
||||||
// onSelected: (_) {},
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 4.0),
|
|
||||||
// FilterChip(
|
|
||||||
// label: const SizedBox(width: 64),
|
|
||||||
// onSelected: (_) {},
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 4.0),
|
|
||||||
// FilterChip(
|
|
||||||
// label: const SizedBox(width: 100),
|
|
||||||
// onSelected: (_) {},
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 4.0),
|
|
||||||
// FilterChip(
|
|
||||||
// label: const SizedBox(width: 32),
|
|
||||||
// onSelected: (_) {},
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 4.0),
|
|
||||||
// FilterChip(
|
|
||||||
// label: const SizedBox(width: 48),
|
|
||||||
// onSelected: (_) {},
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _onCreatePressed(BuildContext context, DocumentFilter filter) async {
|
|
||||||
// final newView = await Navigator.of(context).push<SavedView?>(
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => AddSavedViewPage(
|
|
||||||
// currentFilter: filter,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// if (newView != null) {
|
|
||||||
// try {
|
|
||||||
// await context.read<SavedViewCubit>().add(newView);
|
|
||||||
// } on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
// showErrorMessage(context, error, stackTrace);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _onSelected(
|
|
||||||
// bool selectionIntent,
|
|
||||||
// BuildContext context,
|
|
||||||
// SavedView view,
|
|
||||||
// ) async {
|
|
||||||
// if (selectionIntent) {
|
|
||||||
// context.read<DocumentsCubit>().selectView(view.id!);
|
|
||||||
// } else {
|
|
||||||
// context.read<DocumentsCubit>().unselectView();
|
|
||||||
// context.read<DocumentsCubit>().resetFilter();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _onDelete(BuildContext context, SavedView view) async {
|
|
||||||
// {
|
|
||||||
// final delete = await showDialog<bool>(
|
|
||||||
// context: context,
|
|
||||||
// builder: (context) => ConfirmDeleteSavedViewDialog(view: view),
|
|
||||||
// ) ??
|
|
||||||
// false;
|
|
||||||
// if (delete) {
|
|
||||||
// try {
|
|
||||||
// context.read<SavedViewCubit>().remove(view);
|
|
||||||
// if (context.read<DocumentsCubit>().state.selectedSavedViewId ==
|
|
||||||
// view.id) {
|
|
||||||
// await context.read<DocumentsCubit>().resetFilter();
|
|
||||||
// }
|
|
||||||
// } on PaperlessServerException catch (error, stackTrace) {
|
|
||||||
// showErrorMessage(context, error, stackTrace);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
|
||||||
|
part 'saved_view_details_cubit.g.dart';
|
||||||
|
part 'saved_view_details_state.dart';
|
||||||
|
|
||||||
|
class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
|
@override
|
||||||
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
|
final SavedView savedView;
|
||||||
|
SavedViewDetailsCubit(
|
||||||
|
this.api,
|
||||||
|
this.notifier, {
|
||||||
|
required this.savedView,
|
||||||
|
}) : super(const SavedViewDetailsState()) {
|
||||||
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onDeleted: remove,
|
||||||
|
onUpdated: replace,
|
||||||
|
);
|
||||||
|
updateFilter(filter: savedView.toDocumentFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setViewType(ViewType viewType) {
|
||||||
|
emit(state.copyWith(viewType: viewType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SavedViewDetailsState? fromJson(Map<String, dynamic> json) {
|
||||||
|
return SavedViewDetailsState.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic>? toJson(SavedViewDetailsState state) {
|
||||||
|
return state.toJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
part of 'saved_view_details_cubit.dart';
|
part of 'saved_view_details_cubit.dart';
|
||||||
|
|
||||||
class SavedViewDetailsState extends PagedDocumentsState {
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
|
class SavedViewDetailsState extends DocumentPagingState {
|
||||||
|
@JsonKey()
|
||||||
|
final ViewType viewType;
|
||||||
|
|
||||||
const SavedViewDetailsState({
|
const SavedViewDetailsState({
|
||||||
|
this.viewType = ViewType.list,
|
||||||
super.filter,
|
super.filter,
|
||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
super.isLoading,
|
super.isLoading,
|
||||||
@@ -10,10 +15,8 @@ class SavedViewDetailsState extends PagedDocumentsState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
filter,
|
viewType,
|
||||||
hasLoaded,
|
...super.props,
|
||||||
isLoading,
|
|
||||||
value,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,12 +39,19 @@ class SavedViewDetailsState extends PagedDocumentsState {
|
|||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
List<PagedSearchResult<DocumentModel>>? value,
|
List<PagedSearchResult<DocumentModel>>? value,
|
||||||
DocumentFilter? filter,
|
DocumentFilter? filter,
|
||||||
|
ViewType? viewType,
|
||||||
}) {
|
}) {
|
||||||
return SavedViewDetailsState(
|
return SavedViewDetailsState(
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
filter: filter ?? this.filter,
|
filter: filter ?? this.filter,
|
||||||
|
viewType: viewType ?? this.viewType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory SavedViewDetailsState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SavedViewDetailsStateFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$SavedViewDetailsStateToJson(this);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||||
|
|
||||||
|
class SavedViewDetailsPage extends StatefulWidget {
|
||||||
|
final Future<void> Function(SavedView savedView) onDelete;
|
||||||
|
const SavedViewDetailsPage({
|
||||||
|
super.key,
|
||||||
|
required this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SavedViewDetailsPage> createState() => _SavedViewDetailsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
||||||
|
with DocumentPagingViewMixin {
|
||||||
|
@override
|
||||||
|
final pagingScrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cubit = context.read<SavedViewDetailsCubit>();
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(cubit.savedView.name),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () async {
|
||||||
|
final shouldDelete = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConfirmDeleteSavedViewDialog(
|
||||||
|
view: cubit.savedView,
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
if (shouldDelete) {
|
||||||
|
await widget.onDelete(cubit.savedView);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return ViewTypeSelectionWidget(
|
||||||
|
viewType: state.viewType,
|
||||||
|
onChanged: cubit.setViewType,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.hasLoaded && state.documents.isEmpty) {
|
||||||
|
return DocumentsEmptyState(state: state);
|
||||||
|
}
|
||||||
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
|
builder: (context, connectivity) {
|
||||||
|
return CustomScrollView(
|
||||||
|
controller: pagingScrollController,
|
||||||
|
slivers: [
|
||||||
|
SliverAdaptiveDocumentsView(
|
||||||
|
documents: state.documents,
|
||||||
|
hasInternetConnection: connectivity.isConnected,
|
||||||
|
isLabelClickable: false,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
hasLoaded: state.hasLoaded,
|
||||||
|
onTap: (document) {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
DocumentDetailsRoute.routeName,
|
||||||
|
arguments: DocumentDetailsRouteArguments(
|
||||||
|
document: document,
|
||||||
|
isLabelClickable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
viewType: state.viewType,
|
||||||
|
),
|
||||||
|
if (state.hasLoaded && state.isLoading)
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
|
part 'application_settings_cubit.g.dart';
|
||||||
|
part 'application_settings_state.dart';
|
||||||
|
|
||||||
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
||||||
final LocalAuthenticationService _localAuthenticationService;
|
final LocalAuthenticationService _localAuthenticationService;
|
||||||
@@ -33,11 +38,6 @@ class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
|||||||
_updateSettings(updatedSettings);
|
_updateSettings(updatedSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setViewType(ViewType viewType) {
|
|
||||||
final updatedSettings = state.copyWith(preferredViewType: viewType);
|
|
||||||
_updateSettings(updatedSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColorSchemeOption(ColorSchemeOption schemeOption) {
|
void setColorSchemeOption(ColorSchemeOption schemeOption) {
|
||||||
final updatedSettings =
|
final updatedSettings =
|
||||||
state.copyWith(preferredColorSchemeOption: schemeOption);
|
state.copyWith(preferredColorSchemeOption: schemeOption);
|
||||||