mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 12:07:54 -06:00
Improved error handling, added multithreading for fromJson calls, made receive sharing intent more robust
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
|
||||
///
|
||||
/// Class for handling generic errors which usually only require to inform the user via a Snackbar
|
||||
/// or similar that an error has occurred.
|
||||
///
|
||||
@singleton
|
||||
class GlobalErrorCubit extends Cubit<GlobalErrorState> {
|
||||
static const _waitBeforeNextErrorDuration = Duration(seconds: 5);
|
||||
|
||||
GlobalErrorCubit() : super(GlobalErrorState.initial);
|
||||
|
||||
///
|
||||
/// Adds a new error to this bloc. If the new error is equal to the current error, the new error
|
||||
/// will not be published unless the previous error occured over 5 seconds ago.
|
||||
///
|
||||
void add(ErrorMessage error) {
|
||||
final now = DateTime.now();
|
||||
if (error != state.error || (error == state.error && _canEmitNewError())) {
|
||||
emit(GlobalErrorState(error: error, errorTimestamp: now));
|
||||
}
|
||||
}
|
||||
|
||||
bool _canEmitNewError() {
|
||||
if (state.errorTimestamp != null) {
|
||||
return DateTime.now().difference(state.errorTimestamp!) >=
|
||||
_waitBeforeNextErrorDuration;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
emit(GlobalErrorState.initial);
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalErrorState {
|
||||
static const GlobalErrorState initial = GlobalErrorState();
|
||||
final ErrorMessage? error;
|
||||
final DateTime? errorTimestamp;
|
||||
|
||||
const GlobalErrorState({this.error, this.errorTimestamp});
|
||||
|
||||
bool get hasError => error != null;
|
||||
}
|
||||
@@ -1,75 +1,42 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/labels/model/label.model.dart';
|
||||
import 'package:paperless_mobile/features/labels/repository/label_repository.dart';
|
||||
|
||||
abstract class LabelCubit<T extends Label> extends Cubit<Map<int, T>> {
|
||||
final LabelRepository labelRepository;
|
||||
final GlobalErrorCubit errorCubit;
|
||||
|
||||
LabelCubit(this.labelRepository, this.errorCubit) : super({});
|
||||
LabelCubit(this.labelRepository) : super({});
|
||||
|
||||
@protected
|
||||
void loadFrom(Iterable<T> items) =>
|
||||
emit(Map.fromIterable(items, key: (e) => (e as T).id!));
|
||||
|
||||
Future<T> add(
|
||||
T item, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
Future<T> add(T item) async {
|
||||
assert(item.id == null);
|
||||
try {
|
||||
final addedItem = await save(item);
|
||||
final newState = {...state};
|
||||
newState.putIfAbsent(addedItem.id!, () => addedItem);
|
||||
emit(newState);
|
||||
return addedItem;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
return Future.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> replace(
|
||||
T item, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
Future<T> replace(T item) async {
|
||||
assert(item.id != null);
|
||||
try {
|
||||
final updatedItem = await update(item);
|
||||
final newState = {...state};
|
||||
newState[item.id!] = updatedItem;
|
||||
emit(newState);
|
||||
return updatedItem;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
return Future.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> remove(
|
||||
T item, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
Future<void> remove(T item) async {
|
||||
assert(item.id != null);
|
||||
if (state.containsKey(item.id)) {
|
||||
try {
|
||||
final deletedId = await delete(item);
|
||||
final newState = {...state};
|
||||
newState.remove(deletedId);
|
||||
emit(newState);
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
return Future.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
lib/core/global/constants.dart
Normal file
1
lib/core/global/constants.dart
Normal file
@@ -0,0 +1 @@
|
||||
const supportedFileExtensions = ['pdf', 'png', 'tiff', 'gif', 'jpg', 'jpeg'];
|
||||
@@ -66,5 +66,7 @@ String translateError(BuildContext context, ErrorCode code) {
|
||||
return S.of(context).errorMessageRequestTimedOut;
|
||||
case ErrorCode.unsupportedFileFormat:
|
||||
return S.of(context).errorMessageUnsupportedFileFormat;
|
||||
case ErrorCode.missingClientCertificate:
|
||||
return S.of(context).errorMessageMissingClientCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,6 @@ enum ErrorCode {
|
||||
createSavedViewError,
|
||||
deleteSavedViewError,
|
||||
requestTimedOut,
|
||||
unsupportedFileFormat;
|
||||
unsupportedFileFormat,
|
||||
missingClientCertificate;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/document_processing_status.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -89,8 +90,11 @@ class LongPollingStatusService implements StatusService {
|
||||
Uri.parse(
|
||||
'$httpUrl/api/documents/?query=$documentFileName added:${formatDate(today)}'),
|
||||
);
|
||||
final data = PagedSearchResult.fromJson(
|
||||
jsonDecode(response.body), DocumentModel.fromJson);
|
||||
final data = await compute(
|
||||
PagedSearchResult.fromJson,
|
||||
PagedSearchResultJsonSerializer(
|
||||
jsonDecode(response.body), DocumentModel.fromJson),
|
||||
);
|
||||
if (data.count > 0) {
|
||||
consumptionFinished = true;
|
||||
final docId = data.results[0].id;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:paperless_mobile/core/logic/timeout_client.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
const requestTimeout = Duration(seconds: 5);
|
||||
|
||||
@@ -23,7 +19,10 @@ Future<T> getSingleResult<T>(
|
||||
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return fromJson(jsonDecode(utf8.decode(response.bodyBytes)) as JSON);
|
||||
return compute(
|
||||
fromJson,
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as JSON,
|
||||
);
|
||||
}
|
||||
return Future.error(errorCode);
|
||||
}
|
||||
@@ -45,12 +44,25 @@ Future<List<T>> getCollection<T>(
|
||||
if (body['count'] == 0) {
|
||||
return <T>[];
|
||||
} else {
|
||||
return body['results']
|
||||
.cast<JSON>()
|
||||
.map<T>((result) => fromJson(result))
|
||||
.toList();
|
||||
return compute(
|
||||
_collectionFromJson,
|
||||
_CollectionFromJsonSerializationParams(
|
||||
fromJson, (body['results'] as List).cast<JSON>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Future.error(errorCode);
|
||||
}
|
||||
|
||||
List<T> _collectionFromJson<T>(
|
||||
_CollectionFromJsonSerializationParams<T> params) {
|
||||
return params.list.map<T>((result) => params.fromJson(result)).toList();
|
||||
}
|
||||
|
||||
class _CollectionFromJsonSerializationParams<T> {
|
||||
final T Function(JSON) fromJson;
|
||||
final List<JSON> list;
|
||||
|
||||
_CollectionFromJsonSerializationParams(this.fromJson, this.list);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
@@ -13,10 +12,8 @@ import 'package:injectable/injectable.dart';
|
||||
@singleton
|
||||
class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
final DocumentRepository documentRepository;
|
||||
final GlobalErrorCubit errorCubit;
|
||||
|
||||
DocumentsCubit(this.documentRepository, this.errorCubit)
|
||||
: super(DocumentsState.initial);
|
||||
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
||||
|
||||
Future<void> addDocument(
|
||||
Uint8List bytes,
|
||||
@@ -27,9 +24,7 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
int? correspondent,
|
||||
List<int>? tags,
|
||||
DateTime? createdAt,
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
await documentRepository.create(
|
||||
bytes,
|
||||
fileName,
|
||||
@@ -39,197 +34,89 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// documentRepository
|
||||
// .waitForConsumptionFinished(fileName, title)
|
||||
// .then((value) => onConsumptionFinished(value));
|
||||
}
|
||||
|
||||
Future<void> removeDocument(
|
||||
DocumentModel document, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> removeDocument(DocumentModel document) async {
|
||||
await documentRepository.delete(document);
|
||||
return await reloadDocuments();
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkRemoveDocuments(List<DocumentModel> documents,
|
||||
{bool propagateEventOnError = true}) async {
|
||||
try {
|
||||
Future<void> bulkRemoveDocuments(List<DocumentModel> documents) async {
|
||||
await documentRepository.bulkDelete(documents);
|
||||
return await reloadDocuments();
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateDocument(
|
||||
DocumentModel document, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> updateDocument(DocumentModel document) async {
|
||||
await documentRepository.update(document);
|
||||
await reloadDocuments();
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadDocuments({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> loadDocuments() async {
|
||||
final result = await documentRepository.find(state.filter);
|
||||
emit(DocumentsState(
|
||||
isLoaded: true,
|
||||
value: [...state.value, result],
|
||||
filter: state.filter,
|
||||
));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reloadDocuments({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
Future<void> reloadDocuments() async {
|
||||
if (state.currentPageNumber >= 5) {
|
||||
return _bulkReloadDocuments();
|
||||
}
|
||||
var newPages = <PagedSearchResult>[];
|
||||
try {
|
||||
for (final page in state.value) {
|
||||
final result = await documentRepository
|
||||
.find(state.filter.copyWith(page: page.pageKey));
|
||||
newPages.add(result);
|
||||
}
|
||||
emit(DocumentsState(
|
||||
isLoaded: true, value: newPages, filter: state.filter));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
emit(DocumentsState(isLoaded: true, value: newPages, filter: state.filter));
|
||||
}
|
||||
|
||||
Future<void> _bulkReloadDocuments({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
final result = await documentRepository.find(
|
||||
state.filter.copyWith(page: 1, pageSize: state.documents.length));
|
||||
emit(DocumentsState(
|
||||
isLoaded: true, value: [result], filter: state.filter));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
Future<void> _bulkReloadDocuments() async {
|
||||
final result = await documentRepository
|
||||
.find(state.filter.copyWith(page: 1, pageSize: state.documents.length));
|
||||
emit(DocumentsState(isLoaded: true, value: [result], filter: state.filter));
|
||||
}
|
||||
|
||||
Future<void> loadMore({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
Future<void> loadMore() async {
|
||||
if (state.isLastPageLoaded) {
|
||||
return;
|
||||
}
|
||||
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
||||
try {
|
||||
final result = await documentRepository.find(newFilter);
|
||||
emit(DocumentsState(
|
||||
isLoaded: true, value: [...state.value, result], filter: newFilter));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assignAsn(
|
||||
DocumentModel document, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> assignAsn(DocumentModel document) async {
|
||||
if (document.archiveSerialNumber == null) {
|
||||
final int asn = await documentRepository.findNextAsn();
|
||||
updateDocument(document.copyWith(archiveSerialNumber: asn));
|
||||
}
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Update filter state and automatically reload documents. Always resets page to 1.
|
||||
/// Use [DocumentsCubit.loadMore] to load more data.
|
||||
Future<void> updateFilter(
|
||||
{final DocumentFilter filter = DocumentFilter.initial,
|
||||
bool propagateEventOnError = true}) async {
|
||||
try {
|
||||
Future<void> updateFilter({
|
||||
final DocumentFilter filter = DocumentFilter.initial,
|
||||
}) async {
|
||||
final result = await documentRepository.find(filter.copyWith(page: 1));
|
||||
emit(DocumentsState(filter: filter, value: [result], isLoaded: true));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
|
||||
///
|
||||
Future<void> updateCurrentFilter(
|
||||
final DocumentFilter Function(DocumentFilter) transformFn, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
return updateFilter(filter: transformFn(state.filter));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
return errorCubit.add(error);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
final DocumentFilter Function(DocumentFilter) transformFn,
|
||||
) async =>
|
||||
updateFilter(filter: transformFn(state.filter));
|
||||
|
||||
void toggleDocumentSelection(DocumentModel model) {
|
||||
if (state.selection.contains(model)) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_state.dart';
|
||||
@@ -9,25 +8,13 @@ import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
final GlobalErrorCubit errorCubit;
|
||||
SavedViewCubit(this.errorCubit) : super(SavedViewState(value: {}));
|
||||
SavedViewCubit() : super(SavedViewState(value: {}));
|
||||
|
||||
void selectView(SavedView? view, {bool propagateEventOnError = true}) {
|
||||
try {
|
||||
void selectView(SavedView? view) {
|
||||
emit(SavedViewState(value: state.value, selectedSavedViewId: view?.id));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SavedView> add(
|
||||
SavedView view, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<SavedView> add(SavedView view) async {
|
||||
final savedView = await getIt<SavedViewsRepository>().save(view);
|
||||
emit(
|
||||
SavedViewState(
|
||||
@@ -36,19 +23,9 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
),
|
||||
);
|
||||
return savedView;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> remove(
|
||||
SavedView view, {
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<int> remove(SavedView view) async {
|
||||
final id = await getIt<SavedViewsRepository>().delete(view);
|
||||
final newValue = {...state.value};
|
||||
newValue.removeWhere((key, value) => key == id);
|
||||
@@ -61,27 +38,12 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
),
|
||||
);
|
||||
return id;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initialize({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> initialize() async {
|
||||
final views = await getIt<SavedViewsRepository>().getAll();
|
||||
final values = {for (var element in views) element.id!: element};
|
||||
emit(SavedViewState(value: values));
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void resetSelection() {
|
||||
|
||||
@@ -4,6 +4,13 @@ import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
|
||||
const pageRegex = r".*page=(\d+).*";
|
||||
|
||||
class PagedSearchResultJsonSerializer<T> {
|
||||
final JSON json;
|
||||
final T Function(JSON) fromJson;
|
||||
|
||||
PagedSearchResultJsonSerializer(this.json, this.fromJson);
|
||||
}
|
||||
|
||||
class PagedSearchResult<T> extends Equatable {
|
||||
/// Total number of available items
|
||||
final int count;
|
||||
@@ -46,12 +53,14 @@ class PagedSearchResult<T> extends Equatable {
|
||||
});
|
||||
|
||||
factory PagedSearchResult.fromJson(
|
||||
Map<dynamic, dynamic> json, T Function(JSON) fromJson) {
|
||||
PagedSearchResultJsonSerializer<T> serializer) {
|
||||
return PagedSearchResult(
|
||||
count: json['count'],
|
||||
next: json['next'],
|
||||
previous: json['previous'],
|
||||
results: List<JSON>.from(json['results']).map<T>(fromJson).toList(),
|
||||
count: serializer.json['count'],
|
||||
next: serializer.json['next'],
|
||||
previous: serializer.json['previous'],
|
||||
results: List<JSON>.from(serializer.json['results'])
|
||||
.map<T>(serializer.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,8 +138,10 @@ class DocumentRepositoryImpl implements DocumentRepository {
|
||||
body: json.encode(doc.toJson()),
|
||||
headers: {"Content-Type": "application/json"}).timeout(requestTimeout);
|
||||
if (response.statusCode == 200) {
|
||||
return DocumentModel.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return compute(
|
||||
DocumentModel.fromJson,
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as JSON,
|
||||
);
|
||||
} else {
|
||||
throw const ErrorMessage(ErrorCode.documentUpdateFailed);
|
||||
}
|
||||
@@ -152,11 +154,13 @@ class DocumentRepositoryImpl implements DocumentRepository {
|
||||
Uri.parse("/api/documents/?$filterParams"),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final searchResult = PagedSearchResult.fromJson(
|
||||
return compute(
|
||||
PagedSearchResult.fromJson,
|
||||
PagedSearchResultJsonSerializer<DocumentModel>(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
DocumentModel.fromJson,
|
||||
),
|
||||
);
|
||||
return searchResult;
|
||||
} else {
|
||||
throw const ErrorMessage(ErrorCode.documentLoadFailed);
|
||||
}
|
||||
@@ -261,8 +265,10 @@ class DocumentRepositoryImpl implements DocumentRepository {
|
||||
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
|
||||
final response = await httpClient
|
||||
.get(Uri.parse("/api/documents/${document.id}/metadata/"));
|
||||
return DocumentMetaData.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
return compute(
|
||||
DocumentMetaData.fromJson,
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as JSON,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -280,10 +286,14 @@ class DocumentRepositoryImpl implements DocumentRepository {
|
||||
final response = await httpClient
|
||||
.get(Uri.parse("/api/documents/?more_like=$docId&pageSize=10"));
|
||||
if (response.statusCode == 200) {
|
||||
return PagedSearchResult<SimilarDocumentModel>.fromJson(
|
||||
return (await compute(
|
||||
PagedSearchResult<SimilarDocumentModel>.fromJson,
|
||||
PagedSearchResultJsonSerializer(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)),
|
||||
SimilarDocumentModel.fromJson,
|
||||
).results;
|
||||
),
|
||||
))
|
||||
.results;
|
||||
}
|
||||
throw const ErrorMessage(ErrorCode.similarQueryError);
|
||||
}
|
||||
|
||||
@@ -198,10 +198,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: Text(S
|
||||
.of(context)
|
||||
.documentDetailsPageAssignAsnButtonLabel),
|
||||
onPressed: widget.allowEdit
|
||||
? () => BlocProvider.of<DocumentsCubit>(context)
|
||||
.assignAsn(document)
|
||||
: null,
|
||||
onPressed:
|
||||
widget.allowEdit ? () => _assignAsn(document) : null,
|
||||
),
|
||||
),
|
||||
_separator(),
|
||||
@@ -233,6 +231,14 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _assignAsn(DocumentModel document) async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context).assignAsn(document);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDocumentContentView(DocumentModel document, String? match) {
|
||||
return SingleChildScrollView(
|
||||
child: _DetailsItem(
|
||||
@@ -392,21 +398,23 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(DocumentModel document) async {
|
||||
showDialog(
|
||||
void _onDelete(DocumentModel document) async {
|
||||
final delete = await showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
DeleteDocumentConfirmationDialog(document: document))
|
||||
.then((delete) {
|
||||
if (delete ?? false) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.removeDocument(document)
|
||||
.then((value) {
|
||||
Navigator.pop(context);
|
||||
DeleteDocumentConfirmationDialog(document: document),
|
||||
) ??
|
||||
false;
|
||||
if (delete) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context).removeDocument(document);
|
||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||
});
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
} finally {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onOpen(DocumentModel document) async {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
@@ -76,10 +77,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
setState(() {
|
||||
_isSubmitLoading = true;
|
||||
});
|
||||
try {
|
||||
await getIt<DocumentsCubit>().updateDocument(updatedDocument);
|
||||
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
} finally {
|
||||
Navigator.pop(context);
|
||||
showSnackBar(
|
||||
context, "Document successfully updated."); //TODO: INTL
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
|
||||
@@ -44,13 +44,20 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (!documentsCubit.state.isLoaded) {
|
||||
documentsCubit.loadDocuments();
|
||||
if (!BlocProvider.of<DocumentsCubit>(context).state.isLoaded) {
|
||||
_initDocuments();
|
||||
}
|
||||
_pagingController.addPageRequestListener(_loadNewPage);
|
||||
}
|
||||
|
||||
Future<void> _initDocuments() async {
|
||||
try {
|
||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pagingController.dispose();
|
||||
@@ -64,17 +71,25 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
documentsCubit.loadMore();
|
||||
try {
|
||||
await documentsCubit.loadMore();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(DocumentModel model) {
|
||||
BlocProvider.of<DocumentsCubit>(context).toggleDocumentSelection(model);
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() {
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
return documentsCubit.updateFilter(
|
||||
filter: documentsCubit.state.filter.copyWith(page: 1));
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -86,9 +101,9 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
_panelController.close();
|
||||
return false;
|
||||
}
|
||||
final docBloc = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (docBloc.state.selection.isNotEmpty) {
|
||||
docBloc.resetSelection();
|
||||
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (documentsCubit.state.selection.isNotEmpty) {
|
||||
documentsCubit.resetSelection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -513,7 +513,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onApplyFilter() {
|
||||
void _onApplyFilter() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final v = _formKey.currentState!.value;
|
||||
final docCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
@@ -530,13 +530,15 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
||||
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
||||
);
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: newFilter)
|
||||
.then((value) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: newFilter);
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.panelController.close();
|
||||
});
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,13 +75,21 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
void _onDelete(BuildContext context, DocumentsState documentsState) async {
|
||||
final shouldDelete = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => BulkDeleteConfirmationDialog(state: documentsState),
|
||||
builder: (context) =>
|
||||
BulkDeleteConfirmationDialog(state: documentsState),
|
||||
) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkRemoveDocuments(documentsState.selection);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
||||
);
|
||||
if (shouldDelete ?? false) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkRemoveDocuments(documentsState.selection)
|
||||
.then((_) => showSnackBar(
|
||||
context, S.of(context).documentsPageBulkDeleteSuccessfulText));
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,15 +78,22 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
final newView = await Navigator.of(context).push<SavedView?>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddSavedViewPage(
|
||||
currentFilter: getIt<DocumentsCubit>().state.filter),
|
||||
currentFilter: getIt<DocumentsCubit>().state.filter,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (newView != null) {
|
||||
BlocProvider.of<SavedViewCubit>(context).add(newView);
|
||||
try {
|
||||
await BlocProvider.of<SavedViewCubit>(context).add(newView);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(bool isSelected, BuildContext context, SavedView view) {
|
||||
void _onSelected(
|
||||
bool isSelected, BuildContext context, SavedView view) async {
|
||||
try {
|
||||
if (isSelected) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: view.toDocumentFilter());
|
||||
@@ -95,6 +102,9 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).selectView(null);
|
||||
}
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
void _onDelete(BuildContext context, SavedView view) async {
|
||||
@@ -105,7 +115,11 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
) ??
|
||||
false;
|
||||
if (delete) {
|
||||
try {
|
||||
BlocProvider.of<SavedViewCubit>(context).remove(view);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,18 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() => _isLoading = true);
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(
|
||||
filter: state.filter
|
||||
.copyWith(sortOrder: state.filter.sortOrder.toggle()))
|
||||
.whenComplete(() => setState(() => _isLoading = false));
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortOrder: state.filter.sortOrder.toggle(),
|
||||
),
|
||||
);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
@@ -74,10 +74,14 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
|
||||
initializeLabelData(BuildContext context) {
|
||||
try {
|
||||
BlocProvider.of<DocumentTypeCubit>(context).initialize();
|
||||
BlocProvider.of<CorrespondentCubit>(context).initialize();
|
||||
BlocProvider.of<TagCubit>(context).initialize();
|
||||
BlocProvider.of<StoragePathCubit>(context).initialize();
|
||||
BlocProvider.of<SavedViewCubit>(context).initialize();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
@@ -129,14 +127,16 @@ class InfoDrawer extends StatelessWidget {
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||
onTap: () {
|
||||
// Clear all bloc data
|
||||
try {
|
||||
BlocProvider.of<AuthenticationCubit>(context).logout();
|
||||
getIt<DocumentsCubit>().reset();
|
||||
getIt<CorrespondentCubit>().reset();
|
||||
getIt<DocumentTypeCubit>().reset();
|
||||
getIt<TagCubit>().reset();
|
||||
getIt<DocumentScannerCubit>().reset();
|
||||
getIt<GlobalErrorCubit>().reset();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class CorrespondentCubit extends LabelCubit<Correspondent> {
|
||||
CorrespondentCubit(super.metaDataService, super.errorCubit);
|
||||
CorrespondentCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
|
||||
@@ -18,21 +18,28 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
return EditLabelPage<Correspondent>(
|
||||
label: correspondent,
|
||||
onSubmit: BlocProvider.of<CorrespondentCubit>(context).replace,
|
||||
onDelete: (correspondent) => _onDelete(correspondent, context),
|
||||
onDelete: (correspondent) => _onDelete(context, correspondent),
|
||||
fromJson: Correspondent.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(
|
||||
Correspondent correspondent, BuildContext context) async {
|
||||
BuildContext context,
|
||||
Correspondent correspondent,
|
||||
) async {
|
||||
try {
|
||||
await BlocProvider.of<CorrespondentCubit>(context).remove(correspondent);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.correspondent.id == correspondent.id) {
|
||||
cubit.updateFilter(
|
||||
filter: cubit.state.filter
|
||||
.copyWith(correspondent: const CorrespondentQuery.unset()),
|
||||
await cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
correspondent: const CorrespondentQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class CorrespondentWidget extends StatelessWidget {
|
||||
final int? correspondentId;
|
||||
@@ -44,6 +46,7 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
|
||||
void _addCorrespondentToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == correspondentId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
@@ -56,5 +59,8 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class DocumentTypeCubit extends LabelCubit<DocumentType> {
|
||||
DocumentTypeCubit(super.metaDataService, super.errorCubit);
|
||||
DocumentTypeCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentTypeWidget extends StatelessWidget {
|
||||
final int? documentTypeId;
|
||||
@@ -39,6 +41,7 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
|
||||
void _addDocumentTypeToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.documentType.id == documentTypeId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
@@ -51,5 +54,8 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:paperless_mobile/features/labels/storage_path/model/storage_path
|
||||
|
||||
@singleton
|
||||
class StoragePathCubit extends LabelCubit<StoragePath> {
|
||||
StoragePathCubit(super.metaDataService, super.errorCubit);
|
||||
StoragePathCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
|
||||
@@ -32,13 +32,19 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _onDelete(StoragePath path, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<StoragePathCubit>(context).remove(path);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.storagePath.id == path.id) {
|
||||
cubit.updateFilter(
|
||||
filter: cubit.state.filter
|
||||
.copyWith(storagePath: const StoragePathQuery.unset()));
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
storagePath: const StoragePathQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class StoragePathWidget extends StatelessWidget {
|
||||
final int? pathId;
|
||||
@@ -43,6 +45,7 @@ class StoragePathWidget extends StatelessWidget {
|
||||
|
||||
void _addStoragePathToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.correspondent.id == pathId) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) =>
|
||||
@@ -55,5 +58,8 @@ class StoragePathWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
afterSelected?.call();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class TagCubit extends LabelCubit<Tag> {
|
||||
TagCubit(super.metaDataService, super.errorCubit);
|
||||
TagCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
|
||||
@@ -43,6 +43,7 @@ class EditTagPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _onDelete(Tag tag, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<TagCubit>(context).remove(tag);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
final currentFilter = cubit.state.filter;
|
||||
@@ -56,5 +57,8 @@ class EditTagPage extends StatelessWidget {
|
||||
}
|
||||
cubit.updateFilter(filter: updatedFilter);
|
||||
Navigator.pop(context);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,13 @@ class TagWidget extends StatelessWidget {
|
||||
|
||||
void _addTagToFilter(BuildContext context) {
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (cubit.state.filter.tags.ids.contains(tag.id)) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
tags: TagsQuery.fromIds(
|
||||
cubit.state.filter.tags.ids.where((id) => id != tag.id).toList()),
|
||||
tags: TagsQuery.fromIds(cubit.state.filter.tags.ids
|
||||
.where((id) => id != tag.id)
|
||||
.toList()),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -55,5 +57,8 @@ class TagWidget extends StatelessWidget {
|
||||
if (afterTagTapped != null) {
|
||||
afterTagTapped!();
|
||||
}
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/model/matching_algorithm.dart';
|
||||
import 'package:paperless_mobile/features/labels/model/label.model.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditLabelPage<T extends Label> extends StatefulWidget {
|
||||
final T label;
|
||||
@@ -144,6 +146,8 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
Navigator.pop(context);
|
||||
} on PaperlessValidationErrors catch (errorMessages) {
|
||||
setState(() => _errors = errorMessages);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
@@ -53,9 +52,8 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LabelBlocProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentsCubit(
|
||||
getIt<DocumentRepository>(),
|
||||
getIt<GlobalErrorCubit>())
|
||||
create: (context) =>
|
||||
DocumentsCubit(getIt<DocumentRepository>())
|
||||
..updateFilter(filter: filter),
|
||||
child: LinkedDocumentsPreview(filter: filter),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -17,13 +17,11 @@ const authenticationKey = "authentication";
|
||||
@singleton
|
||||
class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final LocalVault localStore;
|
||||
final GlobalErrorCubit errorCubit;
|
||||
final AuthenticationService authenticationService;
|
||||
|
||||
AuthenticationCubit(
|
||||
this.localStore,
|
||||
this.authenticationService,
|
||||
this.errorCubit,
|
||||
) : super(AuthenticationState.initial);
|
||||
|
||||
Future<void> initialize() {
|
||||
@@ -34,7 +32,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
required UserCredentials credentials,
|
||||
required String serverUrl,
|
||||
ClientCertificate? clientCertificate,
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
assert(credentials.username != null && credentials.password != null);
|
||||
try {
|
||||
@@ -75,40 +72,23 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
} on TlsException catch (_) {
|
||||
const error =
|
||||
ErrorMessage(ErrorCode.invalidClientCertificateConfiguration);
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
throw error;
|
||||
} on SocketException catch (err) {
|
||||
late ErrorMessage error;
|
||||
if (err.message.contains("connection timed out")) {
|
||||
error = const ErrorMessage(ErrorCode.requestTimedOut);
|
||||
throw const ErrorMessage(ErrorCode.requestTimedOut);
|
||||
} else {
|
||||
error = ErrorMessage.unknown();
|
||||
throw ErrorMessage.unknown();
|
||||
}
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreSessionState({
|
||||
bool propagateEventOnError = true,
|
||||
}) async {
|
||||
try {
|
||||
Future<void> restoreSessionState() async {
|
||||
final storedAuth = await localStore.loadAuthenticationInformation();
|
||||
final appSettings = await localStore.loadApplicationSettings() ??
|
||||
ApplicationSettingsState.defaultSettings;
|
||||
|
||||
if (storedAuth == null || !storedAuth.isValid) {
|
||||
emit(
|
||||
AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
} else {
|
||||
if (!appSettings.isLocalAuthenticationEnabled ||
|
||||
await authenticationService
|
||||
@@ -122,13 +102,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(AuthenticationState(
|
||||
isAuthenticated: false, wasLoginStored: true));
|
||||
}
|
||||
}
|
||||
} on ErrorMessage catch (error) {
|
||||
if (propagateEventOnError) {
|
||||
errorCubit.add(error);
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,19 @@ class AuthenticationService {
|
||||
required String password,
|
||||
required String serverUrl,
|
||||
}) async {
|
||||
final response = await httpClient.post(
|
||||
late Response response;
|
||||
try {
|
||||
response = await httpClient.post(
|
||||
Uri.parse("/api/token/"),
|
||||
body: {"username": username, "password": password},
|
||||
);
|
||||
} on FormatException catch (e) {
|
||||
final source = e.source;
|
||||
if (source is String &&
|
||||
source.contains("400 No required SSL certificate was sent")) {
|
||||
throw const ErrorMessage(ErrorCode.missingClientCertificate);
|
||||
}
|
||||
}
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(utf8.decode(response.bodyBytes));
|
||||
return data['token'];
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
@@ -72,7 +74,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
return ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Theme.of(context).colorScheme.primaryContainer),
|
||||
Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
elevation: const MaterialStatePropertyAll(0),
|
||||
),
|
||||
onPressed: _login,
|
||||
@@ -82,19 +85,25 @@ class _LoginPageState extends State<LoginPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _login() {
|
||||
void _login() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
setState(() => _isLoginLoading = true);
|
||||
final form = _formKey.currentState?.value;
|
||||
BlocProvider.of<AuthenticationCubit>(context)
|
||||
.login(
|
||||
credentials: form?[UserCredentialsFormField.fkCredentials],
|
||||
serverUrl: form?[ServerAddressFormField.fkServerAddress],
|
||||
final form = _formKey.currentState!.value;
|
||||
try {
|
||||
await BlocProvider.of<AuthenticationCubit>(context).login(
|
||||
credentials: form[UserCredentialsFormField.fkCredentials],
|
||||
serverUrl: form[ServerAddressFormField.fkServerAddress],
|
||||
clientCertificate:
|
||||
form?[ClientCertificateFormField.fkClientCertificate],
|
||||
)
|
||||
.whenComplete(() => setState(() => _isLoginLoading = false));
|
||||
form[ClientCertificateFormField.fkClientCertificate],
|
||||
);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
} catch (unknownError) {
|
||||
showSnackBar(context, unknownError.toString());
|
||||
} finally {
|
||||
setState(() => _isLoginLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,19 +22,22 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
scans.removeAt(fileIndex);
|
||||
emit(scans);
|
||||
} catch (_) {
|
||||
addError(const ErrorMessage(ErrorCode.scanRemoveFailed));
|
||||
throw const ErrorMessage(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
try {
|
||||
for (final doc in state) {
|
||||
doc.deleteSync();
|
||||
if (kDebugMode) {
|
||||
log('[ScannerCubit]: Removed ${doc.path}');
|
||||
}
|
||||
}
|
||||
|
||||
imageCache.clear();
|
||||
emit(initialState);
|
||||
} catch (_) {
|
||||
throw const ErrorMessage(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,51 +185,32 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
_formKey.currentState?.save();
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
try {
|
||||
setState(() {
|
||||
_isUploadLoading = true;
|
||||
});
|
||||
setState(() => _isUploadLoading = true);
|
||||
|
||||
final fv = _formKey.currentState!.value;
|
||||
|
||||
final createdAt = fv[DocumentModel.createdKey] as DateTime?;
|
||||
final title = fv[DocumentModel.titleKey] as String;
|
||||
final docType = fv[DocumentModel.documentTypeKey] as IdQueryParameter;
|
||||
final tags = fv[DocumentModel.tagsKey] as TagsQuery;
|
||||
final correspondent =
|
||||
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
||||
|
||||
await BlocProvider.of<DocumentsCubit>(context).addDocument(
|
||||
widget.fileBytes,
|
||||
_formKey.currentState?.value[fkFileName],
|
||||
onConsumptionFinished: (document) {
|
||||
ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar(
|
||||
SnackBar(
|
||||
action: SnackBarAction(
|
||||
onPressed: () {
|
||||
getIt<DocumentsCubit>().reloadDocuments();
|
||||
},
|
||||
label: S
|
||||
.of(context)
|
||||
.documentUploadProcessingSuccessfulReloadActionText,
|
||||
),
|
||||
content:
|
||||
Text(S.of(context).documentUploadProcessingSuccessfulText),
|
||||
),
|
||||
onConsumptionFinished: _onConsumptionFinished,
|
||||
title: title,
|
||||
documentType: docType.id,
|
||||
correspondent: correspondent.id,
|
||||
tags: tags.ids,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
},
|
||||
title: _formKey.currentState?.value[DocumentModel.titleKey],
|
||||
documentType: (_formKey.currentState
|
||||
?.value[DocumentModel.documentTypeKey] as IdQueryParameter)
|
||||
.id,
|
||||
correspondent: (_formKey.currentState
|
||||
?.value[DocumentModel.correspondentKey] as IdQueryParameter)
|
||||
.id,
|
||||
tags:
|
||||
(_formKey.currentState?.value[DocumentModel.tagsKey] as TagsQuery)
|
||||
.ids,
|
||||
createdAt: (_formKey.currentState?.value[DocumentModel.createdKey]
|
||||
as DateTime?),
|
||||
propagateEventOnError: false,
|
||||
);
|
||||
setState(() {
|
||||
_isUploadLoading = false;
|
||||
});
|
||||
getIt<DocumentScannerCubit>().reset();
|
||||
Navigator.pop(context);
|
||||
getIt<DocumentScannerCubit>().reset(); //TODO: Access via provider
|
||||
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
||||
Navigator.pop(context);
|
||||
widget.afterUpload?.call();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
@@ -239,9 +220,28 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
showSnackBar(context, other.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isUploadLoading = true;
|
||||
_isUploadLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onConsumptionFinished(document) {
|
||||
ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar(
|
||||
SnackBar(
|
||||
action: SnackBarAction(
|
||||
onPressed: () async {
|
||||
try {
|
||||
getIt<DocumentsCubit>().reloadDocuments();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
},
|
||||
label:
|
||||
S.of(context).documentUploadProcessingSuccessfulReloadActionText,
|
||||
),
|
||||
content: Text(S.of(context).documentUploadProcessingSuccessfulText),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -19,6 +19,7 @@ import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'
|
||||
import 'package:paperless_mobile/features/scan/view/document_upload_page.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
@@ -32,15 +33,6 @@ class ScannerPage extends StatefulWidget {
|
||||
|
||||
class _ScannerPageState extends State<ScannerPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const _supportedExtensions = [
|
||||
'pdf',
|
||||
'png',
|
||||
'tiff',
|
||||
'gif',
|
||||
'jpg',
|
||||
'jpeg'
|
||||
];
|
||||
|
||||
late final AnimationController _fabPulsingController;
|
||||
late final Animation _animation;
|
||||
|
||||
@@ -205,8 +197,14 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
itemBuilder: (context, index) {
|
||||
return GridImageItemWidget(
|
||||
file: scans[index],
|
||||
onDelete: () => BlocProvider.of<DocumentScannerCubit>(context)
|
||||
.removeScan(index),
|
||||
onDelete: () async {
|
||||
try {
|
||||
BlocProvider.of<DocumentScannerCubit>(context)
|
||||
.removeScan(index);
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
},
|
||||
index: index,
|
||||
totalNumberOfFiles: scans.length,
|
||||
);
|
||||
@@ -214,7 +212,11 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
}
|
||||
|
||||
void _reset(BuildContext context) {
|
||||
try {
|
||||
BlocProvider.of<DocumentScannerCubit>(context).reset();
|
||||
} on ErrorMessage catch (error) {
|
||||
showError(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _requestCameraPermissions() async {
|
||||
@@ -227,15 +229,14 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
void _onUploadFromFilesystem() async {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: _supportedExtensions,
|
||||
allowedExtensions: supportedFileExtensions,
|
||||
withData: true,
|
||||
);
|
||||
if (result?.files.single.path != null) {
|
||||
File file = File(result!.files.single.path!);
|
||||
if (!_supportedExtensions.contains(file.path.split('.').last)) {
|
||||
return getIt<GlobalErrorCubit>().add(
|
||||
const ErrorMessage(ErrorCode.unsupportedFileFormat),
|
||||
);
|
||||
if (!supportedFileExtensions.contains(file.path.split('.').last)) {
|
||||
//TODO: Show error message;
|
||||
return;
|
||||
}
|
||||
final mimeType = lookupMimeType(file.path) ?? '';
|
||||
late Uint8List fileBytes;
|
||||
|
||||
@@ -187,5 +187,7 @@
|
||||
"editLabelPageConfirmDeletionDialogTitle": "Löschen bestätigen",
|
||||
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
||||
"settingsPageStorageSettingsLabel": "Storage",
|
||||
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space"
|
||||
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space",
|
||||
"documentUpdateErrorMessage": "Document successfully updated.",
|
||||
"errorMessageMissingClientCertificate": "Ein Client Zerfitikat wurde erwartet, aber nicht gesendet. Bitte konfiguriere ein gültiges Zertifikat."
|
||||
}
|
||||
@@ -188,5 +188,7 @@
|
||||
"editLabelPageConfirmDeletionDialogTitle": "Confirm deletion",
|
||||
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
||||
"settingsPageStorageSettingsLabel": "Storage",
|
||||
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space"
|
||||
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space",
|
||||
"documentUpdateErrorMessage": "Document successfully updated.",
|
||||
"errorMessageMissingClientCertificate": "A client certificate was expected but not sent. Please provide a valid client certificate."
|
||||
}
|
||||
@@ -9,11 +9,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/global/http_self_signed_certificate_override.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/util.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -148,51 +149,46 @@ class AuthenticationWrapper extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
bool isFileTypeSupported(SharedMediaFile file) {
|
||||
return supportedFileExtensions.contains(file.path.split('.').last);
|
||||
}
|
||||
|
||||
void handleReceivedFiles(List<SharedMediaFile> files) async {
|
||||
if (files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (!isFileTypeSupported(files.first)) {
|
||||
showError(context, const ErrorMessage(ErrorCode.unsupportedFileFormat));
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 2),
|
||||
() => SystemNavigator.pop(),
|
||||
);
|
||||
}
|
||||
final bytes = File(files.first.path).readAsBytesSync();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: () => SystemNavigator.pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// For sharing files coming from outside the app while the app is still opened
|
||||
ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
|
||||
final bytes = File(value.first.path).readAsBytesSync();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: () => SystemNavigator.pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, onError: (err) {
|
||||
log(err);
|
||||
});
|
||||
ReceiveSharingIntent.getMediaStream().listen(handleReceivedFiles);
|
||||
|
||||
// For sharing files coming from outside the app while the app is closed
|
||||
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
||||
if (value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final bytes = File(value.first.path).readAsBytesSync();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: () => SystemNavigator.pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
ReceiveSharingIntent.getInitialMedia().then(handleReceivedFiles);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -206,15 +202,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: getIt<GlobalErrorCubit>(),
|
||||
child: BlocListener<GlobalErrorCubit, GlobalErrorState>(
|
||||
listener: (context, state) {
|
||||
if (state.hasError) {
|
||||
showSnackBar(context, translateError(context, state.error!.code));
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
return SafeArea(
|
||||
top: true,
|
||||
left: false,
|
||||
right: false,
|
||||
@@ -246,8 +234,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:paperless_mobile/core/bloc/global_error_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
@@ -15,32 +14,32 @@ import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
@GenerateNiceMocks([MockSpec<DocumentRepository>()])
|
||||
@GenerateNiceMocks([MockSpec<GlobalErrorCubit>()])
|
||||
import 'document_cubit_test.mocks.dart';
|
||||
|
||||
void main() async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final List<DocumentModel> documents = List.unmodifiable(
|
||||
await loadCollection("test/fixtures/documents/documents.json", DocumentModel.fromJson),
|
||||
await loadCollection(
|
||||
"test/fixtures/documents/documents.json", DocumentModel.fromJson),
|
||||
);
|
||||
final List<Tag> tags = List.unmodifiable(
|
||||
await loadCollection("test/fixtures/tags/tags.json", Tag.fromJson),
|
||||
);
|
||||
final List<Correspondent> correspondents = List.unmodifiable(
|
||||
await loadCollection(
|
||||
"test/fixtures/correspondents/correspondents.json", Correspondent.fromJson),
|
||||
await loadCollection("test/fixtures/correspondents/correspondents.json",
|
||||
Correspondent.fromJson),
|
||||
);
|
||||
final List<DocumentType> documentTypes = List.unmodifiable(
|
||||
await loadCollection("test/fixtures/document_types/document_types.json", DocumentType.fromJson),
|
||||
await loadCollection("test/fixtures/document_types/document_types.json",
|
||||
DocumentType.fromJson),
|
||||
);
|
||||
|
||||
final MockDocumentRepository documentRepository = MockDocumentRepository();
|
||||
final MockGlobalErrorCubit globalErrorCubit = MockGlobalErrorCubit();
|
||||
|
||||
group("Test DocumentsCubit reloadDocuments", () {
|
||||
test("Assert correct initial state", () {
|
||||
expect(DocumentsCubit(documentRepository, globalErrorCubit).state, DocumentsState.initial);
|
||||
expect(DocumentsCubit(documentRepository).state, DocumentsState.initial);
|
||||
});
|
||||
|
||||
blocTest<DocumentsCubit, DocumentsState>(
|
||||
@@ -53,7 +52,7 @@ void main() async {
|
||||
results: documents,
|
||||
),
|
||||
),
|
||||
build: () => DocumentsCubit(documentRepository, globalErrorCubit),
|
||||
build: () => DocumentsCubit(documentRepository),
|
||||
seed: () => DocumentsState.initial,
|
||||
act: (bloc) => bloc.loadDocuments(),
|
||||
expect: () => [
|
||||
@@ -82,7 +81,7 @@ void main() async {
|
||||
results: documents,
|
||||
),
|
||||
),
|
||||
build: () => DocumentsCubit(documentRepository, globalErrorCubit),
|
||||
build: () => DocumentsCubit(documentRepository),
|
||||
seed: () => DocumentsState.initial,
|
||||
act: (bloc) => bloc.loadDocuments(),
|
||||
expect: () => [
|
||||
|
||||
Reference in New Issue
Block a user