Implemented inbox (still WIP)

This commit is contained in:
Anton Stubenbord
2022-11-24 13:37:25 +01:00
parent 8e7a5dddbf
commit eb5025e8ca
44 changed files with 674 additions and 316 deletions

View File

@@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:flutter_bloc/flutter_bloc.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/bulk_edit.model.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/model/paged_search_result.dart';
@@ -36,9 +37,9 @@ class DocumentsCubit extends Cubit<DocumentsState> {
createdAt: createdAt,
);
// documentRepository
// .waitForConsumptionFinished(fileName, title)
// .then((value) => onConsumptionFinished(value));
documentRepository
.waitForConsumptionFinished(fileName, title)
.then((value) => onConsumptionFinished(value));
}
Future<void> removeDocument(DocumentModel document) async {
@@ -47,8 +48,23 @@ class DocumentsCubit extends Cubit<DocumentsState> {
}
Future<void> bulkRemoveDocuments(List<DocumentModel> documents) async {
await documentRepository.bulkDelete(documents);
return await reloadDocuments();
await documentRepository.bulkAction(
BulkDeleteAction(documents.map((doc) => doc.id)),
);
await reloadDocuments();
}
Future<void> bulkEditTags(
List<DocumentModel> documents, {
Iterable<int> addTags = const [],
Iterable<int> removeTags = const [],
}) async {
await documentRepository.bulkAction(BulkModifyTagsAction(
documents.map((doc) => doc.id),
addTags: addTags,
removeTags: removeTags,
));
await reloadDocuments();
}
Future<void> updateDocument(DocumentModel document) async {
@@ -135,15 +151,20 @@ class DocumentsCubit extends Cubit<DocumentsState> {
}
}
Future<void> removeInboxTags(
///
/// Updates the given document with the inbox tags removed and returns the remoed inbox tags.
///
Future<Iterable<int>> removeInboxTags(
DocumentModel document, final Iterable<int> inboxTags) async {
final updatedTags = document.tags.where((id) => !inboxTags.contains(id));
return updateDocument(
final tagsToRemove = document.tags.toSet().intersection(inboxTags.toSet());
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
await updateDocument(
document.copyWith(
tags: updatedTags,
overwriteTags: true,
),
);
return tagsToRemove;
}
void resetSelection() {

View File

@@ -1,24 +1,50 @@
import 'package:paperless_mobile/core/type/types.dart';
class BulkEditAction {
final List<int> documents;
final BulkEditActionMethod _method;
final Map<String, dynamic> parameters;
abstract class BulkAction {
final Iterable<int> documentIds;
BulkEditAction.delete(this.documents)
: _method = BulkEditActionMethod.delete,
parameters = {};
BulkAction(this.documentIds);
JSON toJson();
}
class BulkDeleteAction extends BulkAction {
BulkDeleteAction(super.documents);
@override
JSON toJson() {
return {
'documents': documents,
'method': _method.name,
'parameters': parameters,
'documents': documentIds.toList(),
'method': 'delete',
};
}
}
enum BulkEditActionMethod {
delete,
edit;
class BulkModifyTagsAction extends BulkAction {
final Iterable<int> removeTags;
final Iterable<int> addTags;
BulkModifyTagsAction(
super.documents, {
this.removeTags = const [],
this.addTags = const [],
});
BulkModifyTagsAction.addTags(super.documents, this.addTags)
: removeTags = const [];
BulkModifyTagsAction.removeTags(super.documents, this.removeTags)
: addTags = const [];
@override
JSON toJson() {
return {
'documents': documentIds.toList(),
'method': 'modify_tags',
'parameters': {
'add_tags': addTags.toList(),
'remove_tags': removeTags.toList(),
}
};
}
}

View File

@@ -17,13 +17,19 @@ class OnlyNotAssignedTagsQuery extends TagsQuery {
}
class AnyAssignedTagsQuery extends TagsQuery {
const AnyAssignedTagsQuery();
final Iterable<int> tagIds;
const AnyAssignedTagsQuery({
this.tagIds = const [],
});
@override
List<Object?> get props => [];
@override
String toQueryParameter() {
return '&is_tagged=1';
if (tagIds.isEmpty) {
return '&is_tagged=1';
}
return '&tags__id__in=${tagIds.join(',')}';
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:paperless_mobile/features/documents/model/bulk_edit.model.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/model/document_meta_data.model.dart';
@@ -23,7 +24,7 @@ abstract class DocumentRepository {
Future<List<SimilarDocumentModel>> findSimilar(int docId);
Future<int> delete(DocumentModel doc);
Future<DocumentMetaData> getMetaData(DocumentModel document);
Future<List<int>> bulkDelete(List<DocumentModel> models);
Future<Iterable<int>> bulkAction(BulkAction action);
Future<Uint8List> getPreview(int docId);
String getThumbnailUrl(int docId);
Future<DocumentModel> waitForConsumptionFinished(

View File

@@ -216,18 +216,16 @@ class DocumentRepositoryImpl implements DocumentRepository {
}
@override
Future<List<int>> bulkDelete(List<DocumentModel> documentModels) async {
final List<int> ids = documentModels.map((e) => e.id).toList();
final action = BulkEditAction.delete(ids);
Future<Iterable<int>> bulkAction(BulkAction action) async {
final response = await httpClient.post(
Uri.parse("/api/documents/bulk_edit/"),
body: json.encode(action.toJson()),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
return ids;
return action.documentIds;
} else {
throw const ErrorMessage(ErrorCode.documentBulkDeleteFailed);
throw const ErrorMessage(ErrorCode.documentBulkActionFailed);
}
}

View File

@@ -5,7 +5,8 @@ import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/features/labels/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';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
@@ -345,16 +346,20 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return const SizedBox(height: 32.0);
}
void _onEdit(DocumentModel document) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => LabelBlocProvider(
child: DocumentEditPage(document: document),
),
maintainState: true,
),
);
void _onEdit(DocumentModel document) async {
final wasUpdated = await Navigator.push<bool>(
context,
MaterialPageRoute(
builder: (_) => LabelBlocProvider(
child: DocumentEditPage(document: document),
),
maintainState: true,
),
) ??
false;
if (wasUpdated) {
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
}
}
Future<void> _onDownload(DocumentModel document) async {

View File

@@ -3,6 +3,8 @@ 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:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.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';
@@ -20,6 +22,7 @@ import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_co
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/features/labels/document_type/view/pages/add_document_type_page.dart';
import 'package:paperless_mobile/features/labels/model/label_state.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/features/labels/storage_path/view/pages/add_storage_path_page.dart';
@@ -27,8 +30,6 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.dart';
class DocumentEditPage extends StatefulWidget {
final DocumentModel document;
@@ -80,13 +81,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
setState(() {
_isSubmitLoading = true;
});
bool wasUpdated = false;
try {
await getIt<DocumentsCubit>().updateDocument(updatedDocument);
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
wasUpdated = true;
} on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
} finally {
Navigator.pop(context);
Navigator.pop(context, wasUpdated);
}
}
},
@@ -115,7 +118,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: ListView(children: [
_buildTitleFormField().padded(),
_buildCreatedAtFormField().padded(),
BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>(
BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>(
notAssignedSelectable: false,
@@ -130,7 +133,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
label: S.of(context).documentDocumentTypePropertyLabel,
initialValue:
DocumentTypeQuery.fromId(widget.document.documentType),
state: state,
state: state.labels,
name: fkDocumentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder:
@@ -139,7 +142,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
);
},
).padded(),
BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>(
BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>(
notAssignedSelectable: false,
@@ -150,7 +153,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddCorrespondentPage(initalValue: initialValue),
),
label: S.of(context).documentCorrespondentPropertyLabel,
state: state,
state: state.labels,
initialValue:
CorrespondentQuery.fromId(widget.document.correspondent),
name: fkCorrespondent,
@@ -161,7 +164,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
);
},
).padded(),
BlocBuilder<StoragePathCubit, Map<int, StoragePath>>(
BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>(
notAssignedSelectable: false,
@@ -172,7 +175,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddStoragePathPage(initalValue: initialValue),
),
label: S.of(context).documentStoragePathPropertyLabel,
state: state,
state: state.labels,
initialValue:
StoragePathQuery.fromId(widget.document.storagePath),
name: fkStoragePath,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.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/bloc/documents_state.dart';
@@ -237,6 +238,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
BlocProvider.value(
value: BlocProvider.of<StoragePathCubit>(context)),
BlocProvider.value(
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
],
child: DocumentDetailsPage(
documentId: model.id,

View File

@@ -43,7 +43,6 @@ class DocumentListItem extends StatelessWidget {
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondentId: document.correspondent,
afterSelected: () {},
),
),
],

View File

@@ -18,6 +18,7 @@ import 'package:paperless_mobile/features/labels/correspondent/bloc/corresponden
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.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/features/labels/model/label_state.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/features/labels/tags/view/widgets/tags_form_field.dart';
@@ -53,14 +54,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
final _formKey = GlobalKey<FormBuilderState>();
late final DocumentsCubit _documentsCubit;
@override
void initState() {
super.initState();
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
}
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
if (start == null && end == null) {
return null;
@@ -181,12 +174,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
}
Widget _buildDocumentTypeFormField(DocumentsState docState) {
return BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>(
return BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>(
formBuilderState: _formKey.currentState,
name: fkDocumentType,
state: state,
state: state.labels,
label: S.of(context).documentDocumentTypePropertyLabel,
initialValue: docState.filter.documentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
@@ -198,12 +191,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
}
Widget _buildCorrespondentFormField(DocumentsState docState) {
return BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>(
return BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>(
formBuilderState: _formKey.currentState,
name: fkCorrespondent,
state: state,
state: state.labels,
label: S.of(context).documentCorrespondentPropertyLabel,
initialValue: docState.filter.correspondent,
queryParameterIdBuilder: CorrespondentQuery.fromId,
@@ -215,12 +208,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
}
Widget _buildStoragePathFormField(DocumentsState docState) {
return BlocBuilder<StoragePathCubit, Map<int, StoragePath>>(
return BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>(
formBuilderState: _formKey.currentState,
name: fkStoragePath,
state: state,
state: state.labels,
label: S.of(context).documentStoragePathPropertyLabel,
initialValue: docState.filter.storagePath,
queryParameterIdBuilder: StoragePathQuery.fromId,