mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 15:15:50 -06:00
Cleaned up code, implemented message queue to notify subscribers of document updates.
This commit is contained in:
@@ -68,11 +68,11 @@ android {
|
|||||||
storePassword keystoreProperties['storePassword']
|
storePassword keystoreProperties['storePassword']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ PODS:
|
|||||||
- DKPhotoGallery/Resource (0.0.17):
|
- DKPhotoGallery/Resource (0.0.17):
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
- edge_detection (1.0.9):
|
- edge_detection (1.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WeScan
|
- WeScan
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
@@ -44,6 +44,8 @@ PODS:
|
|||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_keyboard_visibility (0.0.1):
|
- flutter_keyboard_visibility (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_local_notifications (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_native_splash (0.0.1):
|
- flutter_native_splash (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
@@ -56,10 +58,13 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- local_auth_ios (0.0.1):
|
- local_auth_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- open_filex (0.0.2):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- pdfx (1.0.0):
|
- pdfx (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- permission_handler_apple (9.0.4):
|
- permission_handler_apple (9.0.4):
|
||||||
@@ -72,8 +77,9 @@ PODS:
|
|||||||
- SDWebImage/Core (5.13.5)
|
- SDWebImage/Core (5.13.5)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_ios (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
@@ -90,17 +96,19 @@ DEPENDENCIES:
|
|||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||||
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||||
|
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- pdfx (from `.symlinks/plugins/pdfx/ios`)
|
- pdfx (from `.symlinks/plugins/pdfx/ios`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
@@ -128,6 +136,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_keyboard_visibility:
|
flutter_keyboard_visibility:
|
||||||
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
||||||
|
flutter_local_notifications:
|
||||||
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
@@ -136,10 +146,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/integration_test/ios"
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
local_auth_ios:
|
local_auth_ios:
|
||||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||||
|
open_filex:
|
||||||
|
:path: ".symlinks/plugins/open_filex/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_ios:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
pdfx:
|
pdfx:
|
||||||
:path: ".symlinks/plugins/pdfx/ios"
|
:path: ".symlinks/plugins/pdfx/ios"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
@@ -148,8 +160,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_ios:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
@@ -160,28 +172,30 @@ SPEC CHECKSUMS:
|
|||||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||||
edge_detection: 9bc5ee35073b5a17c0b3b679908f01017ce3062a
|
edge_detection: fa02aa120e00d87ada0ca2430b6c6087a501b1e9
|
||||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
|
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||||
fluttertoast: 74526702fea2c060ea55dde75895b7e1bde1c86b
|
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||||
local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d
|
local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d
|
||||||
|
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||||
pdfx: 7b876b09de8b7a0bf444a4f82b439ffcff4ee1ec
|
pdfx: 7b876b09de8b7a0bf444a4f82b439ffcff4ee1ec
|
||||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
||||||
SDWebImage: 23d714cd599354ee7906dbae26dff89b421c4370
|
SDWebImage: 23d714cd599354ee7906dbae26dff89b421c4370
|
||||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
|
||||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||||
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
|
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
|
||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -321,10 +321,12 @@
|
|||||||
};
|
};
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
);
|
);
|
||||||
name = "Thin Binary";
|
name = "Thin Binary";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
@@ -335,6 +337,7 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,5 +65,7 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
|
|
||||||
@@ -9,26 +11,40 @@ class DocumentChangedNotifier {
|
|||||||
final Subject<DocumentModel> _updated = PublishSubject();
|
final Subject<DocumentModel> _updated = PublishSubject();
|
||||||
final Subject<DocumentModel> _deleted = PublishSubject();
|
final Subject<DocumentModel> _deleted = PublishSubject();
|
||||||
|
|
||||||
|
final Map<dynamic, List<StreamSubscription>> _subscribers = {};
|
||||||
|
|
||||||
void notifyUpdated(DocumentModel updated) {
|
void notifyUpdated(DocumentModel updated) {
|
||||||
|
debugPrint("Notifying updated document ${updated.id}");
|
||||||
_updated.add(updated);
|
_updated.add(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void notifyDeleted(DocumentModel deleted) {
|
void notifyDeleted(DocumentModel deleted) {
|
||||||
|
debugPrint("Notifying deleted document ${deleted.id}");
|
||||||
_deleted.add(deleted);
|
_deleted.add(deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StreamSubscription> listen({
|
void subscribe(
|
||||||
|
dynamic subscriber, {
|
||||||
DocumentChangedCallback? onUpdated,
|
DocumentChangedCallback? onUpdated,
|
||||||
DocumentChangedCallback? onDeleted,
|
DocumentChangedCallback? onDeleted,
|
||||||
}) {
|
}) {
|
||||||
return [
|
_subscribers.putIfAbsent(
|
||||||
_updated.listen((value) {
|
subscriber,
|
||||||
onUpdated?.call(value);
|
() => [
|
||||||
}),
|
_updated.listen((value) {
|
||||||
_updated.listen((value) {
|
onUpdated?.call(value);
|
||||||
onDeleted?.call(value);
|
}),
|
||||||
}),
|
_deleted.listen((value) {
|
||||||
];
|
onDeleted?.call(value);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unsubscribe(dynamic subscriber) {
|
||||||
|
_subscribers[subscriber]?.forEach((element) {
|
||||||
|
element.cancel();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Base repository class which all repositories should implement
|
/// Base repository class which all repositories should implement
|
||||||
///
|
///
|
||||||
abstract class BaseRepository<State extends RepositoryState, Type>
|
abstract class BaseRepository<T> extends Cubit<IndexedRepositoryState<T>>
|
||||||
extends Cubit<State> with HydratedMixin {
|
with HydratedMixin {
|
||||||
final State _initialState;
|
final IndexedRepositoryState<T> _initialState;
|
||||||
|
|
||||||
BaseRepository(this._initialState) : super(_initialState) {
|
BaseRepository(this._initialState) : super(_initialState) {
|
||||||
hydrate();
|
hydrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<State?> get values =>
|
Stream<IndexedRepositoryState<T>?> get values =>
|
||||||
BehaviorSubject.seeded(state)..addStream(super.stream);
|
BehaviorSubject.seeded(state)..addStream(super.stream);
|
||||||
|
|
||||||
State? get current => state;
|
IndexedRepositoryState<T>? get current => state;
|
||||||
|
|
||||||
bool get isInitialized => state.hasLoaded;
|
bool get isInitialized => state.hasLoaded;
|
||||||
|
|
||||||
Future<Type> create(Type object);
|
Future<T> create(T object);
|
||||||
Future<Type?> find(int id);
|
Future<T?> find(int id);
|
||||||
Future<Iterable<Type>> findAll([Iterable<int>? ids]);
|
Future<Iterable<T>> findAll([Iterable<int>? ids]);
|
||||||
Future<Type> update(Type object);
|
Future<T> update(T object);
|
||||||
Future<int> delete(Type object);
|
Future<int> delete(T object);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import 'dart:async';
|
|||||||
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/core/repository/state/repository_state.dart';
|
|
||||||
|
|
||||||
class CorrespondentRepositoryImpl
|
class CorrespondentRepositoryImpl extends LabelRepository<Correspondent> {
|
||||||
extends LabelRepository<Correspondent, CorrespondentRepositoryState> {
|
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
CorrespondentRepositoryImpl(this._api)
|
CorrespondentRepositoryImpl(this._api)
|
||||||
@@ -15,7 +13,7 @@ class CorrespondentRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<Correspondent> create(Correspondent correspondent) async {
|
Future<Correspondent> create(Correspondent correspondent) async {
|
||||||
final created = await _api.saveCorrespondent(correspondent);
|
final created = await _api.saveCorrespondent(correspondent);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return created;
|
return created;
|
||||||
@@ -24,7 +22,7 @@ class CorrespondentRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<int> delete(Correspondent correspondent) async {
|
Future<int> delete(Correspondent correspondent) async {
|
||||||
await _api.deleteCorrespondent(correspondent);
|
await _api.deleteCorrespondent(correspondent);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..removeWhere((k, v) => k == correspondent.id);
|
..removeWhere((k, v) => k == correspondent.id);
|
||||||
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return correspondent.id!;
|
return correspondent.id!;
|
||||||
@@ -34,7 +32,7 @@ class CorrespondentRepositoryImpl
|
|||||||
Future<Correspondent?> find(int id) async {
|
Future<Correspondent?> find(int id) async {
|
||||||
final correspondent = await _api.getCorrespondent(id);
|
final correspondent = await _api.getCorrespondent(id);
|
||||||
if (correspondent != null) {
|
if (correspondent != null) {
|
||||||
final updatedState = {...state.values}..[id] = correspondent;
|
final updatedState = {...state.values ?? {}}..[id] = correspondent;
|
||||||
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return correspondent;
|
return correspondent;
|
||||||
}
|
}
|
||||||
@@ -44,7 +42,7 @@ class CorrespondentRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
||||||
final correspondents = await _api.getCorrespondents(ids);
|
final correspondents = await _api.getCorrespondents(ids);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
||||||
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return correspondents;
|
return correspondents;
|
||||||
@@ -53,7 +51,8 @@ class CorrespondentRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<Correspondent> update(Correspondent correspondent) async {
|
Future<Correspondent> update(Correspondent correspondent) async {
|
||||||
final updated = await _api.updateCorrespondent(correspondent);
|
final updated = await _api.updateCorrespondent(correspondent);
|
||||||
final updatedState = {...state.values}..update(updated.id!, (_) => updated);
|
final updatedState = {...state.values ?? {}}
|
||||||
|
..update(updated.id!, (_) => updated);
|
||||||
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,7 @@ class CorrespondentRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson(CorrespondentRepositoryState state) {
|
Map<String, dynamic> toJson(covariant CorrespondentRepositoryState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
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:rxdart/rxdart.dart' show BehaviorSubject;
|
|
||||||
|
|
||||||
class DocumentTypeRepositoryImpl
|
class DocumentTypeRepositoryImpl extends LabelRepository<DocumentType> {
|
||||||
extends LabelRepository<DocumentType, DocumentTypeRepositoryState> {
|
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
DocumentTypeRepositoryImpl(this._api)
|
DocumentTypeRepositoryImpl(this._api)
|
||||||
@@ -13,7 +11,7 @@ class DocumentTypeRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<DocumentType> create(DocumentType documentType) async {
|
Future<DocumentType> create(DocumentType documentType) async {
|
||||||
final created = await _api.saveDocumentType(documentType);
|
final created = await _api.saveDocumentType(documentType);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return created;
|
return created;
|
||||||
@@ -22,7 +20,7 @@ class DocumentTypeRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<int> delete(DocumentType documentType) async {
|
Future<int> delete(DocumentType documentType) async {
|
||||||
await _api.deleteDocumentType(documentType);
|
await _api.deleteDocumentType(documentType);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..removeWhere((k, v) => k == documentType.id);
|
..removeWhere((k, v) => k == documentType.id);
|
||||||
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return documentType.id!;
|
return documentType.id!;
|
||||||
@@ -32,7 +30,7 @@ class DocumentTypeRepositoryImpl
|
|||||||
Future<DocumentType?> find(int id) async {
|
Future<DocumentType?> find(int id) async {
|
||||||
final documentType = await _api.getDocumentType(id);
|
final documentType = await _api.getDocumentType(id);
|
||||||
if (documentType != null) {
|
if (documentType != null) {
|
||||||
final updatedState = {...state.values}..[id] = documentType;
|
final updatedState = {...state.values ?? {}}..[id] = documentType;
|
||||||
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return documentType;
|
return documentType;
|
||||||
}
|
}
|
||||||
@@ -42,7 +40,7 @@ class DocumentTypeRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
||||||
final documentTypes = await _api.getDocumentTypes(ids);
|
final documentTypes = await _api.getDocumentTypes(ids);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
||||||
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return documentTypes;
|
return documentTypes;
|
||||||
@@ -51,7 +49,8 @@ class DocumentTypeRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<DocumentType> update(DocumentType documentType) async {
|
Future<DocumentType> update(DocumentType documentType) async {
|
||||||
final updated = await _api.updateDocumentType(documentType);
|
final updated = await _api.updateDocumentType(documentType);
|
||||||
final updatedState = {...state.values}..update(updated.id!, (_) => updated);
|
final updatedState = {...state.values ?? {}}
|
||||||
|
..update(updated.id!, (_) => updated);
|
||||||
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -62,7 +61,7 @@ class DocumentTypeRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson(DocumentTypeRepositoryState state) {
|
Map<String, dynamic> toJson(covariant DocumentTypeRepositoryState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
|||||||
@override
|
@override
|
||||||
Future<SavedView> create(SavedView object) async {
|
Future<SavedView> create(SavedView object) async {
|
||||||
final created = await _api.save(object);
|
final created = await _api.save(object);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return created;
|
return created;
|
||||||
@@ -19,7 +19,7 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
|||||||
@override
|
@override
|
||||||
Future<int> delete(SavedView view) async {
|
Future<int> delete(SavedView view) async {
|
||||||
await _api.delete(view);
|
await _api.delete(view);
|
||||||
final updatedState = {...state.values}..remove(view.id);
|
final updatedState = {...state.values ?? {}}..remove(view.id);
|
||||||
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return view.id!;
|
return view.id!;
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
|||||||
@override
|
@override
|
||||||
Future<SavedView?> find(int id) async {
|
Future<SavedView?> find(int id) async {
|
||||||
final found = await _api.find(id);
|
final found = await _api.find(id);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..update(id, (_) => found, ifAbsent: () => found);
|
..update(id, (_) => found, ifAbsent: () => found);
|
||||||
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return found;
|
return found;
|
||||||
@@ -37,7 +37,7 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
|||||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||||
final found = await _api.findAll(ids);
|
final found = await _api.findAll(ids);
|
||||||
final updatedState = {
|
final updatedState = {
|
||||||
...state.values,
|
...state.values ?? {},
|
||||||
...{for (final view in found) view.id!: view},
|
...{for (final view in found) view.id!: view},
|
||||||
};
|
};
|
||||||
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
@@ -56,7 +56,7 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson(SavedViewRepositoryState state) {
|
Map<String, dynamic> toJson(covariant SavedViewRepositoryState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ 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:rxdart/rxdart.dart' show BehaviorSubject;
|
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||||
|
|
||||||
class StoragePathRepositoryImpl
|
class StoragePathRepositoryImpl extends LabelRepository<StoragePath> {
|
||||||
extends LabelRepository<StoragePath, StoragePathRepositoryState> {
|
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
StoragePathRepositoryImpl(this._api)
|
StoragePathRepositoryImpl(this._api)
|
||||||
@@ -13,7 +12,7 @@ class StoragePathRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<StoragePath> create(StoragePath storagePath) async {
|
Future<StoragePath> create(StoragePath storagePath) async {
|
||||||
final created = await _api.saveStoragePath(storagePath);
|
final created = await _api.saveStoragePath(storagePath);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return created;
|
return created;
|
||||||
@@ -22,7 +21,7 @@ class StoragePathRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<int> delete(StoragePath storagePath) async {
|
Future<int> delete(StoragePath storagePath) async {
|
||||||
await _api.deleteStoragePath(storagePath);
|
await _api.deleteStoragePath(storagePath);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..removeWhere((k, v) => k == storagePath.id);
|
..removeWhere((k, v) => k == storagePath.id);
|
||||||
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return storagePath.id!;
|
return storagePath.id!;
|
||||||
@@ -32,7 +31,7 @@ class StoragePathRepositoryImpl
|
|||||||
Future<StoragePath?> find(int id) async {
|
Future<StoragePath?> find(int id) async {
|
||||||
final storagePath = await _api.getStoragePath(id);
|
final storagePath = await _api.getStoragePath(id);
|
||||||
if (storagePath != null) {
|
if (storagePath != null) {
|
||||||
final updatedState = {...state.values}..[id] = storagePath;
|
final updatedState = {...state.values ?? {}}..[id] = storagePath;
|
||||||
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return storagePath;
|
return storagePath;
|
||||||
}
|
}
|
||||||
@@ -42,7 +41,7 @@ class StoragePathRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
||||||
final storagePaths = await _api.getStoragePaths(ids);
|
final storagePaths = await _api.getStoragePaths(ids);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
||||||
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return storagePaths;
|
return storagePaths;
|
||||||
@@ -51,7 +50,8 @@ class StoragePathRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<StoragePath> update(StoragePath storagePath) async {
|
Future<StoragePath> update(StoragePath storagePath) async {
|
||||||
final updated = await _api.updateStoragePath(storagePath);
|
final updated = await _api.updateStoragePath(storagePath);
|
||||||
final updatedState = {...state.values}..update(updated.id!, (_) => updated);
|
final updatedState = {...state.values ?? {}}
|
||||||
|
..update(updated.id!, (_) => updated);
|
||||||
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ class StoragePathRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson(StoragePathRepositoryState state) {
|
Map<String, dynamic> toJson(covariant StoragePathRepositoryState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
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/tag_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
|
||||||
|
|
||||||
class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
class TagRepositoryImpl extends LabelRepository<Tag> {
|
||||||
final PaperlessLabelsApi _api;
|
final PaperlessLabelsApi _api;
|
||||||
|
|
||||||
TagRepositoryImpl(this._api) : super(const TagRepositoryState());
|
TagRepositoryImpl(this._api) : super(const TagRepositoryState());
|
||||||
@@ -12,7 +10,7 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
@override
|
@override
|
||||||
Future<Tag> create(Tag object) async {
|
Future<Tag> create(Tag object) async {
|
||||||
final created = await _api.saveTag(object);
|
final created = await _api.saveTag(object);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..putIfAbsent(created.id!, () => created);
|
..putIfAbsent(created.id!, () => created);
|
||||||
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return created;
|
return created;
|
||||||
@@ -21,7 +19,8 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
@override
|
@override
|
||||||
Future<int> delete(Tag tag) async {
|
Future<int> delete(Tag tag) async {
|
||||||
await _api.deleteTag(tag);
|
await _api.deleteTag(tag);
|
||||||
final updatedState = {...state.values}..removeWhere((k, v) => k == tag.id);
|
final updatedState = {...state.values ?? {}}
|
||||||
|
..removeWhere((k, v) => k == tag.id);
|
||||||
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return tag.id!;
|
return tag.id!;
|
||||||
}
|
}
|
||||||
@@ -30,7 +29,7 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
Future<Tag?> find(int id) async {
|
Future<Tag?> find(int id) async {
|
||||||
final tag = await _api.getTag(id);
|
final tag = await _api.getTag(id);
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
final updatedState = {...state.values}..[id] = tag;
|
final updatedState = {...state.values ?? {}}..[id] = tag;
|
||||||
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
@@ -40,7 +39,7 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
@override
|
@override
|
||||||
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
||||||
final tags = await _api.getTags(ids);
|
final tags = await _api.getTags(ids);
|
||||||
final updatedState = {...state.values}
|
final updatedState = {...state.values ?? {}}
|
||||||
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
||||||
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return tags;
|
return tags;
|
||||||
@@ -49,7 +48,8 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
@override
|
@override
|
||||||
Future<Tag> update(Tag tag) async {
|
Future<Tag> update(Tag tag) async {
|
||||||
final updated = await _api.updateTag(tag);
|
final updated = await _api.updateTag(tag);
|
||||||
final updatedState = {...state.values}..update(updated.id!, (_) => updated);
|
final updatedState = {...state.values ?? {}}
|
||||||
|
..update(updated.id!, (_) => updated);
|
||||||
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ class TagRepositoryImpl extends LabelRepository<Tag, TagRepositoryState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic>? toJson(TagRepositoryState state) {
|
Map<String, dynamic>? toJson(covariant TagRepositoryState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
|
|
||||||
abstract class LabelRepository<T extends Label, State extends RepositoryState>
|
abstract class LabelRepository<T extends Label> extends BaseRepository<T> {
|
||||||
extends BaseRepository<State, T> {
|
LabelRepository(IndexedRepositoryState<T> initial) : super(initial);
|
||||||
LabelRepository(State initial) : super(initial);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,16 @@ class LabelRepositoriesProvider extends StatelessWidget {
|
|||||||
return MultiRepositoryProvider(
|
return MultiRepositoryProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) => context
|
create: (context) => context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) =>
|
create: (context) => context.read<LabelRepository<Tag>>(),
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: child,
|
child: child,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
import 'package:paperless_mobile/core/repository/base_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
|
|
||||||
abstract class SavedViewRepository
|
abstract class SavedViewRepository extends BaseRepository<SavedView> {
|
||||||
extends BaseRepository<SavedViewRepositoryState, SavedView> {
|
|
||||||
SavedViewRepository(super.initialState);
|
SavedViewRepository(super.initialState);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:json_annotation/json_annotation.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/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
|
|
||||||
part 'correspondent_repository_state.g.dart';
|
part 'correspondent_repository_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class CorrespondentRepositoryState
|
class CorrespondentRepositoryState
|
||||||
extends RepositoryState<Map<int, Correspondent>> {
|
extends IndexedRepositoryState<Correspondent> {
|
||||||
const CorrespondentRepositoryState({
|
const CorrespondentRepositoryState({
|
||||||
super.values = const {},
|
super.values = const {},
|
||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ CorrespondentRepositoryState _$CorrespondentRepositoryStateFromJson(
|
|||||||
Map<String, dynamic> _$CorrespondentRepositoryStateToJson(
|
Map<String, dynamic> _$CorrespondentRepositoryStateToJson(
|
||||||
CorrespondentRepositoryState instance) =>
|
CorrespondentRepositoryState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'values': instance.values.map((k, e) => MapEntry(k.toString(), e)),
|
'values': instance.values?.map((k, e) => MapEntry(k.toString(), e)),
|
||||||
'hasLoaded': instance.hasLoaded,
|
'hasLoaded': instance.hasLoaded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'document_type_repository_state.g.dart';
|
part 'document_type_repository_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class DocumentTypeRepositoryState
|
class DocumentTypeRepositoryState extends IndexedRepositoryState<DocumentType> {
|
||||||
extends RepositoryState<Map<int, DocumentType>> {
|
|
||||||
const DocumentTypeRepositoryState({
|
const DocumentTypeRepositoryState({
|
||||||
super.values = const {},
|
super.values = const {},
|
||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DocumentTypeRepositoryState copyWith(
|
DocumentTypeRepositoryState copyWith({
|
||||||
{Map<int, DocumentType>? values, bool? hasLoaded}) {
|
Map<int, DocumentType>? values,
|
||||||
|
bool? hasLoaded,
|
||||||
|
}) {
|
||||||
return DocumentTypeRepositoryState(
|
return DocumentTypeRepositoryState(
|
||||||
values: values ?? this.values,
|
values: values ?? this.values,
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ DocumentTypeRepositoryState _$DocumentTypeRepositoryStateFromJson(
|
|||||||
Map<String, dynamic> _$DocumentTypeRepositoryStateToJson(
|
Map<String, dynamic> _$DocumentTypeRepositoryStateToJson(
|
||||||
DocumentTypeRepositoryState instance) =>
|
DocumentTypeRepositoryState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'values': instance.values.map((k, e) => MapEntry(k.toString(), e)),
|
'values': instance.values?.map((k, e) => MapEntry(k.toString(), e)),
|
||||||
'hasLoaded': instance.hasLoaded,
|
'hasLoaded': instance.hasLoaded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'saved_view_repository_state.g.dart';
|
part 'saved_view_repository_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class SavedViewRepositoryState extends RepositoryState<Map<int, SavedView>> {
|
class SavedViewRepositoryState extends IndexedRepositoryState<SavedView> {
|
||||||
const SavedViewRepositoryState({
|
const SavedViewRepositoryState({
|
||||||
super.values = const {},
|
super.values = const {},
|
||||||
super.hasLoaded = false,
|
super.hasLoaded = false,
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ SavedViewRepositoryState _$SavedViewRepositoryStateFromJson(
|
|||||||
Map<String, dynamic> _$SavedViewRepositoryStateToJson(
|
Map<String, dynamic> _$SavedViewRepositoryStateToJson(
|
||||||
SavedViewRepositoryState instance) =>
|
SavedViewRepositoryState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'values': instance.values.map((k, e) => MapEntry(k.toString(), e)),
|
'values': instance.values?.map((k, e) => MapEntry(k.toString(), e)),
|
||||||
'hasLoaded': instance.hasLoaded,
|
'hasLoaded': instance.hasLoaded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'storage_path_repository_state.g.dart';
|
part 'storage_path_repository_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class StoragePathRepositoryState
|
class StoragePathRepositoryState extends IndexedRepositoryState<StoragePath> {
|
||||||
extends RepositoryState<Map<int, StoragePath>> {
|
|
||||||
const StoragePathRepositoryState({
|
const StoragePathRepositoryState({
|
||||||
super.values = const {},
|
super.values = const {},
|
||||||
super.hasLoaded = false,
|
super.hasLoaded = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
StoragePathRepositoryState copyWith(
|
StoragePathRepositoryState copyWith({
|
||||||
{Map<int, StoragePath>? values, bool? hasLoaded}) {
|
Map<int, StoragePath>? values,
|
||||||
|
bool? hasLoaded,
|
||||||
|
}) {
|
||||||
return StoragePathRepositoryState(
|
return StoragePathRepositoryState(
|
||||||
values: values ?? this.values,
|
values: values ?? this.values,
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ StoragePathRepositoryState _$StoragePathRepositoryStateFromJson(
|
|||||||
Map<String, dynamic> _$StoragePathRepositoryStateToJson(
|
Map<String, dynamic> _$StoragePathRepositoryStateToJson(
|
||||||
StoragePathRepositoryState instance) =>
|
StoragePathRepositoryState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'values': instance.values.map((k, e) => MapEntry(k.toString(), e)),
|
'values': instance.values?.map((k, e) => MapEntry(k.toString(), e)),
|
||||||
'hasLoaded': instance.hasLoaded,
|
'hasLoaded': instance.hasLoaded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import 'package:json_annotation/json_annotation.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/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
|
|
||||||
part 'tag_repository_state.g.dart';
|
part 'tag_repository_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class TagRepositoryState extends RepositoryState<Map<int, Tag>> {
|
class TagRepositoryState extends IndexedRepositoryState<Tag> {
|
||||||
const TagRepositoryState({
|
const TagRepositoryState({
|
||||||
super.values = const {},
|
super.values = const {},
|
||||||
super.hasLoaded = false,
|
super.hasLoaded = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TagRepositoryState copyWith({Map<int, Tag>? values, bool? hasLoaded}) {
|
TagRepositoryState copyWith({
|
||||||
|
Map<int, Tag>? values,
|
||||||
|
bool? hasLoaded,
|
||||||
|
}) {
|
||||||
return TagRepositoryState(
|
return TagRepositoryState(
|
||||||
values: values ?? this.values,
|
values: values ?? this.values,
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ TagRepositoryState _$TagRepositoryStateFromJson(Map<String, dynamic> json) =>
|
|||||||
|
|
||||||
Map<String, dynamic> _$TagRepositoryStateToJson(TagRepositoryState instance) =>
|
Map<String, dynamic> _$TagRepositoryStateToJson(TagRepositoryState instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'values': instance.values.map((k, e) => MapEntry(k.toString(), e)),
|
'values': instance.values?.map((k, e) => MapEntry(k.toString(), e)),
|
||||||
'hasLoaded': instance.hasLoaded,
|
'hasLoaded': instance.hasLoaded,
|
||||||
};
|
};
|
||||||
|
|||||||
16
lib/core/repository/state/indexed_repository_state.dart
Normal file
16
lib/core/repository/state/indexed_repository_state.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
abstract class IndexedRepositoryState<T> {
|
||||||
|
final Map<int, T>? values;
|
||||||
|
final bool hasLoaded;
|
||||||
|
|
||||||
|
const IndexedRepositoryState({
|
||||||
|
required this.values,
|
||||||
|
this.hasLoaded = false,
|
||||||
|
}) : assert(!(values == null) || !hasLoaded);
|
||||||
|
|
||||||
|
IndexedRepositoryState.loaded(this.values) : hasLoaded = true;
|
||||||
|
|
||||||
|
IndexedRepositoryState<T> copyWith({
|
||||||
|
Map<int, T>? values,
|
||||||
|
bool? hasLoaded,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
abstract class RepositoryState<T> {
|
|
||||||
final T values;
|
|
||||||
final bool hasLoaded;
|
|
||||||
|
|
||||||
const RepositoryState({
|
|
||||||
required this.values,
|
|
||||||
this.hasLoaded = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
RepositoryState.loaded(this.values) : hasLoaded = true;
|
|
||||||
|
|
||||||
RepositoryState<T> copyWith({
|
|
||||||
T? values,
|
|
||||||
bool? hasLoaded,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ class GithubIssueService {
|
|||||||
..tryPutIfAbsent('assignees', () => assignees?.join(','))
|
..tryPutIfAbsent('assignees', () => assignees?.join(','))
|
||||||
..tryPutIfAbsent('project', () => project),
|
..tryPutIfAbsent('project', () => project),
|
||||||
);
|
);
|
||||||
log("[GitHubIssueService] Creating GitHub issue: " + uri.toString());
|
debugPrint("[GitHubIssueService] Creating GitHub issue: " + uri.toString());
|
||||||
launchUrl(
|
launchUrl(
|
||||||
uri,
|
uri,
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
/// Shows a full screen search page and returns the search result selected by
|
/// Shows a full screen search page and returns the search result selected by
|
||||||
/// the user when the page is closed.
|
/// the user when the page is closed.
|
||||||
@@ -221,12 +222,13 @@ abstract class SearchDelegate<T> {
|
|||||||
final ColorScheme colorScheme = theme.colorScheme;
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
return theme.copyWith(
|
return theme.copyWith(
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
brightness: colorScheme.brightness,
|
systemOverlayStyle: colorScheme.brightness == Brightness.light
|
||||||
|
? SystemUiOverlayStyle.light
|
||||||
|
: SystemUiOverlayStyle.dark,
|
||||||
backgroundColor: colorScheme.brightness == Brightness.dark
|
backgroundColor: colorScheme.brightness == Brightness.dark
|
||||||
? Colors.grey[900]
|
? Colors.grey[900]
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
||||||
textTheme: theme.textTheme,
|
|
||||||
),
|
),
|
||||||
inputDecorationTheme: searchFieldDecorationTheme ??
|
inputDecorationTheme: searchFieldDecorationTheme ??
|
||||||
InputDecorationTheme(
|
InputDecorationTheme(
|
||||||
|
|||||||
21
lib/core/widgets/shimmer_placeholder.dart
Normal file
21
lib/core/widgets/shimmer_placeholder.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class ShimmerPlaceholder extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const ShimmerPlaceholder({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return 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: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,32 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.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/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
|
|
||||||
part 'document_details_state.dart';
|
part 'document_details_state.dart';
|
||||||
|
|
||||||
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||||
final PaperlessDocumentsApi _api;
|
final PaperlessDocumentsApi _api;
|
||||||
|
final DocumentChangedNotifier _notifier;
|
||||||
|
|
||||||
DocumentDetailsCubit(this._api, DocumentModel initialDocument)
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
: super(DocumentDetailsState(document: initialDocument)) {
|
DocumentDetailsCubit(
|
||||||
|
this._api,
|
||||||
|
this._notifier, {
|
||||||
|
required DocumentModel initialDocument,
|
||||||
|
}) : super(DocumentDetailsState(document: initialDocument)) {
|
||||||
|
_notifier.subscribe(this, onUpdated: replace);
|
||||||
loadSuggestions();
|
loadSuggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> delete(DocumentModel document) async {
|
Future<void> delete(DocumentModel document) async {
|
||||||
await _api.delete(document);
|
await _api.delete(document);
|
||||||
|
_notifier.notifyDeleted(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadSuggestions() async {
|
Future<void> loadSuggestions() async {
|
||||||
@@ -41,7 +50,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
final int asn = await _api.findNextAsn();
|
final int asn = await _api.findNextAsn();
|
||||||
final updatedDocument =
|
final updatedDocument =
|
||||||
await _api.update(document.copyWith(archiveSerialNumber: asn));
|
await _api.update(document.copyWith(archiveSerialNumber: asn));
|
||||||
emit(state.copyWith(document: updatedDocument));
|
_notifier.notifyUpdated(updatedDocument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +69,16 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceDocument(DocumentModel document) {
|
void replace(DocumentModel document) {
|
||||||
emit(state.copyWith(document: document));
|
emit(state.copyWith(document: document));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
for (final element in _subscriptions) {
|
||||||
|
element.cancel();
|
||||||
|
}
|
||||||
|
_notifier.unsubscribe(this);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
|
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';
|
||||||
@@ -8,13 +8,11 @@ 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/repository/state/impl/correspondent_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/core/widgets/highlighted_text.dart';
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_widget.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/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/similar_documents_view.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/documents/view/pages/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';
|
||||||
@@ -30,9 +28,7 @@ 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:badges/badges.dart' as b;
|
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
||||||
|
|
||||||
import '../../../../core/repository/state/impl/document_type_repository_state.dart';
|
|
||||||
|
|
||||||
//TODO: Refactor this into several widgets
|
//TODO: Refactor this into several widgets
|
||||||
class DocumentDetailsPage extends StatefulWidget {
|
class DocumentDetailsPage extends StatefulWidget {
|
||||||
@@ -79,16 +75,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
leading: IconButton(
|
leading: const BackButton(),
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_back,
|
|
||||||
color: Colors
|
|
||||||
.black, //TODO: check if there is a way to dynamically determine color...
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.of(context).pop(
|
|
||||||
context.read<DocumentDetailsCubit>().state.document,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
expandedHeight: 200.0,
|
expandedHeight: 200.0,
|
||||||
@@ -153,6 +140,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => SimilarDocumentsCubit(
|
create: (context) => SimilarDocumentsCubit(
|
||||||
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
documentId: state.document.id,
|
documentId: state.document.id,
|
||||||
),
|
),
|
||||||
@@ -168,7 +156,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
_buildDocumentMetaDataView(
|
_buildDocumentMetaDataView(
|
||||||
state.document,
|
state.document,
|
||||||
),
|
),
|
||||||
_buildSimilarDocumentsView(),
|
const SimilarDocumentsView(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).paddedSymmetrically(horizontal: 8);
|
).paddedSymmetrically(horizontal: 8);
|
||||||
@@ -284,6 +272,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
documentTypeRepository: context.read(),
|
documentTypeRepository: context.read(),
|
||||||
storagePathRepository: context.read(),
|
storagePathRepository: context.read(),
|
||||||
tagRepository: context.read(),
|
tagRepository: context.read(),
|
||||||
|
notifier: context.read(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<DocumentDetailsCubit>.value(
|
BlocProvider<DocumentDetailsCubit>.value(
|
||||||
@@ -294,7 +283,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.document != current.document,
|
previous.document != current.document,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
cubit.replaceDocument(state.document);
|
cubit.replace(state.document);
|
||||||
},
|
},
|
||||||
child: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
child: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -461,7 +450,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
visible: document.documentType != null,
|
visible: document.documentType != null,
|
||||||
child: _DetailsItem(
|
child: _DetailsItem(
|
||||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||||
content: LabelText<DocumentType, DocumentTypeRepositoryState>(
|
content: LabelText<DocumentType>(
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
id: document.documentType,
|
id: document.documentType,
|
||||||
),
|
),
|
||||||
@@ -471,7 +460,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
visible: document.correspondent != null,
|
visible: document.correspondent != null,
|
||||||
child: _DetailsItem(
|
child: _DetailsItem(
|
||||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||||
content: LabelText<Correspondent, CorrespondentRepositoryState>(
|
content: LabelText<Correspondent>(
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
id: document.correspondent,
|
id: document.correspondent,
|
||||||
),
|
),
|
||||||
@@ -555,10 +544,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSimilarDocumentsView() {
|
|
||||||
return const SimilarDocumentsView();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DetailsItem extends StatelessWidget {
|
class _DetailsItem extends StatelessWidget {
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
|||||||
final DocumentChangedNotifier notifier;
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
DocumentSearchCubit(this.api, this.notifier)
|
DocumentSearchCubit(this.api, this.notifier)
|
||||||
: super(const DocumentSearchState());
|
: super(const DocumentSearchState()) {
|
||||||
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onDeleted: remove,
|
||||||
|
onUpdated: replace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> search(String query) async {
|
Future<void> search(String query) async {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@@ -61,6 +67,12 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
notifier.unsubscribe(this);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DocumentSearchState? fromJson(Map<String, dynamic> json) {
|
DocumentSearchState? fromJson(Map<String, dynamic> json) {
|
||||||
return DocumentSearchState.fromJson(json);
|
return DocumentSearchState.fromJson(json);
|
||||||
|
|||||||
@@ -158,18 +158,16 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
onTap: (document) async {
|
enableHeroAnimation: false,
|
||||||
final updatedDocument = await Navigator.pushNamed(
|
onTap: (document) {
|
||||||
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
DocumentDetailsRoute.routeName,
|
DocumentDetailsRoute.routeName,
|
||||||
arguments: DocumentDetailsRouteArguments(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: document,
|
document: document,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
),
|
),
|
||||||
) as DocumentModel?;
|
);
|
||||||
if (updatedDocument != document) {
|
|
||||||
context.read<DocumentSearchCubit>().reload();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,21 +14,17 @@ part 'document_upload_state.dart';
|
|||||||
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||||
final PaperlessDocumentsApi _documentApi;
|
final PaperlessDocumentsApi _documentApi;
|
||||||
|
|
||||||
final LabelRepository<Tag, TagRepositoryState> _tagRepository;
|
final LabelRepository<Tag> _tagRepository;
|
||||||
final LabelRepository<Correspondent, CorrespondentRepositoryState>
|
final LabelRepository<Correspondent> _correspondentRepository;
|
||||||
_correspondentRepository;
|
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||||
final LabelRepository<DocumentType, DocumentTypeRepositoryState>
|
|
||||||
_documentTypeRepository;
|
|
||||||
|
|
||||||
final List<StreamSubscription> _subs = [];
|
final List<StreamSubscription> _subs = [];
|
||||||
|
|
||||||
DocumentUploadCubit({
|
DocumentUploadCubit({
|
||||||
required PaperlessDocumentsApi documentApi,
|
required PaperlessDocumentsApi documentApi,
|
||||||
required LabelRepository<Tag, TagRepositoryState> tagRepository,
|
required LabelRepository<Tag> tagRepository,
|
||||||
required LabelRepository<Correspondent, CorrespondentRepositoryState>
|
required LabelRepository<Correspondent> correspondentRepository,
|
||||||
correspondentRepository,
|
required LabelRepository<DocumentType> documentTypeRepository,
|
||||||
required LabelRepository<DocumentType, DocumentTypeRepositoryState>
|
|
||||||
documentTypeRepository,
|
|
||||||
}) : _documentApi = documentApi,
|
}) : _documentApi = documentApi,
|
||||||
_tagRepository = tagRepository,
|
_tagRepository = tagRepository,
|
||||||
_correspondentRepository = correspondentRepository,
|
_correspondentRepository = correspondentRepository,
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ import 'package:intl/date_symbol_data_local.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.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/edit_label/view/impl/add_correspondent_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||||
@@ -20,7 +17,6 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
|
|||||||
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';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:paperless_mobile/constants.dart';
|
|
||||||
|
|
||||||
class DocumentUploadPreparationPage extends StatefulWidget {
|
class DocumentUploadPreparationPage extends StatefulWidget {
|
||||||
final Uint8List fileBytes;
|
final Uint8List fileBytes;
|
||||||
@@ -173,9 +169,8 @@ class _DocumentUploadPreparationPageState
|
|||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialName) =>
|
labelCreationWidgetBuilder: (initialName) =>
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) =>
|
||||||
LabelRepository<DocumentType,
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
DocumentTypeRepositoryState>>(),
|
|
||||||
child: AddDocumentTypePage(initialName: initialName),
|
child: AddDocumentTypePage(initialName: initialName),
|
||||||
),
|
),
|
||||||
textFieldLabel:
|
textFieldLabel:
|
||||||
@@ -189,9 +184,8 @@ class _DocumentUploadPreparationPageState
|
|||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialName) =>
|
labelCreationWidgetBuilder: (initialName) =>
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) =>
|
||||||
LabelRepository<Correspondent,
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
CorrespondentRepositoryState>>(),
|
|
||||||
child: AddCorrespondentPage(initialName: initialName),
|
child: AddCorrespondentPage(initialName: initialName),
|
||||||
),
|
),
|
||||||
textFieldLabel:
|
textFieldLabel:
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
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/core/repository/saved_view_repository.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
||||||
|
|
||||||
@@ -17,14 +17,21 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
final DocumentChangedNotifier notifier;
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
DocumentsCubit(this.api, this.notifier) : super(const DocumentsState()) {
|
DocumentsCubit(this.api, this.notifier) : super(const DocumentsState()) {
|
||||||
reload();
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onUpdated: replace,
|
||||||
|
onDeleted: remove,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
Future<void> bulkDelete(List<DocumentModel> documents) async {
|
||||||
log("[DocumentsCubit] bulkRemove");
|
debugPrint("[DocumentsCubit] bulkRemove");
|
||||||
await api.bulkAction(
|
await api.bulkAction(
|
||||||
BulkDeleteAction(documents.map((doc) => doc.id)),
|
BulkDeleteAction(documents.map((doc) => doc.id)),
|
||||||
);
|
);
|
||||||
|
for (final deletedDoc in documents) {
|
||||||
|
notifier.notifyDeleted(deletedDoc);
|
||||||
|
}
|
||||||
await reload();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +40,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
Iterable<int> addTags = const [],
|
Iterable<int> addTags = const [],
|
||||||
Iterable<int> removeTags = const [],
|
Iterable<int> removeTags = const [],
|
||||||
}) async {
|
}) async {
|
||||||
log("[DocumentsCubit] bulkEditTags");
|
debugPrint("[DocumentsCubit] bulkEditTags");
|
||||||
await api.bulkAction(BulkModifyTagsAction(
|
await api.bulkAction(BulkModifyTagsAction(
|
||||||
documents.map((doc) => doc.id),
|
documents.map((doc) => doc.id),
|
||||||
addTags: addTags,
|
addTags: addTags,
|
||||||
@@ -43,7 +50,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void toggleDocumentSelection(DocumentModel model) {
|
void toggleDocumentSelection(DocumentModel model) {
|
||||||
log("[DocumentsCubit] toggleSelection");
|
debugPrint("[DocumentsCubit] toggleSelection");
|
||||||
if (state.selectedIds.contains(model.id)) {
|
if (state.selectedIds.contains(model.id)) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@@ -58,12 +65,12 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetSelection() {
|
void resetSelection() {
|
||||||
log("[DocumentsCubit] resetSelection");
|
debugPrint("[DocumentsCubit] resetSelection");
|
||||||
emit(state.copyWith(selection: []));
|
emit(state.copyWith(selection: []));
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
log("[DocumentsCubit] reset");
|
debugPrint("[DocumentsCubit] reset");
|
||||||
emit(const DocumentsState());
|
emit(const DocumentsState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,4 +88,10 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
|||||||
Map<String, dynamic>? toJson(DocumentsState state) {
|
Map<String, dynamic>? toJson(DocumentsState state) {
|
||||||
return state.toJson();
|
return state.toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
notifier.unsubscribe(this);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:paperless_api/paperless_api.dart';
|
|||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
||||||
|
|
||||||
class DocumentsState extends PagedDocumentsState {
|
class DocumentsState extends PagedDocumentsState {
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(includeFromJson: true, includeToJson: false)
|
||||||
final List<DocumentModel> selection;
|
final List<DocumentModel> selection;
|
||||||
|
|
||||||
const DocumentsState({
|
const DocumentsState({
|
||||||
@@ -34,11 +34,8 @@ class DocumentsState extends PagedDocumentsState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
hasLoaded,
|
|
||||||
filter,
|
|
||||||
value,
|
|
||||||
selection,
|
selection,
|
||||||
isLoading,
|
...super.props,
|
||||||
];
|
];
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
|
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<StoragePath>>(),
|
||||||
LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
child: AddStoragePathPage(initalValue: initialValue),
|
child: AddStoragePathPage(initalValue: initialValue),
|
||||||
),
|
),
|
||||||
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
|
||||||
@@ -182,8 +181,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
|
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
child: AddCorrespondentPage(initialName: initialValue),
|
child: AddCorrespondentPage(initialName: initialValue),
|
||||||
),
|
),
|
||||||
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
|
||||||
@@ -215,8 +213,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
notAssignedSelectable: false,
|
notAssignedSelectable: false,
|
||||||
formBuilderState: _formKey.currentState,
|
formBuilderState: _formKey.currentState,
|
||||||
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider(
|
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
child: AddDocumentTypePage(
|
child: AddDocumentTypePage(
|
||||||
initialName: currentInput,
|
initialName: currentInput,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
edgeOffset: kToolbarHeight,
|
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
||||||
onRefresh: _onReloadDocuments,
|
onRefresh: _onReloadDocuments,
|
||||||
notificationPredicate: (_) =>
|
notificationPredicate: (_) =>
|
||||||
connectivityState.isConnected,
|
connectivityState.isConnected,
|
||||||
@@ -263,13 +263,14 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
),
|
),
|
||||||
_buildViewActions(),
|
_buildViewActions(),
|
||||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
buildWhen: (previous, current) =>
|
// Not required anymore since saved views are now handled separately
|
||||||
!const ListEquality().equals(
|
// buildWhen: (previous, current) =>
|
||||||
previous.documents,
|
// !const ListEquality().equals(
|
||||||
current.documents,
|
// previous.documents,
|
||||||
) ||
|
// current.documents,
|
||||||
previous.selectedIds !=
|
// ) ||
|
||||||
current.selectedIds,
|
// previous.selectedIds !=
|
||||||
|
// current.selectedIds,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.hasLoaded &&
|
if (state.hasLoaded &&
|
||||||
state.documents.isEmpty) {
|
state.documents.isEmpty) {
|
||||||
@@ -323,7 +324,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
edgeOffset: kToolbarHeight,
|
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
||||||
onRefresh: _onReloadSavedViews,
|
onRefresh: _onReloadSavedViews,
|
||||||
notificationPredicate: (_) =>
|
notificationPredicate: (_) =>
|
||||||
connectivityState.isConnected,
|
connectivityState.isConnected,
|
||||||
@@ -390,7 +391,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
try {
|
try {
|
||||||
await context
|
await context
|
||||||
.read<DocumentsCubit>()
|
.read<DocumentsCubit>()
|
||||||
.bulkRemove(documentsState.selection);
|
.bulkDelete(documentsState.selection);
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
||||||
@@ -467,20 +468,14 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openDetails(DocumentModel document) async {
|
void _openDetails(DocumentModel document) {
|
||||||
final updatedModel = await Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
DocumentDetailsRoute.routeName,
|
DocumentDetailsRoute.routeName,
|
||||||
arguments: DocumentDetailsRouteArguments(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: document,
|
document: document,
|
||||||
),
|
),
|
||||||
) as DocumentModel?;
|
);
|
||||||
// final updatedModel = await Navigator.of(context).push<DocumentModel?>(
|
|
||||||
// _buildDetailsPageRoute(document),
|
|
||||||
// );
|
|
||||||
if (updatedModel != document) {
|
|
||||||
context.read<DocumentsCubit>().reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addTagToFilter(int tagId) {
|
void _addTagToFilter(int tagId) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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/core/repository/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/document_grid_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
@@ -23,6 +23,7 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
|
|||||||
final void Function(int? id)? onDocumentTypeSelected;
|
final void Function(int? id)? onDocumentTypeSelected;
|
||||||
final void Function(int? id)? onStoragePathSelected;
|
final void Function(int? id)? onStoragePathSelected;
|
||||||
|
|
||||||
|
bool get showLoadingPlaceholder => (!hasLoaded && isLoading);
|
||||||
const AdaptiveDocumentsView({
|
const AdaptiveDocumentsView({
|
||||||
super.key,
|
super.key,
|
||||||
this.selectedDocumentIds = const [],
|
this.selectedDocumentIds = const [],
|
||||||
@@ -56,6 +57,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
super.onTap,
|
super.onTap,
|
||||||
super.selectedDocumentIds,
|
super.selectedDocumentIds,
|
||||||
super.viewType,
|
super.viewType,
|
||||||
|
super.enableHeroAnimation,
|
||||||
required super.isLoading,
|
required super.isLoading,
|
||||||
required super.hasLoaded,
|
required super.hasLoaded,
|
||||||
});
|
});
|
||||||
@@ -71,8 +73,8 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView() {
|
Widget _buildListView() {
|
||||||
if (!hasLoaded && isLoading) {
|
if (showLoadingPlaceholder) {
|
||||||
return const DocumentsListLoadingWidget();
|
return DocumentsListLoadingWidget.sliver();
|
||||||
}
|
}
|
||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
@@ -91,6 +93,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onCorrespondentSelected: onCorrespondentSelected,
|
onCorrespondentSelected: onCorrespondentSelected,
|
||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -99,8 +102,8 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGridView() {
|
Widget _buildGridView() {
|
||||||
if (!hasLoaded && isLoading) {
|
if (showLoadingPlaceholder) {
|
||||||
return const DocumentsListLoadingWidget();
|
return DocumentGridLoadingWidget.sliver();
|
||||||
}
|
}
|
||||||
return SliverGrid.builder(
|
return SliverGrid.builder(
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
@@ -162,10 +165,8 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView() {
|
Widget _buildListView() {
|
||||||
if (!hasLoaded && isLoading) {
|
if (showLoadingPlaceholder) {
|
||||||
return const CustomScrollView(slivers: [
|
return DocumentsListLoadingWidget();
|
||||||
DocumentsListLoadingWidget(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
@@ -194,12 +195,8 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGridView() {
|
Widget _buildGridView() {
|
||||||
if (!hasLoaded && isLoading) {
|
if (showLoadingPlaceholder) {
|
||||||
return const CustomScrollView(
|
return DocumentGridLoadingWidget();
|
||||||
slivers: [
|
|
||||||
DocumentsListLoadingWidget(),
|
|
||||||
],
|
|
||||||
); //TODO: Build grid skeleton
|
|
||||||
}
|
}
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class DocumentGridLoadingWidget extends StatelessWidget
|
||||||
|
with DocumentItemPlaceholder {
|
||||||
|
final bool _isSliver;
|
||||||
|
@override
|
||||||
|
final Random random = Random(1257195195);
|
||||||
|
DocumentGridLoadingWidget({super.key}) : _isSliver = false;
|
||||||
|
|
||||||
|
DocumentGridLoadingWidget.sliver({super.key}) : _isSliver = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const delegate = SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
mainAxisSpacing: 4,
|
||||||
|
crossAxisSpacing: 4,
|
||||||
|
childAspectRatio: 1 / 2,
|
||||||
|
);
|
||||||
|
if (_isSliver) {
|
||||||
|
return SliverGrid.builder(
|
||||||
|
gridDelegate: delegate,
|
||||||
|
itemBuilder: (context, index) => _buildPlaceholderGridItem(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GridView.builder(
|
||||||
|
gridDelegate: delegate,
|
||||||
|
itemBuilder: (context, index) => _buildPlaceholderGridItem(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPlaceholderGridItem(BuildContext context) {
|
||||||
|
final values = nextValues;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Card(
|
||||||
|
elevation: 1.0,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ShimmerPlaceholder(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: ShimmerPlaceholder(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextPlaceholder(
|
||||||
|
length: values.correspondentLength,
|
||||||
|
fontSize: 16,
|
||||||
|
).padded(1),
|
||||||
|
TextPlaceholder(
|
||||||
|
length: values.titleLength,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
if (values.tagCount > 0) ...[
|
||||||
|
const Spacer(),
|
||||||
|
TagsPlaceholder(
|
||||||
|
count: values.tagCount,
|
||||||
|
dense: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const Spacer(),
|
||||||
|
TextPlaceholder(
|
||||||
|
length: 100,
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.bodySmall!.fontSize!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
||||||
|
|
||||||
class DocumentsListLoadingWidget extends StatelessWidget {
|
class DocumentsListLoadingWidget extends StatelessWidget
|
||||||
static const _tags = [" ", " ", " "];
|
with DocumentItemPlaceholder {
|
||||||
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
|
final bool _isSliver;
|
||||||
static const _correspondentLengths = <double>[200.0, 300.0, 150.0];
|
DocumentsListLoadingWidget({super.key}) : _isSliver = false;
|
||||||
static const _fontSize = 16.0;
|
|
||||||
|
|
||||||
const DocumentsListLoadingWidget({super.key
|
DocumentsListLoadingWidget.sliver({super.key}) : _isSliver = true;
|
||||||
});
|
|
||||||
|
@override
|
||||||
|
final Random random = Random(1209571050);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _random = Random();
|
if (_isSliver) {
|
||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, index) {
|
(context, index) => _buildFakeListItem(context),
|
||||||
return _buildFakeListItem(context, _random);
|
),
|
||||||
},
|
);
|
||||||
),
|
} else {
|
||||||
);
|
return ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) => _buildFakeListItem(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFakeListItem(BuildContext context, Random random) {
|
Widget _buildFakeListItem(BuildContext context) {
|
||||||
final tagCount = random.nextInt(_tags.length + 1);
|
const fontSize = 14.0;
|
||||||
final correspondentLength =
|
final values = nextValues;
|
||||||
_correspondentLengths[random.nextInt(_correspondentLengths.length - 1)];
|
return ShimmerPlaceholder(
|
||||||
final titleLength = _titleLengths[random.nextInt(_titleLengths.length - 1)];
|
|
||||||
return 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: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.all(8),
|
contentPadding: const EdgeInsets.all(8),
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -45,15 +45,17 @@ class DocumentsListLoadingWidget extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
height: 50,
|
height: double.infinity,
|
||||||
width: 35,
|
width: 35,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Container(
|
title: Row(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
children: [
|
||||||
width: correspondentLength,
|
TextPlaceholder(
|
||||||
height: _fontSize,
|
length: values.correspondentLength,
|
||||||
color: Colors.white,
|
fontSize: fontSize,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
@@ -61,21 +63,16 @@ class DocumentsListLoadingWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
TextPlaceholder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
length: values.titleLength,
|
||||||
height: _fontSize,
|
fontSize: fontSize,
|
||||||
width: titleLength,
|
),
|
||||||
color: Colors.white,
|
if (values.tagCount > 0)
|
||||||
|
TagsPlaceholder(count: values.tagCount, dense: true),
|
||||||
|
TextPlaceholder(
|
||||||
|
length: 100,
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize!,
|
||||||
),
|
),
|
||||||
Wrap(
|
|
||||||
spacing: 2.0,
|
|
||||||
children: List.generate(
|
|
||||||
tagCount,
|
|
||||||
(index) => InputChip(
|
|
||||||
label: Text(_tags[random.nextInt(_tags.length)]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).paddedOnly(top: 4),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class DocumentListItem extends DocumentItem {
|
|||||||
Text(
|
Text(
|
||||||
document.title,
|
document.title,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: document.tags.isEmpty ? 2 : 1,
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
AbsorbPointer(
|
AbsorbPointer(
|
||||||
absorbing: isSelectionActive,
|
absorbing: isSelectionActive,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
mixin DocumentItemPlaceholder {
|
||||||
|
static const _tags = [" ", " ", " "];
|
||||||
|
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
|
||||||
|
static const _correspondentLengths = <double>[120.0, 80.0, 40.0];
|
||||||
|
|
||||||
|
Random get random;
|
||||||
|
|
||||||
|
RandomDocumentItemPlaceholderValues get nextValues {
|
||||||
|
return RandomDocumentItemPlaceholderValues(
|
||||||
|
tagCount: random.nextInt(_tags.length + 1),
|
||||||
|
correspondentLength: _correspondentLengths[
|
||||||
|
random.nextInt(_correspondentLengths.length - 1)],
|
||||||
|
titleLength: _titleLengths[random.nextInt(_titleLengths.length - 1)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RandomDocumentItemPlaceholderValues {
|
||||||
|
final int tagCount;
|
||||||
|
final double correspondentLength;
|
||||||
|
final double titleLength;
|
||||||
|
|
||||||
|
RandomDocumentItemPlaceholderValues({
|
||||||
|
required this.tagCount,
|
||||||
|
required this.correspondentLength,
|
||||||
|
required this.titleLength,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TagsPlaceholder extends StatelessWidget {
|
||||||
|
static const _lengths = [24, 36, 16, 48];
|
||||||
|
final int count;
|
||||||
|
final bool dense;
|
||||||
|
const TagsPlaceholder({
|
||||||
|
super.key,
|
||||||
|
required this.count,
|
||||||
|
required this.dense,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 32,
|
||||||
|
child: ListView.separated(
|
||||||
|
itemCount: count,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemBuilder: (context, index) => FilterChip(
|
||||||
|
labelPadding:
|
||||||
|
dense ? const EdgeInsets.symmetric(horizontal: 2) : null,
|
||||||
|
padding: dense ? const EdgeInsets.all(4) : null,
|
||||||
|
visualDensity: const VisualDensity(vertical: -2),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
side: BorderSide.none,
|
||||||
|
onSelected: (_) {},
|
||||||
|
selected: false,
|
||||||
|
label: Text(
|
||||||
|
List.filled(_lengths[index], " ").join(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
separatorBuilder: (context, _) => const SizedBox(width: 4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
|
||||||
|
class TextPlaceholder extends StatelessWidget {
|
||||||
|
final double length;
|
||||||
|
final double fontSize;
|
||||||
|
|
||||||
|
const TextPlaceholder({
|
||||||
|
super.key,
|
||||||
|
required this.length,
|
||||||
|
required this.fontSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
width: length,
|
||||||
|
height: fontSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,16 +44,12 @@ class SortDocumentsButton extends StatelessWidget {
|
|||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => LabelCubit<DocumentType>(
|
create: (context) => LabelCubit<DocumentType>(
|
||||||
context.read<
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType,
|
|
||||||
DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => LabelCubit<Correspondent>(
|
create: (context) => LabelCubit<Correspondent>(
|
||||||
context.read<
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent,
|
|
||||||
CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.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/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:collection/collection.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/correspondent_repository_state.dart';
|
||||||
@@ -16,31 +17,28 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
final DocumentModel _initialDocument;
|
final DocumentModel _initialDocument;
|
||||||
final PaperlessDocumentsApi _docsApi;
|
final PaperlessDocumentsApi _docsApi;
|
||||||
|
|
||||||
final LabelRepository<Correspondent, CorrespondentRepositoryState>
|
final DocumentChangedNotifier _notifier;
|
||||||
_correspondentRepository;
|
final LabelRepository<Correspondent> _correspondentRepository;
|
||||||
final LabelRepository<DocumentType, DocumentTypeRepositoryState>
|
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||||
_documentTypeRepository;
|
final LabelRepository<StoragePath> _storagePathRepository;
|
||||||
final LabelRepository<StoragePath, StoragePathRepositoryState>
|
final LabelRepository<Tag> _tagRepository;
|
||||||
_storagePathRepository;
|
|
||||||
final LabelRepository<Tag, TagRepositoryState> _tagRepository;
|
|
||||||
|
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
EditDocumentCubit(
|
EditDocumentCubit(
|
||||||
DocumentModel document, {
|
DocumentModel document, {
|
||||||
required PaperlessDocumentsApi documentsApi,
|
required PaperlessDocumentsApi documentsApi,
|
||||||
required LabelRepository<Correspondent, CorrespondentRepositoryState>
|
required LabelRepository<Correspondent> correspondentRepository,
|
||||||
correspondentRepository,
|
required LabelRepository<DocumentType> documentTypeRepository,
|
||||||
required LabelRepository<DocumentType, DocumentTypeRepositoryState>
|
required LabelRepository<StoragePath> storagePathRepository,
|
||||||
documentTypeRepository,
|
required LabelRepository<Tag> tagRepository,
|
||||||
required LabelRepository<StoragePath, StoragePathRepositoryState>
|
required DocumentChangedNotifier notifier,
|
||||||
storagePathRepository,
|
|
||||||
required LabelRepository<Tag, TagRepositoryState> tagRepository,
|
|
||||||
}) : _initialDocument = document,
|
}) : _initialDocument = document,
|
||||||
_docsApi = documentsApi,
|
_docsApi = documentsApi,
|
||||||
_correspondentRepository = correspondentRepository,
|
_correspondentRepository = correspondentRepository,
|
||||||
_documentTypeRepository = documentTypeRepository,
|
_documentTypeRepository = documentTypeRepository,
|
||||||
_storagePathRepository = storagePathRepository,
|
_storagePathRepository = storagePathRepository,
|
||||||
_tagRepository = tagRepository,
|
_tagRepository = tagRepository,
|
||||||
|
_notifier = notifier,
|
||||||
super(
|
super(
|
||||||
EditDocumentState(
|
EditDocumentState(
|
||||||
document: document,
|
document: document,
|
||||||
@@ -50,6 +48,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
tags: tagRepository.current?.values ?? {},
|
tags: tagRepository.current?.values ?? {},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
_notifier.subscribe(this, onUpdated: replace);
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_correspondentRepository.values
|
_correspondentRepository.values
|
||||||
.listen((v) => emit(state.copyWith(correspondents: v?.values))),
|
.listen((v) => emit(state.copyWith(correspondents: v?.values))),
|
||||||
@@ -71,6 +70,8 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
|
|
||||||
Future<void> updateDocument(DocumentModel document) async {
|
Future<void> updateDocument(DocumentModel document) async {
|
||||||
final updated = await _docsApi.update(document);
|
final updated = await _docsApi.update(document);
|
||||||
|
_notifier.notifyUpdated(updated);
|
||||||
|
|
||||||
// Reload changed labels (documentCount property changes with removal/add)
|
// Reload changed labels (documentCount property changes with removal/add)
|
||||||
if (document.documentType != _initialDocument.documentType) {
|
if (document.documentType != _initialDocument.documentType) {
|
||||||
_documentTypeRepository
|
_documentTypeRepository
|
||||||
@@ -88,7 +89,10 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
.equals(document.tags, _initialDocument.tags)) {
|
.equals(document.tags, _initialDocument.tags)) {
|
||||||
_tagRepository.findAll(document.tags);
|
_tagRepository.findAll(document.tags);
|
||||||
}
|
}
|
||||||
emit(state.copyWith(document: updated));
|
}
|
||||||
|
|
||||||
|
void replace(DocumentModel document) {
|
||||||
|
emit(state.copyWith(document: document));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -96,6 +100,7 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
|
|||||||
for (final sub in _subscriptions) {
|
for (final sub in _subscriptions) {
|
||||||
sub.cancel();
|
sub.cancel();
|
||||||
}
|
}
|
||||||
|
_notifier.unsubscribe(this);
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/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/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart';
|
import 'package:paperless_mobile/features/edit_label/cubit/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, RepositoryState<Map<int, T>>> _repository;
|
final LabelRepository<T> _repository;
|
||||||
|
|
||||||
StreamSubscription? _subscription;
|
StreamSubscription? _subscription;
|
||||||
|
|
||||||
EditLabelCubit(LabelRepository<T, RepositoryState<Map<int, T>>> repository)
|
EditLabelCubit(LabelRepository<T> repository)
|
||||||
: _repository = repository,
|
: _repository = repository,
|
||||||
super(const EditLabelInitial()) {
|
super(const EditLabelInitial()) {
|
||||||
_subscription = repository.values.listen(
|
_subscription = repository.values.listen(
|
||||||
|
|||||||
@@ -2,7 +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/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -25,8 +25,7 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit(
|
create: (context) => EditLabelCubit(
|
||||||
context
|
context.read<LabelRepository<T>>(),
|
||||||
.read<LabelRepository<Label, RepositoryState<Map<int, Label>>>>(),
|
|
||||||
),
|
),
|
||||||
child: AddLabelFormWidget(
|
child: AddLabelFormWidget(
|
||||||
pageTitle: pageTitle,
|
pageTitle: pageTitle,
|
||||||
|
|||||||
@@ -5,7 +5,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/core/repository/state/repository_state.dart';
|
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -28,8 +28,7 @@ class EditLabelPage<T extends Label> extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit(
|
create: (context) => EditLabelCubit(
|
||||||
context
|
context.read<LabelRepository<T>>(),
|
||||||
.read<LabelRepository<Label, RepositoryState<Map<int, Label>>>>(),
|
|
||||||
),
|
),
|
||||||
child: EditLabelForm(
|
child: EditLabelForm(
|
||||||
label: label,
|
label: label,
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ class AddCorrespondentPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<Correspondent>(
|
create: (context) => EditLabelCubit<Correspondent>(
|
||||||
context.read<
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: AddLabelPage<Correspondent>(
|
child: AddLabelPage<Correspondent>(
|
||||||
pageTitle: Text(S.of(context).addCorrespondentPageTitle),
|
pageTitle: Text(S.of(context).addCorrespondentPageTitle),
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ class AddDocumentTypePage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<DocumentType>(
|
create: (context) => EditLabelCubit<DocumentType>(
|
||||||
context
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: AddLabelPage<DocumentType>(
|
child: AddLabelPage<DocumentType>(
|
||||||
pageTitle: Text(S.of(context).addDocumentTypePageTitle),
|
pageTitle: Text(S.of(context).addDocumentTypePageTitle),
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ class AddStoragePathPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<StoragePath>(
|
create: (context) => EditLabelCubit<StoragePath>(
|
||||||
context
|
context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: AddLabelPage<StoragePath>(
|
child: AddLabelPage<StoragePath>(
|
||||||
pageTitle: Text(S.of(context).addStoragePathPageTitle),
|
pageTitle: Text(S.of(context).addStoragePathPageTitle),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class AddTagPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<Tag>(
|
create: (context) => EditLabelCubit<Tag>(
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
context.read<LabelRepository<Tag>>(),
|
||||||
),
|
),
|
||||||
child: AddLabelPage<Tag>(
|
child: AddLabelPage<Tag>(
|
||||||
pageTitle: Text(S.of(context).addTagPageTitle),
|
pageTitle: Text(S.of(context).addTagPageTitle),
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ class EditCorrespondentPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<Correspondent>(
|
create: (context) => EditLabelCubit<Correspondent>(
|
||||||
context.read<
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: EditLabelPage<Correspondent>(
|
child: EditLabelPage<Correspondent>(
|
||||||
label: correspondent,
|
label: correspondent,
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ class EditDocumentTypePage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<DocumentType>(
|
create: (context) => EditLabelCubit<DocumentType>(
|
||||||
context
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: EditLabelPage<DocumentType>(
|
child: EditLabelPage<DocumentType>(
|
||||||
label: documentType,
|
label: documentType,
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ class EditStoragePathPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<StoragePath>(
|
create: (context) => EditLabelCubit<StoragePath>(
|
||||||
context
|
context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: EditLabelPage<StoragePath>(
|
child: EditLabelPage<StoragePath>(
|
||||||
label: storagePath,
|
label: storagePath,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class EditTagPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => EditLabelCubit<Tag>(
|
create: (context) => EditLabelCubit<Tag>(
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
context.read<LabelRepository<Tag>>(),
|
||||||
),
|
),
|
||||||
child: EditLabelPage<Tag>(
|
child: EditLabelPage<Tag>(
|
||||||
label: tag,
|
label: tag,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ 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/bloc/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.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/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/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
@@ -59,6 +60,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
|
context.read(),
|
||||||
);
|
);
|
||||||
context.read<ConnectivityCubit>().reload();
|
context.read<ConnectivityCubit>().reload();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
@@ -228,7 +230,23 @@ class _HomePageState extends State<HomePage> {
|
|||||||
value: _scannerCubit,
|
value: _scannerCubit,
|
||||||
child: const ScannerPage(),
|
child: const ScannerPage(),
|
||||||
),
|
),
|
||||||
const LabelsPage(),
|
MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => LabelCubit<Correspondent>(context.read()),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => LabelCubit<DocumentType>(context.read()),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => LabelCubit<Tag>(context.read()),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => LabelCubit<StoragePath>(context.read()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const LabelsPage(),
|
||||||
|
),
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: _inboxCubit,
|
value: _inboxCubit,
|
||||||
child: const InboxPage(),
|
child: const InboxPage(),
|
||||||
@@ -302,16 +320,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
void _initializeData(BuildContext context) {
|
void _initializeData(BuildContext context) {
|
||||||
try {
|
try {
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>().findAll();
|
context.read<LabelRepository<Tag>>().findAll();
|
||||||
context
|
context.read<LabelRepository<Correspondent>>().findAll();
|
||||||
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
|
context.read<LabelRepository<DocumentType>>().findAll();
|
||||||
.findAll();
|
context.read<LabelRepository<StoragePath>>().findAll();
|
||||||
context
|
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
|
|
||||||
.findAll();
|
|
||||||
context
|
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
|
|
||||||
.findAll();
|
|
||||||
context.read<SavedViewRepository>().findAll();
|
context.read<SavedViewRepository>().findAll();
|
||||||
context.read<PaperlessServerInformationCubit>().updateInformtion();
|
context.read<PaperlessServerInformationCubit>().updateInformtion();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
|
|||||||
@@ -70,16 +70,10 @@ class VerifyIdentityPage extends StatelessWidget {
|
|||||||
|
|
||||||
void _logout(BuildContext context) {
|
void _logout(BuildContext context) {
|
||||||
context.read<AuthenticationCubit>().logout();
|
context.read<AuthenticationCubit>().logout();
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
|
context.read<LabelRepository<Tag>>().clear();
|
||||||
context
|
context.read<LabelRepository<Correspondent>>().clear();
|
||||||
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
|
context.read<LabelRepository<DocumentType>>().clear();
|
||||||
.clear();
|
context.read<LabelRepository<StoragePath>>().clear();
|
||||||
context
|
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
|
|
||||||
.clear();
|
|
||||||
context
|
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
|
|
||||||
.clear();
|
|
||||||
context.read<SavedViewRepository>().clear();
|
context.read<SavedViewRepository>().clear();
|
||||||
HydratedBloc.storage.clear();
|
HydratedBloc.storage.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
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: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/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/tag_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
||||||
|
|
||||||
class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
||||||
final LabelRepository<Tag, TagRepositoryState> _tagsRepository;
|
final LabelRepository<Tag> _tagsRepository;
|
||||||
final LabelRepository<Correspondent, CorrespondentRepositoryState>
|
final LabelRepository<Correspondent> _correspondentRepository;
|
||||||
_correspondentRepository;
|
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||||
final LabelRepository<DocumentType, DocumentTypeRepositoryState>
|
|
||||||
_documentTypeRepository;
|
|
||||||
|
|
||||||
final PaperlessDocumentsApi _documentsApi;
|
final PaperlessDocumentsApi _documentsApi;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final DocumentChangedNotifier notifier;
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
@@ -28,7 +25,6 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
@override
|
@override
|
||||||
PaperlessDocumentsApi get api => _documentsApi;
|
PaperlessDocumentsApi get api => _documentsApi;
|
||||||
|
|
||||||
Timer? _taskTimer;
|
|
||||||
InboxCubit(
|
InboxCubit(
|
||||||
this._tagsRepository,
|
this._tagsRepository,
|
||||||
this._documentsApi,
|
this._documentsApi,
|
||||||
@@ -45,11 +41,20 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
availableTags: _tagsRepository.current?.values ?? {},
|
availableTags: _tagsRepository.current?.values ?? {},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
_subscriptions.addAll(
|
notifier.subscribe(
|
||||||
notifier.listen(
|
this,
|
||||||
onDeleted: remove,
|
onDeleted: remove,
|
||||||
onUpdated: replace,
|
onUpdated: (document) {
|
||||||
),
|
if (document.tags
|
||||||
|
.toSet()
|
||||||
|
.intersection(state.inboxTags.toSet())
|
||||||
|
.isEmpty) {
|
||||||
|
remove(document);
|
||||||
|
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
|
||||||
|
} else {
|
||||||
|
replace(document);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
_tagsRepository.values.listen((event) {
|
_tagsRepository.values.listen((event) {
|
||||||
@@ -74,21 +79,35 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
//TODO: Do this properly in a background task.
|
|
||||||
_taskTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
refreshItemsInInboxCount(false);
|
||||||
|
loadInbox();
|
||||||
|
|
||||||
|
Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||||
|
if (isClosed) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
refreshItemsInInboxCount();
|
refreshItemsInInboxCount();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshItemsInInboxCount() async {
|
void refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
||||||
final stats = await _statsApi.getServerStatistics();
|
final stats = await _statsApi.getServerStatistics();
|
||||||
emit(state.copyWith(itemsInInboxCount: stats.documentsInInbox));
|
|
||||||
|
if (stats.documentsInInbox != state.itemsInInboxCount && shouldLoadInbox) {
|
||||||
|
loadInbox();
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
itemsInInboxCount: stats.documentsInInbox,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Fetches inbox tag ids and loads the inbox items (documents).
|
/// Fetches inbox tag ids and loads the inbox items (documents).
|
||||||
///
|
///
|
||||||
Future<void> initializeInbox() async {
|
Future<void> loadInbox() async {
|
||||||
final inboxTags = await _tagsRepository.findAll().then(
|
final inboxTags = await _tagsRepository.findAll().then(
|
||||||
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
|
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
|
||||||
);
|
);
|
||||||
@@ -104,7 +123,7 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
emit(state.copyWith(inboxTags: inboxTags));
|
emit(state.copyWith(inboxTags: inboxTags));
|
||||||
return updateFilter(
|
updateFilter(
|
||||||
filter: DocumentFilter(
|
filter: DocumentFilter(
|
||||||
sortField: SortField.added,
|
sortField: SortField.added,
|
||||||
tags: IdsTagsQuery.fromIds(inboxTags),
|
tags: IdsTagsQuery.fromIds(inboxTags),
|
||||||
@@ -121,11 +140,12 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
document.tags.toSet().intersection(state.inboxTags.toSet());
|
document.tags.toSet().intersection(state.inboxTags.toSet());
|
||||||
|
|
||||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||||
await api.update(
|
final updatedDocument = await api.update(
|
||||||
document.copyWith(tags: updatedTags),
|
document.copyWith(tags: updatedTags),
|
||||||
);
|
);
|
||||||
await remove(document);
|
// Remove first so document is not replaced first.
|
||||||
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
|
remove(document);
|
||||||
|
notifier.notifyUpdated(updatedDocument);
|
||||||
return tagsToRemove;
|
return tagsToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,10 +156,12 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
DocumentModel document,
|
DocumentModel document,
|
||||||
Iterable<int> removedTags,
|
Iterable<int> removedTags,
|
||||||
) async {
|
) async {
|
||||||
final updatedDoc = document.copyWith(
|
final updatedDocument = await _documentsApi.update(
|
||||||
tags: {...document.tags, ...removedTags},
|
document.copyWith(
|
||||||
|
tags: {...document.tags, ...removedTags},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
await _documentsApi.update(updatedDoc);
|
notifier.notifyUpdated(updatedDocument);
|
||||||
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount + 1));
|
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount + 1));
|
||||||
return reload();
|
return reload();
|
||||||
}
|
}
|
||||||
@@ -166,22 +188,12 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceUpdatedDocument(DocumentModel document) {
|
|
||||||
if (document.tags.any((id) => state.inboxTags.contains(id))) {
|
|
||||||
// If replaced document still has inbox tag assigned:
|
|
||||||
replace(document);
|
|
||||||
} else {
|
|
||||||
// Remove document from inbox.
|
|
||||||
remove(document);
|
|
||||||
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> assignAsn(DocumentModel document) async {
|
Future<void> assignAsn(DocumentModel document) async {
|
||||||
if (document.archiveSerialNumber == null) {
|
if (document.archiveSerialNumber == null) {
|
||||||
final int asn = await _documentsApi.findNextAsn();
|
final int asn = await _documentsApi.findNextAsn();
|
||||||
final updatedDocument = await _documentsApi
|
final updatedDocument = await _documentsApi
|
||||||
.update(document.copyWith(archiveSerialNumber: asn));
|
.update(document.copyWith(archiveSerialNumber: asn));
|
||||||
|
|
||||||
replace(updatedDocument);
|
replace(updatedDocument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +214,6 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_taskTimer?.cancel();
|
|
||||||
for (var sub in _subscriptions) {
|
for (var sub in _subscriptions) {
|
||||||
sub.cancel();
|
sub.cancel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import 'package:paperless_mobile/features/paged_document_view/model/paged_docume
|
|||||||
|
|
||||||
part 'inbox_state.g.dart';
|
part 'inbox_state.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
ignoreUnannotated: true,
|
|
||||||
)
|
|
||||||
class InboxState extends PagedDocumentsState {
|
class InboxState extends PagedDocumentsState {
|
||||||
final Iterable<int> inboxTags;
|
final Iterable<int> inboxTags;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
context.read<InboxCubit>().initializeInbox();
|
context.read<InboxCubit>().loadInbox();
|
||||||
_scrollController.addListener(_listenForLoadNewData);
|
_scrollController.addListener(_listenForLoadNewData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,12 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final safeAreaPadding = MediaQuery.of(context).padding;
|
||||||
|
final availableHeight = MediaQuery.of(context).size.height -
|
||||||
|
kToolbarHeight -
|
||||||
|
kBottomNavigationBarHeight -
|
||||||
|
safeAreaPadding.top -
|
||||||
|
safeAreaPadding.bottom;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||||
@@ -76,97 +82,105 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: BlocBuilder<InboxCubit, InboxState>(
|
||||||
edgeOffset: 78,
|
builder: (context, state) {
|
||||||
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
|
return SafeArea(
|
||||||
child: NestedScrollView(
|
top: true,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
child: Builder(
|
||||||
SearchAppBar(
|
builder: (context) {
|
||||||
hintText: S.of(context).documentSearchSearchDocuments,
|
// Build a list of slivers alternating between SliverToBoxAdapter
|
||||||
onOpenSearch: showDocumentSearchPage,
|
// (group header) and a SliverList (inbox items).
|
||||||
),
|
final List<Widget> slivers = _groupByDate(state.documents)
|
||||||
],
|
.entries
|
||||||
body: BlocBuilder<InboxCubit, InboxState>(
|
.map(
|
||||||
builder: (context, state) {
|
(entry) => [
|
||||||
if (!state.hasLoaded) {
|
SliverToBoxAdapter(
|
||||||
return const CustomScrollView(
|
child: Align(
|
||||||
physics: NeverScrollableScrollPhysics(),
|
alignment: Alignment.centerLeft,
|
||||||
slivers: [DocumentsListLoadingWidget()],
|
child: ClipRRect(
|
||||||
);
|
borderRadius: BorderRadius.circular(32.0),
|
||||||
}
|
child: Text(
|
||||||
|
entry.key,
|
||||||
if (state.documents.isEmpty) {
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
return InboxEmptyWidget(
|
textAlign: TextAlign.center,
|
||||||
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
|
).padded(),
|
||||||
);
|
),
|
||||||
}
|
).paddedOnly(top: 8.0),
|
||||||
|
|
||||||
// Build a list of slivers alternating between SliverToBoxAdapter
|
|
||||||
// (group header) and a SliverList (inbox items).
|
|
||||||
final List<Widget> slivers = _groupByDate(state.documents)
|
|
||||||
.entries
|
|
||||||
.map(
|
|
||||||
(entry) => [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
|
||||||
child: Text(
|
|
||||||
entry.key,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).padded(),
|
|
||||||
),
|
|
||||||
).paddedOnly(top: 8.0),
|
|
||||||
),
|
|
||||||
SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
childCount: entry.value.length,
|
|
||||||
(context, index) {
|
|
||||||
if (index < entry.value.length - 1) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
_buildListItem(
|
|
||||||
entry.value[index],
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
indent: 16,
|
|
||||||
endIndent: 16,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _buildListItem(
|
|
||||||
entry.value[index],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
SliverList(
|
||||||
],
|
delegate: SliverChildBuilderDelegate(
|
||||||
)
|
childCount: entry.value.length,
|
||||||
.flattened
|
(context, index) {
|
||||||
.toList()
|
if (index < entry.value.length - 1) {
|
||||||
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
|
return Column(
|
||||||
// edgeOffset: kToolbarHeight,
|
children: [
|
||||||
|
_buildListItem(
|
||||||
|
entry.value[index],
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
indent: 16,
|
||||||
|
endIndent: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildListItem(
|
||||||
|
entry.value[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.flattened
|
||||||
|
.toList()
|
||||||
|
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
|
||||||
|
// edgeOffset: kToolbarHeight,
|
||||||
|
|
||||||
return CustomScrollView(
|
return RefreshIndicator(
|
||||||
controller: _scrollController,
|
edgeOffset: kToolbarHeight,
|
||||||
slivers: [
|
onRefresh: context.read<InboxCubit>().reload,
|
||||||
SliverToBoxAdapter(
|
child: CustomScrollView(
|
||||||
child: HintCard(
|
physics: state.documents.isEmpty
|
||||||
show: !state.isHintAcknowledged,
|
? const NeverScrollableScrollPhysics()
|
||||||
hintText: S.of(context).inboxPageUsageHintText,
|
: const AlwaysScrollableScrollPhysics(),
|
||||||
onHintAcknowledged: () =>
|
controller: _scrollController,
|
||||||
context.read<InboxCubit>().acknowledgeHint(),
|
slivers: [
|
||||||
),
|
SearchAppBar(
|
||||||
|
hintText: S.of(context).documentSearchSearchDocuments,
|
||||||
|
onOpenSearch: showDocumentSearchPage,
|
||||||
|
),
|
||||||
|
if (state.documents.isEmpty)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: availableHeight,
|
||||||
|
child: Center(
|
||||||
|
child: InboxEmptyWidget(
|
||||||
|
emptyStateRefreshIndicatorKey:
|
||||||
|
_emptyStateRefreshIndicatorKey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (!state.hasLoaded)
|
||||||
|
DocumentsListLoadingWidget()
|
||||||
|
else
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: HintCard(
|
||||||
|
show: !state.isHintAcknowledged,
|
||||||
|
hintText: S.of(context).inboxPageUsageHintText,
|
||||||
|
onHintAcknowledged: () =>
|
||||||
|
context.read<InboxCubit>().acknowledgeHint(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...slivers,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
...slivers,
|
);
|
||||||
],
|
},
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -191,12 +205,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
).padded(),
|
).padded(),
|
||||||
confirmDismiss: (_) => _onItemDismissed(doc),
|
confirmDismiss: (_) => _onItemDismissed(doc),
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
child: InboxItem(
|
child: InboxItem(document: doc),
|
||||||
document: doc,
|
|
||||||
onDocumentUpdated: (document) {
|
|
||||||
context.read<InboxCubit>().replaceUpdatedDocument(document);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class InboxEmptyWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
key: _emptyStateRefreshIndicatorKey,
|
key: _emptyStateRefreshIndicatorKey,
|
||||||
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
|
onRefresh: () => context.read<InboxCubit>().loadInbox(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
|||||||
@@ -1,13 +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/provider/label_repositories_provider.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/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/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/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/bloc/inbox_cubit.dart';
|
||||||
@@ -19,12 +14,10 @@ import 'package:paperless_mobile/routes/document_details_route.dart';
|
|||||||
class InboxItem extends StatefulWidget {
|
class InboxItem extends StatefulWidget {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
|
|
||||||
final void Function(DocumentModel model) onDocumentUpdated;
|
|
||||||
final DocumentModel document;
|
final DocumentModel document;
|
||||||
const InboxItem({
|
const InboxItem({
|
||||||
super.key,
|
super.key,
|
||||||
required this.document,
|
required this.document,
|
||||||
required this.onDocumentUpdated,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,17 +34,14 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final updatedDocument = await Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
DocumentDetailsRoute.routeName,
|
DocumentDetailsRoute.routeName,
|
||||||
arguments: DocumentDetailsRouteArguments(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: widget.document,
|
document: widget.document,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
),
|
),
|
||||||
) as DocumentModel?;
|
);
|
||||||
if (updatedDocument != null) {
|
|
||||||
widget.onDocumentUpdated(updatedDocument);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
@@ -104,12 +94,12 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
);
|
);
|
||||||
final actions = [
|
final actions = [
|
||||||
_buildAssignAsnAction(chipShape, context),
|
_buildAssignAsnAction(chipShape, context),
|
||||||
const SizedBox(width: 4.0),
|
const SizedBox(width: 8.0),
|
||||||
ColoredChipWrapper(
|
ColoredChipWrapper(
|
||||||
child: ActionChip(
|
child: ActionChip(
|
||||||
avatar: const Icon(Icons.delete_outline),
|
avatar: const Icon(Icons.delete_outline),
|
||||||
shape: chipShape,
|
shape: chipShape,
|
||||||
label: const Text("Delete document"),
|
label: Text(S.of(context).inboxActionDeleteDocument),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final shouldDelete = await showDialog<bool>(
|
final shouldDelete = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -124,6 +114,7 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// return FutureBuilder<FieldSuggestions>(
|
// return FutureBuilder<FieldSuggestions>(
|
||||||
// future: _fieldSuggestions,
|
// future: _fieldSuggestions,
|
||||||
// builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
@@ -151,12 +142,14 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.bolt_outlined),
|
const Icon(Icons.bolt_outlined),
|
||||||
SizedBox(
|
ConstrainedBox(
|
||||||
width: 40,
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 50,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).inboxPageQuickActionsLabel,
|
S.of(context).inboxPageQuickActionsLabel,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
maxLines: 2,
|
maxLines: 3,
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -199,7 +192,7 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
? Text(
|
? Text(
|
||||||
'${S.of(context).documentArchiveSerialNumberPropertyShortLabel} #${widget.document.archiveSerialNumber}',
|
'${S.of(context).documentArchiveSerialNumberPropertyShortLabel} #${widget.document.archiveSerialNumber}',
|
||||||
)
|
)
|
||||||
: const Text("Assign ASN"),
|
: Text(S.of(context).inboxActionAssignAsn),
|
||||||
onPressed: !hasAsn
|
onPressed: !hasAsn
|
||||||
? () {
|
? () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -233,7 +226,7 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
Icons.description_outlined,
|
Icons.description_outlined,
|
||||||
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
||||||
),
|
),
|
||||||
LabelText<DocumentType, DocumentTypeRepositoryState>(
|
LabelText<DocumentType>(
|
||||||
id: widget.document.documentType,
|
id: widget.document.documentType,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
placeholder: "-",
|
placeholder: "-",
|
||||||
@@ -247,7 +240,7 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
Icons.person_outline,
|
Icons.person_outline,
|
||||||
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
||||||
),
|
),
|
||||||
LabelText<Correspondent, CorrespondentRepositoryState>(
|
LabelText<Correspondent>(
|
||||||
id: widget.document.correspondent,
|
id: widget.document.correspondent,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
placeholder: "-",
|
placeholder: "-",
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ 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/core/repository/state/repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||||
|
|
||||||
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||||
final LabelRepository<T, RepositoryState> _repository;
|
final LabelRepository<T> _repository;
|
||||||
|
|
||||||
late StreamSubscription _subscription;
|
late StreamSubscription _subscription;
|
||||||
|
|
||||||
LabelCubit(LabelRepository<T, RepositoryState> repository)
|
LabelCubit(LabelRepository<T> repository)
|
||||||
: _repository = repository,
|
: _repository = repository,
|
||||||
super(LabelState(
|
super(LabelState(
|
||||||
isLoaded: repository.isInitialized,
|
isLoaded: repository.isInitialized,
|
||||||
@@ -22,7 +21,8 @@ class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
|||||||
if (event == null) {
|
if (event == null) {
|
||||||
emit(LabelState());
|
emit(LabelState());
|
||||||
}
|
}
|
||||||
emit(LabelState(isLoaded: true, labels: event!.values));
|
emit(
|
||||||
|
LabelState(isLoaded: event!.hasLoaded, labels: event.values ?? {}));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
|||||||
|
|
||||||
class CorrespondentBlocProvider extends StatelessWidget {
|
class CorrespondentBlocProvider extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const CorrespondentBlocProvider({super.key, required this.child});
|
const CorrespondentBlocProvider({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LabelCubit<Correspondent>(
|
create: (context) => LabelCubit<Correspondent>(
|
||||||
context.read<
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ class DocumentTypeBlocProvider extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LabelCubit<DocumentType>(
|
create: (context) => LabelCubit<DocumentType>(
|
||||||
context
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,25 +18,22 @@ class LabelsBlocProvider extends StatelessWidget {
|
|||||||
providers: [
|
providers: [
|
||||||
BlocProvider<LabelCubit<StoragePath>>(
|
BlocProvider<LabelCubit<StoragePath>>(
|
||||||
create: (context) => LabelCubit<StoragePath>(
|
create: (context) => LabelCubit<StoragePath>(
|
||||||
context.read<
|
context.read<LabelRepository<StoragePath>>(),
|
||||||
LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<LabelCubit<Correspondent>>(
|
BlocProvider<LabelCubit<Correspondent>>(
|
||||||
create: (context) => LabelCubit<Correspondent>(
|
create: (context) => LabelCubit<Correspondent>(
|
||||||
context.read<
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<LabelCubit<DocumentType>>(
|
BlocProvider<LabelCubit<DocumentType>>(
|
||||||
create: (context) => LabelCubit<DocumentType>(
|
create: (context) => LabelCubit<DocumentType>(
|
||||||
context.read<
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<LabelCubit<Tag>>(
|
BlocProvider<LabelCubit<Tag>>(
|
||||||
create: (context) => LabelCubit<Tag>(
|
create: (context) => LabelCubit<Tag>(
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
context.read<LabelRepository<Tag>>(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ class StoragePathBlocProvider extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LabelCubit<StoragePath>(
|
create: (context) => LabelCubit<StoragePath>(
|
||||||
context
|
context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class TagBlocProvider extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LabelCubit<Tag>(
|
create: (context) => LabelCubit<Tag>(
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
context.read<LabelRepository<Tag>>(),
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -241,8 +241,7 @@ class _TagFormFieldState extends State<TagFormField> {
|
|||||||
final Tag? tag = await Navigator.of(context).push<Tag>(
|
final Tag? tag = await Navigator.of(context).push<Tag>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) =>
|
create: (context) => context.read<LabelRepository<Tag>>(),
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
child: AddTagPage(initialValue: _textEditingController.text),
|
child: AddTagPage(initialValue: _textEditingController.text),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
sliver: SearchAppBar(
|
sliver: SearchAppBar(
|
||||||
hintText: "Search documents", //TODO: INTL
|
hintText: S.of(context).documentSearchSearchDocuments,
|
||||||
onOpenSearch: showDocumentSearchPage,
|
onOpenSearch: showDocumentSearchPage,
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
@@ -141,176 +141,138 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: MultiBlocProvider(
|
child: RefreshIndicator(
|
||||||
providers: [
|
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
||||||
BlocProvider(
|
notificationPredicate: (notification) =>
|
||||||
create: (context) => LabelCubit<Correspondent>(
|
connectedState.isConnected,
|
||||||
context.read<
|
onRefresh: () => [
|
||||||
LabelRepository<Correspondent,
|
context.read<LabelCubit<Correspondent>>(),
|
||||||
CorrespondentRepositoryState>>(),
|
context.read<LabelCubit<DocumentType>>(),
|
||||||
|
context.read<LabelCubit<Tag>>(),
|
||||||
|
context.read<LabelCubit<StoragePath>>(),
|
||||||
|
][_currentIndex]
|
||||||
|
.reload(),
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
LabelTabView<Correspondent>(
|
||||||
|
filterBuilder: (label) => DocumentFilter(
|
||||||
|
correspondent:
|
||||||
|
IdQueryParameter.fromId(label.id),
|
||||||
|
pageSize: label.documentCount ?? 0,
|
||||||
|
),
|
||||||
|
onEdit: _openEditCorrespondentPage,
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddCorrespondentPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
Builder(
|
||||||
BlocProvider(
|
builder: (context) {
|
||||||
create: (context) => LabelCubit<DocumentType>(
|
return CustomScrollView(
|
||||||
context.read<
|
slivers: [
|
||||||
LabelRepository<DocumentType,
|
SliverOverlapInjector(
|
||||||
DocumentTypeRepositoryState>>(),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
LabelTabView<DocumentType>(
|
||||||
|
filterBuilder: (label) => DocumentFilter(
|
||||||
|
documentType:
|
||||||
|
IdQueryParameter.fromId(label.id),
|
||||||
|
pageSize: label.documentCount ?? 0,
|
||||||
|
),
|
||||||
|
onEdit: _openEditDocumentTypePage,
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddDocumentTypePage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
Builder(
|
||||||
BlocProvider(
|
builder: (context) {
|
||||||
create: (context) => LabelCubit<Tag>(
|
return CustomScrollView(
|
||||||
context
|
slivers: [
|
||||||
.read<LabelRepository<Tag, TagRepositoryState>>(),
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
LabelTabView<Tag>(
|
||||||
|
filterBuilder: (label) => DocumentFilter(
|
||||||
|
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||||
|
pageSize: label.documentCount ?? 0,
|
||||||
|
),
|
||||||
|
onEdit: _openEditTagPage,
|
||||||
|
leadingBuilder: (t) => CircleAvatar(
|
||||||
|
backgroundColor: t.color,
|
||||||
|
child: t.isInboxTag ?? false
|
||||||
|
? Icon(
|
||||||
|
Icons.inbox,
|
||||||
|
color: t.textColor,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageTagsEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageTagsEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddTagPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
Builder(
|
||||||
BlocProvider(
|
builder: (context) {
|
||||||
create: (context) => LabelCubit<StoragePath>(
|
return CustomScrollView(
|
||||||
context.read<
|
slivers: [
|
||||||
LabelRepository<StoragePath,
|
SliverOverlapInjector(
|
||||||
StoragePathRepositoryState>>(),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
LabelTabView<StoragePath>(
|
||||||
|
onEdit: _openEditStoragePathPage,
|
||||||
|
filterBuilder: (label) => DocumentFilter(
|
||||||
|
storagePath:
|
||||||
|
IdQueryParameter.fromId(label.id),
|
||||||
|
pageSize: label.documentCount ?? 0,
|
||||||
|
),
|
||||||
|
contentBuilder: (path) => Text(path.path ?? ""),
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageStoragePathEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddStoragePathPage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
child: RefreshIndicator(
|
|
||||||
edgeOffset: kToolbarHeight,
|
|
||||||
notificationPredicate: (notification) =>
|
|
||||||
connectedState.isConnected,
|
|
||||||
onRefresh: () => [
|
|
||||||
context.read<LabelCubit<Correspondent>>(),
|
|
||||||
context.read<LabelCubit<DocumentType>>(),
|
|
||||||
context.read<LabelCubit<Tag>>(),
|
|
||||||
context.read<LabelCubit<StoragePath>>(),
|
|
||||||
][_currentIndex]
|
|
||||||
.reload(),
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: [
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
),
|
|
||||||
LabelTabView<Correspondent>(
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
correspondent:
|
|
||||||
IdQueryParameter.fromId(label.id),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
|
||||||
onEdit: _openEditCorrespondentPage,
|
|
||||||
emptyStateActionButtonLabel: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageCorrespondentEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddCorrespondentPage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
),
|
|
||||||
DocumentTypeBlocProvider(
|
|
||||||
child: LabelTabView<DocumentType>(
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
documentType:
|
|
||||||
IdQueryParameter.fromId(label.id),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
|
||||||
onEdit: _openEditDocumentTypePage,
|
|
||||||
emptyStateActionButtonLabel: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageDocumentTypeEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddDocumentTypePage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
),
|
|
||||||
TagBlocProvider(
|
|
||||||
child: LabelTabView<Tag>(
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
|
||||||
onEdit: _openEditTagPage,
|
|
||||||
leadingBuilder: (t) => CircleAvatar(
|
|
||||||
backgroundColor: t.color,
|
|
||||||
child: t.isInboxTag ?? false
|
|
||||||
? Icon(
|
|
||||||
Icons.inbox,
|
|
||||||
color: t.textColor,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
emptyStateActionButtonLabel: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageTagsEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageTagsEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddTagPage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(context),
|
|
||||||
),
|
|
||||||
StoragePathBlocProvider(
|
|
||||||
child: LabelTabView<StoragePath>(
|
|
||||||
onEdit: _openEditStoragePathPage,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
storagePath:
|
|
||||||
IdQueryParameter.fromId(label.id),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
|
||||||
contentBuilder: (path) =>
|
|
||||||
Text(path.path ?? ""),
|
|
||||||
emptyStateActionButtonLabel: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageStoragePathEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddStoragePathPage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -326,8 +288,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
child: EditCorrespondentPage(correspondent: correspondent),
|
child: EditCorrespondentPage(correspondent: correspondent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -339,8 +300,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
child: EditDocumentTypePage(documentType: docType),
|
child: EditDocumentTypePage(documentType: docType),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -352,8 +312,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) =>
|
create: (context) => context.read<LabelRepository<Tag>>(),
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
child: EditTagPage(tag: tag),
|
child: EditTagPage(tag: tag),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -365,8 +324,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context
|
create: (context) => context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
child: EditStoragePathPage(
|
child: EditStoragePathPage(
|
||||||
storagePath: path,
|
storagePath: path,
|
||||||
),
|
),
|
||||||
@@ -380,8 +338,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<Correspondent>>(),
|
||||||
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
|
|
||||||
child: const AddCorrespondentPage(),
|
child: const AddCorrespondentPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -393,8 +350,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context.read<
|
create: (context) => context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
|
||||||
child: const AddDocumentTypePage(),
|
child: const AddDocumentTypePage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -406,8 +362,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) =>
|
create: (context) => context.read<LabelRepository<Tag>>(),
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
child: const AddTagPage(),
|
child: const AddTagPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -419,8 +374,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => RepositoryProvider(
|
builder: (_) => RepositoryProvider(
|
||||||
create: (context) => context
|
create: (context) => context.read<LabelRepository<StoragePath>>(),
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
|
|
||||||
child: const AddStoragePathPage(),
|
child: const AddStoragePathPage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -48,8 +48,9 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider(
|
builder: (context) => BlocProvider(
|
||||||
create: (context) => LinkedDocumentsCubit(
|
create: (context) => LinkedDocumentsCubit(
|
||||||
context.read<PaperlessDocumentsApi>(),
|
|
||||||
filter,
|
filter,
|
||||||
|
context.read(),
|
||||||
|
context.read(),
|
||||||
),
|
),
|
||||||
child: const LinkedDocumentsPage(),
|
child: const LinkedDocumentsPage(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,60 +37,65 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocProvider(
|
||||||
builder: (context, connectivityState) {
|
create: (context) => LabelCubit<T>(
|
||||||
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
context.read(),
|
||||||
builder: (context, state) {
|
),
|
||||||
if (!state.isLoaded && !connectivityState.isConnected) {
|
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
return const OfflineWidget();
|
builder: (context, connectivityState) {
|
||||||
}
|
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
||||||
final labels = state.labels.values.toList()..sort();
|
builder: (context, state) {
|
||||||
if (labels.isEmpty) {
|
if (!state.isLoaded && !connectivityState.isConnected) {
|
||||||
return SliverFillRemaining(
|
return const OfflineWidget();
|
||||||
child: Center(
|
}
|
||||||
child: Column(
|
final labels = state.labels.values.toList()..sort();
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
if (labels.isEmpty) {
|
||||||
children: [
|
return SliverFillRemaining(
|
||||||
Text(
|
child: Center(
|
||||||
emptyStateDescription,
|
child: Column(
|
||||||
textAlign: TextAlign.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
TextButton(
|
Text(
|
||||||
onPressed: onAddNew,
|
emptyStateDescription,
|
||||||
child: Text(emptyStateActionButtonLabel),
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
].padded(),
|
TextButton(
|
||||||
|
onPressed: onAddNew,
|
||||||
|
child: Text(emptyStateActionButtonLabel),
|
||||||
|
),
|
||||||
|
].padded(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
final l = labels.elementAt(index);
|
||||||
|
return LabelItem<T>(
|
||||||
|
name: l.name,
|
||||||
|
content: contentBuilder?.call(l) ??
|
||||||
|
Text(
|
||||||
|
translateMatchingAlgorithmName(
|
||||||
|
context, l.matchingAlgorithm) +
|
||||||
|
((l.match?.isNotEmpty ?? false)
|
||||||
|
? ": ${l.match}"
|
||||||
|
: ""),
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
onOpenEditPage: onEdit,
|
||||||
|
filterBuilder: filterBuilder,
|
||||||
|
leading: leadingBuilder?.call(l),
|
||||||
|
label: l,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: labels.length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
return SliverList(
|
);
|
||||||
delegate: SliverChildBuilderDelegate(
|
},
|
||||||
(context, index) {
|
),
|
||||||
final l = labels.elementAt(index);
|
|
||||||
return LabelItem<T>(
|
|
||||||
name: l.name,
|
|
||||||
content: contentBuilder?.call(l) ??
|
|
||||||
Text(
|
|
||||||
translateMatchingAlgorithmName(
|
|
||||||
context, l.matchingAlgorithm) +
|
|
||||||
((l.match?.isNotEmpty ?? false)
|
|
||||||
? ": ${l.match}"
|
|
||||||
: ""),
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
onOpenEditPage: onEdit,
|
|
||||||
filterBuilder: filterBuilder,
|
|
||||||
leading: leadingBuilder?.call(l),
|
|
||||||
label: l,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: labels.length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/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/bloc/label_state.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
|
||||||
|
|
||||||
class LabelText<T extends Label, State extends RepositoryState>
|
class LabelText<T extends Label> extends StatelessWidget {
|
||||||
extends StatelessWidget {
|
|
||||||
final int? id;
|
final int? id;
|
||||||
final String placeholder;
|
final String placeholder;
|
||||||
final TextStyle? style;
|
final TextStyle? style;
|
||||||
@@ -24,7 +21,7 @@ class LabelText<T extends Label, State extends RepositoryState>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LabelCubit<T>(
|
create: (context) => LabelCubit<T>(
|
||||||
context.read<LabelRepository<T, State>>(),
|
context.read<LabelRepository<T>>(),
|
||||||
),
|
),
|
||||||
child: BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
child: BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
|||||||
@@ -13,10 +13,25 @@ class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState>
|
|||||||
final DocumentChangedNotifier notifier;
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
LinkedDocumentsCubit(
|
LinkedDocumentsCubit(
|
||||||
this.api,
|
|
||||||
DocumentFilter filter,
|
DocumentFilter filter,
|
||||||
|
this.api,
|
||||||
this.notifier,
|
this.notifier,
|
||||||
) : super(const LinkedDocumentsState()) {
|
) : super(const LinkedDocumentsState()) {
|
||||||
updateFilter(filter: filter);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +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/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.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/adaptive_documents_view.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
|
||||||
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.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/features/linked_documents/bloc/state/linked_documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -60,18 +56,15 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
|||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
onTap: (document) async {
|
onTap: (document) {
|
||||||
final updatedDocument = await Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
DocumentDetailsRoute.routeName,
|
DocumentDetailsRoute.routeName,
|
||||||
arguments: DocumentDetailsRouteArguments(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: document,
|
document: document,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
),
|
),
|
||||||
) as DocumentModel?;
|
);
|
||||||
if (updatedDocument != document) {
|
|
||||||
context.read<LinkedDocumentsCubit>().reload();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/notifications/services/models/notification_payloads/open_created_document_notification_payload.dart';
|
import 'package:paperless_mobile/features/notifications/services/models/notification_payloads/open_created_document_notification_payload.dart';
|
||||||
@@ -121,7 +122,7 @@ class LocalNotificationService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
void onDidReceiveNotificationResponse(NotificationResponse response) {
|
||||||
log("Received Notification: ${response.payload}");
|
debugPrint("Received Notification: ${response.payload}");
|
||||||
if (response.notificationResponseType ==
|
if (response.notificationResponseType ==
|
||||||
NotificationResponseType.selectedNotificationAction) {
|
NotificationResponseType.selectedNotificationAction) {
|
||||||
final action =
|
final action =
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
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';
|
||||||
@@ -73,14 +75,18 @@ mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
|||||||
try {
|
try {
|
||||||
final filter = state.filter.copyWith(page: 1);
|
final filter = state.filter.copyWith(page: 1);
|
||||||
final result = await api.findAll(filter);
|
final result = await api.findAll(filter);
|
||||||
emit(state.copyWithPaged(
|
if (!isClosed) {
|
||||||
hasLoaded: true,
|
emit(state.copyWithPaged(
|
||||||
value: [result],
|
hasLoaded: true,
|
||||||
isLoading: false,
|
value: [result],
|
||||||
filter: filter,
|
isLoading: false,
|
||||||
));
|
filter: filter,
|
||||||
|
));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
emit(state.copyWithPaged(isLoading: false));
|
if (!isClosed) {
|
||||||
|
emit(state.copyWithPaged(isLoading: false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,16 +94,10 @@ mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
|||||||
/// Updates a document. If [shouldReload] is false, the updated document will
|
/// Updates a document. If [shouldReload] is false, the updated document will
|
||||||
/// replace the currently loaded one, otherwise all documents will be reloaded.
|
/// replace the currently loaded one, otherwise all documents will be reloaded.
|
||||||
///
|
///
|
||||||
Future<void> update(
|
Future<void> update(DocumentModel document) async {
|
||||||
DocumentModel document, {
|
|
||||||
bool shouldReload = true,
|
|
||||||
}) async {
|
|
||||||
final updatedDocument = await api.update(document);
|
final updatedDocument = await api.update(document);
|
||||||
if (shouldReload) {
|
notifier.notifyUpdated(updatedDocument);
|
||||||
await reload();
|
// replace(updatedDocument);
|
||||||
} else {
|
|
||||||
replace(updatedDocument);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -107,7 +107,8 @@ mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
|||||||
emit(state.copyWithPaged(isLoading: true));
|
emit(state.copyWithPaged(isLoading: true));
|
||||||
try {
|
try {
|
||||||
await api.delete(document);
|
await api.delete(document);
|
||||||
await remove(document);
|
notifier.notifyDeleted(document);
|
||||||
|
// remove(document); // Removing deleted now works with the change notifier.
|
||||||
} finally {
|
} finally {
|
||||||
emit(state.copyWithPaged(isLoading: false));
|
emit(state.copyWithPaged(isLoading: false));
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
|||||||
/// Removes [document] from the currently loaded state.
|
/// Removes [document] from the currently loaded state.
|
||||||
/// Does not delete it from the server!
|
/// Does not delete it from the server!
|
||||||
///
|
///
|
||||||
Future<void> remove(DocumentModel document) async {
|
void remove(DocumentModel document) {
|
||||||
final index = state.value.indexWhere(
|
final index = state.value.indexWhere(
|
||||||
(page) => page.results.any((element) => element.id == document.id),
|
(page) => page.results.any((element) => element.id == document.id),
|
||||||
);
|
);
|
||||||
@@ -144,23 +145,36 @@ mixin PagedDocumentsMixin<State extends PagedDocumentsState>
|
|||||||
|
|
||||||
///
|
///
|
||||||
/// Replaces the document with the same id as [document] from the currently
|
/// Replaces the document with the same id as [document] from the currently
|
||||||
/// loaded state.
|
/// loaded state if the document's properties still match the given filter criteria, otherwise removes it.
|
||||||
///
|
///
|
||||||
Future<void> replace(DocumentModel document) async {
|
Future<void> replace(DocumentModel document) async {
|
||||||
final index = state.value.indexWhere(
|
final matchesFilterCriteria = state.filter.matches(document);
|
||||||
|
if (!matchesFilterCriteria) {
|
||||||
|
return remove(document);
|
||||||
|
}
|
||||||
|
final pageIndex = state.value.indexWhere(
|
||||||
(page) => page.results.any((element) => element.id == document.id),
|
(page) => page.results.any((element) => element.id == document.id),
|
||||||
);
|
);
|
||||||
if (index != -1) {
|
if (pageIndex != -1) {
|
||||||
final foundPage = state.value[index];
|
final foundPage = state.value[pageIndex];
|
||||||
final replacementPage = foundPage.copyWith(
|
final replacementPage = foundPage.copyWith(
|
||||||
results: foundPage.results..replaceRange(index, index + 1, [document]),
|
results: foundPage.results
|
||||||
|
.map((doc) => doc.id == document.id ? document : doc)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
emit(state.copyWithPaged(
|
final newState = state.copyWithPaged(
|
||||||
value: state.value
|
value: state.value
|
||||||
.mapIndexed((currIndex, element) =>
|
.mapIndexed((currIndex, element) =>
|
||||||
currIndex == index ? replacementPage : element)
|
currIndex == pageIndex ? replacementPage : element)
|
||||||
.toList(),
|
.toList(),
|
||||||
));
|
);
|
||||||
|
emit(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
notifier.unsubscribe(this);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
|||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
final views = await _repository.findAll();
|
final views = await _repository.findAll();
|
||||||
final values = {for (var element in views) element.id!: element};
|
final values = {for (var element in views) element.id!: element};
|
||||||
emit(SavedViewState(value: values, hasLoaded: true));
|
if (!isClosed) {
|
||||||
|
emit(SavedViewState(value: values, hasLoaded: true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reload() => initialize();
|
Future<void> reload() => initialize();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/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/features/paged_document_view/model/paged_documents_state.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';
|
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
||||||
|
|
||||||
@@ -10,11 +11,20 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
|||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
final SavedView savedView;
|
final SavedView savedView;
|
||||||
SavedViewDetailsCubit(
|
SavedViewDetailsCubit(
|
||||||
this.api, {
|
this.api,
|
||||||
|
this.notifier, {
|
||||||
required this.savedView,
|
required this.savedView,
|
||||||
}) : super(const SavedViewDetailsState()) {
|
}) : super(const SavedViewDetailsState()) {
|
||||||
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onDeleted: remove,
|
||||||
|
onUpdated: replace,
|
||||||
|
);
|
||||||
updateFilter(filter: savedView.toDocumentFilter());
|
updateFilter(filter: savedView.toDocumentFilter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class SavedViewList extends StatelessWidget {
|
|||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SavedViewDetailsCubit(
|
create: (context) => SavedViewDetailsCubit(
|
||||||
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
savedView: view,
|
savedView: view,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -117,18 +117,14 @@ class _SavedViewPageState extends State<SavedViewPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onOpenDocumentDetails(DocumentModel document) async {
|
void _onOpenDocumentDetails(DocumentModel document) {
|
||||||
final updatedDocument = await Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context,
|
context,
|
||||||
DocumentDetailsRoute.routeName,
|
DocumentDetailsRoute.routeName,
|
||||||
arguments: DocumentDetailsRouteArguments(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: document,
|
document: document,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
),
|
),
|
||||||
) as DocumentModel?;
|
);
|
||||||
if (updatedDocument != document) {
|
|
||||||
// Reload in case document was edited and might not fulfill filter criteria of saved view anymore
|
|
||||||
context.read<SavedViewDetailsCubit>().reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
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/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.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/tag_repository_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
@@ -46,6 +43,15 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final safeAreaPadding = MediaQuery.of(context).padding;
|
||||||
|
final availableHeight = MediaQuery.of(context).size.height -
|
||||||
|
2 * kToolbarHeight -
|
||||||
|
kTextTabBarHeight -
|
||||||
|
kBottomNavigationBarHeight -
|
||||||
|
safeAreaPadding.top -
|
||||||
|
safeAreaPadding.bottom;
|
||||||
|
|
||||||
|
print(availableHeight);
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, connectedState) {
|
builder: (context, connectedState) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -61,7 +67,33 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
// ),
|
// ),
|
||||||
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return NestedScrollView(
|
return CustomScrollView(
|
||||||
|
physics:
|
||||||
|
state.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||||
|
slivers: [
|
||||||
|
SearchAppBar(
|
||||||
|
hintText: S.of(context).documentSearchSearchDocuments,
|
||||||
|
onOpenSearch: showDocumentSearchPage,
|
||||||
|
bottom: PreferredSize(
|
||||||
|
child: _buildActions(connectedState.isConnected),
|
||||||
|
preferredSize: const Size.fromHeight(kTextTabBarHeight),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.isEmpty)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: availableHeight,
|
||||||
|
child: Center(
|
||||||
|
child: _buildEmptyState(connectedState.isConnected),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_buildImageGrid(state)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
NestedScrollView(
|
||||||
floatHeaderSlivers: false,
|
floatHeaderSlivers: false,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
@@ -76,8 +108,9 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
if (state.isEmpty)
|
if (state.isEmpty)
|
||||||
SliverFillRemaining(
|
SliverFillViewport(
|
||||||
child: _buildEmptyState(connectedState.isConnected),
|
delegate: SliverChildListDelegate.fixed(
|
||||||
|
[_buildEmptyState(connectedState.isConnected)]),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_buildImageGrid(state)
|
_buildImageGrid(state)
|
||||||
@@ -229,13 +262,11 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
documentApi: context.read<PaperlessDocumentsApi>(),
|
documentApi: context.read<PaperlessDocumentsApi>(),
|
||||||
correspondentRepository: context.read<
|
correspondentRepository:
|
||||||
LabelRepository<Correspondent,
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
CorrespondentRepositoryState>>(),
|
documentTypeRepository:
|
||||||
documentTypeRepository: context.read<
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
tagRepository: context.read<LabelRepository<Tag>>(),
|
||||||
tagRepository:
|
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: DocumentUploadPreparationPage(
|
child: DocumentUploadPreparationPage(
|
||||||
fileBytes: file.bytes,
|
fileBytes: file.bytes,
|
||||||
@@ -346,14 +377,11 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
documentApi: context.read<PaperlessDocumentsApi>(),
|
documentApi: context.read<PaperlessDocumentsApi>(),
|
||||||
correspondentRepository: context.read<
|
correspondentRepository:
|
||||||
LabelRepository<Correspondent,
|
context.read<LabelRepository<Correspondent>>(),
|
||||||
CorrespondentRepositoryState>>(),
|
documentTypeRepository:
|
||||||
documentTypeRepository: context.read<
|
context.read<LabelRepository<DocumentType>>(),
|
||||||
LabelRepository<DocumentType,
|
tagRepository: context.read<LabelRepository<Tag>>(),
|
||||||
DocumentTypeRepositoryState>>(),
|
|
||||||
tagRepository:
|
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
|
||||||
),
|
),
|
||||||
child: DocumentUploadPreparationPage(
|
child: DocumentUploadPreparationPage(
|
||||||
fileBytes: file.readAsBytesSync(),
|
fileBytes: file.readAsBytesSync(),
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
|
|||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.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/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/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
@@ -26,9 +22,10 @@ class AccountSettingsDialog extends StatelessWidget {
|
|||||||
scrollable: true,
|
scrollable: true,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: Row(
|
title: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const CloseButton(),
|
|
||||||
Text(S.of(context).accountSettingsTitle),
|
Text(S.of(context).accountSettingsTitle),
|
||||||
|
const CloseButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: BlocBuilder<PaperlessServerInformationCubit,
|
content: BlocBuilder<PaperlessServerInformationCubit,
|
||||||
@@ -55,7 +52,7 @@ class AccountSettingsDialog extends StatelessWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: const Icon(Icons.person_add_rounded),
|
leading: const Icon(Icons.person_add_rounded),
|
||||||
title: const Text("Add another account"), //TODO: INTL
|
title: Text(S.of(context).accountSettingsAddAnotherAccount),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
@@ -87,16 +84,10 @@ class AccountSettingsDialog extends StatelessWidget {
|
|||||||
try {
|
try {
|
||||||
await context.read<AuthenticationCubit>().logout();
|
await context.read<AuthenticationCubit>().logout();
|
||||||
await context.read<ApplicationSettingsCubit>().clear();
|
await context.read<ApplicationSettingsCubit>().clear();
|
||||||
await context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
|
await context.read<LabelRepository<Tag>>().clear();
|
||||||
await context
|
await context.read<LabelRepository<Correspondent>>().clear();
|
||||||
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
|
await context.read<LabelRepository<DocumentType>>().clear();
|
||||||
.clear();
|
await context.read<LabelRepository<StoragePath>>().clear();
|
||||||
await context
|
|
||||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
|
|
||||||
.clear();
|
|
||||||
await context
|
|
||||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
|
|
||||||
.clear();
|
|
||||||
await context.read<SavedViewRepository>().clear();
|
await context.read<SavedViewRepository>().clear();
|
||||||
await HydratedBloc.storage.clear();
|
await HydratedBloc.storage.clear();
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/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/features/paged_document_view/paged_documents_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart';
|
||||||
|
|
||||||
part 'similar_documents_state.dart';
|
part 'similar_documents_state.dart';
|
||||||
|
|
||||||
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||||
with PagedDocumentsMixin<SimilarDocumentsState> {
|
with PagedDocumentsMixin {
|
||||||
final int documentId;
|
final int documentId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final DocumentChangedNotifier notifier;
|
||||||
|
|
||||||
SimilarDocumentsCubit(
|
SimilarDocumentsCubit(
|
||||||
this.api, {
|
this.api,
|
||||||
|
this.notifier, {
|
||||||
required this.documentId,
|
required this.documentId,
|
||||||
}) : super(const SimilarDocumentsState());
|
}) : super(const SimilarDocumentsState()) {
|
||||||
|
notifier.subscribe(
|
||||||
|
this,
|
||||||
|
onDeleted: remove,
|
||||||
|
onUpdated: replace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (!state.hasLoaded) {
|
if (!state.hasLoaded) {
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ 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/bloc/connectivity_cubit.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_list_loading_widget.dart';
|
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.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/items/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.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/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:paperless_mobile/constants.dart';
|
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||||
|
|
||||||
class SimilarDocumentsView extends StatefulWidget {
|
class SimilarDocumentsView extends StatefulWidget {
|
||||||
const SimilarDocumentsView({super.key});
|
const SimilarDocumentsView({super.key});
|
||||||
@@ -54,13 +53,9 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const earlyPreviewHintCard = HintCard(
|
|
||||||
hintIcon: Icons.construction,
|
|
||||||
hintText: "This view is still work in progress.",
|
|
||||||
);
|
|
||||||
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.documents.isEmpty) {
|
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty) {
|
||||||
return DocumentsEmptyState(
|
return DocumentsEmptyState(
|
||||||
state: state,
|
state: state,
|
||||||
onReset: () => context.read<SimilarDocumentsCubit>().updateFilter(
|
onReset: () => context.read<SimilarDocumentsCubit>().updateFilter(
|
||||||
@@ -77,26 +72,23 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
|
|||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverToBoxAdapter(child: earlyPreviewHintCard),
|
|
||||||
SliverAdaptiveDocumentsView(
|
SliverAdaptiveDocumentsView(
|
||||||
documents: state.documents,
|
documents: state.documents,
|
||||||
hasInternetConnection: connectivity.isConnected,
|
hasInternetConnection: connectivity.isConnected,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
|
enableHeroAnimation: false,
|
||||||
),
|
onTap: (document) {
|
||||||
SliverList(
|
Navigator.pushNamed(
|
||||||
delegate: SliverChildBuilderDelegate(
|
context,
|
||||||
childCount: state.documents.length,
|
DocumentDetailsRoute.routeName,
|
||||||
(context, index) => DocumentListItem(
|
arguments: DocumentDetailsRouteArguments(
|
||||||
document: state.documents[index],
|
document: document,
|
||||||
enableHeroAnimation: false,
|
isLabelClickable: false,
|
||||||
isLabelClickable: false,
|
),
|
||||||
isSelected: false,
|
);
|
||||||
isSelectionActive: false,
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
"name": {}
|
"name": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountSettingsAddAnotherAccount": "Add another account",
|
||||||
|
"@accountSettingsAddAnotherAccount": {},
|
||||||
"accountSettingsTitle": "Account",
|
"accountSettingsTitle": "Account",
|
||||||
"@accountSettingsTitle": {},
|
"@accountSettingsTitle": {},
|
||||||
"addCorrespondentPageTitle": "Nový korespondent",
|
"addCorrespondentPageTitle": "Nový korespondent",
|
||||||
@@ -396,6 +398,10 @@
|
|||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Jste offline.",
|
"genericMessageOfflineText": "Jste offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
|
"inboxActionAssignAsn": "Assign ASN",
|
||||||
|
"@inboxActionAssignAsn": {},
|
||||||
|
"inboxActionDeleteDocument": "Delete document",
|
||||||
|
"@inboxActionDeleteDocument": {},
|
||||||
"inboxPageAssignAsnLabel": "Assign ASN",
|
"inboxPageAssignAsnLabel": "Assign ASN",
|
||||||
"@inboxPageAssignAsnLabel": {},
|
"@inboxPageAssignAsnLabel": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Dokument odstraněn z inboxu.",
|
"inboxPageDocumentRemovedMessageText": "Dokument odstraněn z inboxu.",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"name": {}
|
"name": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountSettingsAddAnotherAccount": "Einen Account hinzufügen",
|
||||||
|
"@accountSettingsAddAnotherAccount": {},
|
||||||
"accountSettingsTitle": "Account",
|
"accountSettingsTitle": "Account",
|
||||||
"@accountSettingsTitle": {},
|
"@accountSettingsTitle": {},
|
||||||
"addCorrespondentPageTitle": "Neuer Korrespondent",
|
"addCorrespondentPageTitle": "Neuer Korrespondent",
|
||||||
@@ -396,6 +398,10 @@
|
|||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Du bist offline.",
|
"genericMessageOfflineText": "Du bist offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
|
"inboxActionAssignAsn": "ASN zuweisen",
|
||||||
|
"@inboxActionAssignAsn": {},
|
||||||
|
"inboxActionDeleteDocument": "Dokument löschen",
|
||||||
|
"@inboxActionDeleteDocument": {},
|
||||||
"inboxPageAssignAsnLabel": "ASN zuweisen",
|
"inboxPageAssignAsnLabel": "ASN zuweisen",
|
||||||
"@inboxPageAssignAsnLabel": {},
|
"@inboxPageAssignAsnLabel": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
|
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"name": {}
|
"name": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountSettingsAddAnotherAccount": "Add another account",
|
||||||
|
"@accountSettingsAddAnotherAccount": {},
|
||||||
"accountSettingsTitle": "Account",
|
"accountSettingsTitle": "Account",
|
||||||
"@accountSettingsTitle": {},
|
"@accountSettingsTitle": {},
|
||||||
"addCorrespondentPageTitle": "New Correspondent",
|
"addCorrespondentPageTitle": "New Correspondent",
|
||||||
@@ -396,6 +398,10 @@
|
|||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "You're offline.",
|
"genericMessageOfflineText": "You're offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
|
"inboxActionAssignAsn": "Assign ASN",
|
||||||
|
"@inboxActionAssignAsn": {},
|
||||||
|
"inboxActionDeleteDocument": "Delete document",
|
||||||
|
"@inboxActionDeleteDocument": {},
|
||||||
"inboxPageAssignAsnLabel": "Assign ASN",
|
"inboxPageAssignAsnLabel": "Assign ASN",
|
||||||
"@inboxPageAssignAsnLabel": {},
|
"@inboxPageAssignAsnLabel": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Document removed from inbox.",
|
"inboxPageDocumentRemovedMessageText": "Document removed from inbox.",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"name": {}
|
"name": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountSettingsAddAnotherAccount": "Add another account",
|
||||||
|
"@accountSettingsAddAnotherAccount": {},
|
||||||
"accountSettingsTitle": "Account",
|
"accountSettingsTitle": "Account",
|
||||||
"@accountSettingsTitle": {},
|
"@accountSettingsTitle": {},
|
||||||
"addCorrespondentPageTitle": "New Correspondent",
|
"addCorrespondentPageTitle": "New Correspondent",
|
||||||
@@ -396,6 +398,10 @@
|
|||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Jesteście w trybie offline.",
|
"genericMessageOfflineText": "Jesteście w trybie offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
|
"inboxActionAssignAsn": "Assign ASN",
|
||||||
|
"@inboxActionAssignAsn": {},
|
||||||
|
"inboxActionDeleteDocument": "Delete document",
|
||||||
|
"@inboxActionDeleteDocument": {},
|
||||||
"inboxPageAssignAsnLabel": "Assign ASN",
|
"inboxPageAssignAsnLabel": "Assign ASN",
|
||||||
"@inboxPageAssignAsnLabel": {},
|
"@inboxPageAssignAsnLabel": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Dokument usunięty ze skrzynki odbiorczej",
|
"inboxPageDocumentRemovedMessageText": "Dokument usunięty ze skrzynki odbiorczej",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"name": {}
|
"name": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accountSettingsAddAnotherAccount": "Add another account",
|
||||||
|
"@accountSettingsAddAnotherAccount": {},
|
||||||
"accountSettingsTitle": "Account",
|
"accountSettingsTitle": "Account",
|
||||||
"@accountSettingsTitle": {},
|
"@accountSettingsTitle": {},
|
||||||
"addCorrespondentPageTitle": "Yeni ek yazar",
|
"addCorrespondentPageTitle": "Yeni ek yazar",
|
||||||
@@ -396,6 +398,10 @@
|
|||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Çevrimdışısınız.",
|
"genericMessageOfflineText": "Çevrimdışısınız.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
|
"inboxActionAssignAsn": "Assign ASN",
|
||||||
|
"@inboxActionAssignAsn": {},
|
||||||
|
"inboxActionDeleteDocument": "Delete document",
|
||||||
|
"@inboxActionDeleteDocument": {},
|
||||||
"inboxPageAssignAsnLabel": "ASN ata",
|
"inboxPageAssignAsnLabel": "ASN ata",
|
||||||
"@inboxPageAssignAsnLabel": {},
|
"@inboxPageAssignAsnLabel": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Döküman gelen kutusundan kaldırıldı.",
|
"inboxPageDocumentRemovedMessageText": "Döküman gelen kutusundan kaldırıldı.",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ void main() async {
|
|||||||
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
final connectivityCubit = ConnectivityCubit(connectivityStatusService);
|
||||||
// Remove temporarily downloaded files.
|
// Remove temporarily downloaded files.
|
||||||
|
|
||||||
(await FileService.temporaryDirectory).deleteSync(recursive: true);
|
// (await FileService.temporaryDirectory).deleteSync(recursive: true);
|
||||||
// Load application settings and stored authentication data
|
// Load application settings and stored authentication data
|
||||||
await connectivityCubit.initialize();
|
await connectivityCubit.initialize();
|
||||||
|
|
||||||
@@ -173,20 +173,16 @@ void main() async {
|
|||||||
],
|
],
|
||||||
child: MultiRepositoryProvider(
|
child: MultiRepositoryProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider<LabelRepository<Tag, TagRepositoryState>>.value(
|
RepositoryProvider<LabelRepository<Tag>>.value(
|
||||||
value: tagRepository,
|
value: tagRepository,
|
||||||
),
|
),
|
||||||
RepositoryProvider<
|
RepositoryProvider<LabelRepository<Correspondent>>.value(
|
||||||
LabelRepository<Correspondent,
|
|
||||||
CorrespondentRepositoryState>>.value(
|
|
||||||
value: correspondentRepository,
|
value: correspondentRepository,
|
||||||
),
|
),
|
||||||
RepositoryProvider<
|
RepositoryProvider<LabelRepository<DocumentType>>.value(
|
||||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>.value(
|
|
||||||
value: documentTypeRepository,
|
value: documentTypeRepository,
|
||||||
),
|
),
|
||||||
RepositoryProvider<
|
RepositoryProvider<LabelRepository<StoragePath>>.value(
|
||||||
LabelRepository<StoragePath, StoragePathRepositoryState>>.value(
|
|
||||||
value: storagePathRepository,
|
value: storagePathRepository,
|
||||||
),
|
),
|
||||||
RepositoryProvider<SavedViewRepository>.value(
|
RepositoryProvider<SavedViewRepository>.value(
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ class DocumentDetailsRoute extends StatelessWidget {
|
|||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => DocumentDetailsCubit(
|
create: (context) => DocumentDetailsCubit(
|
||||||
context.read<PaperlessDocumentsApi>(),
|
context.read(),
|
||||||
args.document,
|
context.read(),
|
||||||
|
initialDocument: args.document,
|
||||||
),
|
),
|
||||||
child: LabelRepositoriesProvider(
|
child: LabelRepositoriesProvider(
|
||||||
child: DocumentDetailsPage(
|
child: DocumentDetailsPage(
|
||||||
|
|||||||
@@ -146,7 +146,20 @@ class DocumentFilter extends Equatable {
|
|||||||
///
|
///
|
||||||
/// Checks whether the properties of [document] match the current filter criteria.
|
/// Checks whether the properties of [document] match the current filter criteria.
|
||||||
///
|
///
|
||||||
bool includes(DocumentModel document) {}
|
bool matches(DocumentModel document) {
|
||||||
|
return correspondent.matches(document.correspondent) &&
|
||||||
|
documentType.matches(document.documentType) &&
|
||||||
|
storagePath.matches(document.storagePath) &&
|
||||||
|
tags.matches(document.tags) &&
|
||||||
|
created.matches(document.created) &&
|
||||||
|
added.matches(document.added) &&
|
||||||
|
modified.matches(document.modified) &&
|
||||||
|
query.matches(
|
||||||
|
title: document.title,
|
||||||
|
content: document.content,
|
||||||
|
asn: document.archiveSerialNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int get appliedFiltersCount => [
|
int get appliedFiltersCount => [
|
||||||
documentType != initial.documentType,
|
documentType != initial.documentType,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:paperless_api/src/models/document_model.dart';
|
|
||||||
|
|
||||||
const pageRegex = r".*page=(\d+).*";
|
const pageRegex = r".*page=(\d+).*";
|
||||||
|
|
||||||
@@ -108,5 +107,10 @@ class PagedSearchResult<T> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [count, next, previous, results];
|
List<Object?> get props => [
|
||||||
|
count,
|
||||||
|
next,
|
||||||
|
previous,
|
||||||
|
results,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,17 @@ class AbsoluteDateRangeQuery extends DateRangeQuery {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() => _$AbsoluteDateRangeQueryToJson(this);
|
Map<String, dynamic> toJson() => _$AbsoluteDateRangeQueryToJson(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(DateTime dt) {
|
||||||
|
//TODO: Check if after and before are inclusive or exclusive definitions.
|
||||||
|
bool matches = true;
|
||||||
|
if (after != null) {
|
||||||
|
matches &= dt.isAfter(after!) || dt == after;
|
||||||
|
}
|
||||||
|
if (before != null) {
|
||||||
|
matches &= dt.isBefore(before!) || dt == before;
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ abstract class DateRangeQuery extends Equatable {
|
|||||||
Map<String, String> toQueryParameter(DateRangeQueryField field);
|
Map<String, String> toQueryParameter(DateRangeQueryField field);
|
||||||
|
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
bool matches(DateTime dt);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user