mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 13:15:49 -06:00
fix: Add custom fields, translations, add app logs to login routes
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
* Neue Einstellung um Animationen zu deaktivieren
|
||||
* Verbesserte Validierung von Server-Adressen
|
||||
* Beheben von Fehlern, durch welche es zu Problemen mit Paperless-ngx 2.x.x kam
|
||||
* Weitere, kleinere Fehlerbehebungen
|
||||
* Neue Übersetzungen
|
||||
@@ -1,2 +1,5 @@
|
||||
* Add setting to disable animations
|
||||
* Improved server-address validation
|
||||
* Fixed a bug which caused issues with newer versions of Paperless-ngx (2.x.x)
|
||||
* Minor bugfixes
|
||||
* Updated translations
|
||||
@@ -1 +1 @@
|
||||
../android/fastlane/metadata
|
||||
../../android/fastlane/metadata
|
||||
1
flutter
1
flutter
Submodule flutter deleted from d211f42860
6
lib/core/bloc/loading_status.dart
Normal file
6
lib/core/bloc/loading_status.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
enum LoadingStatus {
|
||||
initial,
|
||||
loading,
|
||||
loaded,
|
||||
error;
|
||||
}
|
||||
41
lib/core/bloc/my_bloc_observer.dart
Normal file
41
lib/core/bloc/my_bloc_observer.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routing/navigation_keys.dart';
|
||||
|
||||
class MyBlocObserver extends BlocObserver {
|
||||
@override
|
||||
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
|
||||
if (error is TransientError) {
|
||||
_handleTransientError(bloc, error, stackTrace);
|
||||
}
|
||||
super.onError(bloc, error, stackTrace);
|
||||
}
|
||||
|
||||
void _handleTransientError(
|
||||
BlocBase bloc,
|
||||
TransientError error,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
assert(rootNavigatorKey.currentContext != null);
|
||||
final message = switch (error) {
|
||||
TransientPaperlessApiError(code: var code) => translateError(
|
||||
rootNavigatorKey.currentContext!,
|
||||
code,
|
||||
),
|
||||
TransientMessageError(message: var message) => message,
|
||||
};
|
||||
final details = switch (error) {
|
||||
TransientPaperlessApiError(details: var details) => details,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
showSnackBar(
|
||||
rootNavigatorKey.currentContext!,
|
||||
message,
|
||||
details: details,
|
||||
);
|
||||
}
|
||||
}
|
||||
16
lib/core/bloc/transient_error.dart
Normal file
16
lib/core/bloc/transient_error.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
sealed class TransientError extends Error {}
|
||||
|
||||
class TransientPaperlessApiError extends TransientError {
|
||||
final ErrorCode code;
|
||||
final String? details;
|
||||
|
||||
TransientPaperlessApiError({required this.code, this.details});
|
||||
}
|
||||
|
||||
class TransientMessageError extends TransientError {
|
||||
final String message;
|
||||
|
||||
TransientMessageError({required this.message});
|
||||
}
|
||||
@@ -28,16 +28,31 @@ class DioHttpErrorInterceptor extends Interceptor {
|
||||
type: DioExceptionType.badResponse,
|
||||
),
|
||||
);
|
||||
} else if (data is String &&
|
||||
data.contains("No required SSL certificate was sent")) {
|
||||
} else if (data is String) {
|
||||
if (data.contains("No required SSL certificate was sent")) {
|
||||
handler.reject(
|
||||
DioException(
|
||||
requestOptions: err.requestOptions,
|
||||
type: DioExceptionType.badResponse,
|
||||
error:
|
||||
const PaperlessApiException(ErrorCode.missingClientCertificate),
|
||||
error: const PaperlessApiException(
|
||||
ErrorCode.missingClientCertificate),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
handler.reject(
|
||||
DioException(
|
||||
requestOptions: err.requestOptions,
|
||||
message: data,
|
||||
error: PaperlessApiException(
|
||||
ErrorCode.documentLoadFailed,
|
||||
details: data,
|
||||
),
|
||||
response: err.response,
|
||||
stackTrace: err.stackTrace,
|
||||
type: DioExceptionType.badResponse,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
handler.reject(err);
|
||||
}
|
||||
|
||||
@@ -1,192 +1,190 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/persistent_repository.dart';
|
||||
|
||||
class LabelRepository extends PersistentRepository<LabelRepositoryState> {
|
||||
class LabelRepository extends ChangeNotifier {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
LabelRepository(this._api) : super(const LabelRepositoryState());
|
||||
Map<int, Correspondent> correspondents = {};
|
||||
Map<int, DocumentType> documentTypes = {};
|
||||
Map<int, StoragePath> storagePaths = {};
|
||||
Map<int, Tag> tags = {};
|
||||
|
||||
Future<void> initialize() async {
|
||||
LabelRepository(this._api);
|
||||
|
||||
// Resets the repository to its initial state and loads all data from the API.
|
||||
Future<void> initialize({
|
||||
required bool loadCorrespondents,
|
||||
required bool loadDocumentTypes,
|
||||
required bool loadStoragePaths,
|
||||
required bool loadTags,
|
||||
}) async {
|
||||
correspondents = {};
|
||||
documentTypes = {};
|
||||
storagePaths = {};
|
||||
tags = {};
|
||||
await Future.wait([
|
||||
findAllCorrespondents(),
|
||||
findAllDocumentTypes(),
|
||||
findAllStoragePaths(),
|
||||
findAllTags(),
|
||||
if (loadCorrespondents) findAllCorrespondents(),
|
||||
if (loadDocumentTypes) findAllDocumentTypes(),
|
||||
if (loadStoragePaths) findAllStoragePaths(),
|
||||
if (loadTags) findAllTags(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<Tag> createTag(Tag object) async {
|
||||
final created = await _api.saveTag(object);
|
||||
final updatedState = {...state.tags}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(state.copyWith(tags: updatedState));
|
||||
tags = {...tags, created.id!: created};
|
||||
notifyListeners();
|
||||
return created;
|
||||
}
|
||||
|
||||
Future<int> deleteTag(Tag tag) async {
|
||||
await _api.deleteTag(tag);
|
||||
final updatedState = {...state.tags}..removeWhere((k, v) => k == tag.id);
|
||||
emit(state.copyWith(tags: updatedState));
|
||||
tags.remove(tag.id!);
|
||||
notifyListeners();
|
||||
return tag.id!;
|
||||
}
|
||||
|
||||
Future<Tag?> findTag(int id) async {
|
||||
final tag = await _api.getTag(id);
|
||||
if (tag != null) {
|
||||
final updatedState = {...state.tags}..[id] = tag;
|
||||
emit(state.copyWith(tags: updatedState));
|
||||
tags = {...tags, id: tag};
|
||||
notifyListeners();
|
||||
return tag;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Iterable<Tag>> findAllTags([Iterable<int>? ids]) async {
|
||||
final tags = await _api.getTags(ids);
|
||||
final updatedState = {...state.tags}
|
||||
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
||||
emit(state.copyWith(tags: updatedState));
|
||||
return tags;
|
||||
final data = await _api.getTags(ids);
|
||||
tags = {for (var tag in data) tag.id!: tag};
|
||||
notifyListeners();
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<Tag> updateTag(Tag tag) async {
|
||||
final updated = await _api.updateTag(tag);
|
||||
final updatedState = {...state.tags}..update(updated.id!, (_) => updated);
|
||||
emit(state.copyWith(tags: updatedState));
|
||||
tags = {...tags, updated.id!: updated};
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
Future<Correspondent> createCorrespondent(Correspondent correspondent) async {
|
||||
final created = await _api.saveCorrespondent(correspondent);
|
||||
final updatedState = {...state.correspondents}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(state.copyWith(correspondents: updatedState));
|
||||
correspondents = {...correspondents, created.id!: created};
|
||||
notifyListeners();
|
||||
return created;
|
||||
}
|
||||
|
||||
Future<int> deleteCorrespondent(Correspondent correspondent) async {
|
||||
await _api.deleteCorrespondent(correspondent);
|
||||
final updatedState = {...state.correspondents}
|
||||
..removeWhere((k, v) => k == correspondent.id);
|
||||
emit(state.copyWith(correspondents: updatedState));
|
||||
|
||||
correspondents.remove(correspondent.id!);
|
||||
notifyListeners();
|
||||
return correspondent.id!;
|
||||
}
|
||||
|
||||
Future<Correspondent?> findCorrespondent(int id) async {
|
||||
final correspondent = await _api.getCorrespondent(id);
|
||||
if (correspondent != null) {
|
||||
final updatedState = {...state.correspondents}..[id] = correspondent;
|
||||
emit(state.copyWith(correspondents: updatedState));
|
||||
correspondents = {...correspondents, id: correspondent};
|
||||
notifyListeners();
|
||||
return correspondent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Iterable<Correspondent>> findAllCorrespondents(
|
||||
[Iterable<int>? ids]) async {
|
||||
final correspondents = await _api.getCorrespondents(ids);
|
||||
final updatedState = {
|
||||
...state.correspondents,
|
||||
}..addAll({for (var element in correspondents) element.id!: element});
|
||||
emit(state.copyWith(correspondents: updatedState));
|
||||
return correspondents;
|
||||
Future<Iterable<Correspondent>> findAllCorrespondents() async {
|
||||
final data = await _api.getCorrespondents();
|
||||
correspondents = {for (var element in data) element.id!: element};
|
||||
notifyListeners();
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
|
||||
final updated = await _api.updateCorrespondent(correspondent);
|
||||
final updatedState = {...state.correspondents}
|
||||
..update(updated.id!, (_) => updated);
|
||||
emit(state.copyWith(correspondents: updatedState));
|
||||
|
||||
correspondents = {...correspondents, updated.id!: updated};
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
Future<DocumentType> createDocumentType(DocumentType documentType) async {
|
||||
final created = await _api.saveDocumentType(documentType);
|
||||
final updatedState = {...state.documentTypes}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(state.copyWith(documentTypes: updatedState));
|
||||
documentTypes = {...documentTypes, created.id!: created};
|
||||
notifyListeners();
|
||||
return created;
|
||||
}
|
||||
|
||||
Future<int> deleteDocumentType(DocumentType documentType) async {
|
||||
await _api.deleteDocumentType(documentType);
|
||||
final updatedState = {...state.documentTypes}
|
||||
..removeWhere((k, v) => k == documentType.id);
|
||||
emit(state.copyWith(documentTypes: updatedState));
|
||||
documentTypes.remove(documentType.id!);
|
||||
notifyListeners();
|
||||
return documentType.id!;
|
||||
}
|
||||
|
||||
Future<DocumentType?> findDocumentType(int id) async {
|
||||
final documentType = await _api.getDocumentType(id);
|
||||
if (documentType != null) {
|
||||
final updatedState = {...state.documentTypes}..[id] = documentType;
|
||||
emit(state.copyWith(documentTypes: updatedState));
|
||||
documentTypes = {...documentTypes, id: documentType};
|
||||
notifyListeners();
|
||||
return documentType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Iterable<DocumentType>> findAllDocumentTypes(
|
||||
[Iterable<int>? ids]) async {
|
||||
final documentTypes = await _api.getDocumentTypes(ids);
|
||||
final updatedState = {...state.documentTypes}
|
||||
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
||||
emit(state.copyWith(documentTypes: updatedState));
|
||||
Future<Iterable<DocumentType>> findAllDocumentTypes() async {
|
||||
final documentTypes = await _api.getDocumentTypes();
|
||||
this.documentTypes = {
|
||||
for (var dt in documentTypes) dt.id!: dt,
|
||||
};
|
||||
notifyListeners();
|
||||
return documentTypes;
|
||||
}
|
||||
|
||||
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
|
||||
final updated = await _api.updateDocumentType(documentType);
|
||||
final updatedState = {...state.documentTypes}
|
||||
..update(updated.id!, (_) => updated);
|
||||
emit(state.copyWith(documentTypes: updatedState));
|
||||
documentTypes = {...documentTypes, updated.id!: updated};
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
Future<StoragePath> createStoragePath(StoragePath storagePath) async {
|
||||
final created = await _api.saveStoragePath(storagePath);
|
||||
final updatedState = {...state.storagePaths}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(state.copyWith(storagePaths: updatedState));
|
||||
storagePaths = {...storagePaths, created.id!: created};
|
||||
notifyListeners();
|
||||
return created;
|
||||
}
|
||||
|
||||
Future<int> deleteStoragePath(StoragePath storagePath) async {
|
||||
await _api.deleteStoragePath(storagePath);
|
||||
final updatedState = {...state.storagePaths}
|
||||
..removeWhere((k, v) => k == storagePath.id);
|
||||
emit(state.copyWith(storagePaths: updatedState));
|
||||
storagePaths.remove(storagePath.id!);
|
||||
notifyListeners();
|
||||
return storagePath.id!;
|
||||
}
|
||||
|
||||
Future<StoragePath?> findStoragePath(int id) async {
|
||||
final storagePath = await _api.getStoragePath(id);
|
||||
if (storagePath != null) {
|
||||
final updatedState = {...state.storagePaths}..[id] = storagePath;
|
||||
emit(state.copyWith(storagePaths: updatedState));
|
||||
storagePaths = {...storagePaths, id: storagePath};
|
||||
notifyListeners();
|
||||
return storagePath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Iterable<StoragePath>> findAllStoragePaths(
|
||||
[Iterable<int>? ids]) async {
|
||||
final storagePaths = await _api.getStoragePaths(ids);
|
||||
final updatedState = {...state.storagePaths}
|
||||
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
||||
emit(state.copyWith(storagePaths: updatedState));
|
||||
Future<Iterable<StoragePath>> findAllStoragePaths() async {
|
||||
final storagePaths = await _api.getStoragePaths();
|
||||
this.storagePaths = {
|
||||
for (var sp in storagePaths) sp.id!: sp,
|
||||
};
|
||||
notifyListeners();
|
||||
return storagePaths;
|
||||
}
|
||||
|
||||
Future<StoragePath> updateStoragePath(StoragePath storagePath) async {
|
||||
final updated = await _api.updateStoragePath(storagePath);
|
||||
final updatedState = {...state.storagePaths}
|
||||
..update(updated.id!, (_) => updated);
|
||||
emit(state.copyWith(storagePaths: updatedState));
|
||||
storagePaths = {...storagePaths, updated.id!: updated};
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
part 'label_repository_state.freezed.dart';
|
||||
part 'label_repository_state.g.dart';
|
||||
|
||||
@freezed
|
||||
class LabelRepositoryState with _$LabelRepositoryState {
|
||||
const factory LabelRepositoryState({
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, Tag> tags,
|
||||
@Default({}) Map<int, StoragePath> storagePaths,
|
||||
}) = _LabelRepositoryState;
|
||||
|
||||
factory LabelRepositoryState.fromJson(Map<String, dynamic> json) =>
|
||||
_$LabelRepositoryStateFromJson(json);
|
||||
}
|
||||
@@ -1,95 +1,54 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/persistent_repository.dart';
|
||||
|
||||
part 'saved_view_repository_state.dart';
|
||||
part 'saved_view_repository.g.dart';
|
||||
part 'saved_view_repository.freezed.dart';
|
||||
|
||||
class SavedViewRepository
|
||||
extends PersistentRepository<SavedViewRepositoryState> {
|
||||
class SavedViewRepository extends ChangeNotifier {
|
||||
final PaperlessSavedViewsApi _api;
|
||||
final Completer _initialized = Completer();
|
||||
Map<int, SavedView> savedViews = {};
|
||||
|
||||
SavedViewRepository(this._api)
|
||||
: super(const SavedViewRepositoryState.initial());
|
||||
SavedViewRepository(this._api);
|
||||
|
||||
Future<void> initialize() async {
|
||||
try {
|
||||
await findAll();
|
||||
_initialized.complete();
|
||||
} catch (e) {
|
||||
_initialized.completeError(e);
|
||||
emit(const SavedViewRepositoryState.error());
|
||||
}
|
||||
}
|
||||
|
||||
Future<SavedView> create(SavedView object) async {
|
||||
await _initialized.future;
|
||||
final created = await _api.save(object);
|
||||
final updatedState = {...state.savedViews}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||
savedViews = {...savedViews, created.id!: created};
|
||||
notifyListeners();
|
||||
return created;
|
||||
}
|
||||
|
||||
Future<SavedView> update(SavedView object) async {
|
||||
await _initialized.future;
|
||||
final updated = await _api.update(object);
|
||||
final updatedState = {...state.savedViews}..update(
|
||||
updated.id!,
|
||||
(_) => updated,
|
||||
ifAbsent: () => updated,
|
||||
);
|
||||
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||
savedViews = {...savedViews, updated.id!: updated};
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
Future<int> delete(SavedView view) async {
|
||||
await _initialized.future;
|
||||
await _api.delete(view);
|
||||
final updatedState = {...state.savedViews}..remove(view.id);
|
||||
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||
savedViews.remove(view.id!);
|
||||
notifyListeners();
|
||||
return view.id!;
|
||||
}
|
||||
|
||||
Future<SavedView?> find(int id) async {
|
||||
await _initialized.future;
|
||||
final found = await _api.find(id);
|
||||
if (found != null) {
|
||||
final updatedState = {...state.savedViews}
|
||||
..update(id, (_) => found, ifAbsent: () => found);
|
||||
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||
savedViews = {...savedViews, id: found};
|
||||
notifyListeners();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||
final found = await _api.findAll(ids);
|
||||
final updatedState = {
|
||||
...state.savedViews,
|
||||
...{for (final view in found) view.id!: view},
|
||||
savedViews = {
|
||||
for (final view in found) view.id!: view,
|
||||
};
|
||||
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||
notifyListeners();
|
||||
return found;
|
||||
}
|
||||
|
||||
// @override
|
||||
// Future<void> clear() async {
|
||||
// await _initialized.future;
|
||||
// await super.clear();
|
||||
// emit(const SavedViewRepositoryState.initial());
|
||||
// }
|
||||
|
||||
// @override
|
||||
// SavedViewRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
// return SavedViewRepositoryState.fromJson(json);
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Map<String, dynamic>? toJson(SavedViewRepositoryState state) {
|
||||
// return state.toJson();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
part of 'saved_view_repository.dart';
|
||||
|
||||
|
||||
|
||||
@freezed
|
||||
class SavedViewRepositoryState with _$SavedViewRepositoryState {
|
||||
const factory SavedViewRepositoryState.initial({
|
||||
@Default({}) Map<int, SavedView> savedViews,
|
||||
}) = _Initial;
|
||||
const factory SavedViewRepositoryState.loading({
|
||||
@Default({}) Map<int, SavedView> savedViews,
|
||||
}) = _Loading;
|
||||
const factory SavedViewRepositoryState.loaded({
|
||||
@Default({}) Map<int, SavedView> savedViews,
|
||||
}) = _Loaded;
|
||||
const factory SavedViewRepositoryState.error({
|
||||
@Default({}) Map<int, SavedView> savedViews,
|
||||
}) = _Error;
|
||||
|
||||
factory SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
|
||||
_$SavedViewRepositoryStateFromJson(json);
|
||||
}
|
||||
@@ -1,31 +1,46 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/persistent_repository.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
|
||||
part 'user_repository_state.dart';
|
||||
|
||||
/// Repository for new users (API v3, server version 1.14.2+)
|
||||
class UserRepository extends PersistentRepository<UserRepositoryState> {
|
||||
final PaperlessUserApiV3 _userApiV3;
|
||||
final PaperlessUserApi _userApi;
|
||||
|
||||
UserRepository(this._userApiV3) : super(const UserRepositoryState());
|
||||
UserRepository(this._userApi) : super(const UserRepositoryState());
|
||||
|
||||
Future<void> initialize() async {
|
||||
await findAll();
|
||||
}
|
||||
|
||||
Future<Iterable<UserModel>> findAll() async {
|
||||
final users = await _userApiV3.findAll();
|
||||
if (_userApi is PaperlessUserApiV3Impl) {
|
||||
final users = await (_userApi as PaperlessUserApiV3Impl).findAll();
|
||||
emit(state.copyWith(users: {for (var e in users) e.id: e}));
|
||||
return users;
|
||||
}
|
||||
logger.fw(
|
||||
"Tried to access API v3 features while using an older API version.",
|
||||
className: 'UserRepository',
|
||||
methodName: 'findAll',
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<UserModel?> find(int id) async {
|
||||
final user = await _userApiV3.find(id);
|
||||
if (_userApi is PaperlessUserApiV3Impl) {
|
||||
final user = await (_userApi as PaperlessUserApiV3Impl).find(id);
|
||||
emit(state.copyWith(users: state.users..[id] = user));
|
||||
return user;
|
||||
}
|
||||
logger.fw(
|
||||
"Tried to access API v3 features while using an older API version.",
|
||||
className: 'UserRepository',
|
||||
methodName: 'findAll',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// @override
|
||||
// UserRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@@ -14,12 +14,12 @@ class FileService {
|
||||
|
||||
static FileService? _singleton;
|
||||
|
||||
late final Directory _logDirectory;
|
||||
late final Directory _temporaryDirectory;
|
||||
late final Directory _documentsDirectory;
|
||||
late final Directory _downloadsDirectory;
|
||||
late final Directory _uploadDirectory;
|
||||
late final Directory _temporaryScansDirectory;
|
||||
late Directory _logDirectory;
|
||||
late Directory _temporaryDirectory;
|
||||
late Directory _documentsDirectory;
|
||||
late Directory _downloadsDirectory;
|
||||
late Directory _uploadDirectory;
|
||||
late Directory _temporaryScansDirectory;
|
||||
|
||||
Directory get logDirectory => _logDirectory;
|
||||
Directory get temporaryDirectory => _temporaryDirectory;
|
||||
@@ -186,14 +186,15 @@ class FileService {
|
||||
}
|
||||
|
||||
Future<void> _initTemporaryDirectory() async {
|
||||
_temporaryDirectory = await getTemporaryDirectory();
|
||||
_temporaryDirectory =
|
||||
await getTemporaryDirectory().then((value) => value.create());
|
||||
}
|
||||
|
||||
Future<void> _initializeDocumentsDirectory() async {
|
||||
if (Platform.isAndroid) {
|
||||
final dirs =
|
||||
await getExternalStorageDirectories(type: StorageDirectory.documents);
|
||||
_documentsDirectory = dirs!.first;
|
||||
_documentsDirectory = await dirs!.first.create(recursive: true);
|
||||
return;
|
||||
} else if (Platform.isIOS) {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
@@ -212,12 +213,12 @@ class FileService {
|
||||
.then((directory) async =>
|
||||
directory?.firstOrNull ??
|
||||
await getApplicationDocumentsDirectory())
|
||||
.then((directory) =>
|
||||
Directory('${directory.path}/logs').create(recursive: true));
|
||||
.then((directory) => Directory(p.join(directory.path, 'logs'))
|
||||
.create(recursive: true));
|
||||
return;
|
||||
} else if (Platform.isIOS) {
|
||||
_logDirectory = await getApplicationDocumentsDirectory().then(
|
||||
(value) => Directory('${value.path}/logs').create(recursive: true));
|
||||
_logDirectory = await getApplicationDocumentsDirectory().then((value) =>
|
||||
Directory(p.join(value.path, 'logs')).create(recursive: true));
|
||||
return;
|
||||
}
|
||||
throw UnsupportedError("Platform not supported.");
|
||||
@@ -246,7 +247,7 @@ class FileService {
|
||||
|
||||
Future<void> _initUploadDirectory() async {
|
||||
final dir = await getApplicationDocumentsDirectory()
|
||||
.then((dir) => Directory('${dir.path}/upload'));
|
||||
.then((dir) => Directory(p.join(dir.path, 'upload')));
|
||||
_uploadDirectory = await dir.create(recursive: true);
|
||||
}
|
||||
|
||||
@@ -265,3 +266,13 @@ enum PaperlessDirectoryType {
|
||||
upload,
|
||||
logs;
|
||||
}
|
||||
|
||||
extension ClearDirectoryExtension on Directory {
|
||||
Future<void> clear() async {
|
||||
final streamedEntities = list();
|
||||
final entities = await streamedEntities.toList();
|
||||
await Future.wait([
|
||||
for (var entity in entities) entity.delete(recursive: true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,5 +76,11 @@ String translateError(BuildContext context, ErrorCode code) {
|
||||
ErrorCode.userNotFound => S.of(context)!.userNotFound,
|
||||
ErrorCode.updateSavedViewError => S.of(context)!.couldNotUpdateSavedView,
|
||||
ErrorCode.userAlreadyExists => S.of(context)!.userAlreadyExists,
|
||||
ErrorCode.customFieldCreateFailed =>
|
||||
'Could not create custom field, please try again.', //TODO: INTL
|
||||
ErrorCode.customFieldLoadFailed =>
|
||||
'Could not load custom field.', //TODO: INTL
|
||||
ErrorCode.customFieldDeleteFailed =>
|
||||
'Could not delete custom field, please try again.', //TODO: INTL
|
||||
};
|
||||
}
|
||||
|
||||
7
lib/core/util/lambda_utils.dart
Normal file
7
lib/core/util/lambda_utils.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
bool isNotNull<T>(T element) {
|
||||
return element != null;
|
||||
}
|
||||
|
||||
bool isNull<T>(T element) {
|
||||
return element == null;
|
||||
}
|
||||
@@ -3,30 +3,23 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'document_bulk_action_state.dart';
|
||||
part 'document_bulk_action_cubit.freezed.dart';
|
||||
|
||||
class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
final PaperlessDocumentsApi _documentsApi;
|
||||
final LabelRepository _labelRepository;
|
||||
final DocumentChangedNotifier _notifier;
|
||||
|
||||
DocumentBulkActionCubit(
|
||||
this._documentsApi,
|
||||
this._labelRepository,
|
||||
this._notifier, {
|
||||
required List<DocumentModel> selection,
|
||||
}) : super(
|
||||
DocumentBulkActionState(
|
||||
selection: selection,
|
||||
correspondents: _labelRepository.state.correspondents,
|
||||
documentTypes: _labelRepository.state.documentTypes,
|
||||
storagePaths: _labelRepository.state.storagePaths,
|
||||
tags: _labelRepository.state.tags,
|
||||
),
|
||||
) {
|
||||
_notifier.addListener(
|
||||
@@ -42,19 +35,6 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
);
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> bulkDelete() async {
|
||||
@@ -69,6 +49,7 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
}
|
||||
|
||||
Future<void> bulkModifyCorrespondent(int? correspondentId) async {
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.correspondent(
|
||||
state.selectedIds,
|
||||
@@ -81,9 +62,18 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkModifyDocumentType(int? documentTypeId) async {
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.documentType(
|
||||
state.selectedIds,
|
||||
@@ -96,9 +86,18 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkModifyStoragePath(int? storagePathId) async {
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.storagePath(
|
||||
state.selectedIds,
|
||||
@@ -111,12 +110,21 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkModifyTags({
|
||||
Iterable<int> addTagIds = const [],
|
||||
Iterable<int> removeTagIds = const [],
|
||||
}) async {
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyTagsAction(
|
||||
state.selectedIds,
|
||||
@@ -133,12 +141,19 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
part of 'document_bulk_action_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class DocumentBulkActionState with _$DocumentBulkActionState {
|
||||
const DocumentBulkActionState._();
|
||||
const factory DocumentBulkActionState({
|
||||
required List<DocumentModel> selection,
|
||||
required Map<int, Correspondent> correspondents,
|
||||
required Map<int, DocumentType> documentTypes,
|
||||
required Map<int, Tag> tags,
|
||||
required Map<int, StoragePath> storagePaths,
|
||||
}) = _DocumentBulkActionState;
|
||||
class DocumentBulkActionState {
|
||||
final List<DocumentModel> selection;
|
||||
|
||||
DocumentBulkActionState({
|
||||
required this.selection,
|
||||
});
|
||||
|
||||
Iterable<int> get selectedIds => selection.map((d) => d.id);
|
||||
DocumentBulkActionState copyWith({
|
||||
List<DocumentModel>? selection,
|
||||
}) {
|
||||
return DocumentBulkActionState(
|
||||
selection: selection ?? this.selection,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
|
||||
@@ -35,11 +36,12 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
void initState() {
|
||||
super.initState();
|
||||
final state = context.read<DocumentBulkActionCubit>().state;
|
||||
final labels = context.read<LabelRepository>();
|
||||
_sharedTags = state.selection
|
||||
.map((e) => e.tags)
|
||||
.map((e) => e.toSet())
|
||||
.fold(
|
||||
state.tags.values.map((e) => e.id!).toSet(),
|
||||
labels.tags.values.map((e) => e.id!).toSet(),
|
||||
(previousValue, element) => previousValue.intersection(element),
|
||||
)
|
||||
.toList();
|
||||
@@ -49,14 +51,10 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
.toSet()
|
||||
.difference(_sharedTags.toSet())
|
||||
.toList();
|
||||
_filteredTags = state.tags.keys.toList();
|
||||
_filteredTags = labels.tags.keys.toList();
|
||||
_controller.addListener(() {
|
||||
setState(() {
|
||||
_filteredTags = context
|
||||
.read<DocumentBulkActionCubit>()
|
||||
.state
|
||||
.tags
|
||||
.values
|
||||
_filteredTags = labels.tags.values
|
||||
.where((e) =>
|
||||
e.name.normalized().contains(_controller.text.normalized()))
|
||||
.map((e) => e.id!)
|
||||
@@ -69,6 +67,7 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
|
||||
builder: (context, state) {
|
||||
return FullscreenSelectionForm(
|
||||
@@ -86,7 +85,7 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
selectionBuilder: (context, index) {
|
||||
return _buildTagOption(
|
||||
_filteredTags[index],
|
||||
state.tags,
|
||||
labelRepository.tags,
|
||||
);
|
||||
},
|
||||
selectionCount: _filteredTags.length,
|
||||
@@ -155,11 +154,12 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
void _submit() async {
|
||||
if (_addTags.isNotEmpty || _removeTags.isNotEmpty) {
|
||||
final bloc = context.read<DocumentBulkActionCubit>();
|
||||
final labelRepository = context.read<LabelRepository>();
|
||||
final addNames = _addTags
|
||||
.map((value) => "\"${bloc.state.tags[value]!.name}\"")
|
||||
.map((value) => "\"${labelRepository.tags[value]!.name}\"")
|
||||
.toList();
|
||||
final removeNames = _removeTags
|
||||
.map((value) => "\"${bloc.state.tags[value]!.name}\"")
|
||||
.map((value) => "\"${labelRepository.tags[value]!.name}\"")
|
||||
.toList();
|
||||
final shouldPerformAction = await showDialog<bool>(
|
||||
context: context,
|
||||
|
||||
@@ -4,8 +4,11 @@ import 'dart:io';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.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/service/file_service.dart';
|
||||
@@ -15,6 +18,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:printing/printing.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
part 'document_details_cubit.freezed.dart';
|
||||
part 'document_details_state.dart';
|
||||
|
||||
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
@@ -22,15 +26,13 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
final DocumentChangedNotifier _notifier;
|
||||
final LocalNotificationService _notificationService;
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
DocumentDetailsCubit(
|
||||
this._api,
|
||||
this._labelRepository,
|
||||
this._notifier,
|
||||
this._notificationService, {
|
||||
required this.id,
|
||||
}) : super(const DocumentDetailsInitial()) {
|
||||
}) : super(const DocumentDetailsState()) {
|
||||
_notifier.addListener(
|
||||
this,
|
||||
onUpdated: (document) {
|
||||
@@ -42,7 +44,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
Future<void> initialize() async {
|
||||
debugPrint("Initialize called");
|
||||
emit(const DocumentDetailsLoading());
|
||||
emit(const DocumentDetailsState(status: LoadingStatus.loading));
|
||||
try {
|
||||
final (document, metaData) = await Future.wait([
|
||||
_api.find(id),
|
||||
@@ -54,11 +56,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
// final document = await _api.find(id);
|
||||
// final metaData = await _api.getMetaData(id);
|
||||
debugPrint("Document data loaded for $id");
|
||||
emit(DocumentDetailsLoaded(
|
||||
emit(DocumentDetailsState(
|
||||
status: LoadingStatus.loaded,
|
||||
document: document,
|
||||
metaData: metaData,
|
||||
));
|
||||
} catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
logger.fe(
|
||||
"An error occurred while loading data for document $id.",
|
||||
className: runtimeType.toString(),
|
||||
@@ -66,13 +69,22 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
emit(const DocumentDetailsError());
|
||||
emit(const DocumentDetailsState(status: LoadingStatus.error));
|
||||
addError(
|
||||
TransientPaperlessApiError(code: error.code, details: error.details),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> delete(DocumentModel document) async {
|
||||
try {
|
||||
await _api.delete(document);
|
||||
_notifier.notifyDeleted(document);
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(code: e.code, details: e.details),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assignAsn(
|
||||
@@ -80,6 +92,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
int? asn,
|
||||
bool autoAssign = false,
|
||||
}) async {
|
||||
try {
|
||||
if (!autoAssign) {
|
||||
final updatedDocument = await _api.update(
|
||||
document.copyWith(archiveSerialNumber: () => asn),
|
||||
@@ -91,18 +104,22 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
.update(document.copyWith(archiveSerialNumber: () => autoAsn));
|
||||
_notifier.notifyUpdated(updatedDocument);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(code: e.code, details: e.details),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<ResultType> openDocumentInSystemViewer() async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
throw Exception(
|
||||
"Document cannot be opened in system viewer "
|
||||
"if document information has not yet been loaded.",
|
||||
);
|
||||
}
|
||||
final cacheDir = FileService.instance.temporaryDirectory;
|
||||
final filePath = s.metaData.mediaFilename.replaceAll("/", " ");
|
||||
final filePath = state.metaData!.mediaFilename.replaceAll("/", " ");
|
||||
|
||||
final fileName = "${p.basenameWithoutExtension(filePath)}.pdf";
|
||||
final file = File("${cacheDir.path}/$fileName");
|
||||
@@ -110,7 +127,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
file.path,
|
||||
);
|
||||
}
|
||||
@@ -121,14 +138,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
}
|
||||
|
||||
void replace(DocumentModel document) {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
return;
|
||||
}
|
||||
emit(DocumentDetailsLoaded(
|
||||
document: document,
|
||||
metaData: s.metaData,
|
||||
));
|
||||
emit(state.copyWith(document: document));
|
||||
}
|
||||
|
||||
Future<void> downloadDocument({
|
||||
@@ -136,12 +146,11 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
required String locale,
|
||||
required String userId,
|
||||
}) async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
String targetPath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
downloadOriginal,
|
||||
FileService.instance.downloadsDirectory,
|
||||
);
|
||||
@@ -150,7 +159,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
await File(targetPath).create();
|
||||
} else {
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
@@ -169,12 +178,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
// );
|
||||
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
targetPath,
|
||||
original: downloadOriginal,
|
||||
onProgressChanged: (progress) {
|
||||
_notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
@@ -185,28 +194,27 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
},
|
||||
);
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
locale: locale,
|
||||
userId: userId,
|
||||
);
|
||||
logger.fi("Document '${s.document.title}' saved to $targetPath.");
|
||||
logger.fi("Document '${state.document!.title}' saved to $targetPath.");
|
||||
}
|
||||
|
||||
Future<void> shareDocument({bool shareOriginal = false}) async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
String filePath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
shareOriginal,
|
||||
FileService.instance.temporaryDirectory,
|
||||
);
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
filePath,
|
||||
original: shareOriginal,
|
||||
);
|
||||
@@ -214,27 +222,26 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
[
|
||||
XFile(
|
||||
filePath,
|
||||
name: s.document.originalFileName,
|
||||
name: state.document!.originalFileName,
|
||||
mimeType: "application/pdf",
|
||||
lastModified: s.document.modified,
|
||||
lastModified: state.document!.modified,
|
||||
),
|
||||
],
|
||||
subject: s.document.title,
|
||||
subject: state.document!.title,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> printDocument() async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
final filePath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
false,
|
||||
FileService.instance.temporaryDirectory,
|
||||
);
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
filePath,
|
||||
original: false,
|
||||
);
|
||||
@@ -243,13 +250,16 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
throw Exception("An error occurred while downloading the document.");
|
||||
}
|
||||
Printing.layoutPdf(
|
||||
name: s.document.title,
|
||||
name: state.document!.title,
|
||||
onLayout: (format) => file.readAsBytesSync(),
|
||||
);
|
||||
}
|
||||
|
||||
String _buildDownloadFilePath(
|
||||
DocumentMetaData meta, bool original, Directory dir) {
|
||||
DocumentMetaData meta,
|
||||
bool original,
|
||||
Directory dir,
|
||||
) {
|
||||
final normalizedPath = meta.mediaFilename.replaceAll("/", " ");
|
||||
final extension = original ? p.extension(normalizedPath) : '.pdf';
|
||||
return "${dir.path}/${p.basenameWithoutExtension(normalizedPath)}$extension";
|
||||
@@ -257,7 +267,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_labelRepository.removeListener(this);
|
||||
_notifier.removeListener(this);
|
||||
await super.close();
|
||||
}
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
part of 'document_details_cubit.dart';
|
||||
|
||||
sealed class DocumentDetailsState {
|
||||
const DocumentDetailsState();
|
||||
@freezed
|
||||
class DocumentDetailsState with _$DocumentDetailsState {
|
||||
const factory DocumentDetailsState({
|
||||
@Default(LoadingStatus.initial) LoadingStatus status,
|
||||
DocumentModel? document,
|
||||
DocumentMetaData? metaData,
|
||||
}) = _DocumentDetailsState;
|
||||
}
|
||||
|
||||
class DocumentDetailsInitial extends DocumentDetailsState {
|
||||
const DocumentDetailsInitial();
|
||||
}
|
||||
|
||||
class DocumentDetailsLoading extends DocumentDetailsState {
|
||||
const DocumentDetailsLoading();
|
||||
}
|
||||
|
||||
class DocumentDetailsLoaded extends DocumentDetailsState {
|
||||
final DocumentModel document;
|
||||
final DocumentMetaData metaData;
|
||||
|
||||
const DocumentDetailsLoaded({
|
||||
required this.document,
|
||||
required this.metaData,
|
||||
});
|
||||
}
|
||||
|
||||
class DocumentDetailsError extends DocumentDetailsState {
|
||||
const DocumentDetailsError();
|
||||
}
|
||||
|
||||
|
||||
// @freezed
|
||||
// class DocumentDetailsState with _$DocumentDetailsState {
|
||||
// const factory DocumentDetailsState({
|
||||
// required DocumentModel document,
|
||||
// DocumentMetaData? metaData,
|
||||
// @Default(false) bool isFullContentLoaded,
|
||||
// @Default({}) Map<int, Correspondent> correspondents,
|
||||
// @Default({}) Map<int, DocumentType> documentTypes,
|
||||
// @Default({}) Map<int, Tag> tags,
|
||||
// @Default({}) Map<int, StoragePath> storagePaths,
|
||||
// }) = _DocumentDetailsState;
|
||||
// }
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/accessibility/accessibility_utils.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:paperless_mobile/features/document_details/view/widgets/document
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.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';
|
||||
@@ -65,7 +67,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
debugPrint(disableAnimations.toString());
|
||||
final hasMultiUserSupport =
|
||||
context.watch<LocalUserAccount>().hasMultiUserSupport;
|
||||
final tabLength = 4 + (hasMultiUserSupport && false ? 1 : 0);
|
||||
final tabLength = 4 + (hasMultiUserSupport ? 1 : 0);
|
||||
return AnnotatedRegion(
|
||||
value: buildOverlayStyle(
|
||||
Theme.of(context),
|
||||
@@ -79,9 +81,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
extendBodyBehindAppBar: false,
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) =>
|
||||
_buildEditButton(document),
|
||||
floatingActionButton: switch (state.status) {
|
||||
LoadingStatus.loaded => _buildEditButton(state.document!),
|
||||
_ => null
|
||||
},
|
||||
bottomNavigationBar: _buildBottomAppBar(),
|
||||
@@ -93,9 +94,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
sliver:
|
||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
final title = switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) =>
|
||||
document.title,
|
||||
final title = switch (state.status) {
|
||||
LoadingStatus.loaded => state.document!.title,
|
||||
_ => widget.title ?? '',
|
||||
};
|
||||
return SliverAppBar(
|
||||
@@ -201,17 +201,17 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (hasMultiUserSupport && false)
|
||||
// Tab(
|
||||
// child: Text(
|
||||
// "Permissions",
|
||||
// style: TextStyle(
|
||||
// color: Theme.of(context)
|
||||
// .colorScheme
|
||||
// .onPrimaryContainer,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
if (hasMultiUserSupport)
|
||||
Tab(
|
||||
child: Text(
|
||||
"Permissions",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -227,7 +227,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
documentId: widget.id,
|
||||
),
|
||||
child: Padding(
|
||||
@@ -243,17 +242,15 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document
|
||||
) =>
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentOverviewWidget(
|
||||
document: document,
|
||||
document: state.document!,
|
||||
itemSpacing: _itemSpacing,
|
||||
queryString:
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
},
|
||||
],
|
||||
@@ -264,16 +261,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document
|
||||
) =>
|
||||
DocumentContentWidget(
|
||||
document: document,
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded => DocumentContentWidget(
|
||||
document: state.document!,
|
||||
queryString:
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
}
|
||||
],
|
||||
@@ -284,17 +278,14 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document,
|
||||
metaData: var metaData,
|
||||
) =>
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentMetaDataWidget(
|
||||
document: document,
|
||||
document: state.document!,
|
||||
itemSpacing: _itemSpacing,
|
||||
metaData: metaData,
|
||||
metaData: state.metaData!,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
},
|
||||
],
|
||||
@@ -312,20 +303,25 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
// if (hasMultiUserSupport && false)
|
||||
// CustomScrollView(
|
||||
// controller: _pagingScrollController,
|
||||
// slivers: [
|
||||
// SliverOverlapInjector(
|
||||
// handle: NestedScrollView
|
||||
// .sliverOverlapAbsorberHandleFor(
|
||||
// context),
|
||||
// ),
|
||||
// DocumentPermissionsWidget(
|
||||
// document: state.document,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
if (hasMultiUserSupport)
|
||||
CustomScrollView(
|
||||
controller: _pagingScrollController,
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
),
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentPermissionsWidget(
|
||||
document: state.document!,
|
||||
),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -383,8 +379,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return BottomAppBar(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) => Row(
|
||||
return switch (state.status) {
|
||||
LoadingStatus.loaded => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -398,7 +394,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: IconButton(
|
||||
tooltip: S.of(context)!.deleteDocumentTooltip,
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => _onDelete(document),
|
||||
onPressed: () => _onDelete(state.document!),
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -408,7 +404,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
enabled: false,
|
||||
),
|
||||
child: DocumentDownloadButton(
|
||||
document: document,
|
||||
document: state.document,
|
||||
),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -422,7 +418,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
onPressed: _onOpenFileInSystemViewer,
|
||||
).paddedOnly(right: 4.0),
|
||||
),
|
||||
DocumentShareButton(document: document),
|
||||
DocumentShareButton(document: state.document),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.print,
|
||||
onPressed: () => context
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
@@ -50,16 +51,13 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous is DocumentDetailsLoaded &&
|
||||
current is DocumentDetailsLoaded &&
|
||||
previous.document.archiveSerialNumber !=
|
||||
current.document.archiveSerialNumber,
|
||||
previous.status == LoadingStatus.loaded &&
|
||||
current.status == LoadingStatus.loaded &&
|
||||
previous.document!.archiveSerialNumber !=
|
||||
current.document!.archiveSerialNumber,
|
||||
listener: (context, state) {
|
||||
_asnEditingController.text = (state as DocumentDetailsLoaded)
|
||||
.document
|
||||
.archiveSerialNumber
|
||||
?.toString() ??
|
||||
'';
|
||||
_asnEditingController.text =
|
||||
state.document!.archiveSerialNumber?.toString() ?? '';
|
||||
setState(() {
|
||||
_canUpdate = false;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/archive_serial_number_field.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -69,6 +70,7 @@ class DocumentMetaDataWidget extends StatelessWidget {
|
||||
context: context,
|
||||
label: S.of(context)!.originalMIMEType,
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = context.watch<LocalUserAccount>().paperlessUser;
|
||||
final availableLabels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
@@ -51,7 +51,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableLabels.documentTypes[document.documentType],
|
||||
label: labelRepository.documentTypes[document.documentType],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.correspondent != null && user.canViewCorrespondents)
|
||||
@@ -59,14 +59,14 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableLabels.correspondents[document.correspondent],
|
||||
label: labelRepository.correspondents[document.correspondent],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.storagePath != null && user.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
label: availableLabels.storagePaths[document.storagePath],
|
||||
label: labelRepository.storagePaths[document.storagePath],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.tags.isNotEmpty && user.canViewTags)
|
||||
@@ -77,7 +77,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
child: TagsWidget(
|
||||
isClickable: false,
|
||||
tags:
|
||||
document.tags.map((e) => availableLabels.tags[e]!).toList(),
|
||||
document.tags.map((e) => labelRepository.tags[e]!).toList(),
|
||||
),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
|
||||
class DocumentPermissionsWidget extends StatefulWidget {
|
||||
final DocumentModel document;
|
||||
@@ -13,8 +16,20 @@ class DocumentPermissionsWidget extends StatefulWidget {
|
||||
class _DocumentPermissionsWidgetState extends State<DocumentPermissionsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Placeholder(),
|
||||
return BlocBuilder<UserRepository, UserRepositoryState>(
|
||||
builder: (context, state) {
|
||||
final owner = state.users[widget.document.owner];
|
||||
return SliverList.list(
|
||||
children: [
|
||||
if (owner != null)
|
||||
DetailsItem.text(
|
||||
owner.username,
|
||||
label: 'Owner',
|
||||
context: context,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,8 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
|
||||
Padding _buildEditForm(BuildContext context, DocumentEditState state,
|
||||
FieldSuggestions? filteredSuggestions, UserModel currentUser) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: TabBarView(
|
||||
@@ -211,8 +213,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
).push<Correspondent>(context),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
options:
|
||||
context.watch<LabelRepository>().state.correspondents,
|
||||
options: labelRepository.correspondents,
|
||||
initialValue: state.document.correspondent != null
|
||||
? SetIdQueryParameter(
|
||||
id: state.document.correspondent!)
|
||||
@@ -243,8 +244,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
? SetIdQueryParameter(
|
||||
id: state.document.documentType!)
|
||||
: const UnsetIdQueryParameter(),
|
||||
options:
|
||||
context.watch<LabelRepository>().state.documentTypes,
|
||||
options: labelRepository.documentTypes,
|
||||
name: _DocumentEditPageState.fkDocumentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
@@ -266,8 +266,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
canCreateNewLabel: currentUser.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options:
|
||||
context.watch<LabelRepository>().state.storagePaths,
|
||||
options: labelRepository.storagePaths,
|
||||
initialValue: state.document.storagePath != null
|
||||
? SetIdQueryParameter(id: state.document.storagePath!)
|
||||
: const UnsetIdQueryParameter(),
|
||||
@@ -280,7 +279,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
// Tag form field
|
||||
if (currentUser.canViewTags)
|
||||
TagsFormField(
|
||||
options: context.watch<LabelRepository>().state.tags,
|
||||
options: labelRepository.tags,
|
||||
name: fkTags,
|
||||
allowOnlySelection: true,
|
||||
allowCreation: true,
|
||||
|
||||
@@ -3,20 +3,24 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'document_scanner_cubit.freezed.dart';
|
||||
part 'document_scanner_state.dart';
|
||||
|
||||
class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
final LocalNotificationService _notificationService;
|
||||
|
||||
DocumentScannerCubit(this._notificationService)
|
||||
: super(const InitialDocumentScannerState());
|
||||
: super(const DocumentScannerState());
|
||||
|
||||
Future<void> initialize() async {
|
||||
logger.fd(
|
||||
@@ -24,7 +28,7 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
className: runtimeType.toString(),
|
||||
methodName: "initialize",
|
||||
);
|
||||
emit(const RestoringDocumentScannerState());
|
||||
emit(const DocumentScannerState(status: LoadingStatus.loading));
|
||||
final tempDir = FileService.instance.temporaryScansDirectory;
|
||||
final allFiles = tempDir.list().whereType<File>();
|
||||
final scans =
|
||||
@@ -36,13 +40,14 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
);
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
? const DocumentScannerState()
|
||||
: DocumentScannerState(scans: scans, status: LoadingStatus.loaded),
|
||||
);
|
||||
}
|
||||
|
||||
void addScan(File file) async {
|
||||
emit(LoadedDocumentScannerState(
|
||||
emit(DocumentScannerState(
|
||||
status: LoadingStatus.loaded,
|
||||
scans: [...state.scans, file],
|
||||
));
|
||||
}
|
||||
@@ -60,21 +65,22 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
final scans = state.scans..remove(file);
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
? const DocumentScannerState()
|
||||
: DocumentScannerState(
|
||||
status: LoadingStatus.loaded,
|
||||
scans: scans,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
try {
|
||||
Future.wait([
|
||||
for (final file in state.scans) file.delete(),
|
||||
]);
|
||||
Future.wait([for (final file in state.scans) file.delete()]);
|
||||
imageCache.clear();
|
||||
} catch (_) {
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
addError(TransientPaperlessApiError(code: ErrorCode.scanRemoveFailed));
|
||||
} finally {
|
||||
emit(const InitialDocumentScannerState());
|
||||
emit(const DocumentScannerState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +89,7 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
String fileName,
|
||||
String locale,
|
||||
) async {
|
||||
try {
|
||||
var file = await FileService.instance.saveToFile(bytes, fileName);
|
||||
_notificationService.notifyFileSaved(
|
||||
filename: fileName,
|
||||
@@ -90,5 +97,8 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
finished: true,
|
||||
locale: locale,
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
addError(TransientMessageError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
part of 'document_scanner_cubit.dart';
|
||||
|
||||
sealed class DocumentScannerState {
|
||||
final List<File> scans;
|
||||
|
||||
const DocumentScannerState({
|
||||
this.scans = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class InitialDocumentScannerState extends DocumentScannerState {
|
||||
const InitialDocumentScannerState();
|
||||
}
|
||||
|
||||
class RestoringDocumentScannerState extends DocumentScannerState {
|
||||
const RestoringDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class LoadedDocumentScannerState extends DocumentScannerState {
|
||||
const LoadedDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class ErrorDocumentScannerState extends DocumentScannerState {
|
||||
final String message;
|
||||
|
||||
const ErrorDocumentScannerState({
|
||||
required this.message,
|
||||
super.scans,
|
||||
});
|
||||
@freezed
|
||||
class DocumentScannerState with _$DocumentScannerState {
|
||||
const factory DocumentScannerState({
|
||||
@Default(LoadingStatus.initial) LoadingStatus status,
|
||||
@Default([]) List<File> scans,
|
||||
}) = _DocumentScannerState;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
@@ -78,13 +79,11 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, DocumentScannerState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
InitialDocumentScannerState() => _buildEmptyState(),
|
||||
RestoringDocumentScannerState() => Center(
|
||||
child: Text("Restoring..."),
|
||||
),
|
||||
LoadedDocumentScannerState() => _buildImageGrid(state.scans),
|
||||
ErrorDocumentScannerState() => Placeholder(),
|
||||
return switch (state.status) {
|
||||
LoadingStatus.initial => _buildEmptyState(),
|
||||
LoadingStatus.loading => Center(child: Text("Restoring...")),
|
||||
LoadingStatus.loaded => _buildImageGrid(state.scans),
|
||||
LoadingStatus.error => Placeholder(),
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart';
|
||||
@@ -33,6 +34,7 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
DateTime? createdAt,
|
||||
int? asn,
|
||||
}) async {
|
||||
try {
|
||||
final taskId = await _documentApi.create(
|
||||
bytes,
|
||||
filename: filename,
|
||||
@@ -52,5 +54,11 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
_tasksNotifier.listenToTaskChanges(taskId);
|
||||
}
|
||||
return taskId;
|
||||
} on PaperlessApiException catch (error) {
|
||||
addError(TransientPaperlessApiError(
|
||||
code: error.code,
|
||||
details: error.details,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class _DocumentUploadPreparationPageState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return BlocBuilder<DocumentUploadCubit, DocumentUploadState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
@@ -242,7 +242,7 @@ class _DocumentUploadPreparationPageState
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: labels.correspondents,
|
||||
options: labelRepository.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
@@ -265,7 +265,7 @@ class _DocumentUploadPreparationPageState
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
options: labels.documentTypes,
|
||||
options: labelRepository.documentTypes,
|
||||
prefixIcon:
|
||||
const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
@@ -283,7 +283,7 @@ class _DocumentUploadPreparationPageState
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: labels.tags,
|
||||
options: labelRepository.tags,
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context)!.uploadInferValuesHint,
|
||||
@@ -353,8 +353,6 @@ class _DocumentUploadPreparationPageState
|
||||
S.of(context)!.documentSuccessfullyUploadedProcessing,
|
||||
);
|
||||
context.pop(DocumentUploadResult(true, taskId));
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessFormValidationException catch (exception) {
|
||||
setState(() => _errors = exception.validationMessages);
|
||||
} catch (error, stackTrace) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
@@ -20,7 +18,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@@ -32,7 +29,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
DocumentsCubit(
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this._userState,
|
||||
this.connectivityStatusService,
|
||||
) : super(DocumentsState(
|
||||
@@ -58,17 +54,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
);
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => emit(
|
||||
state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> bulkDelete(List<DocumentModel> documents) async {
|
||||
@@ -111,7 +96,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
@override
|
||||
Future<void> close() {
|
||||
notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@ part of 'documents_cubit.dart';
|
||||
|
||||
class DocumentsState extends DocumentPagingState {
|
||||
final List<DocumentModel> selection;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
final ViewType viewType;
|
||||
|
||||
@@ -16,10 +12,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
super.filter = const DocumentFilter(),
|
||||
super.hasLoaded = false,
|
||||
super.isLoading = false,
|
||||
this.correspondents = const {},
|
||||
this.documentTypes = const {},
|
||||
this.tags = const {},
|
||||
this.storagePaths = const {},
|
||||
});
|
||||
|
||||
List<int> get selectedIds => selection.map((e) => e.id).toList();
|
||||
@@ -31,10 +23,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
DocumentFilter? filter,
|
||||
List<DocumentModel>? selection,
|
||||
ViewType? viewType,
|
||||
Map<int, Correspondent>? correspondents,
|
||||
Map<int, DocumentType>? documentTypes,
|
||||
Map<int, Tag>? tags,
|
||||
Map<int, StoragePath>? storagePaths,
|
||||
}) {
|
||||
return DocumentsState(
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
@@ -43,10 +31,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
filter: filter ?? this.filter,
|
||||
selection: selection ?? this.selection,
|
||||
viewType: viewType ?? this.viewType,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
tags: tags ?? this.tags,
|
||||
storagePaths: storagePaths ?? this.storagePaths,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,10 +38,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
List<Object?> get props => [
|
||||
selection,
|
||||
viewType,
|
||||
correspondents,
|
||||
documentTypes,
|
||||
tags,
|
||||
storagePaths,
|
||||
super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
|
||||
@@ -489,10 +489,6 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
initialFilter: context.read<DocumentsCubit>().state.filter,
|
||||
scrollController: controller,
|
||||
draggableSheetController: draggableSheetController,
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -18,6 +18,7 @@ class DateAndDocumentTypeLabelWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final subtitleStyle =
|
||||
Theme.of(context).textTheme.labelMedium?.apply(color: Colors.grey);
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return RichText(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -37,11 +38,8 @@ class DateAndDocumentTypeLabelWidget extends StatelessWidget {
|
||||
? () => onDocumentTypeSelected!(document.documentType)
|
||||
: null,
|
||||
child: Text(
|
||||
context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.documentTypes[document.documentType]!
|
||||
.name,
|
||||
labelRepository
|
||||
.documentTypes[document.documentType]!.name,
|
||||
style: subtitleStyle,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -58,7 +55,7 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
final maxHeight = highlights != null
|
||||
? min(600.0, availableHeight)
|
||||
: min(500.0, availableHeight);
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return Card(
|
||||
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
||||
child: InkWell(
|
||||
@@ -93,8 +90,9 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: TagsWidget(
|
||||
tags:
|
||||
document.tags.map((e) => labels.tags[e]!).toList(),
|
||||
tags: document.tags
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
).padded(),
|
||||
),
|
||||
@@ -107,7 +105,8 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondent: labels.correspondents[document.correspondent],
|
||||
correspondent:
|
||||
labelRepository.correspondents[document.correspondent],
|
||||
).paddedLTRB(8, 8, 8, 0),
|
||||
Text(
|
||||
document.title.isEmpty ? '(-)' : document.title,
|
||||
|
||||
@@ -30,6 +30,7 @@ class DocumentGridItem extends DocumentItem {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return Stack(
|
||||
children: [
|
||||
Card(
|
||||
@@ -75,10 +76,7 @@ class DocumentGridItem extends DocumentItem {
|
||||
if (currentUser.canViewTags)
|
||||
TagsWidget.sliver(
|
||||
tags: document.tags
|
||||
.map((e) => context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.tags[e]!)
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
),
|
||||
@@ -102,17 +100,13 @@ class DocumentGridItem extends DocumentItem {
|
||||
children: [
|
||||
if (currentUser.canViewCorrespondents)
|
||||
CorrespondentWidget(
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
correspondent: labelRepository
|
||||
.correspondents[document.correspondent],
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
if (currentUser.canViewDocumentTypes)
|
||||
DocumentTypeWidget(
|
||||
documentType: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
documentType: labelRepository
|
||||
.documentTypes[document.documentType],
|
||||
onSelected: onDocumentTypeSelected,
|
||||
),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/src/models/document_model.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
@@ -32,7 +29,7 @@ class DocumentListItem extends DocumentItem {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return ListTile(
|
||||
tileColor: backgroundColor,
|
||||
@@ -51,10 +48,8 @@ class DocumentListItem extends DocumentItem {
|
||||
absorbing: isSelectionActive,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.correspondents[document.correspondent],
|
||||
correspondent:
|
||||
labelRepository.correspondents[document.correspondent],
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
),
|
||||
@@ -70,8 +65,8 @@ class DocumentListItem extends DocumentItem {
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tags: document.tags
|
||||
.where((e) => labels.tags.containsKey(e))
|
||||
.map((e) => labels.tags[e]!)
|
||||
.where((e) => labelRepository.tags.containsKey(e))
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: (id) => onTagSelected?.call(id),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
@@ -47,10 +48,6 @@ class DocumentFilterForm extends StatefulWidget {
|
||||
final DocumentFilter initialFilter;
|
||||
final ScrollController? scrollController;
|
||||
final EdgeInsets padding;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
const DocumentFilterForm({
|
||||
super.key,
|
||||
@@ -59,10 +56,6 @@ class DocumentFilterForm extends StatefulWidget {
|
||||
required this.initialFilter,
|
||||
this.scrollController,
|
||||
this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -80,13 +73,14 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return FormBuilder(
|
||||
key: widget.formKey,
|
||||
child: CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
if (widget.header != null) widget.header!,
|
||||
..._buildFormFieldList(),
|
||||
..._buildFormFieldList(labelRepository),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
@@ -97,7 +91,7 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildFormFieldList() {
|
||||
List<Widget> _buildFormFieldList(LabelRepository labelRepository) {
|
||||
return [
|
||||
_buildQueryFormField(),
|
||||
Align(
|
||||
@@ -123,10 +117,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
),
|
||||
_buildCorrespondentFormField(),
|
||||
_buildDocumentTypeFormField(),
|
||||
_buildStoragePathFormField(),
|
||||
_buildTagsFormField(),
|
||||
_buildCorrespondentFormField(labelRepository.correspondents),
|
||||
_buildDocumentTypeFormField(labelRepository.documentTypes),
|
||||
_buildStoragePathFormField(labelRepository.storagePaths),
|
||||
_buildTagsFormField(labelRepository.tags),
|
||||
]
|
||||
.map((w) => SliverPadding(
|
||||
padding: widget.padding,
|
||||
@@ -151,10 +145,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
Widget _buildDocumentTypeFormField(Map<int, DocumentType> documentTypes) {
|
||||
return LabelFormField<DocumentType>(
|
||||
name: DocumentFilterForm.fkDocumentType,
|
||||
options: widget.documentTypes,
|
||||
options: documentTypes,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
@@ -166,10 +160,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField() {
|
||||
Widget _buildCorrespondentFormField(Map<int, Correspondent> correspondents) {
|
||||
return LabelFormField<Correspondent>(
|
||||
name: DocumentFilterForm.fkCorrespondent,
|
||||
options: widget.correspondents,
|
||||
options: correspondents,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
@@ -181,10 +175,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField() {
|
||||
Widget _buildStoragePathFormField(Map<int, StoragePath> storagePaths) {
|
||||
return LabelFormField<StoragePath>(
|
||||
name: DocumentFilterForm.fkStoragePath,
|
||||
options: widget.storagePaths,
|
||||
options: storagePaths,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
@@ -202,11 +196,11 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagsFormField() {
|
||||
Widget _buildTagsFormField(Map<int, Tag> tags) {
|
||||
return TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
options: widget.tags,
|
||||
options: tags,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: false,
|
||||
allowCreation: false,
|
||||
|
||||
@@ -13,20 +13,12 @@ class DocumentFilterPanel extends StatefulWidget {
|
||||
final DocumentFilter initialFilter;
|
||||
final ScrollController scrollController;
|
||||
final DraggableScrollableController draggableSheetController;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
const DocumentFilterPanel({
|
||||
Key? key,
|
||||
required this.initialFilter,
|
||||
required this.scrollController,
|
||||
required this.draggableSheetController,
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -104,10 +96,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
scrollController: widget.scrollController,
|
||||
initialFilter: widget.initialFilter,
|
||||
header: _buildPanelHeader(),
|
||||
correspondents: widget.correspondents,
|
||||
documentTypes: widget.documentTypes,
|
||||
storagePaths: widget.storagePaths,
|
||||
tags: widget.tags,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -8,10 +10,6 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
final SortOrder initialSortOrder;
|
||||
final SortField? initialSortField;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
final Future Function(SortField? field, SortOrder order) onSubmit;
|
||||
|
||||
@@ -20,10 +18,6 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
required this.initialSortOrder,
|
||||
required this.initialSortField,
|
||||
required this.onSubmit,
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -45,6 +39,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return ClipRRect(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
@@ -75,7 +70,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
_buildSortOption(SortField.archiveSerialNumber),
|
||||
_buildSortOption(
|
||||
SortField.correspondentName,
|
||||
enabled: widget.correspondents.values.fold<bool>(
|
||||
enabled: labelRepository.correspondents.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
@@ -83,7 +78,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
_buildSortOption(SortField.title),
|
||||
_buildSortOption(
|
||||
SortField.documentType,
|
||||
enabled: widget.documentTypes.values.fold<bool>(
|
||||
enabled: labelRepository.documentTypes.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
|
||||
@@ -69,10 +69,6 @@ class SortDocumentsButton extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'edit_label_state.dart';
|
||||
part 'edit_label_cubit.freezed.dart';
|
||||
|
||||
class EditLabelCubit extends Cubit<EditLabelState>
|
||||
with LabelCubitMixin<EditLabelState> {
|
||||
@override
|
||||
final LabelRepository labelRepository;
|
||||
|
||||
EditLabelCubit(this.labelRepository) : super(const EditLabelState()) {
|
||||
labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
part of 'edit_label_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class EditLabelState with _$EditLabelState {
|
||||
const factory EditLabelState({
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, Tag> tags,
|
||||
@Default({}) Map<int, StoragePath> storagePaths,
|
||||
}) = _EditLabelState;
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.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/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
@@ -25,7 +25,7 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read<LabelRepository>(),
|
||||
),
|
||||
child: AddLabelFormWidget(
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.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/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
@@ -35,7 +35,7 @@ class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read<LabelRepository>(),
|
||||
),
|
||||
child: EditLabelForm(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddCorrespondentPage extends StatelessWidget {
|
||||
@@ -12,7 +12,7 @@ class AddCorrespondentPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<Correspondent>(
|
||||
@@ -20,7 +20,7 @@ class AddCorrespondentPage extends StatelessWidget {
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addCorrespondent(label),
|
||||
context.read<LabelCubit>().addCorrespondent(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddDocumentTypePage extends StatelessWidget {
|
||||
@@ -15,7 +15,7 @@ class AddDocumentTypePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<DocumentType>(
|
||||
@@ -23,7 +23,7 @@ class AddDocumentTypePage extends StatelessWidget {
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addDocumentType(label),
|
||||
context.read<LabelCubit>().addDocumentType(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -13,7 +13,7 @@ class AddStoragePathPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<StoragePath>(
|
||||
@@ -21,7 +21,7 @@ class AddStoragePathPage extends StatelessWidget {
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addStoragePath(label),
|
||||
context.read<LabelCubit>().addStoragePath(label),
|
||||
additionalFields: const [
|
||||
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
|
||||
SizedBox(height: 120.0),
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
@@ -16,15 +16,14 @@ class AddTagPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<Tag>(
|
||||
pageTitle: Text(S.of(context)!.addTag),
|
||||
fromJsonT: Tag.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addTag(label),
|
||||
onSubmit: (context, label) => context.read<LabelCubit>().addTag(label),
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
name: Tag.colorKey,
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
|
||||
class EditCorrespondentPage extends StatelessWidget {
|
||||
final Correspondent correspondent;
|
||||
@@ -13,7 +13,7 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
lazy: false,
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
@@ -21,9 +21,9 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
label: correspondent,
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||
context.read<LabelCubit>().replaceCorrespondent(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||
context.read<LabelCubit>().removeCorrespondent(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
@@ -12,16 +12,16 @@ class EditDocumentTypePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<DocumentType>(
|
||||
label: documentType,
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||
context.read<LabelCubit>().replaceDocumentType(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||
context.read<LabelCubit>().removeDocumentType(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
@@ -13,16 +13,16 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<StoragePath>(
|
||||
label: storagePath,
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||
context.read<LabelCubit>().replaceStoragePath(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||
context.read<LabelCubit>().removeStoragePath(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class EditTagPage extends StatelessWidget {
|
||||
@@ -16,16 +16,16 @@ class EditTagPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<Tag>(
|
||||
label: tag,
|
||||
fromJsonT: Tag.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceTag(label),
|
||||
context.read<LabelCubit>().replaceTag(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeTag(label),
|
||||
context.read<LabelCubit>().removeTag(label),
|
||||
canDelete:
|
||||
context.watch<LocalUserAccount>().paperlessUser.canDeleteTags,
|
||||
additionalFields: [
|
||||
|
||||
@@ -53,8 +53,8 @@ class HomeShellWidget extends StatelessWidget {
|
||||
builder: (context, box, _) {
|
||||
if (currentUserId == null) {
|
||||
//This only happens during logout...
|
||||
//TODO: Find way so this does not occur anymore
|
||||
return SizedBox.shrink();
|
||||
//FIXME: Find way so this does not occur anymore
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final currentLocalUser = box.get(currentUserId)!;
|
||||
return MultiProvider(
|
||||
@@ -107,36 +107,31 @@ class HomeShellWidget extends StatelessWidget {
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => PaperlessUserApiV3Impl(
|
||||
create: (context) => paperlessProviderFactory.createUserApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final repo = LabelRepository(context.read());
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewCorrespondents) {
|
||||
repo.findAllCorrespondents();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewDocumentTypes) {
|
||||
repo.findAllDocumentTypes();
|
||||
}
|
||||
if (currentLocalUser.paperlessUser.canViewTags) {
|
||||
repo.findAllTags();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewStoragePaths) {
|
||||
repo.findAllStoragePaths();
|
||||
}
|
||||
return repo;
|
||||
return LabelRepository(context.read())
|
||||
..initialize(
|
||||
loadCorrespondents: currentLocalUser
|
||||
.paperlessUser.canViewCorrespondents,
|
||||
loadDocumentTypes: currentLocalUser
|
||||
.paperlessUser.canViewDocumentTypes,
|
||||
loadStoragePaths: currentLocalUser
|
||||
.paperlessUser.canViewStoragePaths,
|
||||
loadTags:
|
||||
currentLocalUser.paperlessUser.canViewTags,
|
||||
);
|
||||
},
|
||||
),
|
||||
Provider(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final repo = SavedViewRepository(context.read());
|
||||
if (currentLocalUser.paperlessUser.canViewSavedViews) {
|
||||
@@ -145,6 +140,12 @@ class HomeShellWidget extends StatelessWidget {
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => UserRepository(
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
@@ -152,7 +153,6 @@ class HomeShellWidget extends StatelessWidget {
|
||||
Provider(
|
||||
lazy: false,
|
||||
create: (context) => DocumentsCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(
|
||||
@@ -196,12 +196,6 @@ class HomeShellWidget extends StatelessWidget {
|
||||
context.read(),
|
||||
),
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => UserRepository(
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.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_state.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
@@ -37,7 +36,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
this._labelRepository,
|
||||
this.notifier,
|
||||
this.connectivityStatusService,
|
||||
) : super(InboxState(labels: _labelRepository.state)) {
|
||||
) : super(const InboxState()) {
|
||||
notifier.addListener(
|
||||
this,
|
||||
onDeleted: remove,
|
||||
@@ -62,12 +61,6 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
}
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(state.copyWith(labels: labels));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -256,7 +249,6 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ part of 'inbox_cubit.dart';
|
||||
class InboxState extends DocumentPagingState {
|
||||
final Iterable<int> inboxTags;
|
||||
|
||||
final LabelRepositoryState labels;
|
||||
|
||||
final int itemsInInboxCount;
|
||||
|
||||
@JsonKey()
|
||||
@@ -19,7 +17,6 @@ class InboxState extends DocumentPagingState {
|
||||
this.inboxTags = const [],
|
||||
this.isHintAcknowledged = false,
|
||||
this.itemsInInboxCount = 0,
|
||||
this.labels = const LabelRepositoryState(),
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -32,7 +29,6 @@ class InboxState extends DocumentPagingState {
|
||||
documents,
|
||||
isHintAcknowledged,
|
||||
itemsInInboxCount,
|
||||
labels,
|
||||
];
|
||||
|
||||
InboxState copyWith({
|
||||
@@ -42,7 +38,6 @@ class InboxState extends DocumentPagingState {
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
bool? isHintAcknowledged,
|
||||
LabelRepositoryState? labels,
|
||||
Map<int, FieldSuggestions>? suggestions,
|
||||
int? itemsInInboxCount,
|
||||
}) {
|
||||
@@ -52,7 +47,6 @@ class InboxState extends DocumentPagingState {
|
||||
value: value ?? super.value,
|
||||
inboxTags: inboxTags ?? this.inboxTags,
|
||||
isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged,
|
||||
labels: labels ?? this.labels,
|
||||
filter: filter ?? super.filter,
|
||||
itemsInInboxCount: itemsInInboxCount ?? this.itemsInInboxCount,
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/util/lambda_utils.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
@@ -148,6 +150,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.read<LabelRepository>();
|
||||
return BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
@@ -193,7 +196,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
?.fontSize,
|
||||
),
|
||||
LabelText<Correspondent>(
|
||||
label: state.labels.correspondents[
|
||||
label: labelRepository.correspondents[
|
||||
widget.document.correspondent],
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
placeholder: "-",
|
||||
@@ -208,7 +211,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
?.fontSize,
|
||||
),
|
||||
LabelText<DocumentType>(
|
||||
label: state.labels.documentTypes[
|
||||
label: labelRepository.documentTypes[
|
||||
widget.document.documentType],
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
placeholder: "-",
|
||||
@@ -217,8 +220,8 @@ class _InboxItemState extends State<InboxItem> {
|
||||
const Spacer(),
|
||||
TagsWidget(
|
||||
tags: widget.document.tags
|
||||
.map((e) => state.labels.tags[e])
|
||||
.whereNot((e) => e == null)
|
||||
.map((e) => labelRepository.tags[e])
|
||||
.where(isNotNull)
|
||||
.toList()
|
||||
.cast<Tag>(),
|
||||
isClickable: false,
|
||||
|
||||
@@ -2,41 +2,134 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
||||
|
||||
part 'label_cubit.freezed.dart';
|
||||
part 'label_state.dart';
|
||||
|
||||
class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
|
||||
@override
|
||||
class LabelCubit extends Cubit<LabelState> {
|
||||
final LabelRepository labelRepository;
|
||||
|
||||
LabelCubit(this.labelRepository) : super(const LabelState()) {
|
||||
labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
() {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
correspondents: labelRepository.correspondents,
|
||||
documentTypes: labelRepository.documentTypes,
|
||||
storagePaths: labelRepository.storagePaths,
|
||||
tags: labelRepository.tags,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reload() {
|
||||
return Future.wait([
|
||||
labelRepository.findAllCorrespondents(),
|
||||
labelRepository.findAllDocumentTypes(),
|
||||
labelRepository.findAllTags(),
|
||||
labelRepository.findAllStoragePaths(),
|
||||
]);
|
||||
Future<void> reload({
|
||||
required bool loadCorrespondents,
|
||||
required bool loadDocumentTypes,
|
||||
required bool loadStoragePaths,
|
||||
required bool loadTags,
|
||||
}) {
|
||||
return labelRepository.initialize(
|
||||
loadCorrespondents: loadCorrespondents,
|
||||
loadDocumentTypes: loadDocumentTypes,
|
||||
loadStoragePaths: loadStoragePaths,
|
||||
loadTags: loadTags,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Correspondent> addCorrespondent(Correspondent item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createCorrespondent(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadCorrespondents() {
|
||||
return labelRepository.findAllCorrespondents();
|
||||
}
|
||||
|
||||
Future<Correspondent> replaceCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateCorrespondent(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.correspondents.containsKey(item.id)) {
|
||||
await labelRepository.deleteCorrespondent(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<DocumentType> addDocumentType(DocumentType item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createDocumentType(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadDocumentTypes() {
|
||||
return labelRepository.findAllDocumentTypes();
|
||||
}
|
||||
|
||||
Future<DocumentType> replaceDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateDocumentType(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.documentTypes.containsKey(item.id)) {
|
||||
await labelRepository.deleteDocumentType(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<StoragePath> addStoragePath(StoragePath item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createStoragePath(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadStoragePaths() {
|
||||
return labelRepository.findAllStoragePaths();
|
||||
}
|
||||
|
||||
Future<StoragePath> replaceStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateStoragePath(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.storagePaths.containsKey(item.id)) {
|
||||
await labelRepository.deleteStoragePath(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Tag> addTag(Tag item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createTag(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadTags() {
|
||||
return labelRepository.findAllTags();
|
||||
}
|
||||
|
||||
Future<Tag> replaceTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateTag(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.tags.containsKey(item.id)) {
|
||||
await labelRepository.deleteTag(item);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
|
||||
///
|
||||
/// Mixin which adds functionality to manage labels to [Bloc]s.
|
||||
///
|
||||
mixin LabelCubitMixin<T> on BlocBase<T> {
|
||||
LabelRepository get labelRepository;
|
||||
|
||||
Future<Correspondent> addCorrespondent(Correspondent item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createCorrespondent(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadCorrespondents() {
|
||||
return labelRepository.findAllCorrespondents();
|
||||
}
|
||||
|
||||
Future<Correspondent> replaceCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateCorrespondent(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.correspondents.containsKey(item.id)) {
|
||||
await labelRepository.deleteCorrespondent(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<DocumentType> addDocumentType(DocumentType item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createDocumentType(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadDocumentTypes() {
|
||||
return labelRepository.findAllDocumentTypes();
|
||||
}
|
||||
|
||||
Future<DocumentType> replaceDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateDocumentType(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.documentTypes.containsKey(item.id)) {
|
||||
await labelRepository.deleteDocumentType(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<StoragePath> addStoragePath(StoragePath item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createStoragePath(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadStoragePaths() {
|
||||
return labelRepository.findAllStoragePaths();
|
||||
}
|
||||
|
||||
Future<StoragePath> replaceStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateStoragePath(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.storagePaths.containsKey(item.id)) {
|
||||
await labelRepository.deleteStoragePath(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Tag> addTag(Tag item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createTag(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadTags() {
|
||||
return labelRepository.findAllTags();
|
||||
}
|
||||
|
||||
Future<Tag> replaceTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateTag(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.tags.containsKey(item.id)) {
|
||||
await labelRepository.deleteTag(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,28 +19,13 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
LinkedDocumentsCubit(
|
||||
DocumentFilter filter,
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService,
|
||||
) : super(LinkedDocumentsState(filter: filter)) {
|
||||
updateFilter(filter: filter);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
));
|
||||
},
|
||||
);
|
||||
notifier.addListener(
|
||||
this,
|
||||
onUpdated: replace,
|
||||
|
||||
@@ -5,21 +5,12 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
@JsonKey()
|
||||
final ViewType viewType;
|
||||
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
final Map<int, Tag> tags;
|
||||
|
||||
const LinkedDocumentsState({
|
||||
this.viewType = ViewType.list,
|
||||
super.filter = const DocumentFilter(),
|
||||
super.isLoading,
|
||||
super.hasLoaded,
|
||||
super.value,
|
||||
this.correspondents = const {},
|
||||
this.documentTypes = const {},
|
||||
this.storagePaths = const {},
|
||||
this.tags = const {},
|
||||
});
|
||||
|
||||
LinkedDocumentsState copyWith({
|
||||
@@ -39,10 +30,6 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
value: value ?? this.value,
|
||||
viewType: viewType ?? this.viewType,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
storagePaths: storagePaths ?? this.storagePaths,
|
||||
tags: tags ?? this.tags,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,10 +51,6 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
viewType,
|
||||
correspondents,
|
||||
documentTypes,
|
||||
tags,
|
||||
storagePaths,
|
||||
...super.props,
|
||||
];
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
/// Switches to another account if it exists.
|
||||
Future<void> switchAccount(String localUserId) async {
|
||||
emit(const SwitchingAccountsState());
|
||||
await FileService.instance.initialize();
|
||||
|
||||
final redactedId = redactUserId(localUserId);
|
||||
logger.fd(
|
||||
'Trying to switch to user $redactedId...',
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
@@ -13,10 +17,13 @@ import 'package:paperless_mobile/features/login/model/client_certificate_form_mo
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/login_settings_page.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/assets.gen.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
|
||||
class AddAccountPage extends StatefulWidget {
|
||||
final FutureOr<void> Function(
|
||||
@@ -58,10 +65,172 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
bool _isCheckingConnection = false;
|
||||
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.unknown;
|
||||
|
||||
bool _isFormSubmitted = false;
|
||||
|
||||
final _pageController = PageController();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
),
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Assets.logos.paperlessLogoGreenPng.image(
|
||||
width: 150,
|
||||
height: 150,
|
||||
),
|
||||
Text(
|
||||
'Paperless Mobile',
|
||||
style: Theme.of(context).textTheme.displaySmall,
|
||||
).padded(),
|
||||
SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: PageView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
allowImplicitScrolling: false,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_reachabilityStatus = ReachabilityStatus.unknown;
|
||||
});
|
||||
},
|
||||
).paddedSymmetrically(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
ClientCertificateFormField(
|
||||
initialBytes: widget.initialClientCertificate?.bytes,
|
||||
initialPassphrase:
|
||||
widget.initialClientCertificate?.passphrase,
|
||||
).padded(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
//TODO: Move additional headers and client cert to separate page
|
||||
// IconButton.filledTonal(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(builder: (context) {
|
||||
// return LoginSettingsPage();
|
||||
// }),
|
||||
// );
|
||||
// },
|
||||
// icon: Icon(Icons.settings),
|
||||
// ),
|
||||
SizedBox(width: 8),
|
||||
FilledButton.icon(
|
||||
onPressed: () async {
|
||||
final status = await _updateReachability();
|
||||
if (status == ReachabilityStatus.reachable) {
|
||||
Future.delayed(1.seconds, () {
|
||||
_pageController.nextPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _isCheckingConnection
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondary,
|
||||
),
|
||||
)
|
||||
: _reachabilityStatus ==
|
||||
ReachabilityStatus.reachable
|
||||
? Icon(Icons.done)
|
||||
: Icon(Icons.arrow_forward),
|
||||
label: Text(S.of(context)!.continueLabel),
|
||||
),
|
||||
],
|
||||
).paddedSymmetrically(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
_buildStatusIndicator().padded(),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
UserCredentialsFormField(
|
||||
formKey: _formKey,
|
||||
initialUsername: widget.initialUsername,
|
||||
initialPassword: widget.initialPassword,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_pageController.previousPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.arrow_back),
|
||||
label: Text(S.of(context)!.edit),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_onSubmit();
|
||||
},
|
||||
child: Text(S.of(context)!.signIn),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
children: [
|
||||
TextSpan(text: S.of(context)!.version(packageInfo.version)),
|
||||
WidgetSpan(child: SizedBox(width: 24)),
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
text: S.of(context)!.appLogs(''),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).padded(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
@@ -91,7 +260,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
initialValue: widget.initialServerUrl,
|
||||
onSubmit: (address) {
|
||||
onChanged: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
@@ -117,7 +286,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -125,7 +294,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateReachability([String? address]) async {
|
||||
Future<ReachabilityStatus> _updateReachability([String? address]) async {
|
||||
setState(() {
|
||||
_isCheckingConnection = true;
|
||||
});
|
||||
@@ -150,13 +319,10 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
_isCheckingConnection = false;
|
||||
_reachabilityStatus = status;
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
Widget _buildStatusIndicator() {
|
||||
if (_isCheckingConnection) {
|
||||
return const ListTile();
|
||||
}
|
||||
|
||||
Widget _buildIconText(
|
||||
IconData icon,
|
||||
String text, [
|
||||
@@ -176,14 +342,6 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
|
||||
Color errorColor = Theme.of(context).colorScheme.error;
|
||||
switch (_reachabilityStatus) {
|
||||
case ReachabilityStatus.unknown:
|
||||
return Container();
|
||||
case ReachabilityStatus.reachable:
|
||||
return _buildIconText(
|
||||
Icons.done,
|
||||
S.of(context)!.connectionSuccessfulylEstablished,
|
||||
Colors.green,
|
||||
);
|
||||
case ReachabilityStatus.notReachable:
|
||||
return _buildIconText(
|
||||
Icons.close,
|
||||
@@ -214,6 +372,8 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
S.of(context)!.connectionTimedOut,
|
||||
errorColor,
|
||||
);
|
||||
default:
|
||||
return const ListTile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'obscured_input_text_form_field.dart';
|
||||
|
||||
class ClientCertificateFormField extends StatefulWidget {
|
||||
@@ -16,10 +17,10 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
final String? initialPassphrase;
|
||||
final Uint8List? initialBytes;
|
||||
|
||||
final void Function(ClientCertificateFormModel? cert) onChanged;
|
||||
final ValueChanged<ClientCertificateFormModel?>? onChanged;
|
||||
const ClientCertificateFormField({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.onChanged,
|
||||
this.initialPassphrase,
|
||||
this.initialBytes,
|
||||
});
|
||||
@@ -29,13 +30,15 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
_ClientCertificateFormFieldState();
|
||||
}
|
||||
|
||||
class _ClientCertificateFormFieldState
|
||||
extends State<ClientCertificateFormField> {
|
||||
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
File? _selectedFile;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<ClientCertificateFormModel?>(
|
||||
key: const ValueKey('login-client-cert'),
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
onChanged: widget.onChanged,
|
||||
initialValue: widget.initialBytes != null
|
||||
? ClientCertificateFormModel(
|
||||
@@ -43,16 +46,6 @@ class _ClientCertificateFormFieldState
|
||||
passphrase: widget.initialPassphrase,
|
||||
)
|
||||
: null,
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
assert(_selectedFile != null);
|
||||
if (_selectedFile?.path.split(".").last != 'pfx') {
|
||||
return S.of(context)!.invalidCertificateFormat;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (field) {
|
||||
final theme =
|
||||
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
||||
@@ -127,7 +120,6 @@ class _ClientCertificateFormFieldState
|
||||
),
|
||||
);
|
||||
},
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,6 +132,11 @@ class _ClientCertificateFormFieldState
|
||||
if (result == null || result.files.single.path == null) {
|
||||
return;
|
||||
}
|
||||
final path = result.files.single.path!;
|
||||
if (p.extension(path) != '.pfx') {
|
||||
showSnackBar(context, S.of(context)!.invalidCertificateFormat);
|
||||
return;
|
||||
}
|
||||
File file = File(result.files.single.path!);
|
||||
setState(() {
|
||||
_selectedFile = file;
|
||||
@@ -171,4 +168,7 @@ class _ClientCertificateFormFieldState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class LoginSettingsPage extends StatelessWidget {
|
||||
const LoginSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.settings),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ClientCertificateFormField(onChanged: (certificate) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class ServerAddressFormField extends StatefulWidget {
|
||||
static const String fkServerAddress = "serverAddress";
|
||||
final String? initialValue;
|
||||
final void Function(String? address) onSubmit;
|
||||
final ValueChanged<String?>? onChanged;
|
||||
|
||||
const ServerAddressFormField({
|
||||
Key? key,
|
||||
required this.onSubmit,
|
||||
this.onChanged,
|
||||
this.initialValue,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -20,8 +21,10 @@ class ServerAddressFormField extends StatefulWidget {
|
||||
State<ServerAddressFormField> createState() => _ServerAddressFormFieldState();
|
||||
}
|
||||
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
bool _canClear = false;
|
||||
final _textFieldKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -38,10 +41,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<String>(
|
||||
initialValue: widget.initialValue,
|
||||
name: ServerAddressFormField.fkServerAddress,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: widget.onChanged,
|
||||
builder: (field) {
|
||||
return RawAutocomplete<String>(
|
||||
focusNode: _focusNode,
|
||||
@@ -51,6 +56,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onSelected: onSelected,
|
||||
options: options,
|
||||
maxOptionsHeight: 200.0,
|
||||
maxWidth: MediaQuery.sizeOf(context).width - 40,
|
||||
);
|
||||
},
|
||||
key: const ValueKey('login-server-address'),
|
||||
@@ -60,12 +66,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
.where((element) => element.contains(textEditingValue.text));
|
||||
},
|
||||
onSelected: (option) {
|
||||
_formatInput();
|
||||
field.didChange(_textEditingController.text);
|
||||
_formatInput(field);
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||
return TextFormField(
|
||||
key: _textFieldKey,
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
@@ -78,15 +84,22 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onPressed: () {
|
||||
textEditingController.clear();
|
||||
field.didChange(textEditingController.text);
|
||||
widget.onSubmit(textEditingController.text);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
autofocus: false,
|
||||
onFieldSubmitted: (_) {
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
_formatInput();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
if (!FocusScope.of(context).hasFocus) {
|
||||
return;
|
||||
}
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) {
|
||||
@@ -113,7 +126,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
);
|
||||
}
|
||||
|
||||
void _formatInput() {
|
||||
void _formatInput(FormFieldState<String> field) {
|
||||
String address = _textEditingController.text.trim();
|
||||
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||
_textEditingController.text = address;
|
||||
@@ -121,8 +134,11 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
baseOffset: address.length,
|
||||
extentOffset: address.length,
|
||||
);
|
||||
widget.onSubmit(address);
|
||||
field.didChange(_textEditingController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/// Taken from [Autocomplete]
|
||||
@@ -131,12 +147,14 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
required this.onSelected,
|
||||
required this.options,
|
||||
required this.maxOptionsHeight,
|
||||
required this.maxWidth,
|
||||
});
|
||||
|
||||
final AutocompleteOnSelected<String> onSelected;
|
||||
|
||||
final Iterable<String> options;
|
||||
final double maxOptionsHeight;
|
||||
final double maxWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -145,7 +163,10 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxOptionsHeight),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxOptionsHeight,
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
|
||||
@@ -12,13 +12,13 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class UserCredentialsFormField extends StatefulWidget {
|
||||
static const fkCredentials = 'credentials';
|
||||
|
||||
final void Function() onFieldsSubmitted;
|
||||
final VoidCallback? onFieldsSubmitted;
|
||||
final String? initialUsername;
|
||||
final String? initialPassword;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
const UserCredentialsFormField({
|
||||
Key? key,
|
||||
required this.onFieldsSubmitted,
|
||||
this.onFieldsSubmitted,
|
||||
this.initialUsername,
|
||||
this.initialPassword,
|
||||
required this.formKey,
|
||||
@@ -29,12 +29,14 @@ class UserCredentialsFormField extends StatefulWidget {
|
||||
_UserCredentialsFormFieldState();
|
||||
}
|
||||
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final _usernameFocusNode = FocusNode();
|
||||
final _passwordFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<LoginFormCredentials?>(
|
||||
initialValue: LoginFormCredentials(
|
||||
password: widget.initialPassword,
|
||||
@@ -87,7 +89,7 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
LoginFormCredentials(password: password),
|
||||
),
|
||||
onFieldSubmitted: (_) {
|
||||
widget.onFieldsSubmitted();
|
||||
widget.onFieldsSubmitted?.call();
|
||||
},
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
@@ -100,6 +102,9 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
class LoginTransitionPage extends StatelessWidget {
|
||||
@@ -19,13 +21,28 @@ class LoginTransitionPage extends StatelessWidget {
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(text).paddedOnly(bottom: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: TextButton(
|
||||
child: Text(S.of(context)!.appLogs('')),
|
||||
onPressed: () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(16),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,17 +13,14 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
|
||||
SavedViewCubit(this._savedViewRepository)
|
||||
: super(const SavedViewState.initial()) {
|
||||
_savedViewRepository.addListener(
|
||||
this,
|
||||
onChanged: (views) {
|
||||
views.when(
|
||||
initial: (savedViews) => emit(const SavedViewState.initial()),
|
||||
loading: (savedViews) => emit(const SavedViewState.loading()),
|
||||
loaded: (savedViews) =>
|
||||
emit(SavedViewState.loaded(savedViews: savedViews)),
|
||||
error: (savedViews) => emit(const SavedViewState.error()),
|
||||
);
|
||||
},
|
||||
_savedViewRepository.addListener(_onSavedViewsChanged);
|
||||
}
|
||||
|
||||
void _onSavedViewsChanged() {
|
||||
emit(
|
||||
SavedViewState.loaded(
|
||||
savedViews: _savedViewRepository.savedViews,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +50,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_savedViewRepository.removeListener(this);
|
||||
_savedViewRepository.removeListener(_onSavedViewsChanged);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,32 +34,13 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
||||
required this.savedView,
|
||||
int initialCount = 25,
|
||||
}) : super(
|
||||
SavedViewDetailsState(
|
||||
correspondents: _labelRepository.state.correspondents,
|
||||
documentTypes: _labelRepository.state.documentTypes,
|
||||
tags: _labelRepository.state.tags,
|
||||
storagePaths: _labelRepository.state.storagePaths,
|
||||
viewType: _userState.savedViewsViewType,
|
||||
),
|
||||
SavedViewDetailsState(viewType: _userState.savedViewsViewType),
|
||||
) {
|
||||
notifier.addListener(
|
||||
this,
|
||||
onDeleted: remove,
|
||||
onUpdated: replace,
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
updateFilter(
|
||||
filter: savedView.toDocumentFilter().copyWith(
|
||||
page: 1,
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
|
||||
@@ -3,6 +3,7 @@ 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/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
|
||||
@@ -17,15 +18,12 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
SimilarDocumentsCubit(
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService, {
|
||||
required this.documentId,
|
||||
}) : super(const SimilarDocumentsState(filter: DocumentFilter())) {
|
||||
@@ -39,19 +37,30 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
if (!state.hasLoaded) {
|
||||
try {
|
||||
await updateFilter(
|
||||
filter: state.filter.copyWith(
|
||||
moreLike: () => documentId,
|
||||
sortField: SortField.score,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(error: null));
|
||||
} on PaperlessApiException catch (e, stackTrace) {
|
||||
logger.fe(
|
||||
"An error occurred while loading similar documents for document $documentId",
|
||||
className: "SimilarDocumentsCubit",
|
||||
methodName: "initialize",
|
||||
error: e.details,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
emit(state.copyWith(error: e.code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
part of 'similar_documents_cubit.dart';
|
||||
|
||||
class SimilarDocumentsState extends DocumentPagingState {
|
||||
final ErrorCode? error;
|
||||
const SimilarDocumentsState({
|
||||
required super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
super.value,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
List<Object?> get props => [
|
||||
filter,
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
value,
|
||||
error,
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -36,12 +39,14 @@ class SimilarDocumentsState extends DocumentPagingState {
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
ErrorCode? error,
|
||||
}) {
|
||||
return SimilarDocumentsState(
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
value: value ?? this.value,
|
||||
filter: filter ?? this.filter,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||
@@ -49,6 +51,16 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
||||
child: OfflineWidget(),
|
||||
);
|
||||
}
|
||||
if (state.error != null) {
|
||||
return SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Text(
|
||||
translateError(context, state.error!),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
).padded(),
|
||||
);
|
||||
}
|
||||
if (state.hasLoaded &&
|
||||
!state.isLoading &&
|
||||
state.documents.isEmpty) {
|
||||
|
||||
@@ -1010,14 +1010,19 @@
|
||||
"couldNotLoadLogfileFrom": "No es pot carregar log desde {date}.",
|
||||
"loadingLogsFrom": "Carregant registres des de {date}...",
|
||||
"clearLogs": "Netejar registres des de {date}",
|
||||
"showPdf": "Show PDF",
|
||||
"showPdf": "Mostra PDF",
|
||||
"@showPdf": {
|
||||
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||
},
|
||||
"hidePdf": "Hide PDF",
|
||||
"hidePdf": "Oculta PDF",
|
||||
"@hidePdf": {
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"misc": "Miscel·lanni",
|
||||
"loggingOut": "Sortint...",
|
||||
"testingConnection": "Provant connexió...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Versió {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Sonstige",
|
||||
"loggingOut": "Abmelden..."
|
||||
"loggingOut": "Abmelden...",
|
||||
"testingConnection": "Teste Verbindung...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -873,7 +873,7 @@
|
||||
"@donate": {
|
||||
"description": "Label of the in-app donate button"
|
||||
},
|
||||
"donationDialogContent": "¡Gracias por querer apoyar esta aplicación!\nDebido a las políticas de pago, tanto de Google como de Apple, no se puede mostrar ningún enlace que lo dirija a las donaciones. En este contexto, ni siquiera es posible enlazar la página del repositorio del proyecto. Por lo tanto, puedes visitar la sección \"Donations\" en el archivo README de este proyecto. Tu apoyo es valorado gratamente y ayuda a mantener con vida el desarrollo de esta aplicación.\n¡Muchas gracias!",
|
||||
"donationDialogContent": "¡Gracias por querer apoyar esta aplicación!\nDebido a las políticas de pago, tanto de Google como de Apple, no se puede mostrar ningún enlace que lo dirija a las donaciones. En este contexto, ni siquiera es posible enlazar la página del repositorio del proyecto. Por lo tanto, puedes visitar la sección \"Donaciones\" en el archivo README de este proyecto. Tu apoyo es valorado gratamente y ayuda a mantener con vida el desarrollo de esta aplicación.\n¡Muchas gracias!",
|
||||
"@donationDialogContent": {
|
||||
"description": "Text displayed in the donation dialog"
|
||||
},
|
||||
@@ -881,11 +881,11 @@
|
||||
"@noDocumentsFound": {
|
||||
"description": "Message shown when no documents were found."
|
||||
},
|
||||
"couldNotDeleteCorrespondent": "No se pudo remover el interlocutor, intente nuevamente.",
|
||||
"couldNotDeleteCorrespondent": "No se pudo borrar el interlocutor, intente nuevamente.",
|
||||
"@couldNotDeleteCorrespondent": {
|
||||
"description": "Message shown in snackbar when a correspondent could not be deleted."
|
||||
},
|
||||
"couldNotDeleteDocumentType": "No se pudo remover el tipo de documento, intente nuevamente.",
|
||||
"couldNotDeleteDocumentType": "No se pudo borrar el tipo de documento, intente nuevamente.",
|
||||
"@couldNotDeleteDocumentType": {
|
||||
"description": "Message shown when a document type could not be deleted"
|
||||
},
|
||||
@@ -893,7 +893,7 @@
|
||||
"@couldNotDeleteTag": {
|
||||
"description": "Message shown when a tag could not be deleted"
|
||||
},
|
||||
"couldNotDeleteStoragePath": "No se pudo remover la ruta de almacenamiento, intente nuevamente.",
|
||||
"couldNotDeleteStoragePath": "No se pudo borrar la ruta de almacenamiento, intente nuevamente.",
|
||||
"@couldNotDeleteStoragePath": {
|
||||
"description": "Message shown when a storage path could not be deleted"
|
||||
},
|
||||
@@ -934,7 +934,7 @@
|
||||
"description": "Message shown when a saved view could not be updated"
|
||||
},
|
||||
"couldNotUpdateStoragePath": "No se pudo actualizar la ruta de almacenamiento, intente nuevamente.",
|
||||
"savedViewSuccessfullyUpdated": "La vista guardada se actualizó correctamente.",
|
||||
"savedViewSuccessfullyUpdated": "Vista guardada actualizada correctamente.",
|
||||
"@savedViewSuccessfullyUpdated": {
|
||||
"description": "Message shown when a saved view was successfully updated."
|
||||
},
|
||||
@@ -984,7 +984,7 @@
|
||||
"@authenticatingDots": {
|
||||
"description": "Message shown when the app is authenticating the user"
|
||||
},
|
||||
"persistingUserInformation": "Preservando información del usuario...",
|
||||
"persistingUserInformation": "Guardando información del usuario...",
|
||||
"fetchingUserInformation": "Obteniendo información del usuario...",
|
||||
"@fetchingUserInformation": {
|
||||
"description": "Message shown when the app loads user data from the server"
|
||||
@@ -1001,7 +1001,7 @@
|
||||
"@discardChangesWarning": {
|
||||
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
||||
},
|
||||
"changelog": "Changelog",
|
||||
"changelog": "Registro de cambios",
|
||||
"noLogsFoundOn": "No se encontraron registros en {date}.",
|
||||
"logfileBottomReached": "Has alcanzado el final del archivo de registro.",
|
||||
"appLogs": "Registros de la aplicación {date}",
|
||||
@@ -1010,14 +1010,19 @@
|
||||
"couldNotLoadLogfileFrom": "No se pudo cargar el archivo de registro desde {date}.",
|
||||
"loadingLogsFrom": "Cargando registros desde {date}...",
|
||||
"clearLogs": "Limpiar registros desde {date}",
|
||||
"showPdf": "Show PDF",
|
||||
"showPdf": "Mostrar PDF",
|
||||
"@showPdf": {
|
||||
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||
},
|
||||
"hidePdf": "Hide PDF",
|
||||
"hidePdf": "Ocultar PDF",
|
||||
"@hidePdf": {
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"misc": "Otros",
|
||||
"loggingOut": "Cerrando sesión...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Sonstige",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
1028
lib/l10n/intl_nl.arb
Normal file
1028
lib/l10n/intl_nl.arb
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -1019,5 +1019,10 @@
|
||||
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||
},
|
||||
"misc": "Miscellaneous",
|
||||
"loggingOut": "Logging out..."
|
||||
"loggingOut": "Logging out...",
|
||||
"testingConnection": "Testing connection...",
|
||||
"@testingConnection": {
|
||||
"description": "Text shown while the app tries to establish a connection to the specified host."
|
||||
},
|
||||
"version": "Version {versionCode}"
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/accessibility/accessible_page.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/my_bloc_observer.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
@@ -123,6 +124,7 @@ Future<void> _initHive() async {
|
||||
|
||||
void main() async {
|
||||
runZonedGuarded(() async {
|
||||
Bloc.observer = MyBlocObserver();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await FileService.instance.initialize();
|
||||
|
||||
@@ -371,6 +373,16 @@ class _GoRouterShellState extends State<GoRouterShell> {
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
return MaterialApp.router(
|
||||
builder: (context, child) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
child: child!,
|
||||
value: buildOverlayStyle(
|
||||
Theme.of(context),
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
),
|
||||
);
|
||||
},
|
||||
routerConfig: _router,
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
|
||||
@@ -53,7 +54,6 @@ class DocumentDetailsRoute extends GoRouteData {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
id: id,
|
||||
)..initialize(),
|
||||
lazy: false,
|
||||
@@ -131,9 +131,9 @@ class BulkEditDocumentsRoute extends GoRouteData {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
final labelRepository = context.read<LabelRepository>();
|
||||
return BlocProvider(
|
||||
create: (_) => DocumentBulkActionCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
selection: $extra.selection,
|
||||
@@ -144,9 +144,9 @@ class BulkEditDocumentsRoute extends GoRouteData {
|
||||
LabelType.tag => const FullscreenBulkEditTagsWidget(),
|
||||
_ => FullscreenBulkEditLabelPage(
|
||||
options: switch ($extra.type) {
|
||||
LabelType.correspondent => state.correspondents,
|
||||
LabelType.documentType => state.documentTypes,
|
||||
LabelType.storagePath => state.storagePaths,
|
||||
LabelType.correspondent => labelRepository.correspondents,
|
||||
LabelType.documentType => labelRepository.documentTypes,
|
||||
LabelType.storagePath => labelRepository.storagePaths,
|
||||
_ => throw Exception("Parameter not allowed here."),
|
||||
},
|
||||
selection: state.selection,
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
|
||||
import 'package:paperless_mobile/routing/navigation_keys.dart';
|
||||
|
||||
class LabelsBranch extends StatefulShellBranchData {
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = labelsNavigatorKey;
|
||||
const LabelsBranch();
|
||||
@@ -81,7 +82,6 @@ class LinkedDocumentsRoute extends GoRouteData {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
),
|
||||
child: const LinkedDocumentsPage(),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
enum CustomFieldDataType {
|
||||
text,
|
||||
boolean,
|
||||
date,
|
||||
url,
|
||||
integer,
|
||||
number,
|
||||
monetary;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/src/models/custom_field_data_type.dart';
|
||||
|
||||
part 'custom_field_model.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CustomFieldModel with EquatableMixin {
|
||||
final int? id;
|
||||
final String name;
|
||||
final CustomFieldDataType dataType;
|
||||
|
||||
CustomFieldModel({
|
||||
this.id,
|
||||
required this.name,
|
||||
required this.dataType,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, name, dataType];
|
||||
|
||||
factory CustomFieldModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$CustomFieldModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CustomFieldModelToJson(this);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
|
||||
import 'package:paperless_api/src/models/custom_field_model.dart';
|
||||
import 'package:paperless_api/src/models/search_hit.dart';
|
||||
|
||||
part 'document_model.g.dart';
|
||||
@@ -50,6 +51,7 @@ class DocumentModel extends Equatable {
|
||||
|
||||
// Only present if full_perms=true
|
||||
final Permissions? permissions;
|
||||
final Iterable<CustomFieldModel>? customFields;
|
||||
|
||||
const DocumentModel({
|
||||
required this.id,
|
||||
@@ -69,6 +71,7 @@ class DocumentModel extends Equatable {
|
||||
this.owner,
|
||||
this.userCanChange,
|
||||
this.permissions,
|
||||
this.customFields,
|
||||
});
|
||||
|
||||
factory DocumentModel.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -89,6 +92,8 @@ class DocumentModel extends Equatable {
|
||||
int? Function()? archiveSerialNumber,
|
||||
String? originalFileName,
|
||||
String? archivedFileName,
|
||||
int? Function()? owner,
|
||||
bool? userCanChange,
|
||||
}) {
|
||||
return DocumentModel(
|
||||
id: id,
|
||||
@@ -107,6 +112,8 @@ class DocumentModel extends Equatable {
|
||||
? archiveSerialNumber()
|
||||
: this.archiveSerialNumber,
|
||||
archivedFileName: archivedFileName ?? this.archivedFileName,
|
||||
owner: owner != null ? owner() : this.owner,
|
||||
userCanChange: userCanChange ?? this.userCanChange,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,17 +121,18 @@ class DocumentModel extends Equatable {
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
content.hashCode,
|
||||
tags,
|
||||
documentType,
|
||||
storagePath,
|
||||
content,
|
||||
correspondent,
|
||||
documentType,
|
||||
tags,
|
||||
storagePath,
|
||||
created,
|
||||
modified,
|
||||
added,
|
||||
archiveSerialNumber,
|
||||
originalFileName,
|
||||
archivedFileName,
|
||||
storagePath,
|
||||
owner,
|
||||
userCanChange,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ class PaperlessFormValidationException implements Exception {
|
||||
return validationMessages[formKey];
|
||||
}
|
||||
|
||||
static bool canParse(Map<String, dynamic> json) {
|
||||
static bool canParse(dynamic json) {
|
||||
if (json is Map<String, dynamic>) {
|
||||
return json.values
|
||||
.every((element) => element is String || element is List);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
factory PaperlessFormValidationException.fromJson(Map<String, dynamic> json) {
|
||||
final Map<String, String> validationMessages = {};
|
||||
|
||||
@@ -11,9 +11,8 @@ class PaperlessServerMessageException implements Exception {
|
||||
static bool canParse(dynamic json) {
|
||||
if (json is Map<String, dynamic>) {
|
||||
return json.containsKey('detail') && json.length == 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
factory PaperlessServerMessageException.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -68,5 +68,8 @@ enum ErrorCode {
|
||||
loadTasksError,
|
||||
userNotFound,
|
||||
userAlreadyExists,
|
||||
updateSavedViewError;
|
||||
updateSavedViewError,
|
||||
customFieldCreateFailed,
|
||||
customFieldLoadFailed,
|
||||
customFieldDeleteFailed;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@ class PaperlessServerStatisticsModel {
|
||||
: documentsTotal = json['documents_total'] ?? 0,
|
||||
documentsInInbox = json['documents_inbox'] ?? 0,
|
||||
totalChars = json["character_count"],
|
||||
fileTypeCounts = (json['document_file_type_counts'] as List? ?? [])
|
||||
.map((e) => DocumentFileTypeCount.fromJson(e))
|
||||
.toList();
|
||||
fileTypeCounts =
|
||||
_parseFileTypeCounts(json['document_file_type_counts']);
|
||||
|
||||
static List<DocumentFileTypeCount> _parseFileTypeCounts(dynamic value) {
|
||||
if (value is List) {
|
||||
return value.map((e) => DocumentFileTypeCount.fromJson(e)).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentFileTypeCount {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:paperless_api/src/models/custom_field_model.dart';
|
||||
|
||||
abstract interface class CustomFieldsApi {
|
||||
Future<CustomFieldModel> createCustomField(CustomFieldModel customField);
|
||||
Future<CustomFieldModel?> getCustomField(int id);
|
||||
Future<List<CustomFieldModel>> getCustomFields();
|
||||
Future<int> deleteCustomField(CustomFieldModel customField);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||
import 'package:paperless_api/src/models/custom_field_model.dart';
|
||||
import 'package:paperless_api/src/modules/custom_fields/custom_fields_api.dart';
|
||||
import 'package:paperless_api/src/request_utils.dart';
|
||||
|
||||
class CustomFieldsApiImpl implements CustomFieldsApi {
|
||||
final Dio _dio;
|
||||
|
||||
const CustomFieldsApiImpl(this._dio);
|
||||
|
||||
@override
|
||||
Future<CustomFieldModel> createCustomField(
|
||||
CustomFieldModel customField) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
"/api/custom_fields/",
|
||||
data: customField.toJson(),
|
||||
options: Options(
|
||||
validateStatus: (status) => status == 201,
|
||||
),
|
||||
);
|
||||
return CustomFieldModel.fromJson(response.data);
|
||||
} on DioException catch (exception) {
|
||||
throw exception.unravel(
|
||||
orElse: const PaperlessApiException(
|
||||
ErrorCode.customFieldCreateFailed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> deleteCustomField(CustomFieldModel customField) async {
|
||||
try {
|
||||
await _dio.delete(
|
||||
"/api/custom_fields/${customField.id}/",
|
||||
options: Options(
|
||||
validateStatus: (status) => status == 204,
|
||||
),
|
||||
);
|
||||
return customField.id!;
|
||||
} on DioException catch (exception) {
|
||||
throw exception.unravel(
|
||||
orElse: const PaperlessApiException(
|
||||
ErrorCode.customFieldDeleteFailed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CustomFieldModel?> getCustomField(int id) {
|
||||
return getSingleResult(
|
||||
'/api/custom_fields/$id/',
|
||||
CustomFieldModel.fromJson,
|
||||
ErrorCode.customFieldLoadFailed,
|
||||
client: _dio,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<CustomFieldModel>> getCustomFields() {
|
||||
return getCollection(
|
||||
'/api/custom_fields/?page=1&page_size=100000',
|
||||
CustomFieldModel.fromJson,
|
||||
ErrorCode.customFieldLoadFailed,
|
||||
client: _dio,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,10 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
);
|
||||
} on DioException catch (exception) {
|
||||
throw exception.unravel(
|
||||
orElse: const PaperlessApiException(ErrorCode.documentLoadFailed),
|
||||
orElse: PaperlessApiException(
|
||||
ErrorCode.documentLoadFailed,
|
||||
details: exception.message,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||
import 'package:paperless_api/src/models/models.dart';
|
||||
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
||||
import 'package:paperless_api/src/request_utils.dart';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user