diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 308ee41..3c22aad 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -4,4 +4,8 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0c93ae0..fdd3b40 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
-
+
+
+
\ No newline at end of file
diff --git a/lib/core/interceptor/dio_http_error_interceptor.dart b/lib/core/interceptor/dio_http_error_interceptor.dart
index f7f5670..0283770 100644
--- a/lib/core/interceptor/dio_http_error_interceptor.dart
+++ b/lib/core/interceptor/dio_http_error_interceptor.dart
@@ -47,7 +47,7 @@ class DioHttpErrorInterceptor extends Interceptor {
errorMessages.putIfAbsent(entry.key, () => entry.value.toString());
}
}
- return handler.reject(
+ handler.reject(
DioError(
error: errorMessages,
requestOptions: err.requestOptions,
diff --git a/lib/core/type/types.dart b/lib/core/type/types.dart
index 8130996..3ed65fa 100644
--- a/lib/core/type/types.dart
+++ b/lib/core/type/types.dart
@@ -1,2 +1,8 @@
typedef JSON = Map;
typedef PaperlessValidationErrors = Map;
+typedef PaperlessLocalizedErrorMessage = String;
+
+extension ValidationErrorsUtils on PaperlessValidationErrors {
+ bool get hasFieldUnspecificError => containsKey("non_field_errors");
+ String? get fieldUnspecificError => this['non_field_errors'];
+}
diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart
index 6e52bc4..1f0a2e5 100644
--- a/lib/features/document_upload/view/document_upload_preparation_page.dart
+++ b/lib/features/document_upload/view/document_upload_preparation_page.dart
@@ -22,6 +22,7 @@ class DocumentUploadPreparationPage extends StatefulWidget {
final Uint8List fileBytes;
final String? title;
final String? filename;
+ final String? fileExtension;
final void Function(DocumentModel)? onSuccessfullyConsumed;
const DocumentUploadPreparationPage({
@@ -30,6 +31,7 @@ class DocumentUploadPreparationPage extends StatefulWidget {
this.title,
this.filename,
this.onSuccessfullyConsumed,
+ this.fileExtension,
}) : super(key: key);
@override
@@ -119,7 +121,7 @@ class _DocumentUploadPreparationPageState
name: fkFileName,
decoration: InputDecoration(
labelText: S.of(context).documentUploadFileNameLabel,
- suffixText: ".pdf",
+ suffixText: widget.fileExtension,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkFileName]
diff --git a/lib/features/documents/bloc/documents_cubit.dart b/lib/features/documents/bloc/documents_cubit.dart
index 09ab8e8..ad4b9bc 100644
--- a/lib/features/documents/bloc/documents_cubit.dart
+++ b/lib/features/documents/bloc/documents_cubit.dart
@@ -1,13 +1,13 @@
-import 'package:equatable/equatable.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:json_annotation/json_annotation.dart';
+import 'dart:developer';
+
+import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
-class DocumentsCubit extends Cubit {
+class DocumentsCubit extends HydratedCubit {
final PaperlessDocumentsApi _api;
- DocumentsCubit(this._api) : super(DocumentsState.initial);
+ DocumentsCubit(this._api) : super(const DocumentsState());
Future bulkRemove(List documents) async {
await _api.bulkAction(
@@ -40,42 +40,85 @@ class DocumentsCubit extends Cubit {
}
Future load() async {
- final result = await _api.find(state.filter);
- emit(DocumentsState(
- isLoaded: true,
- value: [...state.value, result],
- filter: state.filter,
- ));
+ emit(state.copyWith(isLoading: true));
+ try {
+ final result = await _api.find(state.filter);
+ emit(state.copyWith(
+ isLoading: false,
+ hasLoaded: true,
+ value: [...state.value, result],
+ ));
+ } catch (err) {
+ emit(state.copyWith(isLoading: false));
+ rethrow;
+ }
}
Future reload() async {
- if (state.currentPageNumber >= 5) {
- return _bulkReloadDocuments();
+ emit(state.copyWith(isLoading: true));
+ try {
+ if (state.currentPageNumber >= 5) {
+ return _bulkReloadDocuments();
+ }
+ var newPages = >[];
+ for (final page in state.value) {
+ final result =
+ await _api.find(state.filter.copyWith(page: page.pageKey));
+ newPages.add(result);
+ }
+ emit(DocumentsState(
+ hasLoaded: true,
+ value: newPages,
+ filter: state.filter,
+ isLoading: false,
+ ));
+ } catch (err) {
+ emit(state.copyWith(isLoading: false));
+ rethrow;
}
- var newPages = [];
- for (final page in state.value) {
- final result = await _api.find(state.filter.copyWith(page: page.pageKey));
- newPages.add(result);
- }
- emit(DocumentsState(isLoaded: true, value: newPages, filter: state.filter));
}
Future _bulkReloadDocuments() async {
- final result = await _api
- .find(state.filter.copyWith(page: 1, pageSize: state.documents.length));
- emit(DocumentsState(isLoaded: true, value: [result], filter: state.filter));
+ emit(state.copyWith(isLoading: true));
+ try {
+ final result = await _api.find(
+ state.filter.copyWith(
+ page: 1,
+ pageSize: state.documents.length,
+ ),
+ );
+ emit(DocumentsState(
+ hasLoaded: true,
+ value: [result],
+ filter: state.filter,
+ isLoading: false,
+ ));
+ } catch (err) {
+ emit(state.copyWith(isLoading: false));
+ rethrow;
+ }
}
Future loadMore() async {
if (state.isLastPageLoaded) {
return;
}
+ emit(state.copyWith(isLoading: true));
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
- final result = await _api.find(newFilter);
- emit(
- DocumentsState(
- isLoaded: true, value: [...state.value, result], filter: newFilter),
- );
+ try {
+ final result = await _api.find(newFilter);
+ emit(
+ DocumentsState(
+ hasLoaded: true,
+ value: [...state.value, result],
+ filter: newFilter,
+ isLoading: false,
+ ),
+ );
+ } catch (err) {
+ emit(state.copyWith(isLoading: false));
+ rethrow;
+ }
}
///
@@ -84,8 +127,21 @@ class DocumentsCubit extends Cubit {
Future updateFilter({
final DocumentFilter filter = DocumentFilter.initial,
}) async {
- final result = await _api.find(filter.copyWith(page: 1));
- emit(DocumentsState(filter: filter, value: [result], isLoaded: true));
+ try {
+ emit(state.copyWith(isLoading: true));
+ final result = await _api.find(filter.copyWith(page: 1));
+ emit(
+ DocumentsState(
+ filter: filter,
+ value: [result],
+ hasLoaded: true,
+ isLoading: false,
+ ),
+ );
+ } catch (err) {
+ emit(state.copyWith(isLoading: false));
+ rethrow;
+ }
}
Future resetFilter() {
@@ -125,6 +181,17 @@ class DocumentsCubit extends Cubit {
}
void reset() {
- emit(DocumentsState.initial);
+ emit(const DocumentsState());
+ }
+
+ @override
+ DocumentsState? fromJson(Map json) {
+ log(json['filter'].toString());
+ return DocumentsState.fromJson(json);
+ }
+
+ @override
+ Map? toJson(DocumentsState state) {
+ return state.toJson();
}
}
diff --git a/lib/features/documents/bloc/documents_state.dart b/lib/features/documents/bloc/documents_state.dart
index 2b0e19a..e3f0f55 100644
--- a/lib/features/documents/bloc/documents_state.dart
+++ b/lib/features/documents/bloc/documents_state.dart
@@ -1,30 +1,27 @@
+import 'dart:developer';
+
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
@JsonSerializable()
class DocumentsState extends Equatable {
- final bool isLoaded;
+ final bool isLoading;
+ final bool hasLoaded;
final DocumentFilter filter;
- final List value;
+ final List> value;
@JsonKey(ignore: true)
final List selection;
const DocumentsState({
- required this.isLoaded,
- required this.value,
- required this.filter,
+ this.hasLoaded = false,
+ this.isLoading = false,
+ this.value = const [],
+ this.filter = const DocumentFilter(),
this.selection = const [],
});
- static const DocumentsState initial = DocumentsState(
- isLoaded: false,
- value: [],
- filter: DocumentFilter.initial,
- selection: [],
- );
-
int get currentPageNumber {
return filter.page;
}
@@ -41,7 +38,7 @@ class DocumentsState extends Equatable {
}
bool get isLastPageLoaded {
- if (!isLoaded) {
+ if (!hasLoaded) {
return false;
}
if (value.isNotEmpty) {
@@ -51,7 +48,7 @@ class DocumentsState extends Equatable {
}
int inferPageCount({required int pageSize}) {
- if (!isLoaded) {
+ if (!hasLoaded) {
return 100000;
}
if (value.isEmpty) {
@@ -67,13 +64,15 @@ class DocumentsState extends Equatable {
DocumentsState copyWith({
bool overwrite = false,
- bool? isLoaded,
- List? value,
+ bool? hasLoaded,
+ bool? isLoading,
+ List>? value,
DocumentFilter? filter,
List? selection,
}) {
return DocumentsState(
- isLoaded: isLoaded ?? this.isLoaded,
+ hasLoaded: hasLoaded ?? this.hasLoaded,
+ isLoading: isLoading ?? this.isLoading,
value: value ?? this.value,
filter: filter ?? this.filter,
selection: selection ?? this.selection,
@@ -81,5 +80,28 @@ class DocumentsState extends Equatable {
}
@override
- List