From 070a57aafd67ff6431e93da3dfed6df4bbfe9722 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Fri, 18 Nov 2022 00:59:14 +0100 Subject: [PATCH] Added new query options for tags, added pdf preview on documents scanner page --- .../model/document_processing_status.dart | 2 +- lib/core/service/status.service.dart | 4 +- lib/extensions/dart_extensions.dart | 15 ++ .../documents/bloc/documents_cubit.dart | 2 +- .../documents/model/bulk_edit.model.dart | 11 +- .../documents/model/document.model.dart | 39 ++--- .../documents/model/document_filter.dart | 2 +- .../documents/model/filter_rule.model.dart | 47 ++++-- .../model/query_parameters/tags_query.dart | 154 ++++++++++++++---- .../repository/document_repository.dart | 2 +- .../repository/document_repository_impl.dart | 18 +- .../view/pages/document_details_page.dart | 4 +- .../view/pages/document_edit_page.dart | 20 ++- .../documents/view/pages/document_view.dart | 7 +- .../view/widgets/grid/document_grid_item.dart | 8 +- .../widgets/search/document_filter_panel.dart | 65 +++++--- .../sort_field_selection_bottom_sheet.dart | 9 +- .../confirm_delete_saved_view_dialog.dart | 4 +- .../selection/documents_page_app_bar.dart | 2 +- .../view/widgets/sort_documents_button.dart | 6 +- .../home/view/widget/info_drawer.dart | 2 +- .../labels/tags/view/pages/edit_tag_page.dart | 14 +- .../labels/tags/view/widgets/tag_widget.dart | 18 +- .../tags/view/widgets/tags_form_field.dart | 124 ++++++++++---- .../labels/tags/view/widgets/tags_widget.dart | 2 +- .../labels/view/pages/labels_page.dart | 14 +- .../scan/view/document_upload_page.dart | 6 +- lib/features/scan/view/scanner_page.dart | 20 +++ lib/l10n/intl_de.arb | 5 +- lib/l10n/intl_en.arb | 5 +- lib/main.dart | 2 +- test/src/saved_view_test.dart | 26 +-- 32 files changed, 454 insertions(+), 205 deletions(-) diff --git a/lib/core/model/document_processing_status.dart b/lib/core/model/document_processing_status.dart index 7d8730a..3dc7ace 100644 --- a/lib/core/model/document_processing_status.dart +++ b/lib/core/model/document_processing_status.dart @@ -19,7 +19,7 @@ class DocumentProcessingStatus { final String taskId; final bool isApproximated; - static const String UNKNOWN_TASK_ID = "NO_TASK_ID"; + static const String unknownTaskId = "NO_TASK_ID"; DocumentProcessingStatus({ required this.currentProgress, diff --git a/lib/core/service/status.service.dart b/lib/core/service/status.service.dart index 66d34db..55f3292 100644 --- a/lib/core/service/status.service.dart +++ b/lib/core/service/status.service.dart @@ -79,7 +79,7 @@ class LongPollingStatusService implements StatusService { maxProgress: 100, message: ProcessingMessage.new_file, status: ProcessingStatus.working, - taskId: DocumentProcessingStatus.UNKNOWN_TASK_ID, + taskId: DocumentProcessingStatus.unknownTaskId, documentId: null, isApproximated: true, ), @@ -105,7 +105,7 @@ class LongPollingStatusService implements StatusService { maxProgress: 100, message: ProcessingMessage.finished, status: ProcessingStatus.success, - taskId: DocumentProcessingStatus.UNKNOWN_TASK_ID, + taskId: DocumentProcessingStatus.unknownTaskId, documentId: docId, isApproximated: true, ), diff --git a/lib/extensions/dart_extensions.dart b/lib/extensions/dart_extensions.dart index f155504..11af7da 100644 --- a/lib/extensions/dart_extensions.dart +++ b/lib/extensions/dart_extensions.dart @@ -7,3 +7,18 @@ extension NullableMapKey on Map { return putIfAbsent(key, () => value); } } + +extension Unique on List { + List unique([Id Function(E element)? id, bool inplace = true]) { + final ids = {}; + var list = inplace ? this : List.from(this); + list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id)); + return list; + } +} + +extension DuplicateCheckable on Iterable { + bool hasDuplicates() { + return toSet().length != length; + } +} diff --git a/lib/features/documents/bloc/documents_cubit.dart b/lib/features/documents/bloc/documents_cubit.dart index 016c5bc..28c590c 100644 --- a/lib/features/documents/bloc/documents_cubit.dart +++ b/lib/features/documents/bloc/documents_cubit.dart @@ -22,7 +22,7 @@ class DocumentsCubit extends Cubit { required void Function(DocumentModel document) onConsumptionFinished, int? documentType, int? correspondent, - List? tags, + Iterable tags = const [], DateTime? createdAt, }) async { await documentRepository.create( diff --git a/lib/features/documents/model/bulk_edit.model.dart b/lib/features/documents/model/bulk_edit.model.dart index 09304b4..454dfb1 100644 --- a/lib/features/documents/model/bulk_edit.model.dart +++ b/lib/features/documents/model/bulk_edit.model.dart @@ -2,18 +2,23 @@ import 'package:paperless_mobile/core/type/types.dart'; class BulkEditAction { final List documents; - final String _method; + final BulkEditActionMethod _method; final Map parameters; BulkEditAction.delete(this.documents) - : _method = 'delete', + : _method = BulkEditActionMethod.delete, parameters = {}; JSON toJson() { return { 'documents': documents, - 'method': _method, + 'method': _method.name, 'parameters': parameters, }; } } + +enum BulkEditActionMethod { + delete, + edit; +} diff --git a/lib/features/documents/model/document.model.dart b/lib/features/documents/model/document.model.dart index 27d2ac5..e5d5e80 100644 --- a/lib/features/documents/model/document.model.dart +++ b/lib/features/documents/model/document.model.dart @@ -23,7 +23,7 @@ class DocumentModel extends Equatable { final int id; final String title; final String? content; - final List tags; + final Iterable tags; final int? documentType; final int? correspondent; final int? storagePath; @@ -78,7 +78,7 @@ class DocumentModel extends Equatable { modifiedKey: modified.toUtc().toIso8601String(), addedKey: added.toUtc().toIso8601String(), originalFileNameKey: originalFileName, - tagsKey: tags, + tagsKey: tags.toList(), storagePathKey: storagePath, }; } @@ -86,10 +86,14 @@ class DocumentModel extends Equatable { DocumentModel copyWith({ String? title, String? content, - TagsQuery? tags, - IdQueryParameter? documentType, - IdQueryParameter? correspondent, - IdQueryParameter? storagePath, + bool overwriteTags = false, + Iterable? tags, + bool overwriteDocumentType = false, + int? documentType, + bool overwriteCorrespondent = false, + int? correspondent, + bool overwriteStoragePath = false, + int? storagePath, DateTime? created, DateTime? modified, DateTime? added, @@ -101,10 +105,11 @@ class DocumentModel extends Equatable { id: id, title: title ?? this.title, content: content ?? this.content, - documentType: fromQuery(documentType, this.documentType), - correspondent: fromQuery(correspondent, this.correspondent), - storagePath: fromQuery(storagePath, this.storagePath), - tags: fromListQuery(tags, this.tags), + documentType: overwriteDocumentType ? documentType : this.documentType, + correspondent: + overwriteCorrespondent ? correspondent : this.correspondent, + storagePath: overwriteDocumentType ? storagePath : this.storagePath, + tags: overwriteTags ? tags ?? [] : this.tags, created: created ?? this.created, modified: modified ?? this.modified, added: added ?? this.added, @@ -114,20 +119,6 @@ class DocumentModel extends Equatable { ); } - int? fromQuery(IdQueryParameter? query, int? previous) { - if (query == null) { - return previous; - } - return query.id; - } - - List fromListQuery(TagsQuery? query, List previous) { - if (query == null) { - return previous; - } - return query.ids; - } - @override List get props => [ id, diff --git a/lib/features/documents/model/document_filter.dart b/lib/features/documents/model/document_filter.dart index aaf79bb..c434486 100644 --- a/lib/features/documents/model/document_filter.dart +++ b/lib/features/documents/model/document_filter.dart @@ -43,7 +43,7 @@ class DocumentFilter with EquatableMixin { this.correspondent = const CorrespondentQuery.unset(), this.storagePath = const StoragePathQuery.unset(), this.asn = const AsnQuery.unset(), - this.tags = const TagsQuery.unset(), + this.tags = const IdsTagsQuery.unset(), this.sortField = SortField.created, this.sortOrder = SortOrder.descending, this.page = 1, diff --git a/lib/features/documents/model/filter_rule.model.dart b/lib/features/documents/model/filter_rule.model.dart index 861f3d5..3e1d506 100644 --- a/lib/features/documents/model/filter_rule.model.dart +++ b/lib/features/documents/model/filter_rule.model.dart @@ -13,24 +13,24 @@ class FilterRule with EquatableMixin { static const int asnRule = 2; static const int correspondentRule = 3; static const int documentTypeRule = 4; - static const int tagRule = 6; + static const int includeTagsRule = 6; + static const int hasAnyTag = 7; // Corresponds to Not assigned static const int createdBeforeRule = 8; static const int createdAfterRule = 9; static const int addedBeforeRule = 13; static const int addedAfterRule = 14; + static const int excludeTagsRule = 17; static const int titleAndContentRule = 19; static const int extendedRule = 20; static const int storagePathRule = 25; - // Currently unsupported view optiosn: + // Currently unsupported view options: static const int _content = 1; static const int _isInInbox = 5; - static const int _hasAnyTag = 7; static const int _createdYearIs = 10; static const int _createdMonthIs = 11; static const int _createdDayIs = 12; static const int _modifiedBefore = 15; static const int _modifiedAfter = 16; - static const int _doesNotHaveTag = 17; static const int _doesNotHaveAsn = 18; static const int _moreLikeThis = 21; static const int _hasTagsIn = 22; @@ -76,11 +76,25 @@ class FilterRule with EquatableMixin { ? const StoragePathQuery.notAssigned() : StoragePathQuery.fromId(int.parse(value!)), ); - case tagRule: + case hasAnyTag: return filter.copyWith( - tags: value == null - ? const TagsQuery.notAssigned() - : TagsQuery.fromIds([...filter.tags.ids, int.parse(value!)]), + tags: value == "true" + ? const AnyAssignedTagsQuery() + : const OnlyNotAssignedTagsQuery(), + ); + case includeTagsRule: + assert(filter.tags is IdsTagsQuery); + return filter.copyWith( + tags: (filter.tags as IdsTagsQuery).withIdQueriesAdded([ + IncludeTagIdQuery(int.parse(value!)), + ]), + ); + case excludeTagsRule: + assert(filter.tags is IdsTagsQuery); + return filter.copyWith( + tags: (filter.tags as IdsTagsQuery).withIdQueriesAdded([ + ExcludeTagIdQuery(int.parse(value!)), + ]), ); case createdBeforeRule: return filter.copyWith( @@ -131,12 +145,19 @@ class FilterRule with EquatableMixin { filterRules .add(FilterRule(storagePathRule, filter.storagePath.id!.toString())); } - if (filter.tags.onlyNotAssigned) { - filterRules.add(FilterRule(tagRule, null)); + if (filter.tags is OnlyNotAssignedTagsQuery) { + filterRules.add(FilterRule(hasAnyTag, "false")); } - if (filter.tags.isSet) { - filterRules.addAll( - filter.tags.ids.map((id) => FilterRule(tagRule, id.toString()))); + if (filter.tags is AnyAssignedTagsQuery) { + filterRules.add(FilterRule(hasAnyTag, "true")); + } + if (filter.tags is IdsTagsQuery) { + filterRules.addAll((filter.tags as IdsTagsQuery) + .includedIds + .map((id) => FilterRule(includeTagsRule, id.toString()))); + filterRules.addAll((filter.tags as IdsTagsQuery) + .excludedIds + .map((id) => FilterRule(excludeTagsRule, id.toString()))); } if (filter.queryText != null) { diff --git a/lib/features/documents/model/query_parameters/tags_query.dart b/lib/features/documents/model/query_parameters/tags_query.dart index c92a7da..c794992 100644 --- a/lib/features/documents/model/query_parameters/tags_query.dart +++ b/lib/features/documents/model/query_parameters/tags_query.dart @@ -1,38 +1,136 @@ import 'package:equatable/equatable.dart'; +import 'package:paperless_mobile/extensions/dart_extensions.dart'; -class TagsQuery with EquatableMixin { - final List _ids; - final bool? _isTagged; +abstract class TagsQuery with EquatableMixin { + const TagsQuery(); + String toQueryParameter(); +} - const TagsQuery.fromIds(List ids) - : _isTagged = null, - _ids = ids; - - const TagsQuery.anyAssigned() - : _isTagged = true, - _ids = const []; - - const TagsQuery.notAssigned() - : _isTagged = false, - _ids = const []; - - const TagsQuery.unset() : this.fromIds(const []); - - bool get onlyNotAssigned => _isTagged == false; - bool get onlyAssigned => _isTagged == true; - - bool get isUnset => _ids.isEmpty && _isTagged == null; - bool get isSet => _ids.isNotEmpty && _isTagged == null; - - List get ids => _ids; +class OnlyNotAssignedTagsQuery extends TagsQuery { + const OnlyNotAssignedTagsQuery(); + @override + List get props => []; + @override String toQueryParameter() { - if (onlyNotAssigned || onlyAssigned) { - return '&is_tagged=$_isTagged'; + return '&is_tagged=0'; + } +} + +class AnyAssignedTagsQuery extends TagsQuery { + const AnyAssignedTagsQuery(); + @override + List get props => []; + + @override + String toQueryParameter() { + return '&is_tagged=1'; + } +} + +class IdsTagsQuery extends TagsQuery { + final Iterable _idQueries; + + const IdsTagsQuery([this._idQueries = const []]); + + const IdsTagsQuery.unset() : _idQueries = const []; + + IdsTagsQuery.included(Iterable ids) + : _idQueries = ids.map((id) => IncludeTagIdQuery(id)); + + IdsTagsQuery.fromIds(Iterable ids) : this.included(ids); + + IdsTagsQuery.excluded(Iterable ids) + : _idQueries = ids.map((id) => ExcludeTagIdQuery(id)); + + IdsTagsQuery withIdQueriesAdded(Iterable idQueries) { + final intersection = _idQueries + .map((idQ) => idQ.id) + .toSet() + .intersection(_idQueries.map((idQ) => idQ.id).toSet()); + return IdsTagsQuery( + [...withIdsRemoved(intersection).queries, ...idQueries], + ); + } + + IdsTagsQuery withIdsRemoved(Iterable ids) { + return IdsTagsQuery( + _idQueries.where((idQuery) => !ids.contains(idQuery.id)), + ); + } + + Iterable get queries => _idQueries; + + Iterable get includedIds { + return _idQueries.whereType().map((e) => e.id); + } + + Iterable get excludedIds { + return _idQueries.whereType().map((e) => e.id); + } + + /// + /// Returns a new instance with the type of the given [id] toggled. + /// E.g. if the provided [id] is currently registered as a [IncludeTagIdQuery], + /// then the new isntance will contain a [ExcludeTagIdQuery] with given id. + /// + IdsTagsQuery withIdQueryToggled(int id) { + return IdsTagsQuery( + _idQueries.map((idQ) => idQ.id == id ? idQ.toggle() : idQ), + ); + } + + Iterable get ids => [...includedIds, ...excludedIds]; + + @override + String toQueryParameter() { + final StringBuffer sb = StringBuffer(""); + if (includedIds.isNotEmpty) { + sb.write('&tags__id__all=${includedIds.join(',')}'); } - return isUnset ? "" : '&tags__id__all=${ids.join(',')}'; + if (excludedIds.isNotEmpty) { + sb.write('&tags__id__none=${excludedIds.join(',')}'); + } + return sb.toString(); } @override - List get props => [_isTagged, _ids]; + List get props => [_idQueries]; +} + +abstract class TagIdQuery with EquatableMixin { + final int id; + + TagIdQuery(this.id); + + String get methodName; + + @override + List get props => [id, methodName]; + + TagIdQuery toggle(); +} + +class IncludeTagIdQuery extends TagIdQuery { + IncludeTagIdQuery(super.id); + + @override + String get methodName => 'include'; + + @override + TagIdQuery toggle() { + return ExcludeTagIdQuery(id); + } +} + +class ExcludeTagIdQuery extends TagIdQuery { + ExcludeTagIdQuery(super.id); + + @override + String get methodName => 'exclude'; + + @override + TagIdQuery toggle() { + return IncludeTagIdQuery(id); + } } diff --git a/lib/features/documents/repository/document_repository.dart b/lib/features/documents/repository/document_repository.dart index ae266ec..3acc499 100644 --- a/lib/features/documents/repository/document_repository.dart +++ b/lib/features/documents/repository/document_repository.dart @@ -13,7 +13,7 @@ abstract class DocumentRepository { required String title, int? documentType, int? correspondent, - List? tags, + Iterable tags = const [], DateTime? createdAt, }); Future update(DocumentModel doc); diff --git a/lib/features/documents/repository/document_repository_impl.dart b/lib/features/documents/repository/document_repository_impl.dart index 5198731..1ef62e2 100644 --- a/lib/features/documents/repository/document_repository_impl.dart +++ b/lib/features/documents/repository/document_repository_impl.dart @@ -5,6 +5,9 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:http/src/boundary_characters.dart'; //TODO: remove once there is either a paperless API update or there is a better solution... +import 'package:injectable/injectable.dart'; import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/store/local_vault.dart'; import 'package:paperless_mobile/core/type/types.dart'; @@ -22,9 +25,6 @@ import 'package:paperless_mobile/features/documents/model/query_parameters/sort_ import 'package:paperless_mobile/features/documents/model/similar_document.model.dart'; import 'package:paperless_mobile/features/documents/repository/document_repository.dart'; import 'package:paperless_mobile/util.dart'; -import 'package:http/http.dart'; -import 'package:http/src/boundary_characters.dart'; //TODO: remove once there is either a paperless API update or there is a better solution... -import 'package:injectable/injectable.dart'; @Injectable(as: DocumentRepository) class DocumentRepositoryImpl implements DocumentRepository { @@ -45,7 +45,7 @@ class DocumentRepositoryImpl implements DocumentRepository { required String title, int? documentType, int? correspondent, - List? tags, + Iterable tags = const [], DateTime? createdAt, }) async { final auth = await localStorage.loadAuthenticationInformation(); @@ -78,7 +78,7 @@ class DocumentRepositoryImpl implements DocumentRepository { bodyBuffer.write(_buildMultipartField(key, fields[key]!, boundary)); } - for (final tag in tags ?? []) { + for (final tag in tags) { bodyBuffer.write(_buildMultipartField('tags', tag.toString(), boundary)); } @@ -124,10 +124,10 @@ class DocumentRepositoryImpl implements DocumentRepository { Random _random = Random(); var prefix = 'dart-http-boundary-'; var list = List.generate( - 70 - prefix.length, - (index) => - boundaryCharacters[_random.nextInt(boundaryCharacters.length)], - growable: false); + 70 - prefix.length, + (index) => boundaryCharacters[_random.nextInt(boundaryCharacters.length)], + growable: false, + ); return '$prefix${String.fromCharCodes(list)}'; } diff --git a/lib/features/documents/view/pages/document_details_page.dart b/lib/features/documents/view/pages/document_details_page.dart index 2dc6cc4..9775b5c 100644 --- a/lib/features/documents/view/pages/document_details_page.dart +++ b/lib/features/documents/view/pages/document_details_page.dart @@ -420,7 +420,9 @@ class _DocumentDetailsPageState extends State { Future _onOpen(DocumentModel document) async { Navigator.of(context).push( MaterialPageRoute( - builder: (context) => DocumentView(document: document), + builder: (context) => DocumentView( + documentBytes: getIt().getPreview(document.id), + ), ), ); } diff --git a/lib/features/documents/view/pages/document_edit_page.dart b/lib/features/documents/view/pages/document_edit_page.dart index c3a11b3..141afa5 100644 --- a/lib/features/documents/view/pages/document_edit_page.dart +++ b/lib/features/documents/view/pages/document_edit_page.dart @@ -53,9 +53,8 @@ class _DocumentEditPageState extends State { @override void initState() { - documentBytes = getIt().getPreview(widget.document.id); - super.initState(); + documentBytes = getIt().getPreview(widget.document.id); } @override @@ -69,10 +68,14 @@ class _DocumentEditPageState extends State { final updatedDocument = widget.document.copyWith( title: values[fkTitle], created: values[fkCreatedDate], - documentType: values[fkDocumentType] as IdQueryParameter, - correspondent: values[fkCorrespondent] as IdQueryParameter, - storagePath: values[fkStoragePath] as IdQueryParameter, - tags: values[fkTags] as TagsQuery, + overwriteDocumentType: true, + documentType: (values[fkDocumentType] as IdQueryParameter).id, + overwriteCorrespondent: true, + correspondent: (values[fkCorrespondent] as IdQueryParameter).id, + overwriteStoragePath: true, + storagePath: (values[fkStoragePath] as IdQueryParameter).id, + overwriteTags: true, + tags: (values[fkTags] as IdsTagsQuery).includedIds, ); setState(() { _isSubmitLoading = true; @@ -181,7 +184,10 @@ class _DocumentEditPageState extends State { }, ).padded(), TagFormField( - initialValue: TagsQuery.fromIds(widget.document.tags), + initialValue: IdsTagsQuery.included(widget.document.tags), + notAssignedSelectable: false, + anyAssignedSelectable: false, + excludeAllowed: false, name: fkTags, ).padded(), ]), diff --git a/lib/features/documents/view/pages/document_view.dart b/lib/features/documents/view/pages/document_view.dart index 2f8a4ba..5f7e83c 100644 --- a/lib/features/documents/view/pages/document_view.dart +++ b/lib/features/documents/view/pages/document_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/features/documents/model/document.model.dart'; @@ -6,11 +7,11 @@ import 'package:paperless_mobile/generated/l10n.dart'; import 'package:pdfx/pdfx.dart'; class DocumentView extends StatefulWidget { - final DocumentModel document; + final Future documentBytes; const DocumentView({ Key? key, - required this.document, + required this.documentBytes, }) : super(key: key); @override @@ -25,7 +26,7 @@ class _DocumentViewState extends State { super.initState(); _pdfController = PdfController( document: PdfDocument.openData( - getIt().getPreview(widget.document.id), + widget.documentBytes, ), ); } diff --git a/lib/features/documents/view/widgets/grid/document_grid_item.dart b/lib/features/documents/view/widgets/grid/document_grid_item.dart index c9e6749..6a207e4 100644 --- a/lib/features/documents/view/widgets/grid/document_grid_item.dart +++ b/lib/features/documents/view/widgets/grid/document_grid_item.dart @@ -66,8 +66,12 @@ class DocumentGridItem extends StatelessWidget { tagIds: document.tags, isMultiLine: false, ), - Text(DateFormat.yMMMd(Intl.getCurrentLocale()) - .format(document.created)), + const Spacer(), + Text( + DateFormat.yMMMd(Intl.getCurrentLocale()) + .format(document.created), + style: Theme.of(context).textTheme.caption, + ), ], ), ), diff --git a/lib/features/documents/view/widgets/search/document_filter_panel.dart b/lib/features/documents/view/widgets/search/document_filter_panel.dart index 15a2dd6..91f038e 100644 --- a/lib/features/documents/view/widgets/search/document_filter_panel.dart +++ b/lib/features/documents/view/widgets/search/document_filter_panel.dart @@ -11,7 +11,6 @@ import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/query_type.dart'; -import 'package:paperless_mobile/features/documents/model/query_parameters/sort_field.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; import 'package:paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart'; @@ -89,8 +88,7 @@ class _DocumentFilterPanelState extends State { builder: (context, state) { return FormBuilder( key: _formKey, - child: ListView( - controller: widget.scrollController, + child: Column( children: [ Stack( alignment: Alignment.center, @@ -127,28 +125,43 @@ class _DocumentFilterPanelState extends State { const SizedBox( height: 16.0, ), - Align( - alignment: Alignment.centerLeft, - child: Text(S.of(context).documentsFilterPageSearchLabel), - ).padded(const EdgeInsets.only(left: 8.0)), - _buildQueryFormField(state), - Align( - alignment: Alignment.centerLeft, - child: Text(S.of(context).documentsFilterPageAdvancedLabel), - ).padded(const EdgeInsets.only(left: 8.0, top: 8.0)), - _buildCreatedDateRangePickerFormField(state).padded(), - _buildAddedDateRangePickerFormField(state).padded(), - _buildCorrespondentFormField(state).padded(), - _buildDocumentTypeFormField(state).padded(), - _buildStoragePathFormField(state).padded(), - TagFormField( - name: DocumentModel.tagsKey, - initialValue: state.filter.tags, - allowCreation: false, - ).padded(), - // Required in order for the storage path field to be visible when typing - const SizedBox( - height: 200, + Expanded( + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + child: ListView( + controller: widget.scrollController, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + S.of(context).documentsFilterPageSearchLabel), + ).padded(const EdgeInsets.only(left: 8.0)), + _buildQueryFormField(state), + Align( + alignment: Alignment.centerLeft, + child: Text( + S.of(context).documentsFilterPageAdvancedLabel), + ).padded(const EdgeInsets.only(left: 8.0, top: 8.0)), + _buildCreatedDateRangePickerFormField(state).padded(), + _buildAddedDateRangePickerFormField(state).padded(), + _buildCorrespondentFormField(state).padded(), + _buildDocumentTypeFormField(state).padded(), + _buildStoragePathFormField(state).padded(), + TagFormField( + name: DocumentModel.tagsKey, + initialValue: state.filter.tags, + allowCreation: false, + ).padded(), + // Required in order for the storage path field to be visible when typing + const SizedBox( + height: 150, + ), + ], + ).padded(), + ), ), ], ), @@ -374,6 +387,7 @@ class _DocumentFilterPanelState extends State { ), ), ), + const SizedBox(height: 4.0), _buildDateRangePickerHelper(state, fkCreatedAt), ], ); @@ -421,6 +435,7 @@ class _DocumentFilterPanelState extends State { ), ), ), + const SizedBox(height: 4.0), _buildDateRangePickerHelper(state, fkAddedAt), ], ); diff --git a/lib/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart b/lib/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart index fbde142..b0b9e3e 100644 --- a/lib/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart +++ b/lib/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart @@ -44,9 +44,10 @@ class _SortFieldSelectionBottomSheetState children: [ Text( S.of(context).documentsPageOrderByLabel, - style: Theme.of(context).textTheme.titleSmall, + style: Theme.of(context).textTheme.caption, textAlign: TextAlign.start, - ).padded(EdgeInsets.symmetric(horizontal: 16, vertical: 16)), + ).padded( + const EdgeInsets.symmetric(horizontal: 16, vertical: 16)), Column( children: _sortFields .map( @@ -110,9 +111,9 @@ class _SortFieldSelectionBottomSheetState Widget _buildOrderIcon(SortOrder order) { if (order == SortOrder.ascending) { - return Icon(Icons.arrow_upward); + return const Icon(Icons.arrow_upward); } - return Icon(Icons.arrow_downward); + return const Icon(Icons.arrow_downward); } String _localizedSortField(SortField sortField) { diff --git a/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart b/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart index 61ba9af..c074848 100644 --- a/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart +++ b/lib/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart @@ -18,10 +18,10 @@ class ConfirmDeleteSavedViewDialog extends StatelessWidget { Widget build(BuildContext context) { return AlertDialog( title: Text( - "Delete view " + view.name + "?", + S.of(context).deleteViewDialogTitleText + view.name + "?", softWrap: true, ), - content: Text("Do you really want to delete this view?"), + content: Text(S.of(context).deleteViewDialogContentText), actions: [ TextButton( child: Text(S.of(context).genericActionCancelLabel), diff --git a/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart b/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart index d3a3b66..6da5534 100644 --- a/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart +++ b/lib/features/documents/view/widgets/selection/documents_page_app_bar.dart @@ -77,7 +77,7 @@ class _DocumentsPageAppBarState extends State { Widget _buildFlexibleArea(bool enabled) { return FlexibleSpaceBar( background: Padding( - padding: EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ diff --git a/lib/features/documents/view/widgets/sort_documents_button.dart b/lib/features/documents/view/widgets/sort_documents_button.dart index 4c46752..64929cc 100644 --- a/lib/features/documents/view/widgets/sort_documents_button.dart +++ b/lib/features/documents/view/widgets/sort_documents_button.dart @@ -27,7 +27,7 @@ class _SortDocumentsButtonState extends State { @override Widget build(BuildContext context) { return IconButton( - icon: Icon(Icons.sort), + icon: const Icon(Icons.sort), onPressed: _onOpenSortBottomSheet, ); } @@ -45,9 +45,9 @@ class _SortDocumentsButtonState extends State { ), builder: (context) => BlocProvider.value( value: getIt(), - child: FractionallySizedBox( + child: const FractionallySizedBox( heightFactor: .6, - child: const SortFieldSelectionBottomSheet(), + child: SortFieldSelectionBottomSheet(), ), ), ); diff --git a/lib/features/home/view/widget/info_drawer.dart b/lib/features/home/view/widget/info_drawer.dart index cb8873f..d43361d 100644 --- a/lib/features/home/view/widget/info_drawer.dart +++ b/lib/features/home/view/widget/info_drawer.dart @@ -23,7 +23,7 @@ class InfoDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return ClipRRect( - borderRadius: BorderRadius.only( + borderRadius: const BorderRadius.only( topRight: Radius.circular(16.0), bottomRight: Radius.circular(16.0), ), diff --git a/lib/features/labels/tags/view/pages/edit_tag_page.dart b/lib/features/labels/tags/view/pages/edit_tag_page.dart index dff5d45..ff2c02b 100644 --- a/lib/features/labels/tags/view/pages/edit_tag_page.dart +++ b/lib/features/labels/tags/view/pages/edit_tag_page.dart @@ -48,12 +48,14 @@ class EditTagPage extends StatelessWidget { final cubit = BlocProvider.of(context); final currentFilter = cubit.state.filter; late DocumentFilter updatedFilter = currentFilter; - if (currentFilter.tags.ids.contains(tag.id)) { - updatedFilter = currentFilter.copyWith( - tags: TagsQuery.fromIds( - currentFilter.tags.ids.where((tagId) => tagId != tag.id).toList(), - ), - ); + if (currentFilter.tags is IdsTagsQuery) { + if ((currentFilter.tags as IdsTagsQuery).includedIds.contains(tag.id)) { + updatedFilter = currentFilter.copyWith( + tags: (currentFilter.tags as IdsTagsQuery).withIdsRemoved( + [tag.id!], + ), + ); + } } cubit.updateFilter(filter: updatedFilter); Navigator.pop(context); diff --git a/lib/features/labels/tags/view/widgets/tag_widget.dart b/lib/features/labels/tags/view/widgets/tag_widget.dart index a0f367a..ed8c58d 100644 --- a/lib/features/labels/tags/view/widgets/tag_widget.dart +++ b/lib/features/labels/tags/view/widgets/tag_widget.dart @@ -18,8 +18,13 @@ class TagWidget extends StatelessWidget { padding: const EdgeInsets.only(right: 4.0), child: BlocBuilder( builder: (context, state) { + final isIdsQuery = state.filter.tags is IdsTagsQuery; return FilterChip( - selected: state.filter.tags.ids.contains(tag.id), + selected: isIdsQuery + ? (state.filter.tags as IdsTagsQuery) + .includedIds + .contains(tag.id) + : false, selectedColor: tag.color, onSelected: (_) => _addTagToFilter(context), visualDensity: const VisualDensity(vertical: -2), @@ -40,18 +45,19 @@ class TagWidget extends StatelessWidget { void _addTagToFilter(BuildContext context) { final cubit = BlocProvider.of(context); try { - if (cubit.state.filter.tags.ids.contains(tag.id)) { + final tagsQuery = cubit.state.filter.tags is IdsTagsQuery + ? cubit.state.filter.tags as IdsTagsQuery + : const IdsTagsQuery(); + if (tagsQuery.includedIds.contains(tag.id)) { cubit.updateCurrentFilter( (filter) => filter.copyWith( - tags: TagsQuery.fromIds(cubit.state.filter.tags.ids - .where((id) => id != tag.id) - .toList()), + tags: tagsQuery.withIdsRemoved([tag.id!]), ), ); } else { cubit.updateCurrentFilter( (filter) => filter.copyWith( - tags: TagsQuery.fromIds([...cubit.state.filter.tags.ids, tag.id!]), + tags: tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tag.id!)]), ), ); } diff --git a/lib/features/labels/tags/view/widgets/tags_form_field.dart b/lib/features/labels/tags/view/widgets/tags_form_field.dart index ad4c14b..2fff5bb 100644 --- a/lib/features/labels/tags/view/widgets/tags_form_field.dart +++ b/lib/features/labels/tags/view/widgets/tags_form_field.dart @@ -13,6 +13,8 @@ class TagFormField extends StatefulWidget { final String name; final bool allowCreation; final bool notAssignedSelectable; + final bool anyAssignedSelectable; + final bool excludeAllowed; const TagFormField({ super.key, @@ -20,6 +22,8 @@ class TagFormField extends StatefulWidget { this.initialValue, this.allowCreation = true, this.notAssignedSelectable = true, + this.anyAssignedSelectable = true, + this.excludeAllowed = true, }); @override @@ -27,8 +31,11 @@ class TagFormField extends StatefulWidget { } class _TagFormFieldState extends State { + static const _onlyNotAssignedId = -1; + static const _anyAssignedId = -2; + late final TextEditingController _textEditingController; - bool _showCreationSuffixIcon = false; + bool _showCreationSuffixIcon = true; bool _showClearSuffixIcon = false; @override @@ -39,12 +46,13 @@ class _TagFormFieldState extends State { ..addListener(() { setState(() { _showCreationSuffixIcon = state.values - .where( - (item) => item.name.toLowerCase().startsWith( - _textEditingController.text.toLowerCase(), - ), - ) - .isEmpty; + .where( + (item) => item.name.toLowerCase().startsWith( + _textEditingController.text.toLowerCase(), + ), + ) + .isEmpty || + _textEditingController.text.isEmpty; }); setState(() => _showClearSuffixIcon = _textEditingController.text.isNotEmpty); @@ -78,21 +86,32 @@ class _TagFormFieldState extends State { .toLowerCase() .startsWith(query.toLowerCase())) .map((e) => e.id!) - .toList() - ..removeWhere((element) => - field.value?.ids.contains(element) ?? false); - if (widget.notAssignedSelectable) { - suggestions.insert(0, -1); + .toList(); + if (field.value is IdsTagsQuery) { + suggestions.removeWhere((element) => + (field.value as IdsTagsQuery).ids.contains(element)); + } + if (widget.notAssignedSelectable && + field.value is! OnlyNotAssignedTagsQuery) { + suggestions.insert(0, _onlyNotAssignedId); + } + if (widget.anyAssignedSelectable && + field.value is! AnyAssignedTagsQuery) { + suggestions.insert(0, _anyAssignedId); } return suggestions; }, getImmediateSuggestions: true, animationStart: 1, itemBuilder: (context, data) { - if (data == -1) { + if (data == _onlyNotAssignedId) { return ListTile( title: Text(S.of(context).labelNotAssignedText), ); + } else if (data == _anyAssignedId) { + return ListTile( + title: Text(S.of(context).labelAnyAssignedText), + ); } final tag = tagState[data]!; return ListTile( @@ -108,33 +127,48 @@ class _TagFormFieldState extends State { ); }, onSuggestionSelected: (id) { - if (id == -1) { - field.didChange(const TagsQuery.notAssigned()); + if (id == _onlyNotAssignedId) { + //Not assigned tag + field.didChange(const OnlyNotAssignedTagsQuery()); return; + } else if (id == _anyAssignedId) { + field.didChange(const AnyAssignedTagsQuery()); } else { - field.didChange( - TagsQuery.fromIds([...field.value?.ids ?? [], id])); + final tagsQuery = field.value is IdsTagsQuery + ? field.value as IdsTagsQuery + : const IdsTagsQuery(); + field.didChange(tagsQuery + .withIdQueriesAdded([IncludeTagIdQuery(id)])); } _textEditingController.clear(); }, direction: AxisDirection.up, ), - if (field.value?.onlyNotAssigned ?? false) ...[ + if (field.value is OnlyNotAssignedTagsQuery) ...[ _buildNotAssignedTag(field) + ] else if (field.value is AnyAssignedTagsQuery) ...[ + _buildAnyAssignedTag(field) ] else ...[ + // field.value is IdsTagsQuery Wrap( alignment: WrapAlignment.start, runAlignment: WrapAlignment.start, spacing: 8.0, - children: (field.value?.ids ?? []) - .map((id) => _buildTag(field, tagState[id]!)) + children: ((field.value as IdsTagsQuery).queries) + .map( + (query) => _buildTag( + field, + query, + tagState[query.id]!, + ), + ) .toList(), ), ] ], ); }, - initialValue: widget.initialValue ?? const TagsQuery.unset(), + initialValue: widget.initialValue ?? const IdsTagsQuery(), name: widget.name, ); }, @@ -172,8 +206,11 @@ class _TagFormFieldState extends State { ), ); if (tag != null) { + final tagsQuery = field.value is IdsTagsQuery + ? field.value as IdsTagsQuery + : const IdsTagsQuery(); field.didChange( - TagsQuery.fromIds([...field.value?.ids ?? [], tag.id!]), + tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tag.id!)]), ); } _textEditingController.clear(); @@ -191,24 +228,43 @@ class _TagFormFieldState extends State { ), backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.12), + onDeleted: () => field.didChange(const IdsTagsQuery()), + ); + } + + Widget _buildTag( + FormFieldState field, + TagIdQuery query, + Tag tag, + ) { + final currentQuery = field.value as IdsTagsQuery; + final isIncludedTag = currentQuery.includedIds.contains(query.id); + + return InputChip( + label: Text( + tag.name, + style: TextStyle( + color: tag.textColor, + decoration: !isIncludedTag ? TextDecoration.lineThrough : null, + decorationThickness: 2.0, + ), + ), + onPressed: widget.excludeAllowed + ? () => field.didChange(currentQuery.withIdQueryToggled(tag.id!)) + : null, + backgroundColor: tag.color, onDeleted: () => field.didChange( - const TagsQuery.unset(), + (field.value as IdsTagsQuery).withIdsRemoved([tag.id!]), ), ); } - Widget _buildTag(FormFieldState field, Tag tag) { + Widget _buildAnyAssignedTag(FormFieldState field) { return InputChip( - label: Text( - tag.name, - style: TextStyle(color: tag.textColor), - ), - backgroundColor: tag.color, - onDeleted: () => field.didChange( - TagsQuery.fromIds( - field.value?.ids.where((element) => element != tag.id).toList() ?? [], - ), - ), + label: Text(S.of(context).labelAnyAssignedText), + backgroundColor: + Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.12), + onDeleted: () => field.didChange(const IdsTagsQuery()), ); } } diff --git a/lib/features/labels/tags/view/widgets/tags_widget.dart b/lib/features/labels/tags/view/widgets/tags_widget.dart index d37d1be..e95c452 100644 --- a/lib/features/labels/tags/view/widgets/tags_widget.dart +++ b/lib/features/labels/tags/view/widgets/tags_widget.dart @@ -7,7 +7,7 @@ import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; class TagsWidget extends StatefulWidget { - final List tagIds; + final Iterable tagIds; final bool isMultiLine; final void Function()? afterTagTapped; final bool isClickable; diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart index 50dff9a..a9ed051 100644 --- a/lib/features/labels/view/pages/labels_page.dart +++ b/lib/features/labels/view/pages/labels_page.dart @@ -3,31 +3,29 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_mobile/core/bloc/label_bloc_provider.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/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; -import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; -import 'package:paperless_mobile/features/labels/correspondent/view/pages/edit_correspondent_page.dart'; -import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; -import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.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/features/labels/correspondent/view/pages/add_correspondent_page.dart'; +import 'package:paperless_mobile/features/labels/correspondent/view/pages/edit_correspondent_page.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/document_type/view/pages/add_document_type_page.dart'; import 'package:paperless_mobile/features/labels/document_type/view/pages/edit_document_type_page.dart'; -import 'package:paperless_mobile/features/labels/model/label.model.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'; import 'package:paperless_mobile/features/labels/storage_path/view/pages/edit_storage_path_page.dart'; +import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart'; import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart'; import 'package:paperless_mobile/features/labels/tags/view/pages/edit_tag_page.dart'; -import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart'; -import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/generated/l10n.dart'; class LabelsPage extends StatefulWidget { @@ -147,7 +145,7 @@ class _LabelsPageState extends State LabelTabView( cubit: BlocProvider.of(context), filterBuilder: (label) => DocumentFilter( - tags: TagsQuery.fromIds([label.id!]), + tags: IdsTagsQuery.fromIds([label.id!]), pageSize: label.documentCount ?? 0, ), onOpenEditPage: _openEditTagPage, diff --git a/lib/features/scan/view/document_upload_page.dart b/lib/features/scan/view/document_upload_page.dart index 68e9659..9ddd09f 100644 --- a/lib/features/scan/view/document_upload_page.dart +++ b/lib/features/scan/view/document_upload_page.dart @@ -172,6 +172,9 @@ class _DocumentUploadPageState extends State { ), const TagFormField( name: DocumentModel.tagsKey, + notAssignedSelectable: false, + anyAssignedSelectable: false, + excludeAllowed: false, //Label: "Tags" + " *", ), Text( @@ -194,10 +197,9 @@ class _DocumentUploadPageState extends State { 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 tags = fv[DocumentModel.tagsKey] as IdsTagsQuery; final correspondent = fv[DocumentModel.correspondentKey] as IdQueryParameter; - await BlocProvider.of(context).addDocument( widget.fileBytes, _formKey.currentState?.value[fkFileName], diff --git a/lib/features/scan/view/scanner_page.dart b/lib/features/scan/view/scanner_page.dart index 4663afd..f301fdc 100644 --- a/lib/features/scan/view/scanner_page.dart +++ b/lib/features/scan/view/scanner_page.dart @@ -14,6 +14,8 @@ 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'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; +import 'package:paperless_mobile/features/documents/repository/document_repository.dart'; +import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; import 'package:paperless_mobile/features/scan/view/document_upload_page.dart'; @@ -53,6 +55,24 @@ class _ScannerPageState extends State return AppBar( title: Text(S.of(context).documentScannerPageTitle), actions: [ + BlocBuilder>( + builder: (context, state) { + return IconButton( + onPressed: state.isNotEmpty + ? () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DocumentView( + documentBytes: + _buildDocumentFromImageFiles(state).save(), + ), + ), + ) + : null, + icon: const Icon(Icons.preview), + tooltip: S.of(context).documentScannerPageResetButtonTooltipText, + ); + }, + ), BlocBuilder>( builder: (context, state) { return IconButton( diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index d05919e..9808aa5 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -192,5 +192,8 @@ "errorMessageMissingClientCertificate": "Ein Client Zerfitikat wurde erwartet, aber nicht gesendet. Bitte konfiguriere ein gültiges Zertifikat.", "serverInformationPaperlessVersionText": "Paperless Server-Version", "errorReportLabel": "MELDEN", - "appDrawerHeaderLoggedInAsText": "Eingeloggt als " + "appDrawerHeaderLoggedInAsText": "Eingeloggt als ", + "labelAnyAssignedText": "Beliebig zugewiesen", + "deleteViewDialogContentText": "Möchtest Du diese Ansicht wirklich löschen?", + "deleteViewDialogTitleText": "Lösche Ansicht " } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0014a65..fe46a00 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -193,5 +193,8 @@ "errorMessageMissingClientCertificate": "A client certificate was expected but not sent. Please provide a valid client certificate.", "serverInformationPaperlessVersionText": "Paperless server version", "errorReportLabel": "REPORT", - "appDrawerHeaderLoggedInAsText": "Logged in as " + "appDrawerHeaderLoggedInAsText": "Logged in as ", + "labelAnyAssignedText": "Any assigned", + "deleteViewDialogContentText": "Do you really want to delete this view?", + "deleteViewDialogTitleText": "Delete view " } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 1a1b7c2..7b2a0dd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,7 +35,7 @@ void main() async { FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); Intl.systemLocale = await findSystemLocale(); - // Required for client certificates + // Required for self signed client certificates HttpOverrides.global = X509HttpOverrides(); configureDependencies(); diff --git a/test/src/saved_view_test.dart b/test/src/saved_view_test.dart index 0d96f83..d733da4 100644 --- a/test/src/saved_view_test.dart +++ b/test/src/saved_view_test.dart @@ -31,15 +31,15 @@ void main() { 'value': "69", }, { - 'rule_type': FilterRule.tagRule, + 'rule_type': FilterRule.includeTagsRule, 'value': "1", }, { - 'rule_type': FilterRule.tagRule, + 'rule_type': FilterRule.includeTagsRule, 'value': "2", }, { - 'rule_type': FilterRule.tagRule, + 'rule_type': FilterRule.includeTagsRule, 'value': "3", }, { @@ -73,7 +73,7 @@ void main() { correspondent: const CorrespondentQuery.fromId(42), documentType: const DocumentTypeQuery.fromId(69), storagePath: const StoragePathQuery.fromId(14), - tags: const TagsQuery.fromIds([1, 2, 3]), + tags: IdsTagsQuery.fromIds([1, 2, 3]), createdDateBefore: DateTime.parse("2022-10-27"), createdDateAfter: DateTime.parse("2022-09-27"), addedDateBefore: DateTime.parse("2022-09-26"), @@ -121,7 +121,7 @@ void main() { 'value': null, }, { - 'rule_type': FilterRule.tagRule, + 'rule_type': FilterRule.includeTagsRule, 'value': null, }, { @@ -134,7 +134,7 @@ void main() { correspondent: const CorrespondentQuery.notAssigned(), documentType: const DocumentTypeQuery.notAssigned(), storagePath: const StoragePathQuery.notAssigned(), - tags: const TagsQuery.notAssigned(), + tags: const OnlyNotAssignedTagsQuery(), )), ); }); @@ -148,7 +148,7 @@ void main() { correspondent: const CorrespondentQuery.fromId(1), documentType: const DocumentTypeQuery.fromId(2), storagePath: const StoragePathQuery.fromId(3), - tags: const TagsQuery.fromIds([4, 5, 6]), + tags: IdsTagsQuery.fromIds([4, 5, 6]), sortField: SortField.added, sortOrder: SortOrder.ascending, addedDateAfter: DateTime.parse("2020-01-01"), @@ -173,9 +173,9 @@ void main() { FilterRule(FilterRule.correspondentRule, "1"), FilterRule(FilterRule.documentTypeRule, "2"), FilterRule(FilterRule.storagePathRule, "3"), - FilterRule(FilterRule.tagRule, "4"), - FilterRule(FilterRule.tagRule, "5"), - FilterRule(FilterRule.tagRule, "6"), + FilterRule(FilterRule.includeTagsRule, "4"), + FilterRule(FilterRule.includeTagsRule, "5"), + FilterRule(FilterRule.includeTagsRule, "6"), FilterRule(FilterRule.addedAfterRule, "2020-01-01"), FilterRule(FilterRule.addedBeforeRule, "2020-03-01"), FilterRule(FilterRule.createdAfterRule, "2020-02-01"), @@ -194,7 +194,7 @@ void main() { correspondent: CorrespondentQuery.unset(), documentType: DocumentTypeQuery.unset(), storagePath: StoragePathQuery.unset(), - tags: TagsQuery.unset(), + tags: IdsTagsQuery.unset(), sortField: SortField.created, sortOrder: SortOrder.descending, addedDateAfter: null, @@ -227,7 +227,7 @@ void main() { correspondent: CorrespondentQuery.notAssigned(), documentType: DocumentTypeQuery.notAssigned(), storagePath: StoragePathQuery.notAssigned(), - tags: TagsQuery.notAssigned(), + tags: OnlyNotAssignedTagsQuery(), sortField: SortField.created, sortOrder: SortOrder.descending, ), @@ -246,7 +246,7 @@ void main() { FilterRule(FilterRule.correspondentRule, null), FilterRule(FilterRule.documentTypeRule, null), FilterRule(FilterRule.storagePathRule, null), - FilterRule(FilterRule.tagRule, null), + FilterRule(FilterRule.includeTagsRule, null), ], ), ),