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