From a2388b014b7c4ee2daa80a282d8e1f50b3a54b9a Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Tue, 4 Apr 2023 20:30:25 +0200 Subject: [PATCH] fix: Add labels to each cubit using repositories and state properties, remove label cubits --- lib/core/repository/base_repository.dart | 34 - .../impl/correspondent_repository_impl.dart | 69 - .../impl/document_type_repository_impl.dart | 67 - .../impl/saved_view_repository_impl.dart | 62 - .../impl/storage_path_repository_impl.dart | 68 - .../repository/impl/tag_repository_impl.dart | 66 - lib/core/repository/label_repository.dart | 221 ++- .../repository/label_repository_state.dart | 18 + .../label_repository_state.freezed.dart | 258 +++ .../provider/label_repositories_provider.dart | 35 - .../repository/saved_view_repository.dart | 84 +- .../saved_view_repository_state.dart | 15 + .../saved_view_repository_state.freezed.dart | 167 ++ .../impl/correspondent_repository_state.dart | 31 - .../impl/document_type_repository_state.dart | 29 - .../impl/saved_view_repository_state.dart | 29 - .../impl/storage_path_repository_state.dart | 29 - .../state/impl/tag_repository_state.dart | 29 - .../state/indexed_repository_state.dart | 16 - .../cubit/document_bulk_action_cubit.dart | 78 +- .../document_bulk_action_cubit.freezed.dart | 269 ++++ .../cubit/document_bulk_action_state.dart | 49 +- .../view/widgets/bulk_edit_page.dart | 23 + .../widgets/bulk_edit_tags_bottom_sheet.dart | 193 ++- .../widgets/label_bulk_selection_widget.dart | 30 + .../cubit/document_details_cubit.dart | 23 +- .../cubit/document_details_cubit.freezed.dart | 350 +++++ .../cubit/document_details_state.dart | 50 +- .../view/pages/document_details_page.dart | 1 + .../widgets/document_overview_widget.dart | 18 +- .../cubit/document_edit_cubit.dart | 67 +- .../cubit/document_edit_cubit.freezed.dart | 256 +++ .../cubit/document_edit_state.dart | 49 +- .../view/document_edit_page.dart | 5 +- .../documents/cubit/documents_cubit.dart | 23 +- .../documents/cubit/documents_state.dart | 21 +- .../view/widgets/adaptive_documents_view.dart | 161 +- .../widgets/items/document_detailed_item.dart | 8 +- .../widgets/items/document_grid_item.dart | 10 +- .../view/widgets/items/document_item.dart | 9 + .../widgets/items/document_list_item.dart | 191 ++- .../document_selection_sliver_app_bar.dart | 24 +- .../view/widgets/sort_documents_button.dart | 9 +- .../edit_label/cubit/edit_label_cubit.dart | 46 +- .../cubit/edit_label_cubit.freezed.dart | 237 +++ .../edit_label/cubit/edit_label_state.dart | 15 +- .../edit_label/view/add_label_page.dart | 10 +- .../edit_label/view/edit_label_page.dart | 21 +- .../view/impl/add_correspondent_page.dart | 8 +- .../view/impl/add_document_type_page.dart | 8 +- .../view/impl/add_storage_path_page.dart | 8 +- .../edit_label/view/impl/add_tag_page.dart | 8 +- .../view/impl/edit_correspondent_page.dart | 10 +- .../view/impl/edit_document_type_page.dart | 9 +- .../view/impl/edit_storage_path_page.dart | 10 +- .../edit_label/view/impl/edit_tag_page.dart | 10 +- lib/features/home/view/home_page.dart | 33 +- .../view/widget/verify_identity_page.dart | 5 +- lib/features/inbox/cubit/inbox_cubit.dart | 56 +- lib/features/inbox/cubit/inbox_state.dart | 24 +- .../inbox/view/widgets/inbox_item.dart | 178 +-- .../view/widgets/correspondent_widget.dart | 38 +- lib/features/labels/cubit/label_cubit.dart | 85 +- .../labels/cubit/label_cubit.freezed.dart | 237 +++ .../labels/cubit/label_cubit_mixin.dart | 104 ++ lib/features/labels/cubit/label_state.dart | 24 +- .../correspondent_bloc_provider.dart | 24 - .../document_type_bloc_provider.dart | 21 - .../cubit/providers/labels_bloc_provider.dart | 43 - .../providers/storage_path_bloc_provider.dart | 21 - .../cubit/providers/tag_bloc_provider.dart | 21 - .../view/widgets/document_type_widget.dart | 34 +- .../view/widgets/storage_path_widget.dart | 33 +- .../tags/view/widgets/tags_form_field.dart | 13 +- .../labels/tags/view/widgets/tags_widget.dart | 64 +- .../labels/view/pages/labels_page.dart | 58 +- .../labels/view/widgets/label_item.dart | 1 + .../labels/view/widgets/label_tab_view.dart | 106 +- .../labels/view/widgets/label_text.dart | 27 +- .../cubit/linked_documents_cubit.dart | 15 + .../cubit/linked_documents_state.dart | 18 + .../view/linked_documents_page.dart | 4 + .../saved_view/cubit/saved_view_cubit.dart | 65 +- .../cubit/saved_view_cubit.freezed.dart | 1382 +++++++++++++++++ .../saved_view/cubit/saved_view_state.dart | 50 +- .../saved_view/view/add_saved_view_page.dart | 38 +- .../saved_view/view/saved_view_list.dart | 90 +- .../view/dialogs/account_settings_dialog.dart | 5 +- lib/main.dart | 23 +- lib/routes/document_details_route.dart | 5 +- .../paperless_saved_views_api.dart | 2 +- .../paperless_saved_views_api_impl.dart | 2 +- .../paperless_api/lib/src/request_utils.dart | 2 +- pubspec.lock | 16 + pubspec.yaml | 2 + 95 files changed, 4790 insertions(+), 1823 deletions(-) delete mode 100644 lib/core/repository/base_repository.dart delete mode 100644 lib/core/repository/impl/correspondent_repository_impl.dart delete mode 100644 lib/core/repository/impl/document_type_repository_impl.dart delete mode 100644 lib/core/repository/impl/saved_view_repository_impl.dart delete mode 100644 lib/core/repository/impl/storage_path_repository_impl.dart delete mode 100644 lib/core/repository/impl/tag_repository_impl.dart create mode 100644 lib/core/repository/label_repository_state.dart create mode 100644 lib/core/repository/label_repository_state.freezed.dart delete mode 100644 lib/core/repository/provider/label_repositories_provider.dart create mode 100644 lib/core/repository/saved_view_repository_state.dart create mode 100644 lib/core/repository/saved_view_repository_state.freezed.dart delete mode 100644 lib/core/repository/state/impl/correspondent_repository_state.dart delete mode 100644 lib/core/repository/state/impl/document_type_repository_state.dart delete mode 100644 lib/core/repository/state/impl/saved_view_repository_state.dart delete mode 100644 lib/core/repository/state/impl/storage_path_repository_state.dart delete mode 100644 lib/core/repository/state/impl/tag_repository_state.dart delete mode 100644 lib/core/repository/state/indexed_repository_state.dart create mode 100644 lib/features/document_bulk_action/cubit/document_bulk_action_cubit.freezed.dart create mode 100644 lib/features/document_bulk_action/view/widgets/bulk_edit_page.dart create mode 100644 lib/features/document_bulk_action/view/widgets/label_bulk_selection_widget.dart create mode 100644 lib/features/document_details/cubit/document_details_cubit.freezed.dart create mode 100644 lib/features/document_edit/cubit/document_edit_cubit.freezed.dart create mode 100644 lib/features/edit_label/cubit/edit_label_cubit.freezed.dart create mode 100644 lib/features/labels/cubit/label_cubit.freezed.dart create mode 100644 lib/features/labels/cubit/label_cubit_mixin.dart delete mode 100644 lib/features/labels/cubit/providers/correspondent_bloc_provider.dart delete mode 100644 lib/features/labels/cubit/providers/document_type_bloc_provider.dart delete mode 100644 lib/features/labels/cubit/providers/labels_bloc_provider.dart delete mode 100644 lib/features/labels/cubit/providers/storage_path_bloc_provider.dart delete mode 100644 lib/features/labels/cubit/providers/tag_bloc_provider.dart create mode 100644 lib/features/saved_view/cubit/saved_view_cubit.freezed.dart diff --git a/lib/core/repository/base_repository.dart b/lib/core/repository/base_repository.dart deleted file mode 100644 index 1eb57c4..0000000 --- a/lib/core/repository/base_repository.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; -import 'package:rxdart/subjects.dart'; - -/// -/// Base repository class which all repositories should implement -/// -abstract class BaseRepository extends Cubit> - with HydratedMixin { - final IndexedRepositoryState _initialState; - - BaseRepository(this._initialState) : super(_initialState) { - hydrate(); - } - - Stream?> get values => - BehaviorSubject.seeded(state)..addStream(super.stream); - - IndexedRepositoryState? get current => state; - - bool get isInitialized => state.hasLoaded; - - Future create(T object); - Future find(int id); - Future> findAll([Iterable? ids]); - Future update(T object); - Future delete(T object); - - @override - Future clear() async { - await super.clear(); - emit(_initialState); - } -} diff --git a/lib/core/repository/impl/correspondent_repository_impl.dart b/lib/core/repository/impl/correspondent_repository_impl.dart deleted file mode 100644 index 7227c58..0000000 --- a/lib/core/repository/impl/correspondent_repository_impl.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; - -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; - -class CorrespondentRepositoryImpl extends LabelRepository { - final PaperlessLabelsApi _api; - - CorrespondentRepositoryImpl(this._api) - : super(const CorrespondentRepositoryState()); - - @override - Future create(Correspondent correspondent) async { - final created = await _api.saveCorrespondent(correspondent); - final updatedState = {...state.values ?? {}} - ..putIfAbsent(created.id!, () => created); - emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true)); - return created; - } - - @override - Future delete(Correspondent correspondent) async { - await _api.deleteCorrespondent(correspondent); - final updatedState = {...state.values ?? {}} - ..removeWhere((k, v) => k == correspondent.id); - emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true)); - return correspondent.id!; - } - - @override - Future find(int id) async { - final correspondent = await _api.getCorrespondent(id); - if (correspondent != null) { - final updatedState = {...state.values ?? {}}..[id] = correspondent; - emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true)); - return correspondent; - } - return null; - } - - @override - Future> findAll([Iterable? ids]) async { - final correspondents = await _api.getCorrespondents(ids); - final updatedState = {...state.values ?? {}} - ..addEntries(correspondents.map((e) => MapEntry(e.id!, e))); - emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true)); - return correspondents; - } - - @override - Future update(Correspondent correspondent) async { - final updated = await _api.updateCorrespondent(correspondent); - final updatedState = {...state.values ?? {}} - ..update(updated.id!, (_) => updated); - emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true)); - return updated; - } - - @override - CorrespondentRepositoryState fromJson(Map json) { - return CorrespondentRepositoryState.fromJson(json); - } - - @override - Map toJson(covariant CorrespondentRepositoryState state) { - return state.toJson(); - } -} diff --git a/lib/core/repository/impl/document_type_repository_impl.dart b/lib/core/repository/impl/document_type_repository_impl.dart deleted file mode 100644 index 5fd7a87..0000000 --- a/lib/core/repository/impl/document_type_repository_impl.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; - -class DocumentTypeRepositoryImpl extends LabelRepository { - final PaperlessLabelsApi _api; - - DocumentTypeRepositoryImpl(this._api) - : super(const DocumentTypeRepositoryState()); - - @override - Future create(DocumentType documentType) async { - final created = await _api.saveDocumentType(documentType); - final updatedState = {...state.values ?? {}} - ..putIfAbsent(created.id!, () => created); - emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true)); - return created; - } - - @override - Future delete(DocumentType documentType) async { - await _api.deleteDocumentType(documentType); - final updatedState = {...state.values ?? {}} - ..removeWhere((k, v) => k == documentType.id); - emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true)); - return documentType.id!; - } - - @override - Future find(int id) async { - final documentType = await _api.getDocumentType(id); - if (documentType != null) { - final updatedState = {...state.values ?? {}}..[id] = documentType; - emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true)); - return documentType; - } - return null; - } - - @override - Future> findAll([Iterable? ids]) async { - final documentTypes = await _api.getDocumentTypes(ids); - final updatedState = {...state.values ?? {}} - ..addEntries(documentTypes.map((e) => MapEntry(e.id!, e))); - emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true)); - return documentTypes; - } - - @override - Future update(DocumentType documentType) async { - final updated = await _api.updateDocumentType(documentType); - final updatedState = {...state.values ?? {}} - ..update(updated.id!, (_) => updated); - emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true)); - return updated; - } - - @override - DocumentTypeRepositoryState fromJson(Map json) { - return DocumentTypeRepositoryState.fromJson(json); - } - - @override - Map toJson(covariant DocumentTypeRepositoryState state) { - return state.toJson(); - } -} diff --git a/lib/core/repository/impl/saved_view_repository_impl.dart b/lib/core/repository/impl/saved_view_repository_impl.dart deleted file mode 100644 index 18eceed..0000000 --- a/lib/core/repository/impl/saved_view_repository_impl.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart'; - -class SavedViewRepositoryImpl extends SavedViewRepository { - final PaperlessSavedViewsApi _api; - - SavedViewRepositoryImpl(this._api) : super(const SavedViewRepositoryState()); - - @override - Future create(SavedView object) async { - final created = await _api.save(object); - final updatedState = {...state.values ?? {}} - ..putIfAbsent(created.id!, () => created); - emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true)); - return created; - } - - @override - Future delete(SavedView view) async { - await _api.delete(view); - final updatedState = {...state.values ?? {}}..remove(view.id); - emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true)); - return view.id!; - } - - @override - Future find(int id) async { - final found = await _api.find(id); - final updatedState = {...state.values ?? {}} - ..update(id, (_) => found, ifAbsent: () => found); - emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true)); - return found; - } - - @override - Future> findAll([Iterable? ids]) async { - final found = await _api.findAll(ids); - final updatedState = { - ...state.values ?? {}, - ...{for (final view in found) view.id!: view}, - }; - emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true)); - return found; - } - - @override - Future update(SavedView object) { - throw UnimplementedError( - "Saved view update is not yet implemented as it is not supported by the API."); - } - - @override - SavedViewRepositoryState fromJson(Map json) { - return SavedViewRepositoryState.fromJson(json); - } - - @override - Map toJson(covariant SavedViewRepositoryState state) { - return state.toJson(); - } -} diff --git a/lib/core/repository/impl/storage_path_repository_impl.dart b/lib/core/repository/impl/storage_path_repository_impl.dart deleted file mode 100644 index 1fb54ea..0000000 --- a/lib/core/repository/impl/storage_path_repository_impl.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; -import 'package:rxdart/rxdart.dart' show BehaviorSubject; - -class StoragePathRepositoryImpl extends LabelRepository { - final PaperlessLabelsApi _api; - - StoragePathRepositoryImpl(this._api) - : super(const StoragePathRepositoryState()); - - @override - Future create(StoragePath storagePath) async { - final created = await _api.saveStoragePath(storagePath); - final updatedState = {...state.values ?? {}} - ..putIfAbsent(created.id!, () => created); - emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true)); - return created; - } - - @override - Future delete(StoragePath storagePath) async { - await _api.deleteStoragePath(storagePath); - final updatedState = {...state.values ?? {}} - ..removeWhere((k, v) => k == storagePath.id); - emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true)); - return storagePath.id!; - } - - @override - Future find(int id) async { - final storagePath = await _api.getStoragePath(id); - if (storagePath != null) { - final updatedState = {...state.values ?? {}}..[id] = storagePath; - emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true)); - return storagePath; - } - return null; - } - - @override - Future> findAll([Iterable? ids]) async { - final storagePaths = await _api.getStoragePaths(ids); - final updatedState = {...state.values ?? {}} - ..addEntries(storagePaths.map((e) => MapEntry(e.id!, e))); - emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true)); - return storagePaths; - } - - @override - Future update(StoragePath storagePath) async { - final updated = await _api.updateStoragePath(storagePath); - final updatedState = {...state.values ?? {}} - ..update(updated.id!, (_) => updated); - emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true)); - return updated; - } - - @override - StoragePathRepositoryState fromJson(Map json) { - return StoragePathRepositoryState.fromJson(json); - } - - @override - Map toJson(covariant StoragePathRepositoryState state) { - return state.toJson(); - } -} diff --git a/lib/core/repository/impl/tag_repository_impl.dart b/lib/core/repository/impl/tag_repository_impl.dart deleted file mode 100644 index a39a77b..0000000 --- a/lib/core/repository/impl/tag_repository_impl.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; - -class TagRepositoryImpl extends LabelRepository { - final PaperlessLabelsApi _api; - - TagRepositoryImpl(this._api) : super(const TagRepositoryState()); - - @override - Future create(Tag object) async { - final created = await _api.saveTag(object); - final updatedState = {...state.values ?? {}} - ..putIfAbsent(created.id!, () => created); - emit(TagRepositoryState(values: updatedState, hasLoaded: true)); - return created; - } - - @override - Future delete(Tag tag) async { - await _api.deleteTag(tag); - final updatedState = {...state.values ?? {}} - ..removeWhere((k, v) => k == tag.id); - emit(TagRepositoryState(values: updatedState, hasLoaded: true)); - return tag.id!; - } - - @override - Future find(int id) async { - final tag = await _api.getTag(id); - if (tag != null) { - final updatedState = {...state.values ?? {}}..[id] = tag; - emit(TagRepositoryState(values: updatedState, hasLoaded: true)); - return tag; - } - return null; - } - - @override - Future> findAll([Iterable? ids]) async { - final tags = await _api.getTags(ids); - final updatedState = {...state.values ?? {}} - ..addEntries(tags.map((e) => MapEntry(e.id!, e))); - emit(TagRepositoryState(values: updatedState, hasLoaded: true)); - return tags; - } - - @override - Future update(Tag tag) async { - final updated = await _api.updateTag(tag); - final updatedState = {...state.values ?? {}} - ..update(updated.id!, (_) => updated); - emit(TagRepositoryState(values: updatedState, hasLoaded: true)); - return updated; - } - - @override - TagRepositoryState? fromJson(Map json) { - return TagRepositoryState.fromJson(json); - } - - @override - Map? toJson(covariant TagRepositoryState state) { - return state.toJson(); - } -} diff --git a/lib/core/repository/label_repository.dart b/lib/core/repository/label_repository.dart index 8fe3458..441993e 100644 --- a/lib/core/repository/label_repository.dart +++ b/lib/core/repository/label_repository.dart @@ -1,7 +1,218 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/base_repository.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; +import 'dart:async'; -abstract class LabelRepository extends BaseRepository { - LabelRepository(IndexedRepositoryState initial) : super(initial); +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/repository/label_repository_state.dart'; + +class LabelRepository extends HydratedCubit { + final PaperlessLabelsApi _api; + final Map _subscribers = {}; + + void subscribe( + Object source, { + required void Function(LabelRepositoryState) onChanged, + }) { + _subscribers.putIfAbsent(source, () { + onChanged(state); + return stream.listen((event) => onChanged(event)); + }); + } + + void unsubscribe(Object source) async { + await _subscribers[source]?.cancel(); + _subscribers.remove(source); + } + + LabelRepository(this._api) : super(const LabelRepositoryState()); + + Future createTag(Tag object) async { + final created = await _api.saveTag(object); + final updatedState = {...state.tags} + ..putIfAbsent(created.id!, () => created); + emit(state.copyWith(tags: updatedState)); + return created; + } + + Future deleteTag(Tag tag) async { + await _api.deleteTag(tag); + final updatedState = {...state.tags}..removeWhere((k, v) => k == tag.id); + emit(state.copyWith(tags: updatedState)); + return tag.id!; + } + + Future findTag(int id) async { + final tag = await _api.getTag(id); + if (tag != null) { + final updatedState = {...state.tags}..[id] = tag; + emit(state.copyWith(tags: updatedState)); + return tag; + } + return null; + } + + Future> findAllTags([Iterable? ids]) async { + final tags = await _api.getTags(ids); + final updatedState = {...state.tags} + ..addEntries(tags.map((e) => MapEntry(e.id!, e))); + emit(state.copyWith(tags: updatedState)); + return tags; + } + + Future updateTag(Tag tag) async { + final updated = await _api.updateTag(tag); + final updatedState = {...state.tags}..update(updated.id!, (_) => updated); + emit(state.copyWith(tags: updatedState)); + return updated; + } + + Future createCorrespondent(Correspondent correspondent) async { + final created = await _api.saveCorrespondent(correspondent); + final updatedState = {...state.correspondents} + ..putIfAbsent(created.id!, () => created); + emit(state.copyWith(correspondents: updatedState)); + return created; + } + + Future deleteCorrespondent(Correspondent correspondent) async { + await _api.deleteCorrespondent(correspondent); + final updatedState = {...state.correspondents} + ..removeWhere((k, v) => k == correspondent.id); + emit(state.copyWith(correspondents: updatedState)); + + return correspondent.id!; + } + + Future findCorrespondent(int id) async { + final correspondent = await _api.getCorrespondent(id); + if (correspondent != null) { + final updatedState = {...state.correspondents}..[id] = correspondent; + emit(state.copyWith(correspondents: updatedState)); + + return correspondent; + } + return null; + } + + Future> findAllCorrespondents( + [Iterable? ids]) async { + final correspondents = await _api.getCorrespondents(ids); + final updatedState = {...state.correspondents} + ..addEntries(correspondents.map((e) => MapEntry(e.id!, e))); + emit(state.copyWith(correspondents: updatedState)); + + return correspondents; + } + + Future updateCorrespondent(Correspondent correspondent) async { + final updated = await _api.updateCorrespondent(correspondent); + final updatedState = {...state.correspondents} + ..update(updated.id!, (_) => updated); + emit(state.copyWith(correspondents: updatedState)); + + return updated; + } + + Future createDocumentType(DocumentType documentType) async { + final created = await _api.saveDocumentType(documentType); + final updatedState = {...state.documentTypes} + ..putIfAbsent(created.id!, () => created); + emit(state.copyWith(documentTypes: updatedState)); + return created; + } + + Future deleteDocumentType(DocumentType documentType) async { + await _api.deleteDocumentType(documentType); + final updatedState = {...state.documentTypes} + ..removeWhere((k, v) => k == documentType.id); + emit(state.copyWith(documentTypes: updatedState)); + return documentType.id!; + } + + Future findDocumentType(int id) async { + final documentType = await _api.getDocumentType(id); + if (documentType != null) { + final updatedState = {...state.documentTypes}..[id] = documentType; + emit(state.copyWith(documentTypes: updatedState)); + return documentType; + } + return null; + } + + Future> findAllDocumentTypes( + [Iterable? ids]) async { + final documentTypes = await _api.getDocumentTypes(ids); + final updatedState = {...state.documentTypes} + ..addEntries(documentTypes.map((e) => MapEntry(e.id!, e))); + emit(state.copyWith(documentTypes: updatedState)); + return documentTypes; + } + + Future updateDocumentType(DocumentType documentType) async { + final updated = await _api.updateDocumentType(documentType); + final updatedState = {...state.documentTypes} + ..update(updated.id!, (_) => updated); + emit(state.copyWith(documentTypes: updatedState)); + return updated; + } + + Future createStoragePath(StoragePath storagePath) async { + final created = await _api.saveStoragePath(storagePath); + final updatedState = {...state.storagePaths} + ..putIfAbsent(created.id!, () => created); + emit(state.copyWith(storagePaths: updatedState)); + return created; + } + + Future deleteStoragePath(StoragePath storagePath) async { + await _api.deleteStoragePath(storagePath); + final updatedState = {...state.storagePaths} + ..removeWhere((k, v) => k == storagePath.id); + emit(state.copyWith(storagePaths: updatedState)); + return storagePath.id!; + } + + Future findStoragePath(int id) async { + final storagePath = await _api.getStoragePath(id); + if (storagePath != null) { + final updatedState = {...state.storagePaths}..[id] = storagePath; + emit(state.copyWith(storagePaths: updatedState)); + return storagePath; + } + return null; + } + + Future> findAllStoragePaths( + [Iterable? ids]) async { + final storagePaths = await _api.getStoragePaths(ids); + final updatedState = {...state.storagePaths} + ..addEntries(storagePaths.map((e) => MapEntry(e.id!, e))); + emit(state.copyWith(storagePaths: updatedState)); + return storagePaths; + } + + Future updateStoragePath(StoragePath storagePath) async { + final updated = await _api.updateStoragePath(storagePath); + final updatedState = {...state.storagePaths} + ..update(updated.id!, (_) => updated); + emit(state.copyWith(storagePaths: updatedState)); + return updated; + } + + @override + Future close() { + _subscribers.forEach((key, subscription) { + subscription.cancel(); + }); + return super.close(); + } + + @override + LabelRepositoryState? fromJson(Map json) { + return LabelRepositoryState.fromJson(json); + } + + @override + Map? toJson(LabelRepositoryState state) { + return state.toJson(); + } } diff --git a/lib/core/repository/label_repository_state.dart b/lib/core/repository/label_repository_state.dart new file mode 100644 index 0000000..26e7d87 --- /dev/null +++ b/lib/core/repository/label_repository_state.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paperless_api/paperless_api.dart'; + +part 'label_repository_state.freezed.dart'; +part 'label_repository_state.g.dart'; + +@freezed +class LabelRepositoryState with _$LabelRepositoryState { + const factory LabelRepositoryState({ + @Default({}) Map correspondents, + @Default({}) Map documentTypes, + @Default({}) Map tags, + @Default({}) Map storagePaths, + }) = _LabelRepositoryState; + + factory LabelRepositoryState.fromJson(Map json) => + _$LabelRepositoryStateFromJson(json); +} diff --git a/lib/core/repository/label_repository_state.freezed.dart b/lib/core/repository/label_repository_state.freezed.dart new file mode 100644 index 0000000..faf36ef --- /dev/null +++ b/lib/core/repository/label_repository_state.freezed.dart @@ -0,0 +1,258 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'label_repository_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +LabelRepositoryState _$LabelRepositoryStateFromJson(Map json) { + return _LabelRepositoryState.fromJson(json); +} + +/// @nodoc +mixin _$LabelRepositoryState { + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $LabelRepositoryStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LabelRepositoryStateCopyWith<$Res> { + factory $LabelRepositoryStateCopyWith(LabelRepositoryState value, + $Res Function(LabelRepositoryState) then) = + _$LabelRepositoryStateCopyWithImpl<$Res, LabelRepositoryState>; + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$LabelRepositoryStateCopyWithImpl<$Res, + $Val extends LabelRepositoryState> + implements $LabelRepositoryStateCopyWith<$Res> { + _$LabelRepositoryStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_LabelRepositoryStateCopyWith<$Res> + implements $LabelRepositoryStateCopyWith<$Res> { + factory _$$_LabelRepositoryStateCopyWith(_$_LabelRepositoryState value, + $Res Function(_$_LabelRepositoryState) then) = + __$$_LabelRepositoryStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_LabelRepositoryStateCopyWithImpl<$Res> + extends _$LabelRepositoryStateCopyWithImpl<$Res, _$_LabelRepositoryState> + implements _$$_LabelRepositoryStateCopyWith<$Res> { + __$$_LabelRepositoryStateCopyWithImpl(_$_LabelRepositoryState _value, + $Res Function(_$_LabelRepositoryState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_LabelRepositoryState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_LabelRepositoryState implements _LabelRepositoryState { + const _$_LabelRepositoryState( + {final Map correspondents = const {}, + final Map documentTypes = const {}, + final Map tags = const {}, + final Map storagePaths = const {}}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + factory _$_LabelRepositoryState.fromJson(Map json) => + _$$_LabelRepositoryStateFromJson(json); + + final Map _correspondents; + @override + @JsonKey() + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + @JsonKey() + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + @JsonKey() + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + @JsonKey() + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'LabelRepositoryState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_LabelRepositoryState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_LabelRepositoryStateCopyWith<_$_LabelRepositoryState> get copyWith => + __$$_LabelRepositoryStateCopyWithImpl<_$_LabelRepositoryState>( + this, _$identity); + + @override + Map toJson() { + return _$$_LabelRepositoryStateToJson( + this, + ); + } +} + +abstract class _LabelRepositoryState implements LabelRepositoryState { + const factory _LabelRepositoryState( + {final Map correspondents, + final Map documentTypes, + final Map tags, + final Map storagePaths}) = _$_LabelRepositoryState; + + factory _LabelRepositoryState.fromJson(Map json) = + _$_LabelRepositoryState.fromJson; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_LabelRepositoryStateCopyWith<_$_LabelRepositoryState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/core/repository/provider/label_repositories_provider.dart b/lib/core/repository/provider/label_repositories_provider.dart deleted file mode 100644 index e9634be..0000000 --- a/lib/core/repository/provider/label_repositories_provider.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; - -class LabelRepositoriesProvider extends StatelessWidget { - final Widget child; - const LabelRepositoriesProvider({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return MultiRepositoryProvider( - providers: [ - RepositoryProvider( - create: (context) => context.read>(), - ), - RepositoryProvider( - create: (context) => context.read>(), - ), - RepositoryProvider( - create: (context) => context.read>(), - ), - RepositoryProvider( - create: (context) => context.read>(), - ), - ], - child: child, - ); - } -} diff --git a/lib/core/repository/saved_view_repository.dart b/lib/core/repository/saved_view_repository.dart index bb1c4e3..78d6b8e 100644 --- a/lib/core/repository/saved_view_repository.dart +++ b/lib/core/repository/saved_view_repository.dart @@ -1,8 +1,80 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/base_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; +import 'dart:async'; -abstract class SavedViewRepository extends BaseRepository { - SavedViewRepository(super.initialState); +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/repository/saved_view_repository_state.dart'; + +class SavedViewRepository extends HydratedCubit { + final PaperlessSavedViewsApi _api; + final Map _subscribers = {}; + + void subscribe( + Object source, + void Function(Map) onChanged, + ) { + _subscribers.putIfAbsent(source, () { + onChanged(state.savedViews); + return stream.listen((event) => onChanged(event.savedViews)); + }); + } + + void unsubscribe(Object source) async { + await _subscribers[source]?.cancel(); + _subscribers.remove(source); + } + + SavedViewRepository(this._api) : super(const SavedViewRepositoryState()); + + Future create(SavedView object) async { + final created = await _api.save(object); + final updatedState = {...state.savedViews} + ..putIfAbsent(created.id!, () => created); + emit(state.copyWith(savedViews: updatedState)); + return created; + } + + Future delete(SavedView view) async { + await _api.delete(view); + final updatedState = {...state.savedViews}..remove(view.id); + emit(state.copyWith(savedViews: updatedState)); + return view.id!; + } + + Future find(int id) async { + final found = await _api.find(id); + if (found != null) { + final updatedState = {...state.savedViews} + ..update(id, (_) => found, ifAbsent: () => found); + emit(state.copyWith(savedViews: updatedState)); + } + return found; + } + + Future> findAll([Iterable? ids]) async { + final found = await _api.findAll(ids); + final updatedState = { + ...state.savedViews, + ...{for (final view in found) view.id!: view}, + }; + emit(state.copyWith(savedViews: updatedState)); + return found; + } + + @override + Future close() { + _subscribers.forEach((key, subscription) { + subscription.cancel(); + }); + return super.close(); + } + + @override + SavedViewRepositoryState? fromJson(Map json) { + return SavedViewRepositoryState.fromJson(json); + } + + @override + Map? toJson(SavedViewRepositoryState state) { + return state.toJson(); + } } diff --git a/lib/core/repository/saved_view_repository_state.dart b/lib/core/repository/saved_view_repository_state.dart new file mode 100644 index 0000000..fb21b14 --- /dev/null +++ b/lib/core/repository/saved_view_repository_state.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paperless_api/paperless_api.dart'; + +part 'saved_view_repository_state.freezed.dart'; +part 'saved_view_repository_state.g.dart'; + +@freezed +class SavedViewRepositoryState with _$SavedViewRepositoryState { + const factory SavedViewRepositoryState({ + @Default({}) Map savedViews, + }) = _SavedViewRepositoryState; + + factory SavedViewRepositoryState.fromJson(Map json) => + _$SavedViewRepositoryStateFromJson(json); +} diff --git a/lib/core/repository/saved_view_repository_state.freezed.dart b/lib/core/repository/saved_view_repository_state.freezed.dart new file mode 100644 index 0000000..cec7882 --- /dev/null +++ b/lib/core/repository/saved_view_repository_state.freezed.dart @@ -0,0 +1,167 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'saved_view_repository_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +SavedViewRepositoryState _$SavedViewRepositoryStateFromJson( + Map json) { + return _SavedViewRepositoryState.fromJson(json); +} + +/// @nodoc +mixin _$SavedViewRepositoryState { + Map get savedViews => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SavedViewRepositoryStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SavedViewRepositoryStateCopyWith<$Res> { + factory $SavedViewRepositoryStateCopyWith(SavedViewRepositoryState value, + $Res Function(SavedViewRepositoryState) then) = + _$SavedViewRepositoryStateCopyWithImpl<$Res, SavedViewRepositoryState>; + @useResult + $Res call({Map savedViews}); +} + +/// @nodoc +class _$SavedViewRepositoryStateCopyWithImpl<$Res, + $Val extends SavedViewRepositoryState> + implements $SavedViewRepositoryStateCopyWith<$Res> { + _$SavedViewRepositoryStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? savedViews = null, + }) { + return _then(_value.copyWith( + savedViews: null == savedViews + ? _value.savedViews + : savedViews // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_SavedViewRepositoryStateCopyWith<$Res> + implements $SavedViewRepositoryStateCopyWith<$Res> { + factory _$$_SavedViewRepositoryStateCopyWith( + _$_SavedViewRepositoryState value, + $Res Function(_$_SavedViewRepositoryState) then) = + __$$_SavedViewRepositoryStateCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map savedViews}); +} + +/// @nodoc +class __$$_SavedViewRepositoryStateCopyWithImpl<$Res> + extends _$SavedViewRepositoryStateCopyWithImpl<$Res, + _$_SavedViewRepositoryState> + implements _$$_SavedViewRepositoryStateCopyWith<$Res> { + __$$_SavedViewRepositoryStateCopyWithImpl(_$_SavedViewRepositoryState _value, + $Res Function(_$_SavedViewRepositoryState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? savedViews = null, + }) { + return _then(_$_SavedViewRepositoryState( + savedViews: null == savedViews + ? _value._savedViews + : savedViews // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_SavedViewRepositoryState implements _SavedViewRepositoryState { + const _$_SavedViewRepositoryState( + {final Map savedViews = const {}}) + : _savedViews = savedViews; + + factory _$_SavedViewRepositoryState.fromJson(Map json) => + _$$_SavedViewRepositoryStateFromJson(json); + + final Map _savedViews; + @override + @JsonKey() + Map get savedViews { + if (_savedViews is EqualUnmodifiableMapView) return _savedViews; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_savedViews); + } + + @override + String toString() { + return 'SavedViewRepositoryState(savedViews: $savedViews)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SavedViewRepositoryState && + const DeepCollectionEquality() + .equals(other._savedViews, _savedViews)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_savedViews)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SavedViewRepositoryStateCopyWith<_$_SavedViewRepositoryState> + get copyWith => __$$_SavedViewRepositoryStateCopyWithImpl< + _$_SavedViewRepositoryState>(this, _$identity); + + @override + Map toJson() { + return _$$_SavedViewRepositoryStateToJson( + this, + ); + } +} + +abstract class _SavedViewRepositoryState implements SavedViewRepositoryState { + const factory _SavedViewRepositoryState( + {final Map savedViews}) = _$_SavedViewRepositoryState; + + factory _SavedViewRepositoryState.fromJson(Map json) = + _$_SavedViewRepositoryState.fromJson; + + @override + Map get savedViews; + @override + @JsonKey(ignore: true) + _$$_SavedViewRepositoryStateCopyWith<_$_SavedViewRepositoryState> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/core/repository/state/impl/correspondent_repository_state.dart b/lib/core/repository/state/impl/correspondent_repository_state.dart deleted file mode 100644 index fce9efb..0000000 --- a/lib/core/repository/state/impl/correspondent_repository_state.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; - -part 'correspondent_repository_state.g.dart'; - -@JsonSerializable() -class CorrespondentRepositoryState - extends IndexedRepositoryState { - const CorrespondentRepositoryState({ - super.values = const {}, - super.hasLoaded, - }); - - @override - CorrespondentRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }) { - return CorrespondentRepositoryState( - values: values ?? this.values, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } - - factory CorrespondentRepositoryState.fromJson(Map json) => - _$CorrespondentRepositoryStateFromJson(json); - - Map toJson() => _$CorrespondentRepositoryStateToJson(this); -} diff --git a/lib/core/repository/state/impl/document_type_repository_state.dart b/lib/core/repository/state/impl/document_type_repository_state.dart deleted file mode 100644 index 4a4ab1f..0000000 --- a/lib/core/repository/state/impl/document_type_repository_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'document_type_repository_state.g.dart'; - -@JsonSerializable() -class DocumentTypeRepositoryState extends IndexedRepositoryState { - const DocumentTypeRepositoryState({ - super.values = const {}, - super.hasLoaded, - }); - - @override - DocumentTypeRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }) { - return DocumentTypeRepositoryState( - values: values ?? this.values, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } - - factory DocumentTypeRepositoryState.fromJson(Map json) => - _$DocumentTypeRepositoryStateFromJson(json); - - Map toJson() => _$DocumentTypeRepositoryStateToJson(this); -} diff --git a/lib/core/repository/state/impl/saved_view_repository_state.dart b/lib/core/repository/state/impl/saved_view_repository_state.dart deleted file mode 100644 index 9cd7672..0000000 --- a/lib/core/repository/state/impl/saved_view_repository_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'saved_view_repository_state.g.dart'; - -@JsonSerializable() -class SavedViewRepositoryState extends IndexedRepositoryState { - const SavedViewRepositoryState({ - super.values = const {}, - super.hasLoaded = false, - }); - - @override - SavedViewRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }) { - return SavedViewRepositoryState( - values: values ?? this.values, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } - - factory SavedViewRepositoryState.fromJson(Map json) => - _$SavedViewRepositoryStateFromJson(json); - - Map toJson() => _$SavedViewRepositoryStateToJson(this); -} diff --git a/lib/core/repository/state/impl/storage_path_repository_state.dart b/lib/core/repository/state/impl/storage_path_repository_state.dart deleted file mode 100644 index b9ed856..0000000 --- a/lib/core/repository/state/impl/storage_path_repository_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'storage_path_repository_state.g.dart'; - -@JsonSerializable() -class StoragePathRepositoryState extends IndexedRepositoryState { - const StoragePathRepositoryState({ - super.values = const {}, - super.hasLoaded = false, - }); - - @override - StoragePathRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }) { - return StoragePathRepositoryState( - values: values ?? this.values, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } - - factory StoragePathRepositoryState.fromJson(Map json) => - _$StoragePathRepositoryStateFromJson(json); - - Map toJson() => _$StoragePathRepositoryStateToJson(this); -} diff --git a/lib/core/repository/state/impl/tag_repository_state.dart b/lib/core/repository/state/impl/tag_repository_state.dart deleted file mode 100644 index 4558bfe..0000000 --- a/lib/core/repository/state/impl/tag_repository_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; - -part 'tag_repository_state.g.dart'; - -@JsonSerializable() -class TagRepositoryState extends IndexedRepositoryState { - const TagRepositoryState({ - super.values = const {}, - super.hasLoaded = false, - }); - - @override - TagRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }) { - return TagRepositoryState( - values: values ?? this.values, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } - - factory TagRepositoryState.fromJson(Map json) => - _$TagRepositoryStateFromJson(json); - - Map toJson() => _$TagRepositoryStateToJson(this); -} diff --git a/lib/core/repository/state/indexed_repository_state.dart b/lib/core/repository/state/indexed_repository_state.dart deleted file mode 100644 index d3caee5..0000000 --- a/lib/core/repository/state/indexed_repository_state.dart +++ /dev/null @@ -1,16 +0,0 @@ -abstract class IndexedRepositoryState { - final Map? values; - final bool hasLoaded; - - const IndexedRepositoryState({ - required this.values, - this.hasLoaded = false, - }) : assert(!(values == null) || !hasLoaded); - - IndexedRepositoryState.loaded(this.values) : hasLoaded = true; - - IndexedRepositoryState copyWith({ - Map? values, - bool? hasLoaded, - }); -} diff --git a/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.dart b/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.dart index 5a88560..063d8a4 100644 --- a/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.dart +++ b/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.dart @@ -6,45 +6,28 @@ import 'package:equatable/equatable.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; part 'document_bulk_action_state.dart'; +part 'document_bulk_action_cubit.freezed.dart'; class DocumentBulkActionCubit extends Cubit { final PaperlessDocumentsApi _documentsApi; - final LabelRepository _correspondentRepository; - final LabelRepository _documentTypeRepository; - final LabelRepository _tagRepository; - final LabelRepository _storagePathRepository; + final LabelRepository _labelRepository; final DocumentChangedNotifier _notifier; - final List _subscriptions = []; - DocumentBulkActionCubit( this._documentsApi, - this._correspondentRepository, - this._documentTypeRepository, - this._tagRepository, - this._storagePathRepository, + this._labelRepository, this._notifier, { required List selection, }) : super( DocumentBulkActionState( selection: selection, - correspondentOptions: - (_correspondentRepository.current?.hasLoaded ?? false) - ? _correspondentRepository.current!.values! - : {}, - tagOptions: (_tagRepository.current?.hasLoaded ?? false) - ? _tagRepository.current!.values! - : {}, - documentTypeOptions: - (_documentTypeRepository.current?.hasLoaded ?? false) - ? _documentTypeRepository.current!.values! - : {}, - storagePathOptions: - (_storagePathRepository.current?.hasLoaded ?? false) - ? _storagePathRepository.current!.values! - : {}, + correspondents: _labelRepository.state.correspondents, + documentTypes: _labelRepository.state.documentTypes, + storagePaths: _labelRepository.state.storagePaths, + tags: _labelRepository.state.tags, ), ) { _notifier.subscribe( @@ -60,35 +43,18 @@ class DocumentBulkActionCubit extends Cubit { ); }, ); - _subscriptions.add( - _tagRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith(tagOptions: event!.values)); - } - }), - ); - _subscriptions.add( - _correspondentRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith( - correspondentOptions: event!.values, - )); - } - }), - ); - _subscriptions.add( - _documentTypeRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith(documentTypeOptions: event!.values)); - } - }), - ); - _subscriptions.add( - _storagePathRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith(storagePathOptions: event!.values)); - } - }), + _labelRepository.subscribe( + this, + onChanged: (labels) { + emit( + state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + storagePaths: labels.storagePaths, + tags: labels.tags, + ), + ); + }, ); } @@ -177,9 +143,7 @@ class DocumentBulkActionCubit extends Cubit { @override Future close() { _notifier.unsubscribe(this); - for (final sub in _subscriptions) { - sub.cancel(); - } + _labelRepository.unsubscribe(this); return super.close(); } } diff --git a/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.freezed.dart b/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.freezed.dart new file mode 100644 index 0000000..77db26b --- /dev/null +++ b/lib/features/document_bulk_action/cubit/document_bulk_action_cubit.freezed.dart @@ -0,0 +1,269 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'document_bulk_action_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$DocumentBulkActionState { + List get selection => throw _privateConstructorUsedError; + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $DocumentBulkActionStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DocumentBulkActionStateCopyWith<$Res> { + factory $DocumentBulkActionStateCopyWith(DocumentBulkActionState value, + $Res Function(DocumentBulkActionState) then) = + _$DocumentBulkActionStateCopyWithImpl<$Res, DocumentBulkActionState>; + @useResult + $Res call( + {List selection, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$DocumentBulkActionStateCopyWithImpl<$Res, + $Val extends DocumentBulkActionState> + implements $DocumentBulkActionStateCopyWith<$Res> { + _$DocumentBulkActionStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? selection = null, + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + selection: null == selection + ? _value.selection + : selection // ignore: cast_nullable_to_non_nullable + as List, + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_DocumentBulkActionStateCopyWith<$Res> + implements $DocumentBulkActionStateCopyWith<$Res> { + factory _$$_DocumentBulkActionStateCopyWith(_$_DocumentBulkActionState value, + $Res Function(_$_DocumentBulkActionState) then) = + __$$_DocumentBulkActionStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List selection, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_DocumentBulkActionStateCopyWithImpl<$Res> + extends _$DocumentBulkActionStateCopyWithImpl<$Res, + _$_DocumentBulkActionState> + implements _$$_DocumentBulkActionStateCopyWith<$Res> { + __$$_DocumentBulkActionStateCopyWithImpl(_$_DocumentBulkActionState _value, + $Res Function(_$_DocumentBulkActionState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? selection = null, + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_DocumentBulkActionState( + selection: null == selection + ? _value._selection + : selection // ignore: cast_nullable_to_non_nullable + as List, + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_DocumentBulkActionState extends _DocumentBulkActionState { + const _$_DocumentBulkActionState( + {required final List selection, + required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) + : _selection = selection, + _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths, + super._(); + + final List _selection; + @override + List get selection { + if (_selection is EqualUnmodifiableListView) return _selection; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_selection); + } + + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'DocumentBulkActionState(selection: $selection, correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_DocumentBulkActionState && + const DeepCollectionEquality() + .equals(other._selection, _selection) && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_selection), + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_DocumentBulkActionStateCopyWith<_$_DocumentBulkActionState> + get copyWith => + __$$_DocumentBulkActionStateCopyWithImpl<_$_DocumentBulkActionState>( + this, _$identity); +} + +abstract class _DocumentBulkActionState extends DocumentBulkActionState { + const factory _DocumentBulkActionState( + {required final List selection, + required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) = + _$_DocumentBulkActionState; + const _DocumentBulkActionState._() : super._(); + + @override + List get selection; + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_DocumentBulkActionStateCopyWith<_$_DocumentBulkActionState> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/document_bulk_action/cubit/document_bulk_action_state.dart b/lib/features/document_bulk_action/cubit/document_bulk_action_state.dart index 80e506a..11b77c6 100644 --- a/lib/features/document_bulk_action/cubit/document_bulk_action_state.dart +++ b/lib/features/document_bulk_action/cubit/document_bulk_action_state.dart @@ -1,44 +1,15 @@ part of 'document_bulk_action_cubit.dart'; -class DocumentBulkActionState extends Equatable { - final List selection; - final Map correspondentOptions; - final Map documentTypeOptions; - final Map tagOptions; - final Map storagePathOptions; - - const DocumentBulkActionState({ - this.correspondentOptions = const {}, - this.documentTypeOptions = const {}, - this.tagOptions = const {}, - this.storagePathOptions = const {}, - this.selection = const [], - }); - - @override - List get props => [ - selection, - correspondentOptions, - documentTypeOptions, - tagOptions, - storagePathOptions, - ]; +@freezed +class DocumentBulkActionState with _$DocumentBulkActionState { + const DocumentBulkActionState._(); + const factory DocumentBulkActionState({ + required List selection, + required Map correspondents, + required Map documentTypes, + required Map tags, + required Map storagePaths, + }) = _DocumentBulkActionState; Iterable get selectedIds => selection.map((d) => d.id); - - DocumentBulkActionState copyWith({ - List? selection, - Map? correspondentOptions, - Map? documentTypeOptions, - Map? tagOptions, - Map? storagePathOptions, - }) { - return DocumentBulkActionState( - selection: selection ?? this.selection, - correspondentOptions: correspondentOptions ?? this.correspondentOptions, - documentTypeOptions: documentTypeOptions ?? this.documentTypeOptions, - storagePathOptions: storagePathOptions ?? this.storagePathOptions, - tagOptions: tagOptions ?? this.tagOptions, - ); - } } diff --git a/lib/features/document_bulk_action/view/widgets/bulk_edit_page.dart b/lib/features/document_bulk_action/view/widgets/bulk_edit_page.dart new file mode 100644 index 0000000..4edb372 --- /dev/null +++ b/lib/features/document_bulk_action/view/widgets/bulk_edit_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:paperless_api/paperless_api.dart'; + +class BulkEditPage extends StatefulWidget { + final bool enableMultipleChoice; + final Map availableOptions; + + const BulkEditPage({ + super.key, + required this.enableMultipleChoice, + required this.availableOptions, + }); + + @override + State createState() => _BulkEditPageState(); +} + +class _BulkEditPageState extends State { + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/features/document_bulk_action/view/widgets/bulk_edit_tags_bottom_sheet.dart b/lib/features/document_bulk_action/view/widgets/bulk_edit_tags_bottom_sheet.dart index c2b4b18..42ae9aa 100644 --- a/lib/features/document_bulk_action/view/widgets/bulk_edit_tags_bottom_sheet.dart +++ b/lib/features/document_bulk_action/view/widgets/bulk_edit_tags_bottom_sheet.dart @@ -2,12 +2,11 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart'; -import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; -import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; class BulkEditTagsBottomSheet extends StatefulWidget { @@ -20,29 +19,42 @@ class BulkEditTagsBottomSheet extends StatefulWidget { class _BulkEditTagsBottomSheetState extends State { final _formKey = GlobalKey(); - List _tagsToRemove = []; - List _tagsToAdd = []; + final _textEditingController = TextEditingController(); + late Set _sharedTags; + late Set _nonSharedTags; + final Set _sharedTagsToRemove = {}; + final Set _nonSharedTagsToRemove = {}; + final Set _tagsToAdd = {}; + + @override + void initState() { + super.initState(); + final state = context.read().state; + _sharedTags = state.selection + .map((doc) => doc.tags) + .reduce((previousValue, element) => + previousValue.toSet().intersection(element.toSet())) + .toSet(); + print(_sharedTags.map((e) => e).join(", ")); + _nonSharedTags = state.selection + .map((doc) => doc.tags) + .flattened + .toSet() + .difference(_sharedTags) + .toSet(); + print(_nonSharedTags.map((e) => e).join(", ")); + } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final sharedTags = state.selection - .map((doc) => doc.tags) - .reduce((previousValue, element) => - previousValue.toSet().intersection(element.toSet())) - .toList(); - final nonSharedTags = state.selection - .map((doc) => doc.tags) - .flattened - .toSet() - .difference(sharedTags.toSet()) - .toList(); return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: BlocBuilder( builder: (context, state) { + print(state); return Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( @@ -54,25 +66,123 @@ class _BulkEditTagsBottomSheetState extends State { "Bulk modify tags", style: Theme.of(context).textTheme.titleLarge, ).paddedOnly(bottom: 24), - FormBuilder( - key: _formKey, - child: TagFormField( - initialValue: IdsTagsQuery( - sharedTags.map((tag) => IncludeTagIdQuery(tag)), + TypeAheadFormField( + textFieldConfiguration: TextFieldConfiguration( + controller: _textEditingController, + decoration: const InputDecoration( + labelText: "Tags", + hintText: "Start typing to add tags...", ), - name: "labelFormField", - selectableOptions: state.tagOptions, - allowCreation: false, - anyAssignedSelectable: false, - excludeAllowed: false, ), + onSuggestionSelected: (suggestion) { + setState(() { + _tagsToAdd.add(suggestion.id!); + }); + _textEditingController.clear(); + }, + itemBuilder: (context, option) { + return ListTile( + leading: SizedBox( + width: 32, + height: 32, + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: option.color!, + ), + ), + ), + title: Text(option.name), + ); + }, + suggestionsCallback: (pattern) { + final searchString = pattern.toLowerCase(); + return state.tags.entries + .where( + (tag) => tag.value.name + .toLowerCase() + .contains(searchString), + ) + .map((e) => e.key) + .toSet() + .difference(_sharedTags) + .difference(_nonSharedTags) + .map((e) => state.tags[e]!); + }, + ), + Text("Shared tags"), + Wrap( + children: _sharedTags + .map( + (tag) => RemovableTagWidget( + tag: state.tags[tag]!, + onDeleted: (tag) { + setState(() { + _sharedTagsToRemove.add(tag); + _sharedTags.remove(tag); + }); + }, + ), + ) + .toList(), ), const SizedBox(height: 8), - Text("Tags removed after apply"), - Wrap(), + Text("Non-shared tags"), + Wrap( + children: _nonSharedTags + .map( + (tag) => RemovableTagWidget( + tag: state.tags[tag]!, + onDeleted: (tag) { + setState(() { + _nonSharedTagsToRemove.add(tag); + _nonSharedTags.remove(tag); + }); + }, + ), + ) + .toList(), + ), + Text("Remove"), + Wrap( + children: _sharedTagsToRemove.map((tag) { + return RemovableTagWidget( + tag: state.tags[tag]!, + onDeleted: (tag) { + setState(() { + _sharedTagsToRemove.remove(tag); + _sharedTags.add(tag); + }); + }, + ); + }).toList() + + _nonSharedTagsToRemove.map((tag) { + return RemovableTagWidget( + tag: state.tags[tag]!, + onDeleted: (tag) { + setState(() { + _nonSharedTagsToRemove.remove(tag); + _nonSharedTags.add(tag); + }); + }, + ); + }).toList(), + ), const SizedBox(height: 8), - Text("Tags added after apply"), - Wrap(), + Text("Add"), + Wrap( + children: _tagsToAdd + .map( + (tag) => RemovableTagWidget( + tag: state.tags[tag]!, + onDeleted: (tag) { + setState(() { + _tagsToAdd.remove(tag); + }); + }), + ) + .toList(), + ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -107,3 +217,28 @@ class _BulkEditTagsBottomSheetState extends State { ); } } + +class RemovableTagWidget extends StatelessWidget { + final Tag tag; + final void Function(int tagId) onDeleted; + const RemovableTagWidget( + {super.key, required this.tag, required this.onDeleted}); + + @override + Widget build(BuildContext context) { + return Chip( + label: Text( + tag.name, + style: TextStyle( + color: tag.textColor, + ), + ), + onDeleted: () => onDeleted(tag.id!), + deleteIcon: Icon(Icons.clear), + backgroundColor: tag.color, + deleteIconColor: tag.textColor, + padding: EdgeInsets.zero, + side: BorderSide.none, + ); + } +} diff --git a/lib/features/document_bulk_action/view/widgets/label_bulk_selection_widget.dart b/lib/features/document_bulk_action/view/widgets/label_bulk_selection_widget.dart new file mode 100644 index 0000000..8bde625 --- /dev/null +++ b/lib/features/document_bulk_action/view/widgets/label_bulk_selection_widget.dart @@ -0,0 +1,30 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter/src/widgets/framework.dart'; +// import 'package:flutter/src/widgets/placeholder.dart'; + +// class LabelBulkSelectionWidget extends StatelessWidget { +// final int labelId; +// final String title; +// final bool selected; +// final bool excluded; +// final Widget Function(int id) leadingWidgetBuilder; +// final void Function(int id) onSelected; +// final void Function(int id) onUnselected; +// final void Function(int id) onRemoved; + +// const LabelBulkSelectionWidget({ +// super.key, +// required this.labelId, +// required this.title, +// required this.leadingWidgetBuilder, +// required this.onSelected, +// required this.onUnselected, +// required this.onRemoved, +// }); +// @override +// Widget build(BuildContext context) { +// return ListTile( +// title: Text(title), +// ); +// } +// } diff --git a/lib/features/document_details/cubit/document_details_cubit.dart b/lib/features/document_details/cubit/document_details_cubit.dart index 026b1f4..2b4ca23 100644 --- a/lib/features/document_details/cubit/document_details_cubit.dart +++ b/lib/features/document_details/cubit/document_details_cubit.dart @@ -2,32 +2,47 @@ import 'dart:async'; import 'dart:io'; import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:open_filex/open_filex.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; +import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/service/file_description.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; +part 'document_details_cubit.freezed.dart'; part 'document_details_state.dart'; class DocumentDetailsCubit extends Cubit { final PaperlessDocumentsApi _api; final DocumentChangedNotifier _notifier; final LocalNotificationService _notificationService; - + final LabelRepository _labelRepository; final List _subscriptions = []; DocumentDetailsCubit( this._api, + this._labelRepository, this._notifier, this._notificationService, { required DocumentModel initialDocument, - }) : super(DocumentDetailsState(document: initialDocument)) { + }) : super(DocumentDetailsState( + document: initialDocument, + )) { _notifier.subscribe(this, onUpdated: replace); + _labelRepository.subscribe( + this, + onChanged: (labels) => emit( + state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + tags: labels.tags, + storagePaths: labels.storagePaths, + ), + ), + ); loadSuggestions(); loadMetaData(); } diff --git a/lib/features/document_details/cubit/document_details_cubit.freezed.dart b/lib/features/document_details/cubit/document_details_cubit.freezed.dart new file mode 100644 index 0000000..4d4f483 --- /dev/null +++ b/lib/features/document_details/cubit/document_details_cubit.freezed.dart @@ -0,0 +1,350 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'document_details_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$DocumentDetailsState { + DocumentModel get document => throw _privateConstructorUsedError; + DocumentMetaData? get metaData => throw _privateConstructorUsedError; + bool get isFullContentLoaded => throw _privateConstructorUsedError; + String? get fullContent => throw _privateConstructorUsedError; + FieldSuggestions? get suggestions => throw _privateConstructorUsedError; + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $DocumentDetailsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DocumentDetailsStateCopyWith<$Res> { + factory $DocumentDetailsStateCopyWith(DocumentDetailsState value, + $Res Function(DocumentDetailsState) then) = + _$DocumentDetailsStateCopyWithImpl<$Res, DocumentDetailsState>; + @useResult + $Res call( + {DocumentModel document, + DocumentMetaData? metaData, + bool isFullContentLoaded, + String? fullContent, + FieldSuggestions? suggestions, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$DocumentDetailsStateCopyWithImpl<$Res, + $Val extends DocumentDetailsState> + implements $DocumentDetailsStateCopyWith<$Res> { + _$DocumentDetailsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? document = null, + Object? metaData = freezed, + Object? isFullContentLoaded = null, + Object? fullContent = freezed, + Object? suggestions = freezed, + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + document: null == document + ? _value.document + : document // ignore: cast_nullable_to_non_nullable + as DocumentModel, + metaData: freezed == metaData + ? _value.metaData + : metaData // ignore: cast_nullable_to_non_nullable + as DocumentMetaData?, + isFullContentLoaded: null == isFullContentLoaded + ? _value.isFullContentLoaded + : isFullContentLoaded // ignore: cast_nullable_to_non_nullable + as bool, + fullContent: freezed == fullContent + ? _value.fullContent + : fullContent // ignore: cast_nullable_to_non_nullable + as String?, + suggestions: freezed == suggestions + ? _value.suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as FieldSuggestions?, + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_DocumentDetailsStateCopyWith<$Res> + implements $DocumentDetailsStateCopyWith<$Res> { + factory _$$_DocumentDetailsStateCopyWith(_$_DocumentDetailsState value, + $Res Function(_$_DocumentDetailsState) then) = + __$$_DocumentDetailsStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {DocumentModel document, + DocumentMetaData? metaData, + bool isFullContentLoaded, + String? fullContent, + FieldSuggestions? suggestions, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_DocumentDetailsStateCopyWithImpl<$Res> + extends _$DocumentDetailsStateCopyWithImpl<$Res, _$_DocumentDetailsState> + implements _$$_DocumentDetailsStateCopyWith<$Res> { + __$$_DocumentDetailsStateCopyWithImpl(_$_DocumentDetailsState _value, + $Res Function(_$_DocumentDetailsState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? document = null, + Object? metaData = freezed, + Object? isFullContentLoaded = null, + Object? fullContent = freezed, + Object? suggestions = freezed, + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_DocumentDetailsState( + document: null == document + ? _value.document + : document // ignore: cast_nullable_to_non_nullable + as DocumentModel, + metaData: freezed == metaData + ? _value.metaData + : metaData // ignore: cast_nullable_to_non_nullable + as DocumentMetaData?, + isFullContentLoaded: null == isFullContentLoaded + ? _value.isFullContentLoaded + : isFullContentLoaded // ignore: cast_nullable_to_non_nullable + as bool, + fullContent: freezed == fullContent + ? _value.fullContent + : fullContent // ignore: cast_nullable_to_non_nullable + as String?, + suggestions: freezed == suggestions + ? _value.suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as FieldSuggestions?, + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_DocumentDetailsState implements _DocumentDetailsState { + const _$_DocumentDetailsState( + {required this.document, + this.metaData, + this.isFullContentLoaded = false, + this.fullContent, + this.suggestions, + final Map correspondents = const {}, + final Map documentTypes = const {}, + final Map tags = const {}, + final Map storagePaths = const {}}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + @override + final DocumentModel document; + @override + final DocumentMetaData? metaData; + @override + @JsonKey() + final bool isFullContentLoaded; + @override + final String? fullContent; + @override + final FieldSuggestions? suggestions; + final Map _correspondents; + @override + @JsonKey() + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + @JsonKey() + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + @JsonKey() + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + @JsonKey() + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'DocumentDetailsState(document: $document, metaData: $metaData, isFullContentLoaded: $isFullContentLoaded, fullContent: $fullContent, suggestions: $suggestions, correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_DocumentDetailsState && + (identical(other.document, document) || + other.document == document) && + (identical(other.metaData, metaData) || + other.metaData == metaData) && + (identical(other.isFullContentLoaded, isFullContentLoaded) || + other.isFullContentLoaded == isFullContentLoaded) && + (identical(other.fullContent, fullContent) || + other.fullContent == fullContent) && + (identical(other.suggestions, suggestions) || + other.suggestions == suggestions) && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + document, + metaData, + isFullContentLoaded, + fullContent, + suggestions, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_DocumentDetailsStateCopyWith<_$_DocumentDetailsState> get copyWith => + __$$_DocumentDetailsStateCopyWithImpl<_$_DocumentDetailsState>( + this, _$identity); +} + +abstract class _DocumentDetailsState implements DocumentDetailsState { + const factory _DocumentDetailsState( + {required final DocumentModel document, + final DocumentMetaData? metaData, + final bool isFullContentLoaded, + final String? fullContent, + final FieldSuggestions? suggestions, + final Map correspondents, + final Map documentTypes, + final Map tags, + final Map storagePaths}) = _$_DocumentDetailsState; + + @override + DocumentModel get document; + @override + DocumentMetaData? get metaData; + @override + bool get isFullContentLoaded; + @override + String? get fullContent; + @override + FieldSuggestions? get suggestions; + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_DocumentDetailsStateCopyWith<_$_DocumentDetailsState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/document_details/cubit/document_details_state.dart b/lib/features/document_details/cubit/document_details_state.dart index 35c2e1b..54a3751 100644 --- a/lib/features/document_details/cubit/document_details_state.dart +++ b/lib/features/document_details/cubit/document_details_state.dart @@ -1,42 +1,16 @@ part of 'document_details_cubit.dart'; -class DocumentDetailsState with EquatableMixin { - final DocumentModel document; - final DocumentMetaData? metaData; - final bool isFullContentLoaded; - final String? fullContent; - final FieldSuggestions suggestions; - - const DocumentDetailsState({ - required this.document, - this.metaData, - this.suggestions = const FieldSuggestions(), - this.isFullContentLoaded = false, - this.fullContent, - }); - - @override - List get props => [ - document, - suggestions, - isFullContentLoaded, - fullContent, - metaData, - ]; - - DocumentDetailsState copyWith({ - DocumentModel? document, - FieldSuggestions? suggestions, - bool? isFullContentLoaded, - String? fullContent, +@freezed +class DocumentDetailsState with _$DocumentDetailsState { + const factory DocumentDetailsState({ + required DocumentModel document, DocumentMetaData? metaData, - }) { - return DocumentDetailsState( - document: document ?? this.document, - suggestions: suggestions ?? this.suggestions, - isFullContentLoaded: isFullContentLoaded ?? this.isFullContentLoaded, - fullContent: fullContent ?? this.fullContent, - metaData: metaData ?? this.metaData, - ); - } + @Default(false) bool isFullContentLoaded, + String? fullContent, + FieldSuggestions? suggestions, + @Default({}) Map correspondents, + @Default({}) Map documentTypes, + @Default({}) Map tags, + @Default({}) Map storagePaths, + }) = _DocumentDetailsState; } diff --git a/lib/features/document_details/view/pages/document_details_page.dart b/lib/features/document_details/view/pages/document_details_page.dart index a0e5d17..0da1044 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -186,6 +186,7 @@ class _DocumentDetailsPageState extends State { document: state.document, itemSpacing: _itemSpacing, queryString: widget.titleAndContentQueryString, + ), DocumentContentWidget( isFullContentLoaded: state.isFullContentLoaded, diff --git a/lib/features/document_details/view/widgets/document_overview_widget.dart b/lib/features/document_details/view/widgets/document_overview_widget.dart index d498a8b..441396f 100644 --- a/lib/features/document_details/view/widgets/document_overview_widget.dart +++ b/lib/features/document_details/view/widgets/document_overview_widget.dart @@ -11,6 +11,10 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; class DocumentOverviewWidget extends StatelessWidget { final DocumentModel document; + final Map availableCorrespondents; + final Map availableDocumentTypes; + final Map availableTags; + final Map availableStoragePaths; final String? queryString; final double itemSpacing; const DocumentOverviewWidget({ @@ -18,6 +22,10 @@ class DocumentOverviewWidget extends StatelessWidget { required this.document, this.queryString, required this.itemSpacing, + required this.availableCorrespondents, + required this.availableDocumentTypes, + required this.availableTags, + required this.availableStoragePaths, }); @override @@ -47,7 +55,7 @@ class DocumentOverviewWidget extends StatelessWidget { label: S.of(context)!.documentType, content: LabelText( style: Theme.of(context).textTheme.bodyLarge, - id: document.documentType, + label: availableDocumentTypes[document.documentType], ), ).paddedOnly(bottom: itemSpacing), ), @@ -57,7 +65,7 @@ class DocumentOverviewWidget extends StatelessWidget { label: S.of(context)!.correspondent, content: LabelText( style: Theme.of(context).textTheme.bodyLarge, - id: document.correspondent, + label: availableCorrespondents[document.correspondent], ), ).paddedOnly(bottom: itemSpacing), ), @@ -65,8 +73,8 @@ class DocumentOverviewWidget extends StatelessWidget { visible: document.storagePath != null, child: DetailsItem( label: S.of(context)!.storagePath, - content: StoragePathWidget( - pathId: document.storagePath, + content: LabelText( + label: availableStoragePaths[document.storagePath], ), ).paddedOnly(bottom: itemSpacing), ), @@ -78,7 +86,7 @@ class DocumentOverviewWidget extends StatelessWidget { padding: const EdgeInsets.only(top: 8.0), child: TagsWidget( isClickable: false, - tagIds: document.tags, + tags: document.tags.map((e) => availableTags[e]!).toList(), ), ), ).paddedOnly(bottom: itemSpacing), diff --git a/lib/features/document_edit/cubit/document_edit_cubit.dart b/lib/features/document_edit/cubit/document_edit_cubit.dart index 33cbcea..23e3d35 100644 --- a/lib/features/document_edit/cubit/document_edit_cubit.dart +++ b/lib/features/document_edit/cubit/document_edit_cubit.dart @@ -1,66 +1,41 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; part 'document_edit_state.dart'; +part 'document_edit_cubit.freezed.dart'; class DocumentEditCubit extends Cubit { final DocumentModel _initialDocument; final PaperlessDocumentsApi _docsApi; final DocumentChangedNotifier _notifier; - final LabelRepository _correspondentRepository; - final LabelRepository _documentTypeRepository; - final LabelRepository _storagePathRepository; - final LabelRepository _tagRepository; + final LabelRepository _labelRepository; final List _subscriptions = []; DocumentEditCubit( - DocumentModel document, { - required PaperlessDocumentsApi documentsApi, - required LabelRepository correspondentRepository, - required LabelRepository documentTypeRepository, - required LabelRepository storagePathRepository, - required LabelRepository tagRepository, - required DocumentChangedNotifier notifier, + this._labelRepository, + this._docsApi, + this._notifier, { + required DocumentModel document, }) : _initialDocument = document, - _docsApi = documentsApi, - _correspondentRepository = correspondentRepository, - _documentTypeRepository = documentTypeRepository, - _storagePathRepository = storagePathRepository, - _tagRepository = tagRepository, - _notifier = notifier, super( DocumentEditState( document: document, - correspondents: correspondentRepository.current?.values ?? {}, - documentTypes: documentTypeRepository.current?.values ?? {}, - storagePaths: storagePathRepository.current?.values ?? {}, - tags: tagRepository.current?.values ?? {}, + correspondents: _labelRepository.state.correspondents, + documentTypes: _labelRepository.state.documentTypes, + storagePaths: _labelRepository.state.storagePaths, + tags: _labelRepository.state.tags, ), ) { _notifier.subscribe(this, onUpdated: replace); - _subscriptions.add( - _correspondentRepository.values - .listen((v) => emit(state.copyWith(correspondents: v?.values))), - ); - _subscriptions.add( - _documentTypeRepository.values - .listen((v) => emit(state.copyWith(documentTypes: v?.values))), - ); - _subscriptions.add( - _storagePathRepository.values - .listen((v) => emit(state.copyWith(storagePaths: v?.values))), - ); - _subscriptions.add( - _tagRepository.values.listen( - (v) => emit(state.copyWith(tags: v?.values)), - ), + _labelRepository.subscribe( + this, + onStateChanged: (labels) => emit(state.copyWith()), ); } @@ -70,20 +45,20 @@ class DocumentEditCubit extends Cubit { // Reload changed labels (documentCount property changes with removal/add) if (document.documentType != _initialDocument.documentType) { - _documentTypeRepository - .find((document.documentType ?? _initialDocument.documentType)!); + _labelRepository.findDocumentType( + (document.documentType ?? _initialDocument.documentType)!); } if (document.correspondent != _initialDocument.correspondent) { - _correspondentRepository - .find((document.correspondent ?? _initialDocument.correspondent)!); + _labelRepository.findCorrespondent( + (document.correspondent ?? _initialDocument.correspondent)!); } if (document.storagePath != _initialDocument.storagePath) { - _storagePathRepository - .find((document.storagePath ?? _initialDocument.storagePath)!); + _labelRepository.findStoragePath( + (document.storagePath ?? _initialDocument.storagePath)!); } if (!const DeepCollectionEquality.unordered() .equals(document.tags, _initialDocument.tags)) { - _tagRepository.findAll(document.tags); + _labelRepository.findAllTags(document.tags); } } diff --git a/lib/features/document_edit/cubit/document_edit_cubit.freezed.dart b/lib/features/document_edit/cubit/document_edit_cubit.freezed.dart new file mode 100644 index 0000000..f4f3a32 --- /dev/null +++ b/lib/features/document_edit/cubit/document_edit_cubit.freezed.dart @@ -0,0 +1,256 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'document_edit_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$DocumentEditState { + DocumentModel get document => throw _privateConstructorUsedError; + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $DocumentEditStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DocumentEditStateCopyWith<$Res> { + factory $DocumentEditStateCopyWith( + DocumentEditState value, $Res Function(DocumentEditState) then) = + _$DocumentEditStateCopyWithImpl<$Res, DocumentEditState>; + @useResult + $Res call( + {DocumentModel document, + Map correspondents, + Map documentTypes, + Map storagePaths, + Map tags}); +} + +/// @nodoc +class _$DocumentEditStateCopyWithImpl<$Res, $Val extends DocumentEditState> + implements $DocumentEditStateCopyWith<$Res> { + _$DocumentEditStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? document = null, + Object? correspondents = null, + Object? documentTypes = null, + Object? storagePaths = null, + Object? tags = null, + }) { + return _then(_value.copyWith( + document: null == document + ? _value.document + : document // ignore: cast_nullable_to_non_nullable + as DocumentModel, + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_DocumentEditStateCopyWith<$Res> + implements $DocumentEditStateCopyWith<$Res> { + factory _$$_DocumentEditStateCopyWith(_$_DocumentEditState value, + $Res Function(_$_DocumentEditState) then) = + __$$_DocumentEditStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {DocumentModel document, + Map correspondents, + Map documentTypes, + Map storagePaths, + Map tags}); +} + +/// @nodoc +class __$$_DocumentEditStateCopyWithImpl<$Res> + extends _$DocumentEditStateCopyWithImpl<$Res, _$_DocumentEditState> + implements _$$_DocumentEditStateCopyWith<$Res> { + __$$_DocumentEditStateCopyWithImpl( + _$_DocumentEditState _value, $Res Function(_$_DocumentEditState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? document = null, + Object? correspondents = null, + Object? documentTypes = null, + Object? storagePaths = null, + Object? tags = null, + }) { + return _then(_$_DocumentEditState( + document: null == document + ? _value.document + : document // ignore: cast_nullable_to_non_nullable + as DocumentModel, + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_DocumentEditState implements _DocumentEditState { + const _$_DocumentEditState( + {required this.document, + required final Map correspondents, + required final Map documentTypes, + required final Map storagePaths, + required final Map tags}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _storagePaths = storagePaths, + _tags = tags; + + @override + final DocumentModel document; + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + @override + String toString() { + return 'DocumentEditState(document: $document, correspondents: $correspondents, documentTypes: $documentTypes, storagePaths: $storagePaths, tags: $tags)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_DocumentEditState && + (identical(other.document, document) || + other.document == document) && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths) && + const DeepCollectionEquality().equals(other._tags, _tags)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + document, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_storagePaths), + const DeepCollectionEquality().hash(_tags)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_DocumentEditStateCopyWith<_$_DocumentEditState> get copyWith => + __$$_DocumentEditStateCopyWithImpl<_$_DocumentEditState>( + this, _$identity); +} + +abstract class _DocumentEditState implements DocumentEditState { + const factory _DocumentEditState( + {required final DocumentModel document, + required final Map correspondents, + required final Map documentTypes, + required final Map storagePaths, + required final Map tags}) = _$_DocumentEditState; + + @override + DocumentModel get document; + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get storagePaths; + @override + Map get tags; + @override + @JsonKey(ignore: true) + _$$_DocumentEditStateCopyWith<_$_DocumentEditState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/document_edit/cubit/document_edit_state.dart b/lib/features/document_edit/cubit/document_edit_state.dart index b2868f9..04f61cb 100644 --- a/lib/features/document_edit/cubit/document_edit_state.dart +++ b/lib/features/document_edit/cubit/document_edit_state.dart @@ -1,43 +1,12 @@ part of 'document_edit_cubit.dart'; -class DocumentEditState extends Equatable { - final DocumentModel document; - - final Map correspondents; - final Map documentTypes; - final Map storagePaths; - final Map tags; - - const DocumentEditState({ - required this.correspondents, - required this.documentTypes, - required this.storagePaths, - required this.tags, - required this.document, - }); - - @override - List get props => [ - correspondents, - documentTypes, - storagePaths, - tags, - document, - ]; - - DocumentEditState copyWith({ - Map? correspondents, - Map? documentTypes, - Map? storagePaths, - Map? tags, - DocumentModel? document, - }) { - return DocumentEditState( - document: document ?? this.document, - correspondents: correspondents ?? this.correspondents, - documentTypes: documentTypes ?? this.documentTypes, - storagePaths: storagePaths ?? this.storagePaths, - tags: tags ?? this.tags, - ); - } +@freezed +class DocumentEditState with _$DocumentEditState { + const factory DocumentEditState({ + required DocumentModel document, + required Map correspondents, + required Map documentTypes, + required Map storagePaths, + required Map tags, + }) = _DocumentEditState; } diff --git a/lib/features/document_edit/view/document_edit_page.dart b/lib/features/document_edit/view/document_edit_page.dart index 81cf859..ae91e34 100644 --- a/lib/features/document_edit/view/document_edit_page.dart +++ b/lib/features/document_edit/view/document_edit_page.dart @@ -194,8 +194,9 @@ class _DocumentEditPageState extends State { LabelFormField( notAssignedSelectable: false, formBuilderState: _formKey.currentState, - labelCreationWidgetBuilder: (initialValue) => RepositoryProvider( - create: (context) => context.read>(), + labelCreationWidgetBuilder: (initialValue) => + RepositoryProvider.value( + value: context.read(), child: AddStoragePathPage(initalName: initialValue), ), textFieldLabel: S.of(context)!.storagePath, diff --git a/lib/features/documents/cubit/documents_cubit.dart b/lib/features/documents/cubit/documents_cubit.dart index ca5345e..e97648c 100644 --- a/lib/features/documents/cubit/documents_cubit.dart +++ b/lib/features/documents/cubit/documents_cubit.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; +import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -19,10 +20,18 @@ class DocumentsCubit extends HydratedCubit @override final PaperlessDocumentsApi api; + final LabelRepository _labelRepository; + @override final DocumentChangedNotifier notifier; - DocumentsCubit(this.api, this.notifier) : super(const DocumentsState()) { + DocumentsCubit(this.api, this.notifier, this._labelRepository) + : super(DocumentsState( + correspondents: _labelRepository.state.correspondents, + documentTypes: _labelRepository.state.documentTypes, + storagePaths: _labelRepository.state.storagePaths, + tags: _labelRepository.state.tags, + )) { notifier.subscribe( this, onUpdated: (document) { @@ -45,6 +54,17 @@ class DocumentsCubit extends HydratedCubit ); }, ); + _labelRepository.subscribe( + this, + onStateChanged: (labels) => emit( + state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + storagePaths: labels.storagePaths, + tags: labels.tags, + ), + ), + ); } Future bulkDelete(List documents) async { @@ -101,6 +121,7 @@ class DocumentsCubit extends HydratedCubit @override Future close() { notifier.unsubscribe(this); + _labelRepository.unsubscribe(this); return super.close(); } diff --git a/lib/features/documents/cubit/documents_state.dart b/lib/features/documents/cubit/documents_state.dart index a60b878..95df536 100644 --- a/lib/features/documents/cubit/documents_state.dart +++ b/lib/features/documents/cubit/documents_state.dart @@ -1,10 +1,15 @@ part of 'documents_cubit.dart'; -@JsonSerializable() +@JsonSerializable(ignoreUnannotated: true) class DocumentsState extends DocumentPagingState { - @JsonKey(includeFromJson: false, includeToJson: false) final List selection; + final Map correspondents; + final Map documentTypes; + final Map tags; + final Map storagePaths; + + @JsonKey() final ViewType viewType; const DocumentsState({ @@ -14,6 +19,10 @@ class DocumentsState extends DocumentPagingState { super.filter = const DocumentFilter(), super.hasLoaded = false, super.isLoading = false, + this.correspondents = const {}, + this.documentTypes = const {}, + this.tags = const {}, + this.storagePaths = const {}, }); List get selectedIds => selection.map((e) => e.id).toList(); @@ -25,6 +34,10 @@ class DocumentsState extends DocumentPagingState { DocumentFilter? filter, List? selection, ViewType? viewType, + Map? correspondents, + Map? documentTypes, + Map? tags, + Map? storagePaths, }) { return DocumentsState( hasLoaded: hasLoaded ?? this.hasLoaded, @@ -33,6 +46,10 @@ class DocumentsState extends DocumentPagingState { filter: filter ?? this.filter, selection: selection ?? this.selection, viewType: viewType ?? this.viewType, + correspondents: correspondents ?? this.correspondents, + documentTypes: documentTypes ?? this.documentTypes, + tags: tags ?? this.tags, + storagePaths: storagePaths ?? this.storagePaths, ); } diff --git a/lib/features/documents/view/widgets/adaptive_documents_view.dart b/lib/features/documents/view/widgets/adaptive_documents_view.dart index 149c8e1..02e6bac 100644 --- a/lib/features/documents/view/widgets/adaptive_documents_view.dart +++ b/lib/features/documents/view/widgets/adaptive_documents_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart'; import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart'; @@ -25,7 +24,13 @@ abstract class AdaptiveDocumentsView extends StatelessWidget { final void Function(int? id)? onDocumentTypeSelected; final void Function(int? id)? onStoragePathSelected; - bool get showLoadingPlaceholder => (!hasLoaded && isLoading); + final Map correspondents; + final Map documentTypes; + final Map tags; + final Map storagePaths; + + bool get showLoadingPlaceholder => !hasLoaded && isLoading; + const AdaptiveDocumentsView({ super.key, this.selectedDocumentIds = const [], @@ -42,6 +47,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget { required this.isLoading, required this.hasLoaded, this.enableHeroAnimation = true, + required this.correspondents, + required this.documentTypes, + required this.tags, + required this.storagePaths, }); AdaptiveDocumentsView.fromPagedState( @@ -58,6 +67,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget { required this.hasInternetConnection, this.viewType = ViewType.list, this.selectedDocumentIds = const [], + required this.correspondents, + required this.documentTypes, + required this.tags, + required this.storagePaths, }) : documents = state.documents, isLoading = state.isLoading, hasLoaded = state.hasLoaded; @@ -80,6 +93,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView { super.enableHeroAnimation, required super.isLoading, required super.hasLoaded, + required super.correspondents, + required super.documentTypes, + required super.tags, + required super.storagePaths, }); @override @@ -96,27 +113,29 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView { Widget _buildListView() { if (showLoadingPlaceholder) { - return DocumentsListLoadingWidget.sliver(); + return const DocumentsListLoadingWidget.sliver(); } return SliverList( delegate: SliverChildBuilderDelegate( childCount: documents.length, (context, index) { final document = documents.elementAt(index); - return LabelRepositoriesProvider( - child: DocumentListItem( - isLabelClickable: isLabelClickable, - document: document, - onTap: onTap, - isSelected: selectedDocumentIds.contains(document.id), - onSelected: onSelected, - isSelectionActive: selectedDocumentIds.isNotEmpty, - onTagSelected: onTagSelected, - onCorrespondentSelected: onCorrespondentSelected, - onDocumentTypeSelected: onDocumentTypeSelected, - onStoragePathSelected: onStoragePathSelected, - enableHeroAnimation: enableHeroAnimation, - ), + return DocumentListItem( + isLabelClickable: isLabelClickable, + document: document, + onTap: onTap, + isSelected: selectedDocumentIds.contains(document.id), + onSelected: onSelected, + isSelectionActive: selectedDocumentIds.isNotEmpty, + onTagSelected: onTagSelected, + onCorrespondentSelected: onCorrespondentSelected, + onDocumentTypeSelected: onDocumentTypeSelected, + onStoragePathSelected: onStoragePathSelected, + enableHeroAnimation: enableHeroAnimation, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ), @@ -133,21 +152,23 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView { childCount: documents.length, (context, index) { final document = documents.elementAt(index); - return LabelRepositoriesProvider( - child: DocumentDetailedItem( - isLabelClickable: isLabelClickable, - document: document, - onTap: onTap, - isSelected: selectedDocumentIds.contains(document.id), - onSelected: onSelected, - isSelectionActive: selectedDocumentIds.isNotEmpty, - onTagSelected: onTagSelected, - onCorrespondentSelected: onCorrespondentSelected, - onDocumentTypeSelected: onDocumentTypeSelected, - onStoragePathSelected: onStoragePathSelected, - enableHeroAnimation: enableHeroAnimation, - highlights: document.searchHit?.highlights, - ), + return DocumentDetailedItem( + isLabelClickable: isLabelClickable, + document: document, + onTap: onTap, + isSelected: selectedDocumentIds.contains(document.id), + onSelected: onSelected, + isSelectionActive: selectedDocumentIds.isNotEmpty, + onTagSelected: onTagSelected, + onCorrespondentSelected: onCorrespondentSelected, + onDocumentTypeSelected: onDocumentTypeSelected, + onStoragePathSelected: onStoragePathSelected, + enableHeroAnimation: enableHeroAnimation, + highlights: document.searchHit?.highlights, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ), @@ -180,6 +201,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView { onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ); @@ -205,6 +230,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { super.selectedDocumentIds, super.viewType, super.enableHeroAnimation = true, + required super.correspondents, + required super.documentTypes, + required super.tags, + required super.storagePaths, }); @override @@ -231,20 +260,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { itemCount: documents.length, itemBuilder: (context, index) { final document = documents.elementAt(index); - return LabelRepositoriesProvider( - child: DocumentListItem( - isLabelClickable: isLabelClickable, - document: document, - onTap: onTap, - isSelected: selectedDocumentIds.contains(document.id), - onSelected: onSelected, - isSelectionActive: selectedDocumentIds.isNotEmpty, - onTagSelected: onTagSelected, - onCorrespondentSelected: onCorrespondentSelected, - onDocumentTypeSelected: onDocumentTypeSelected, - onStoragePathSelected: onStoragePathSelected, - enableHeroAnimation: enableHeroAnimation, - ), + return DocumentListItem( + isLabelClickable: isLabelClickable, + document: document, + onTap: onTap, + isSelected: selectedDocumentIds.contains(document.id), + onSelected: onSelected, + isSelectionActive: selectedDocumentIds.isNotEmpty, + onTagSelected: onTagSelected, + onCorrespondentSelected: onCorrespondentSelected, + onDocumentTypeSelected: onDocumentTypeSelected, + onStoragePathSelected: onStoragePathSelected, + enableHeroAnimation: enableHeroAnimation, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ); @@ -252,7 +283,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { Widget _buildFullView() { if (showLoadingPlaceholder) { - return DocumentsListLoadingWidget(); + return const DocumentsListLoadingWidget(); } return ListView.builder( @@ -263,20 +294,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { itemCount: documents.length, itemBuilder: (context, index) { final document = documents.elementAt(index); - return LabelRepositoriesProvider( - child: DocumentDetailedItem( - isLabelClickable: isLabelClickable, - document: document, - onTap: onTap, - isSelected: selectedDocumentIds.contains(document.id), - onSelected: onSelected, - isSelectionActive: selectedDocumentIds.isNotEmpty, - onTagSelected: onTagSelected, - onCorrespondentSelected: onCorrespondentSelected, - onDocumentTypeSelected: onDocumentTypeSelected, - onStoragePathSelected: onStoragePathSelected, - enableHeroAnimation: enableHeroAnimation, - ), + return DocumentDetailedItem( + isLabelClickable: isLabelClickable, + document: document, + onTap: onTap, + isSelected: selectedDocumentIds.contains(document.id), + onSelected: onSelected, + isSelectionActive: selectedDocumentIds.isNotEmpty, + onTagSelected: onTagSelected, + onCorrespondentSelected: onCorrespondentSelected, + onDocumentTypeSelected: onDocumentTypeSelected, + onStoragePathSelected: onStoragePathSelected, + enableHeroAnimation: enableHeroAnimation, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ); @@ -284,7 +317,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { Widget _buildGridView() { if (showLoadingPlaceholder) { - return DocumentGridLoadingWidget(); + return const DocumentGridLoadingWidget(); } return GridView.builder( padding: EdgeInsets.zero, @@ -311,6 +344,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView { onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation, + correspondents: correspondents, + documentTypes: documentTypes, + storagePaths: storagePaths, + tags: tags, ); }, ); diff --git a/lib/features/documents/view/widgets/items/document_detailed_item.dart b/lib/features/documents/view/widgets/items/document_detailed_item.dart index e4a5ec8..b4ac2a5 100644 --- a/lib/features/documents/view/widgets/items/document_detailed_item.dart +++ b/lib/features/documents/view/widgets/items/document_detailed_item.dart @@ -1,8 +1,10 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; @@ -26,6 +28,10 @@ class DocumentDetailedItem extends DocumentItem { super.onStoragePathSelected, super.onTagSelected, super.onTap, + required super.tags, + required super.correspondents, + required super.documentTypes, + required super.storagePaths, }); @override @@ -113,7 +119,7 @@ class DocumentDetailedItem extends DocumentItem { textStyle: Theme.of(context).textTheme.titleSmall?.apply( color: Theme.of(context).colorScheme.onSurfaceVariant, ), - correspondentId: document.correspondent, + correspondent: context.read().correspondent, ), ], ).paddedLTRB(8, 0, 8, 4), diff --git a/lib/features/documents/view/widgets/items/document_grid_item.dart b/lib/features/documents/view/widgets/items/document_grid_item.dart index e8856e0..21f05a4 100644 --- a/lib/features/documents/view/widgets/items/document_grid_item.dart +++ b/lib/features/documents/view/widgets/items/document_grid_item.dart @@ -21,6 +21,10 @@ class DocumentGridItem extends DocumentItem { super.onTagSelected, super.onTap, required super.enableHeroAnimation, + required super.tags, + required super.correspondents, + required super.documentTypes, + required super.storagePaths, }); @override @@ -54,10 +58,10 @@ class DocumentGridItem extends DocumentItem { crossAxisAlignment: CrossAxisAlignment.start, children: [ CorrespondentWidget( - correspondentId: document.correspondent, + correspondent: correspondents[document.correspondent], ), DocumentTypeWidget( - documentTypeId: document.documentType, + documentType: documentTypes[document.documentType], ), Text( document.title, @@ -67,7 +71,7 @@ class DocumentGridItem extends DocumentItem { ), const Spacer(), TagsWidget( - tagIds: document.tags, + tags: document.tags.map((e) => tags[e]!).toList(), isMultiLine: false, onTagSelected: onTagSelected, ), diff --git a/lib/features/documents/view/widgets/items/document_item.dart b/lib/features/documents/view/widgets/items/document_item.dart index a19fef3..bf29f45 100644 --- a/lib/features/documents/view/widgets/items/document_item.dart +++ b/lib/features/documents/view/widgets/items/document_item.dart @@ -10,6 +10,11 @@ abstract class DocumentItem extends StatelessWidget { final bool isLabelClickable; final bool enableHeroAnimation; + final Map tags; + final Map correspondents; + final Map documentTypes; + final Map storagePaths; + final void Function(int tagId)? onTagSelected; final void Function(int? correspondentId)? onCorrespondentSelected; final void Function(int? documentTypeId)? onDocumentTypeSelected; @@ -28,5 +33,9 @@ abstract class DocumentItem extends StatelessWidget { this.onDocumentTypeSelected, this.onStoragePathSelected, required this.enableHeroAnimation, + required this.tags, + required this.correspondents, + required this.documentTypes, + required this.storagePaths, }); } diff --git a/lib/features/documents/view/widgets/items/document_list_item.dart b/lib/features/documents/view/widgets/items/document_list_item.dart index 16f1d49..f2cca3f 100644 --- a/lib/features/documents/view/widgets/items/document_list_item.dart +++ b/lib/features/documents/view/widgets/items/document_list_item.dart @@ -5,7 +5,6 @@ import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; -import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; @@ -25,113 +24,109 @@ class DocumentListItem extends DocumentItem { super.onTagSelected, super.onTap, super.enableHeroAnimation = true, + required super.tags, + required super.correspondents, + required super.documentTypes, + required super.storagePaths, }); @override Widget build(BuildContext context) { - return DocumentTypeBlocProvider( - child: Material( - child: ListTile( - dense: true, - selected: isSelected, - onTap: () => _onTap(), - selectedTileColor: Theme.of(context).colorScheme.inversePrimary, - onLongPress: () => onSelected?.call(document), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Row( - children: [ - AbsorbPointer( - absorbing: isSelectionActive, - child: CorrespondentWidget( - isClickable: isLabelClickable, - correspondentId: document.correspondent, - onSelected: onCorrespondentSelected, - ), + return Material( + child: ListTile( + dense: true, + selected: isSelected, + onTap: () => _onTap(), + selectedTileColor: Theme.of(context).colorScheme.inversePrimary, + onLongPress: () => onSelected?.call(document), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + AbsorbPointer( + absorbing: isSelectionActive, + child: CorrespondentWidget( + isClickable: isLabelClickable, + correspondent: correspondents[document.correspondent], + onSelected: onCorrespondentSelected, ), - ], - ), - Text( - document.title, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - AbsorbPointer( - absorbing: isSelectionActive, - child: TagsWidget( - isClickable: isLabelClickable, - tagIds: document.tags, - isMultiLine: false, - onTagSelected: (id) => onTagSelected?.call(id), ), - ) - ], - ), - subtitle: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: BlocBuilder, - LabelState>( - builder: (context, docTypes) { - return RichText( - maxLines: 1, - overflow: TextOverflow.ellipsis, - text: TextSpan( - text: DateFormat.yMMMd().format(document.created), - style: Theme.of(context) - .textTheme - .labelSmall - ?.apply(color: Colors.grey), - children: document.documentType != null - ? [ - const TextSpan(text: '\u30FB'), - TextSpan( - text: docTypes - .labels[document.documentType]?.name, - ), - ] - : null, - ), - ); - }, - ) - // Row( - // children: [ - // Text( - // DateFormat.yMMMd().format(document.created), - // style: Theme.of(context) - // .textTheme - // .bodySmall - // ?.apply(color: Colors.grey), - // ), - // if (document.documentType != null) ...[ - // Text("\u30FB"), - // DocumentTypeWidget( - // documentTypeId: document.documentType, - // textStyle: Theme.of(context).textTheme.bodySmall?.apply( - // color: Colors.grey, - // overflow: TextOverflow.ellipsis, - // ), - // ), - // ], - // ], - // ), - ), - isThreeLine: document.tags.isNotEmpty, - leading: AspectRatio( - aspectRatio: _a4AspectRatio, - child: GestureDetector( - child: DocumentPreview( - document: document, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - enableHero: enableHeroAnimation, + ], + ), + Text( + document.title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + AbsorbPointer( + absorbing: isSelectionActive, + child: TagsWidget( + isClickable: isLabelClickable, + tags: document.tags.map((e) => tags[e]!).toList(), + isMultiLine: false, + onTagSelected: (id) => onTagSelected?.call(id), ), + ) + ], + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( + text: DateFormat.yMMMd().format(document.created), + style: Theme.of(context) + .textTheme + .labelSmall + ?.apply(color: Colors.grey), + children: document.documentType != null + ? [ + const TextSpan(text: '\u30FB'), + TextSpan( + text: documentTypes[document.documentType]?.name, + ), + ] + : null, ), ), - contentPadding: const EdgeInsets.all(8.0), + // Row( + // children: [ + // Text( + // DateFormat.yMMMd().format(document.created), + // style: Theme.of(context) + // .textTheme + // .bodySmall + // ?.apply(color: Colors.grey), + // ), + // if (document.documentType != null) ...[ + // Text("\u30FB"), + // DocumentTypeWidget( + // documentTypeId: document.documentType, + // textStyle: Theme.of(context).textTheme.bodySmall?.apply( + // color: Colors.grey, + // overflow: TextOverflow.ellipsis, + // ), + // ), + // ], + // ], + // ), ), + isThreeLine: document.tags.isNotEmpty, + leading: AspectRatio( + aspectRatio: _a4AspectRatio, + child: GestureDetector( + child: DocumentPreview( + document: document, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + enableHero: enableHeroAnimation, + ), + ), + ), + contentPadding: const EdgeInsets.all(8.0), ), ); } diff --git a/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart b/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart index bdd13f3..3a9d0e1 100644 --- a/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart +++ b/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart @@ -71,7 +71,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { .paddedOnly(left: 4, right: 4), _buildBulkEditStoragePathChip(context) .paddedOnly(left: 4, right: 4), - // _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4), + _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4), ], ), ), @@ -100,9 +100,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { builder: (_) { return BlocProvider( create: (context) => DocumentBulkActionCubit( - context.read(), - context.read(), - context.read(), context.read(), context.read(), context.read(), @@ -112,8 +109,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { return BulkEditLabelBottomSheet( initialValue: initialValue, title: "Bulk edit correspondent", - availableOptionsSelector: (state) => - state.correspondentOptions, + availableOptionsSelector: (state) => state.correspondents, formFieldLabel: S.of(context)!.correspondent, formFieldPrefixIcon: const Icon(Icons.person_outline), onSubmit: (selectedId) async { @@ -152,9 +148,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { builder: (_) { return BlocProvider( create: (context) => DocumentBulkActionCubit( - context.read(), - context.read(), - context.read(), context.read(), context.read(), context.read(), @@ -164,8 +157,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { return BulkEditLabelBottomSheet( initialValue: initialValue, title: "Bulk edit document type", - availableOptionsSelector: (state) => - state.documentTypeOptions, + availableOptionsSelector: (state) => state.documentTypes, formFieldLabel: S.of(context)!.documentType, formFieldPrefixIcon: const Icon(Icons.person_outline), onSubmit: (selectedId) async { @@ -204,9 +196,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { builder: (_) { return BlocProvider( create: (context) => DocumentBulkActionCubit( - context.read(), - context.read(), - context.read(), context.read(), context.read(), context.read(), @@ -216,7 +205,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { return BulkEditLabelBottomSheet( initialValue: initialValue, title: "Bulk edit storage path", - availableOptionsSelector: (state) => state.storagePathOptions, + availableOptionsSelector: (state) => state.storagePaths, formFieldLabel: S.of(context)!.storagePath, formFieldPrefixIcon: const Icon(Icons.folder_open_outlined), onSubmit: (selectedId) async { @@ -246,14 +235,11 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { topRight: Radius.circular(16), ), ), - isScrollControlled: false, + isScrollControlled: true, context: context, builder: (_) { return BlocProvider( create: (context) => DocumentBulkActionCubit( - context.read(), - context.read(), - context.read(), context.read(), context.read(), context.read(), diff --git a/lib/features/documents/view/widgets/sort_documents_button.dart b/lib/features/documents/view/widgets/sort_documents_button.dart index e20631d..3d72daf 100644 --- a/lib/features/documents/view/widgets/sort_documents_button.dart +++ b/lib/features/documents/view/widgets/sort_documents_button.dart @@ -43,14 +43,7 @@ class SortDocumentsButton extends StatelessWidget { child: MultiBlocProvider( providers: [ BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - ), - BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), + create: (context) => LabelCubit(context.read()), ), ], child: SortFieldSelectionBottomSheet( diff --git a/lib/features/edit_label/cubit/edit_label_cubit.dart b/lib/features/edit_label/cubit/edit_label_cubit.dart index dfd6bd8..ed5c8e3 100644 --- a/lib/features/edit_label/cubit/edit_label_cubit.dart +++ b/lib/features/edit_label/cubit/edit_label_cubit.dart @@ -1,34 +1,44 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; - +import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; part 'edit_label_state.dart'; +part 'edit_label_cubit.freezed.dart'; -class EditLabelCubit extends Cubit> { - final LabelRepository _repository; +class EditLabelCubit extends Cubit with LabelCubitMixin { + @override + final LabelRepository labelRepository; - StreamSubscription? _subscription; - - EditLabelCubit(LabelRepository repository) - : _repository = repository, - super(EditLabelState(labels: repository.current?.values ?? {})) { - _subscription = repository.values.listen( - (event) => emit(EditLabelState(labels: event?.values ?? {})), + EditLabelCubit(this.labelRepository) : super(const EditLabelState()) { + labelRepository.subscribe( + this, + onChanged: (labels) => state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + tags: labels.tags, + storagePaths: labels.storagePaths, + ), ); } - Future create(T label) => _repository.create(label); - - Future update(T label) => _repository.update(label); - - Future delete(T label) => _repository.delete(label); - @override Future close() { - _subscription?.cancel(); + labelRepository.unsubscribe(this); return super.close(); } + + @override + Map get correspondents => state.correspondents; + + @override + Map get documentTypes => state.documentTypes; + + @override + Map get storagePaths => state.storagePaths; + + @override + Map get tags => state.tags; } diff --git a/lib/features/edit_label/cubit/edit_label_cubit.freezed.dart b/lib/features/edit_label/cubit/edit_label_cubit.freezed.dart new file mode 100644 index 0000000..e085d11 --- /dev/null +++ b/lib/features/edit_label/cubit/edit_label_cubit.freezed.dart @@ -0,0 +1,237 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'edit_label_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$EditLabelState { + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $EditLabelStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EditLabelStateCopyWith<$Res> { + factory $EditLabelStateCopyWith( + EditLabelState value, $Res Function(EditLabelState) then) = + _$EditLabelStateCopyWithImpl<$Res, EditLabelState>; + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$EditLabelStateCopyWithImpl<$Res, $Val extends EditLabelState> + implements $EditLabelStateCopyWith<$Res> { + _$EditLabelStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_EditLabelStateCopyWith<$Res> + implements $EditLabelStateCopyWith<$Res> { + factory _$$_EditLabelStateCopyWith( + _$_EditLabelState value, $Res Function(_$_EditLabelState) then) = + __$$_EditLabelStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_EditLabelStateCopyWithImpl<$Res> + extends _$EditLabelStateCopyWithImpl<$Res, _$_EditLabelState> + implements _$$_EditLabelStateCopyWith<$Res> { + __$$_EditLabelStateCopyWithImpl( + _$_EditLabelState _value, $Res Function(_$_EditLabelState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_EditLabelState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_EditLabelState implements _EditLabelState { + const _$_EditLabelState( + {final Map correspondents = const {}, + final Map documentTypes = const {}, + final Map tags = const {}, + final Map storagePaths = const {}}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _correspondents; + @override + @JsonKey() + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + @JsonKey() + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + @JsonKey() + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + @JsonKey() + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'EditLabelState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_EditLabelState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_EditLabelStateCopyWith<_$_EditLabelState> get copyWith => + __$$_EditLabelStateCopyWithImpl<_$_EditLabelState>(this, _$identity); +} + +abstract class _EditLabelState implements EditLabelState { + const factory _EditLabelState( + {final Map correspondents, + final Map documentTypes, + final Map tags, + final Map storagePaths}) = _$_EditLabelState; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_EditLabelStateCopyWith<_$_EditLabelState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/edit_label/cubit/edit_label_state.dart b/lib/features/edit_label/cubit/edit_label_state.dart index 798d9a6..3d40045 100644 --- a/lib/features/edit_label/cubit/edit_label_state.dart +++ b/lib/features/edit_label/cubit/edit_label_state.dart @@ -1,10 +1,11 @@ part of 'edit_label_cubit.dart'; -class EditLabelState extends Equatable { - final Map labels; - - const EditLabelState({this.labels = const {}}); - - @override - List get props => [labels]; +@freezed +class EditLabelState with _$EditLabelState { + const factory EditLabelState({ + @Default({}) Map correspondents, + @Default({}) Map documentTypes, + @Default({}) Map tags, + @Default({}) Map storagePaths, + }) = _EditLabelState; } diff --git a/lib/features/edit_label/view/add_label_page.dart b/lib/features/edit_label/view/add_label_page.dart index caf54e5..7d0b3e9 100644 --- a/lib/features/edit_label/view/add_label_page.dart +++ b/lib/features/edit_label/view/add_label_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/label_form.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; @@ -12,6 +11,7 @@ class AddLabelPage extends StatelessWidget { final Widget pageTitle; final T Function(Map json) fromJsonT; final List additionalFields; + final Future Function(BuildContext context, T label) onSubmit; const AddLabelPage({ super.key, @@ -19,19 +19,21 @@ class AddLabelPage extends StatelessWidget { required this.pageTitle, required this.fromJsonT, this.additionalFields = const [], + required this.onSubmit, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => EditLabelCubit( - context.read>(), + context.read(), ), child: AddLabelFormWidget( pageTitle: pageTitle, label: initialName != null ? fromJsonT({'name': initialName}) : null, additionalFields: additionalFields, fromJsonT: fromJsonT, + onSubmit: onSubmit, ), ); } @@ -41,6 +43,7 @@ class AddLabelFormWidget extends StatelessWidget { final T? label; final T Function(Map json) fromJsonT; final List additionalFields; + final Future Function(BuildContext context, T label) onSubmit; final Widget pageTitle; const AddLabelFormWidget({ @@ -49,6 +52,7 @@ class AddLabelFormWidget extends StatelessWidget { required this.fromJsonT, required this.additionalFields, required this.pageTitle, + required this.onSubmit, }); @override @@ -63,7 +67,7 @@ class AddLabelFormWidget extends StatelessWidget { submitButtonConfig: SubmitButtonConfig( icon: const Icon(Icons.add), label: Text(S.of(context)!.create), - onSubmit: context.read>().create, + onSubmit: (label) => onSubmit(context, label), ), additionalFields: additionalFields, ), diff --git a/lib/features/edit_label/view/edit_label_page.dart b/lib/features/edit_label/view/edit_label_page.dart index 9df9c0b..64660f5 100644 --- a/lib/features/edit_label/view/edit_label_page.dart +++ b/lib/features/edit_label/view/edit_label_page.dart @@ -1,40 +1,43 @@ import 'dart:developer'; -import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/label_form.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; -import 'package:paperless_mobile/constants.dart'; class EditLabelPage extends StatelessWidget { final T label; final T Function(Map json) fromJsonT; final List additionalFields; + final Future Function(BuildContext context, T label) onSubmit; + final Future Function(BuildContext context, T label) onDelete; const EditLabelPage({ super.key, required this.label, required this.fromJsonT, this.additionalFields = const [], + required this.onSubmit, + required this.onDelete, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => EditLabelCubit( - context.read>(), + context.read(), ), child: EditLabelForm( label: label, additionalFields: additionalFields, fromJsonT: fromJsonT, + onSubmit: onSubmit, + onDelete: onDelete, ), ); } @@ -44,12 +47,16 @@ class EditLabelForm extends StatelessWidget { final T label; final T Function(Map json) fromJsonT; final List additionalFields; + final Future Function(BuildContext context, T label) onSubmit; + final Future Function(BuildContext context, T label) onDelete; const EditLabelForm({ super.key, required this.label, required this.fromJsonT, required this.additionalFields, + required this.onSubmit, + required this.onDelete, }); @override @@ -70,7 +77,7 @@ class EditLabelForm extends StatelessWidget { submitButtonConfig: SubmitButtonConfig( icon: const Icon(Icons.save), label: Text(S.of(context)!.saveChanges), - onSubmit: context.read>().update, + onSubmit: (label) => onSubmit(context, label), ), additionalFields: additionalFields, ), @@ -107,7 +114,7 @@ class EditLabelForm extends StatelessWidget { false; if (shouldDelete) { try { - context.read>().delete(label); + onDelete(context, label); } on PaperlessServerException catch (error) { showErrorMessage(context, error); } catch (error, stackTrace) { @@ -116,7 +123,7 @@ class EditLabelForm extends StatelessWidget { Navigator.pop(context); } } else { - context.read>().delete(label); + onDelete(context, label); Navigator.pop(context); } } diff --git a/lib/features/edit_label/view/impl/add_correspondent_page.dart b/lib/features/edit_label/view/impl/add_correspondent_page.dart index dfcc0da..8d615c8 100644 --- a/lib/features/edit_label/view/impl/add_correspondent_page.dart +++ b/lib/features/edit_label/view/impl/add_correspondent_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; @@ -14,13 +12,15 @@ class AddCorrespondentPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: AddLabelPage( pageTitle: Text(S.of(context)!.addCorrespondent), fromJsonT: Correspondent.fromJson, initialName: initialName, + onSubmit: (context, label) => + context.read().addCorrespondent(label), ), ); } diff --git a/lib/features/edit_label/view/impl/add_document_type_page.dart b/lib/features/edit_label/view/impl/add_document_type_page.dart index 63a848d..35a8b40 100644 --- a/lib/features/edit_label/view/impl/add_document_type_page.dart +++ b/lib/features/edit_label/view/impl/add_document_type_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; @@ -17,13 +15,15 @@ class AddDocumentTypePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: AddLabelPage( pageTitle: Text(S.of(context)!.addDocumentType), fromJsonT: DocumentType.fromJson, initialName: initialName, + onSubmit: (context, label) => + context.read().addDocumentType(label), ), ); } diff --git a/lib/features/edit_label/view/impl/add_storage_path_page.dart b/lib/features/edit_label/view/impl/add_storage_path_page.dart index e306711..3e2d311 100644 --- a/lib/features/edit_label/view/impl/add_storage_path_page.dart +++ b/lib/features/edit_label/view/impl/add_storage_path_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; @@ -15,13 +13,15 @@ class AddStoragePathPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: AddLabelPage( pageTitle: Text(S.of(context)!.addStoragePath), fromJsonT: StoragePath.fromJson, initialName: initalName, + onSubmit: (context, label) => + context.read().addStoragePath(label), additionalFields: const [ StoragePathAutofillFormBuilderField(name: StoragePath.pathKey), SizedBox(height: 120.0), diff --git a/lib/features/edit_label/view/impl/add_tag_page.dart b/lib/features/edit_label/view/impl/add_tag_page.dart index 770402a..9c26d10 100644 --- a/lib/features/edit_label/view/impl/add_tag_page.dart +++ b/lib/features/edit_label/view/impl/add_tag_page.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; @@ -18,13 +16,15 @@ class AddTagPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: AddLabelPage( pageTitle: Text(S.of(context)!.addTag), fromJsonT: Tag.fromJson, initialName: initialValue, + onSubmit: (context, label) => + context.read().addTag(label), additionalFields: [ FormBuilderColorPickerField( name: Tag.colorKey, diff --git a/lib/features/edit_label/view/impl/edit_correspondent_page.dart b/lib/features/edit_label/view/impl/edit_correspondent_page.dart index 5c01408..40344c9 100644 --- a/lib/features/edit_label/view/impl/edit_correspondent_page.dart +++ b/lib/features/edit_label/view/impl/edit_correspondent_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; @@ -13,12 +11,16 @@ class EditCorrespondentPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: EditLabelPage( label: correspondent, fromJsonT: Correspondent.fromJson, + onSubmit: (context, label) => + context.read().addCorrespondent(label), + onDelete: (context, label) => + context.read().removeCorrespondent(label), ), ); } diff --git a/lib/features/edit_label/view/impl/edit_document_type_page.dart b/lib/features/edit_label/view/impl/edit_document_type_page.dart index d698aec..c93408d 100644 --- a/lib/features/edit_label/view/impl/edit_document_type_page.dart +++ b/lib/features/edit_label/view/impl/edit_document_type_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; @@ -13,12 +12,16 @@ class EditDocumentTypePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: EditLabelPage( label: documentType, fromJsonT: DocumentType.fromJson, + onSubmit: (context, label) => + context.read().addDocumentType(label), + onDelete: (context, label) => + context.read().removeDocumentType(label), ), ); } diff --git a/lib/features/edit_label/view/impl/edit_storage_path_page.dart b/lib/features/edit_label/view/impl/edit_storage_path_page.dart index 73a66e0..1aa86dc 100644 --- a/lib/features/edit_label/view/impl/edit_storage_path_page.dart +++ b/lib/features/edit_label/view/impl/edit_storage_path_page.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; @@ -14,12 +12,16 @@ class EditStoragePathPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: EditLabelPage( label: storagePath, fromJsonT: StoragePath.fromJson, + onSubmit: (context, label) => + context.read().addStoragePath(label), + onDelete: (context, label) => + context.read().removeStoragePath(label), additionalFields: [ StoragePathAutofillFormBuilderField( name: StoragePath.pathKey, diff --git a/lib/features/edit_label/view/impl/edit_tag_page.dart b/lib/features/edit_label/view/impl/edit_tag_page.dart index 8b6650a..d2074dd 100644 --- a/lib/features/edit_label/view/impl/edit_tag_page.dart +++ b/lib/features/edit_label/view/impl/edit_tag_page.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; @@ -17,12 +15,16 @@ class EditTagPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => EditLabelCubit( - context.read>(), + create: (context) => EditLabelCubit( + context.read(), ), child: EditLabelPage( label: tag, fromJsonT: Tag.fromJson, + onSubmit: (context, label) => + context.read().addTag(label), + onDelete: (context, label) => + context.read().removeTag(label), additionalFields: [ FormBuilderColorPickerField( initialValue: tag.color, diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index b5e76ce..86247d0 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -31,9 +31,7 @@ import 'package:paperless_mobile/features/sharing/share_intent_queue.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; -import 'package:paperless_mobile/helpers/file_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; -import 'package:path/path.dart' as p; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:responsive_builder/responsive_builder.dart'; @@ -60,8 +58,6 @@ class _HomePageState extends State with WidgetsBindingObserver { context.read(), context.read(), context.read(), - context.read(), - context.read(), ); _listenToInboxChanges(); @@ -260,17 +256,8 @@ class _HomePageState extends State with WidgetsBindingObserver { MultiBlocProvider( providers: [ BlocProvider( - create: (context) => LabelCubit(context.read()), - ), - BlocProvider( - create: (context) => LabelCubit(context.read()), - ), - BlocProvider( - create: (context) => LabelCubit(context.read()), - ), - BlocProvider( - create: (context) => LabelCubit(context.read()), - ), + create: (context) => LabelCubit(context.read()), + ) ], child: const LabelsPage(), ), @@ -345,15 +332,13 @@ class _HomePageState extends State with WidgetsBindingObserver { } void _initializeData(BuildContext context) { - try { - context.read>().findAll(); - context.read>().findAll(); - context.read>().findAll(); - context.read>().findAll(); - context.read().findAll(); - context.read().updateInformtion(); - } on PaperlessServerException catch (error, stackTrace) { + Future.wait([ + context.read().initialize(), + context.read().findAll(), + context.read().updateInformtion(), + ]).onError((error, stackTrace) { showErrorMessage(context, error, stackTrace); - } + throw error; + }); } } diff --git a/lib/features/home/view/widget/verify_identity_page.dart b/lib/features/home/view/widget/verify_identity_page.dart index fd34b79..ea63ba1 100644 --- a/lib/features/home/view/widget/verify_identity_page.dart +++ b/lib/features/home/view/widget/verify_identity_page.dart @@ -65,10 +65,7 @@ class VerifyIdentityPage extends StatelessWidget { void _logout(BuildContext context) { context.read().logout(); - context.read>().clear(); - context.read>().clear(); - context.read>().clear(); - context.read>().clear(); + context.read().clear(); context.read().clear(); HydratedBloc.storage.clear(); } diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index c3642c4..9265b76 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -5,6 +5,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; +import 'package:paperless_mobile/core/repository/label_repository_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; @@ -13,9 +14,7 @@ part 'inbox_state.dart'; class InboxCubit extends HydratedCubit with DocumentPagingBlocMixin { - final LabelRepository _tagsRepository; - final LabelRepository _correspondentRepository; - final LabelRepository _documentTypeRepository; + final LabelRepository _labelRepository; final PaperlessDocumentsApi _documentsApi; @@ -24,27 +23,15 @@ class InboxCubit extends HydratedCubit final PaperlessServerStatsApi _statsApi; - final List _subscriptions = []; - @override PaperlessDocumentsApi get api => _documentsApi; InboxCubit( - this._tagsRepository, this._documentsApi, - this._correspondentRepository, - this._documentTypeRepository, this._statsApi, + this._labelRepository, this.notifier, - ) : super( - InboxState( - availableCorrespondents: - _correspondentRepository.current?.values ?? {}, - availableDocumentTypes: - _documentTypeRepository.current?.values ?? {}, - availableTags: _tagsRepository.current?.values ?? {}, - ), - ) { + ) : super(InboxState(labels: _labelRepository.state)) { notifier.subscribe( this, onDeleted: remove, @@ -60,28 +47,11 @@ class InboxCubit extends HydratedCubit } }, ); - _subscriptions.add( - _tagsRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith(availableTags: event!.values)); - } - }), - ); - _subscriptions.add( - _correspondentRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith( - availableCorrespondents: event!.values, - )); - } - }), - ); - _subscriptions.add( - _documentTypeRepository.values.listen((event) { - if (event?.hasLoaded ?? false) { - emit(state.copyWith(availableDocumentTypes: event!.values)); - } - }), + _labelRepository.subscribe( + this, + onChanged: (labels) { + emit(state.copyWith(labels: labels)); + }, ); refreshItemsInInboxCount(false); @@ -105,7 +75,7 @@ class InboxCubit extends HydratedCubit /// Fetches inbox tag ids and loads the inbox items (documents). /// Future loadInbox() async { - final inboxTags = await _tagsRepository.findAll().then( + final inboxTags = await _labelRepository.findAllTags().then( (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), ); @@ -133,7 +103,7 @@ class InboxCubit extends HydratedCubit /// Future reloadInbox() async { emit(state.copyWith(hasLoaded: false, isLoading: true)); - final inboxTags = await _tagsRepository.findAll().then( + final inboxTags = await _labelRepository.findAllTags().then( (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), ); @@ -239,9 +209,7 @@ class InboxCubit extends HydratedCubit @override Future close() { - for (var sub in _subscriptions) { - sub.cancel(); - } + _labelRepository.unsubscribe(this); return super.close(); } } diff --git a/lib/features/inbox/cubit/inbox_state.dart b/lib/features/inbox/cubit/inbox_state.dart index 99c0d82..5dc8f7e 100644 --- a/lib/features/inbox/cubit/inbox_state.dart +++ b/lib/features/inbox/cubit/inbox_state.dart @@ -4,11 +4,7 @@ part of 'inbox_cubit.dart'; class InboxState extends DocumentPagingState { final Iterable inboxTags; - final Map availableTags; - - final Map availableDocumentTypes; - - final Map availableCorrespondents; + final LabelRepositoryState labels; final int itemsInInboxCount; @@ -22,10 +18,8 @@ class InboxState extends DocumentPagingState { super.filter = const DocumentFilter(), this.inboxTags = const [], this.isHintAcknowledged = false, - this.availableTags = const {}, - this.availableDocumentTypes = const {}, - this.availableCorrespondents = const {}, this.itemsInInboxCount = 0, + this.labels = const LabelRepositoryState(), }); @override @@ -37,10 +31,8 @@ class InboxState extends DocumentPagingState { inboxTags, documents, isHintAcknowledged, - availableTags, - availableDocumentTypes, - availableCorrespondents, itemsInInboxCount, + labels, ]; InboxState copyWith({ @@ -50,9 +42,7 @@ class InboxState extends DocumentPagingState { List>? value, DocumentFilter? filter, bool? isHintAcknowledged, - Map? availableTags, - Map? availableCorrespondents, - Map? availableDocumentTypes, + LabelRepositoryState? labels, Map? suggestions, int? itemsInInboxCount, }) { @@ -62,11 +52,7 @@ class InboxState extends DocumentPagingState { value: value ?? super.value, inboxTags: inboxTags ?? this.inboxTags, isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged, - availableCorrespondents: - availableCorrespondents ?? this.availableCorrespondents, - availableDocumentTypes: - availableDocumentTypes ?? this.availableDocumentTypes, - availableTags: availableTags ?? this.availableTags, + labels: labels ?? this.labels, filter: filter ?? super.filter, itemsInInboxCount: itemsInInboxCount ?? this.itemsInInboxCount, ); diff --git a/lib/features/inbox/view/widgets/inbox_item.dart b/lib/features/inbox/view/widgets/inbox_item.dart index 645960c..f39b85b 100644 --- a/lib/features/inbox/view/widgets/inbox_item.dart +++ b/lib/features/inbox/view/widgets/inbox_item.dart @@ -32,60 +32,98 @@ class _InboxItemState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () async { - Navigator.pushNamed( - context, - DocumentDetailsRoute.routeName, - arguments: DocumentDetailsRouteArguments( - document: widget.document, - isLabelClickable: false, + return BlocBuilder( + builder: (context, state) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () async { + Navigator.pushNamed( + context, + DocumentDetailsRoute.routeName, + arguments: DocumentDetailsRouteArguments( + document: widget.document, + isLabelClickable: false, + ), + ); + }, + child: SizedBox( + height: 200, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Row( + children: [ + AspectRatio( + aspectRatio: InboxItem.a4AspectRatio, + child: DocumentPreview( + document: widget.document, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + enableHero: false, + ), + ).padded(), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTitle().paddedOnly(left: 8, right: 8, top: 8), + const Spacer(), + _buildTextWithLeadingIcon( + Icon( + Icons.person_outline, + size: Theme.of(context) + .textTheme + .bodyMedium + ?.fontSize, + ), + LabelText( + label: state.labels.correspondents[ + widget.document.correspondent], + style: Theme.of(context).textTheme.bodyMedium, + placeholder: "-", + ), + ).paddedSymmetrically(horizontal: 8), + _buildTextWithLeadingIcon( + Icon( + Icons.description_outlined, + size: Theme.of(context) + .textTheme + .bodyMedium + ?.fontSize, + ), + LabelText( + label: state.labels.documentTypes[ + widget.document.documentType], + style: Theme.of(context).textTheme.bodyMedium, + placeholder: "-", + ), + ).paddedSymmetrically(horizontal: 8), + const Spacer(), + TagsWidget( + tags: widget.document.tags + .map((e) => state.labels.tags[e]!) + .toList(), + isMultiLine: false, + isClickable: false, + showShortNames: true, + dense: true, + ).paddedOnly(left: 8, bottom: 8), + ], + ), + ), + ], + ), + ), + SizedBox( + height: 56, + child: _buildActions(context), + ), + ], + ).paddedOnly(left: 8, top: 8, bottom: 8), ), ); }, - child: SizedBox( - height: 200, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Row( - children: [ - AspectRatio( - aspectRatio: InboxItem.a4AspectRatio, - child: DocumentPreview( - document: widget.document, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - enableHero: false, - ), - ).padded(), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitle().paddedOnly(left: 8, right: 8, top: 8), - const Spacer(), - _buildCorrespondent(context) - .paddedSymmetrically(horizontal: 8), - _buildDocumentType(context) - .paddedSymmetrically(horizontal: 8), - const Spacer(), - _buildTags().paddedOnly(left: 8, bottom: 8), - ], - ), - ), - ], - ), - ), - SizedBox( - height: 56, - child: _buildActions(context), - ), - ], - ).paddedOnly(left: 8, top: 8, bottom: 8), - ), ); } @@ -211,44 +249,6 @@ class _InboxItemState extends State { ); } - TagsWidget _buildTags() { - return TagsWidget( - tagIds: widget.document.tags, - isMultiLine: false, - isClickable: false, - showShortNames: true, - dense: true, - ); - } - - Row _buildDocumentType(BuildContext context) { - return _buildTextWithLeadingIcon( - Icon( - Icons.description_outlined, - size: Theme.of(context).textTheme.bodyMedium?.fontSize, - ), - LabelText( - id: widget.document.documentType, - style: Theme.of(context).textTheme.bodyMedium, - placeholder: "-", - ), - ); - } - - Row _buildCorrespondent(BuildContext context) { - return _buildTextWithLeadingIcon( - Icon( - Icons.person_outline, - size: Theme.of(context).textTheme.bodyMedium?.fontSize, - ), - LabelText( - id: widget.document.correspondent, - style: Theme.of(context).textTheme.bodyMedium, - placeholder: "-", - ), - ); - } - Text _buildTitle() { return Text( widget.document.title, diff --git a/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart b/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart index 3c29a23..8d357c4 100644 --- a/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart +++ b/lib/features/labels/correspondent/view/widgets/correspondent_widget.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; -import 'package:paperless_mobile/features/labels/cubit/providers/correspondent_bloc_provider.dart'; class CorrespondentWidget extends StatelessWidget { - final int? correspondentId; + final Correspondent? correspondent; final void Function(int? id)? onSelected; final Color? textColor; final bool isClickable; @@ -13,7 +10,7 @@ class CorrespondentWidget extends StatelessWidget { const CorrespondentWidget({ Key? key, - required this.correspondentId, + required this.correspondent, this.textColor, this.isClickable = true, this.textStyle, @@ -22,25 +19,18 @@ class CorrespondentWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return CorrespondentBlocProvider( - child: AbsorbPointer( - absorbing: !isClickable, - child: - BlocBuilder, LabelState>( - builder: (context, state) { - return GestureDetector( - onTap: () => onSelected?.call(correspondentId!), - child: Text( - (state.getLabel(correspondentId)?.name) ?? "-", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: (textStyle ?? Theme.of(context).textTheme.bodyMedium) - ?.copyWith( - color: textColor ?? Theme.of(context).colorScheme.primary, - ), - ), - ); - }, + return AbsorbPointer( + absorbing: !isClickable, + child: GestureDetector( + onTap: () => onSelected?.call(correspondent?.id), + child: Text( + correspondent?.name ?? "-", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: + (textStyle ?? Theme.of(context).textTheme.bodyMedium)?.copyWith( + color: textColor ?? Theme.of(context).colorScheme.primary, + ), ), ), ); diff --git a/lib/features/labels/cubit/label_cubit.dart b/lib/features/labels/cubit/label_cubit.dart index 6b3615d..52a5e56 100644 --- a/lib/features/labels/cubit/label_cubit.dart +++ b/lib/features/labels/cubit/label_cubit.dart @@ -1,68 +1,45 @@ -import 'dart:async'; - -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; +import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart'; part 'label_state.dart'; +part 'label_cubit.freezed.dart'; -class LabelCubit extends Cubit> { - final LabelRepository _repository; +class LabelCubit extends Cubit with LabelCubitMixin { + @override + final LabelRepository labelRepository; - late StreamSubscription _subscription; - - LabelCubit(LabelRepository repository) - : _repository = repository, - super(LabelState( - isLoaded: repository.isInitialized, - labels: repository.current?.values ?? {}, - )) { - _subscription = _repository.values.listen( - (event) { - if (event == null) { - emit(LabelState()); - } - emit( - LabelState(isLoaded: event!.hasLoaded, labels: event.values ?? {})); + LabelCubit(this.labelRepository) : super(const LabelState()) { + labelRepository.subscribe( + this, + onChanged: (labels) { + emit(state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + storagePaths: labels.storagePaths, + tags: labels.tags, + )); }, ); } - /// - /// Adds [item] to the current state. A new state is automatically pushed - /// due to the subscription to the repository, which updates the state on - /// operation. - /// - Future add(T item) async { - assert(item.id == null); - final addedItem = await _repository.create(item); - return addedItem; - } - - Future reload() { - return _repository.findAll(); - } - - Future replace(T item) async { - assert(item.id != null); - final updatedItem = await _repository.update(item); - return updatedItem; - } - - Future remove(T item) async { - assert(item.id != null); - if (state.labels.containsKey(item.id)) { - await _repository.delete(item); - } - } - - void reset() { - emit(LabelState(isLoaded: false, labels: {})); + @override + Future close() { + labelRepository.unsubscribe(this); + return super.close(); } @override - Future close() { - _subscription.cancel(); - return super.close(); - } + Map get correspondents => state.correspondents; + + @override + Map get documentTypes => state.documentTypes; + + @override + Map get storagePaths => state.storagePaths; + + @override + Map get tags => state.tags; } diff --git a/lib/features/labels/cubit/label_cubit.freezed.dart b/lib/features/labels/cubit/label_cubit.freezed.dart new file mode 100644 index 0000000..725f412 --- /dev/null +++ b/lib/features/labels/cubit/label_cubit.freezed.dart @@ -0,0 +1,237 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'label_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$LabelState { + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $LabelStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LabelStateCopyWith<$Res> { + factory $LabelStateCopyWith( + LabelState value, $Res Function(LabelState) then) = + _$LabelStateCopyWithImpl<$Res, LabelState>; + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$LabelStateCopyWithImpl<$Res, $Val extends LabelState> + implements $LabelStateCopyWith<$Res> { + _$LabelStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_LabelStateCopyWith<$Res> + implements $LabelStateCopyWith<$Res> { + factory _$$_LabelStateCopyWith( + _$_LabelState value, $Res Function(_$_LabelState) then) = + __$$_LabelStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_LabelStateCopyWithImpl<$Res> + extends _$LabelStateCopyWithImpl<$Res, _$_LabelState> + implements _$$_LabelStateCopyWith<$Res> { + __$$_LabelStateCopyWithImpl( + _$_LabelState _value, $Res Function(_$_LabelState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_LabelState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_LabelState implements _LabelState { + const _$_LabelState( + {final Map correspondents = const {}, + final Map documentTypes = const {}, + final Map tags = const {}, + final Map storagePaths = const {}}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _correspondents; + @override + @JsonKey() + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + @JsonKey() + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + @JsonKey() + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + @JsonKey() + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'LabelState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_LabelState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_LabelStateCopyWith<_$_LabelState> get copyWith => + __$$_LabelStateCopyWithImpl<_$_LabelState>(this, _$identity); +} + +abstract class _LabelState implements LabelState { + const factory _LabelState( + {final Map correspondents, + final Map documentTypes, + final Map tags, + final Map storagePaths}) = _$_LabelState; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_LabelStateCopyWith<_$_LabelState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/labels/cubit/label_cubit_mixin.dart b/lib/features/labels/cubit/label_cubit_mixin.dart new file mode 100644 index 0000000..2a8b8b8 --- /dev/null +++ b/lib/features/labels/cubit/label_cubit_mixin.dart @@ -0,0 +1,104 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/repository/label_repository.dart'; + +mixin LabelCubitMixin on BlocBase { + LabelRepository get labelRepository; + + Map get correspondents; + Map get documentTypes; + Map get tags; + Map get storagePaths; + + Future addCorrespondent(Correspondent item) async { + assert(item.id == null); + final addedItem = await labelRepository.createCorrespondent(item); + return addedItem; + } + + Future reloadCorrespondents() { + return labelRepository.findAllCorrespondents(); + } + + Future replaceCorrespondent(Correspondent item) async { + assert(item.id != null); + final updatedItem = await labelRepository.updateCorrespondent(item); + return updatedItem; + } + + Future removeCorrespondent(Correspondent item) async { + assert(item.id != null); + if (correspondents.containsKey(item.id)) { + await labelRepository.deleteCorrespondent(item); + } + } + + Future addDocumentType(DocumentType item) async { + assert(item.id == null); + final addedItem = await labelRepository.createDocumentType(item); + return addedItem; + } + + Future reloadDocumentTypes() { + return labelRepository.findAllDocumentTypes(); + } + + Future replaceDocumentType(DocumentType item) async { + assert(item.id != null); + final updatedItem = await labelRepository.updateDocumentType(item); + return updatedItem; + } + + Future removeDocumentType(DocumentType item) async { + assert(item.id != null); + if (documentTypes.containsKey(item.id)) { + await labelRepository.deleteDocumentType(item); + } + } + + Future addStoragePath(StoragePath item) async { + assert(item.id == null); + final addedItem = await labelRepository.createStoragePath(item); + return addedItem; + } + + Future reloadStoragePaths() { + return labelRepository.findAllStoragePaths(); + } + + Future replaceStoragePath(StoragePath item) async { + assert(item.id != null); + final updatedItem = await labelRepository.updateStoragePath(item); + return updatedItem; + } + + Future removeStoragePath(StoragePath item) async { + assert(item.id != null); + if (storagePaths.containsKey(item.id)) { + await labelRepository.deleteStoragePath(item); + } + } + + Future addTag(Tag item) async { + assert(item.id == null); + final addedItem = await labelRepository.createTag(item); + return addedItem; + } + + Future reloadTags() { + return labelRepository.findAllTags(); + } + + Future replaceTag(Tag item) async { + assert(item.id != null); + final updatedItem = await labelRepository.updateTag(item); + return updatedItem; + } + + Future removeTag(Tag item) async { + assert(item.id != null); + if (tags.containsKey(item.id)) { + await labelRepository.deleteTag(item); + } + } +} diff --git a/lib/features/labels/cubit/label_state.dart b/lib/features/labels/cubit/label_state.dart index 1767083..55f7f0a 100644 --- a/lib/features/labels/cubit/label_state.dart +++ b/lib/features/labels/cubit/label_state.dart @@ -1,19 +1,11 @@ part of 'label_cubit.dart'; -class LabelState { - LabelState.initial() : this(isLoaded: false, labels: {}); - final bool isLoaded; - final Map labels; - - LabelState({ - this.isLoaded = false, - this.labels = const {}, - }); - - T? getLabel(int? key) { - if (isLoaded) { - return labels[key]; - } - return null; - } +@freezed +class LabelState with _$LabelState { + const factory LabelState({ + @Default({}) Map correspondents, + @Default({}) Map documentTypes, + @Default({}) Map tags, + @Default({}) Map storagePaths, + }) = _LabelState; } diff --git a/lib/features/labels/cubit/providers/correspondent_bloc_provider.dart b/lib/features/labels/cubit/providers/correspondent_bloc_provider.dart deleted file mode 100644 index 959ce92..0000000 --- a/lib/features/labels/cubit/providers/correspondent_bloc_provider.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; - -class CorrespondentBlocProvider extends StatelessWidget { - final Widget child; - const CorrespondentBlocProvider({ - super.key, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - child: child, - ); - } -} diff --git a/lib/features/labels/cubit/providers/document_type_bloc_provider.dart b/lib/features/labels/cubit/providers/document_type_bloc_provider.dart deleted file mode 100644 index 4ddd5c5..0000000 --- a/lib/features/labels/cubit/providers/document_type_bloc_provider.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; - -class DocumentTypeBlocProvider extends StatelessWidget { - final Widget child; - const DocumentTypeBlocProvider({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - child: child, - ); - } -} diff --git a/lib/features/labels/cubit/providers/labels_bloc_provider.dart b/lib/features/labels/cubit/providers/labels_bloc_provider.dart deleted file mode 100644 index b1cae29..0000000 --- a/lib/features/labels/cubit/providers/labels_bloc_provider.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; - -class LabelsBlocProvider extends StatelessWidget { - final Widget child; - const LabelsBlocProvider({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider>( - create: (context) => LabelCubit( - context.read>(), - ), - ), - BlocProvider>( - create: (context) => LabelCubit( - context.read>(), - ), - ), - BlocProvider>( - create: (context) => LabelCubit( - context.read>(), - ), - ), - BlocProvider>( - create: (context) => LabelCubit( - context.read>(), - ), - ), - ], - child: child, - ); - } -} diff --git a/lib/features/labels/cubit/providers/storage_path_bloc_provider.dart b/lib/features/labels/cubit/providers/storage_path_bloc_provider.dart deleted file mode 100644 index 1df688e..0000000 --- a/lib/features/labels/cubit/providers/storage_path_bloc_provider.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; - -class StoragePathBlocProvider extends StatelessWidget { - final Widget child; - const StoragePathBlocProvider({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - child: child, - ); - } -} diff --git a/lib/features/labels/cubit/providers/tag_bloc_provider.dart b/lib/features/labels/cubit/providers/tag_bloc_provider.dart deleted file mode 100644 index dd7dab5..0000000 --- a/lib/features/labels/cubit/providers/tag_bloc_provider.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; - -class TagBlocProvider extends StatelessWidget { - final Widget child; - const TagBlocProvider({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - child: child, - ); - } -} diff --git a/lib/features/labels/document_type/view/widgets/document_type_widget.dart b/lib/features/labels/document_type/view/widgets/document_type_widget.dart index 29e27bf..3800766 100644 --- a/lib/features/labels/document_type/view/widgets/document_type_widget.dart +++ b/lib/features/labels/document_type/view/widgets/document_type_widget.dart @@ -1,17 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; -import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart'; class DocumentTypeWidget extends StatelessWidget { - final int? documentTypeId; + final DocumentType? documentType; final bool isClickable; final TextStyle? textStyle; final void Function(int? id)? onSelected; const DocumentTypeWidget({ Key? key, - required this.documentTypeId, + required this.documentType, this.isClickable = true, this.textStyle, this.onSelected, @@ -19,23 +16,16 @@ class DocumentTypeWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return DocumentTypeBlocProvider( - child: AbsorbPointer( - absorbing: !isClickable, - child: GestureDetector( - onTap: () => onSelected?.call(documentTypeId), - child: - BlocBuilder, LabelState>( - builder: (context, state) { - return Text( - state.labels[documentTypeId]?.toString() ?? "-", - style: (textStyle ?? Theme.of(context).textTheme.bodyMedium) - ?.copyWith(color: Theme.of(context).colorScheme.tertiary), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ); - }, - ), + return AbsorbPointer( + absorbing: !isClickable, + child: GestureDetector( + onTap: () => onSelected?.call(documentType?.id), + child: Text( + documentType?.toString() ?? "-", + style: (textStyle ?? Theme.of(context).textTheme.bodyMedium) + ?.copyWith(color: Theme.of(context).colorScheme.tertiary), + overflow: TextOverflow.ellipsis, + maxLines: 1, ), ), ); diff --git a/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart b/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart index 71f7b10..328b6c9 100644 --- a/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart +++ b/lib/features/labels/storage_path/view/widgets/storage_path_widget.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; -import 'package:paperless_mobile/features/labels/cubit/providers/storage_path_bloc_provider.dart'; class StoragePathWidget extends StatelessWidget { - final int? pathId; + final StoragePath? storagePath; final Color? textColor; final bool isClickable; final void Function(int? id)? onSelected; const StoragePathWidget({ Key? key, - this.pathId, + this.storagePath, this.textColor, this.isClickable = true, this.onSelected, @@ -20,23 +17,17 @@ class StoragePathWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return StoragePathBlocProvider( - child: AbsorbPointer( - absorbing: !isClickable, - child: BlocBuilder, LabelState>( - builder: (context, state) { - return GestureDetector( - onTap: () => onSelected?.call(pathId), - child: Text( - state.getLabel(pathId)?.name ?? "-", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: textColor ?? Theme.of(context).colorScheme.primary, - ), + return AbsorbPointer( + absorbing: !isClickable, + child: GestureDetector( + onTap: () => onSelected?.call(storagePath?.id), + child: Text( + storagePath?.name ?? "-", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: textColor ?? Theme.of(context).colorScheme.primary, ), - ); - }, ), ), ); 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 4d926ca..6c5e71a 100644 --- a/lib/features/labels/tags/view/widgets/tags_form_field.dart +++ b/lib/features/labels/tags/view/widgets/tags_form_field.dart @@ -6,7 +6,6 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart'; import 'package:paperless_mobile/core/workarounds/colored_chip.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart'; @@ -21,6 +20,8 @@ class TagFormField extends StatefulWidget { final bool excludeAllowed; final Map selectableOptions; final Widget? suggestions; + final String? labelText; + final String? hintText; const TagFormField({ super.key, @@ -32,6 +33,8 @@ class TagFormField extends StatefulWidget { this.excludeAllowed = true, required this.selectableOptions, this.suggestions, + this.labelText, + this.hintText, }); @override @@ -94,8 +97,8 @@ class _TagFormFieldState extends State { Icons.label_outline, ), suffixIcon: _buildSuffixIcon(context, field), - labelText: S.of(context)!.tags, - hintText: S.of(context)!.filterTags, + labelText: widget.labelText ?? S.of(context)!.tags, + hintText: widget.hintText ?? S.of(context)!.filterTags, ), controller: _textEditingController, ), @@ -240,8 +243,8 @@ class _TagFormFieldState extends State { void _onAddTag(BuildContext context, FormFieldState field) async { final Tag? tag = await Navigator.of(context).push( MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: AddTagPage(initialValue: _textEditingController.text), ), ), diff --git a/lib/features/labels/tags/view/widgets/tags_widget.dart b/lib/features/labels/tags/view/widgets/tags_widget.dart index 7cb9b2d..609b59b 100644 --- a/lib/features/labels/tags/view/widgets/tags_widget.dart +++ b/lib/features/labels/tags/view/widgets/tags_widget.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; -import 'package:paperless_mobile/features/labels/cubit/providers/tag_bloc_provider.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; class TagsWidget extends StatelessWidget { - final Iterable tagIds; + final List tags; final bool isMultiLine; final void Function(int tagId)? onTagSelected; final bool isClickable; @@ -15,7 +12,7 @@ class TagsWidget extends StatelessWidget { const TagsWidget({ Key? key, - required this.tagIds, + required this.tags, this.isMultiLine = true, this.isClickable = true, this.onTagSelected, @@ -25,36 +22,33 @@ class TagsWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return TagBlocProvider( - child: BlocBuilder, LabelState>( - builder: (context, state) { - final children = tagIds - .where((id) => state.labels.containsKey(id)) - .map( - (id) => TagWidget( - tag: state.getLabel(id)!, - isClickable: isClickable, - onSelected: () => onTagSelected?.call(id), - showShortName: showShortNames, - dense: dense, - ), - ) - .toList(); - if (isMultiLine) { - return Wrap( - runAlignment: WrapAlignment.start, - children: children, - runSpacing: 4, - spacing: 4, - ); - } else { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row(children: children), - ); - } - }, - ), + return Builder( + builder: (context) { + final children = tags + .map( + (tag) => TagWidget( + tag: tag, + isClickable: isClickable, + onSelected: () => onTagSelected?.call(tag.id!), + showShortName: showShortNames, + dense: dense, + ), + ) + .toList(); + if (isMultiLine) { + return Wrap( + runAlignment: WrapAlignment.start, + children: children, + runSpacing: 4, + spacing: 4, + ); + } else { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: children), + ); + } + }, ); } } diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart index acdd44d..9df1856 100644 --- a/lib/features/labels/view/pages/labels_page.dart +++ b/lib/features/labels/view/pages/labels_page.dart @@ -6,7 +6,6 @@ import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_he import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/widgets/material/search/colored_tab_bar.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; -import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart'; @@ -17,6 +16,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_typ import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; +import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; @@ -141,12 +141,12 @@ class _LabelsPageState extends State notificationPredicate: (notification) => connectedState.isConnected, onRefresh: () => [ - context.read>(), - context.read>(), - context.read>(), - context.read>(), + context.read().reloadCorrespondents, + context.read().reloadDocumentTypes, + context.read().reloadTags, + context.read().reloadStoragePaths, ][_currentIndex] - .reload(), + .call(), child: TabBarView( controller: _tabController, children: [ @@ -157,6 +157,10 @@ class _LabelsPageState extends State SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( + labels: context + .watch() + .state + .correspondents, filterBuilder: (label) => DocumentFilter( correspondent: IdQueryParameter.fromId(label.id), @@ -180,6 +184,10 @@ class _LabelsPageState extends State SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( + labels: context + .watch() + .state + .documentTypes, filterBuilder: (label) => DocumentFilter( documentType: IdQueryParameter.fromId(label.id), @@ -203,6 +211,8 @@ class _LabelsPageState extends State SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( + labels: + context.watch().state.tags, filterBuilder: (label) => DocumentFilter( tags: IdsTagsQuery.fromIds([label.id!]), pageSize: label.documentCount ?? 0, @@ -234,6 +244,10 @@ class _LabelsPageState extends State SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( + labels: context + .watch() + .state + .storagePaths, onEdit: _openEditStoragePathPage, filterBuilder: (label) => DocumentFilter( storagePath: @@ -267,8 +281,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: EditCorrespondentPage(correspondent: correspondent), ), ), @@ -279,8 +293,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: EditDocumentTypePage(documentType: docType), ), ), @@ -291,8 +305,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: EditTagPage(tag: tag), ), ), @@ -303,8 +317,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: EditStoragePathPage( storagePath: path, ), @@ -317,8 +331,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: const AddCorrespondentPage(), ), ), @@ -329,8 +343,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: const AddDocumentTypePage(), ), ), @@ -341,8 +355,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: const AddTagPage(), ), ), @@ -353,8 +367,8 @@ class _LabelsPageState extends State Navigator.push( context, MaterialPageRoute( - builder: (_) => RepositoryProvider( - create: (context) => context.read>(), + builder: (_) => RepositoryProvider.value( + value: context.read(), child: const AddStoragePathPage(), ), ), diff --git a/lib/features/labels/view/widgets/label_item.dart b/lib/features/labels/view/widgets/label_item.dart index 9be716f..ee2e993 100644 --- a/lib/features/labels/view/widgets/label_item.dart +++ b/lib/features/labels/view/widgets/label_item.dart @@ -51,6 +51,7 @@ class LabelItem extends StatelessWidget { filter, context.read(), context.read(), + context.read(), ), child: const LinkedDocumentsPage(), ), diff --git a/lib/features/labels/view/widgets/label_tab_view.dart b/lib/features/labels/view/widgets/label_tab_view.dart index b75a016..5174e96 100644 --- a/lib/features/labels/view/widgets/label_tab_view.dart +++ b/lib/features/labels/view/widgets/label_tab_view.dart @@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/core/widgets/offline_widget.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; class LabelTabView extends StatelessWidget { + final Map labels; final DocumentFilter Function(Label) filterBuilder; final void Function(T) onEdit; final void Function() onAddNew; @@ -32,69 +32,61 @@ class LabelTabView extends StatelessWidget { required this.emptyStateDescription, required this.onAddNew, required this.emptyStateActionButtonLabel, + required this.labels, }); @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read(), - ), - child: BlocBuilder( - builder: (context, connectivityState) { - return BlocBuilder, LabelState>( - builder: (context, state) { - if (!state.isLoaded && !connectivityState.isConnected) { - return const OfflineWidget(); - } - final labels = state.labels.values.toList()..sort(); - if (labels.isEmpty) { - return SliverFillRemaining( - child: Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - emptyStateDescription, - textAlign: TextAlign.center, - ), - TextButton( - onPressed: onAddNew, - child: Text(emptyStateActionButtonLabel), - ), - ].padded(), - ), + return BlocBuilder( + builder: (context, connectivityState) { + if (!connectivityState.isConnected) { + return const OfflineWidget(); + } + final sortedLabels = labels.values.toList()..sort(); + if (labels.isEmpty) { + return SliverFillRemaining( + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + emptyStateDescription, + textAlign: TextAlign.center, ), - ); - } - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final l = labels.elementAt(index); - return LabelItem( - name: l.name, - content: contentBuilder?.call(l) ?? - Text( - translateMatchingAlgorithmName( - context, l.matchingAlgorithm) + - ((l.match?.isNotEmpty ?? false) - ? ": ${l.match}" - : ""), - maxLines: 2, - ), - onOpenEditPage: onEdit, - filterBuilder: filterBuilder, - leading: leadingBuilder?.call(l), - label: l, - ); - }, - childCount: labels.length, - ), + TextButton( + onPressed: onAddNew, + child: Text(emptyStateActionButtonLabel), + ), + ].padded(), + ), + ), + ); + } + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final l = sortedLabels.elementAt(index); + return LabelItem( + name: l.name, + content: contentBuilder?.call(l) ?? + Text( + translateMatchingAlgorithmName( + context, l.matchingAlgorithm) + + ((l.match?.isNotEmpty ?? false) + ? ": ${l.match}" + : ""), + maxLines: 2, + ), + onOpenEditPage: onEdit, + filterBuilder: filterBuilder, + leading: leadingBuilder?.call(l), + label: l, ); }, - ); - }, - ), + childCount: labels.length, + ), + ); + }, ); } } diff --git a/lib/features/labels/view/widgets/label_text.dart b/lib/features/labels/view/widgets/label_text.dart index 28a35e0..a6e2df8 100644 --- a/lib/features/labels/view/widgets/label_text.dart +++ b/lib/features/labels/view/widgets/label_text.dart @@ -1,37 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; class LabelText extends StatelessWidget { - final int? id; + final T? label; final String placeholder; final TextStyle? style; - const LabelText({ super.key, this.style, - this.id, this.placeholder = "", + required this.label, }); @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - child: BlocBuilder, LabelState>( - builder: (context, state) { - return Text( - state.labels[id]?.toString() ?? placeholder, - style: style, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, - ), + return Text( + label?.toString() ?? placeholder, + style: style, + maxLines: 1, + overflow: TextOverflow.ellipsis, ); ; } diff --git a/lib/features/linked_documents/cubit/linked_documents_cubit.dart b/lib/features/linked_documents/cubit/linked_documents_cubit.dart index 12731b1..fc47219 100644 --- a/lib/features/linked_documents/cubit/linked_documents_cubit.dart +++ b/lib/features/linked_documents/cubit/linked_documents_cubit.dart @@ -2,6 +2,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; +import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart'; @@ -17,12 +18,26 @@ class LinkedDocumentsCubit extends HydratedCubit @override final DocumentChangedNotifier notifier; + final LabelRepository _labelRepository; + LinkedDocumentsCubit( DocumentFilter filter, this.api, this.notifier, + this._labelRepository, ) : super(const LinkedDocumentsState()) { updateFilter(filter: filter); + _labelRepository.subscribe( + this, + onChanged: (labels) { + emit(state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + tags: labels.tags, + storagePaths: labels.storagePaths, + )); + }, + ); notifier.subscribe( this, onUpdated: replace, diff --git a/lib/features/linked_documents/cubit/linked_documents_state.dart b/lib/features/linked_documents/cubit/linked_documents_state.dart index 5545f0f..b4b8556 100644 --- a/lib/features/linked_documents/cubit/linked_documents_state.dart +++ b/lib/features/linked_documents/cubit/linked_documents_state.dart @@ -4,12 +4,22 @@ part of 'linked_documents_cubit.dart'; class LinkedDocumentsState extends DocumentPagingState { @JsonKey() final ViewType viewType; + + final Map correspondents; + final Map documentTypes; + final Map storagePaths; + final Map tags; + const LinkedDocumentsState({ this.viewType = ViewType.list, super.filter, super.isLoading, super.hasLoaded, super.value, + this.correspondents = const {}, + this.documentTypes = const {}, + this.storagePaths = const {}, + this.tags = const {}, }); LinkedDocumentsState copyWith({ @@ -18,6 +28,10 @@ class LinkedDocumentsState extends DocumentPagingState { bool? hasLoaded, List>? value, ViewType? viewType, + Map? correspondents, + Map? documentTypes, + Map? storagePaths, + Map? tags, }) { return LinkedDocumentsState( filter: filter ?? this.filter, @@ -25,6 +39,10 @@ class LinkedDocumentsState extends DocumentPagingState { hasLoaded: hasLoaded ?? this.hasLoaded, value: value ?? this.value, viewType: viewType ?? this.viewType, + correspondents: correspondents ?? this.correspondents, + documentTypes: documentTypes ?? this.documentTypes, + storagePaths: storagePaths ?? this.storagePaths, + tags: tags ?? this.tags, ); } diff --git a/lib/features/linked_documents/view/linked_documents_page.dart b/lib/features/linked_documents/view/linked_documents_page.dart index 6d370e9..2171f7c 100644 --- a/lib/features/linked_documents/view/linked_documents_page.dart +++ b/lib/features/linked_documents/view/linked_documents_page.dart @@ -61,6 +61,10 @@ class _LinkedDocumentsPageState extends State ), ); }, + correspondents: state.correspondents, + documentTypes: state.documentTypes, + storagePaths: state.storagePaths, + tags: state.tags, ), ], ); diff --git a/lib/features/saved_view/cubit/saved_view_cubit.dart b/lib/features/saved_view/cubit/saved_view_cubit.dart index 2613195..02984a0 100644 --- a/lib/features/saved_view/cubit/saved_view_cubit.dart +++ b/lib/features/saved_view/cubit/saved_view_cubit.dart @@ -2,42 +2,72 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; part 'saved_view_state.dart'; +part 'saved_view_cubit.freezed.dart'; class SavedViewCubit extends Cubit { - final SavedViewRepository _repository; - StreamSubscription? _subscription; + final SavedViewRepository _savedViewRepository; + final LabelRepository _labelRepository; - SavedViewCubit(this._repository) : super(const SavedViewState()) { - _subscription = _repository.values.listen( - (savedViews) { - if (savedViews?.hasLoaded ?? false) { - emit(state.copyWith(value: savedViews?.values, hasLoaded: true)); - } else { - emit(state.copyWith(hasLoaded: false)); - } + SavedViewCubit(this._savedViewRepository, this._labelRepository) + : super(SavedViewState.initial( + correspondents: _labelRepository.state.correspondents, + documentTypes: _labelRepository.state.documentTypes, + storagePaths: _labelRepository.state.storagePaths, + tags: _labelRepository.state.tags, + )) { + _labelRepository.subscribe( + this, + onChanged: (labels) { + emit( + state.copyWith( + correspondents: labels.correspondents, + documentTypes: labels.documentTypes, + tags: labels.tags, + storagePaths: labels.storagePaths, + ), + ); }, ); + + _savedViewRepository.subscribe(this, (views) { + emit( + state.maybeWhen( + loaded: + (savedViews, correspondents, documentTypes, tags, storagePaths) => + (state as _SavedViewLoadedState).copyWith( + savedViews: views, + ), + orElse: () => state, + ), + ); + }); } Future add(SavedView view) async { - final savedView = await _repository.create(view); - emit(state.copyWith(value: {...state.value, savedView.id!: savedView})); - return savedView; + return _savedViewRepository.create(view); } Future remove(SavedView view) { - return _repository.delete(view); + return _savedViewRepository.delete(view); } Future initialize() async { - final views = await _repository.findAll(); + final views = await _savedViewRepository.findAll(); final values = {for (var element in views) element.id!: element}; if (!isClosed) { - emit(SavedViewState(value: values, hasLoaded: true)); + emit(SavedViewState.loaded( + savedViews: values, + correspondents: state.correspondents, + documentTypes: state.documentTypes, + storagePaths: state.storagePaths, + tags: state.tags, + )); } } @@ -45,7 +75,8 @@ class SavedViewCubit extends Cubit { @override Future close() { - _subscription?.cancel(); + _savedViewRepository.unsubscribe(this); + _labelRepository.unsubscribe(this); return super.close(); } } diff --git a/lib/features/saved_view/cubit/saved_view_cubit.freezed.dart b/lib/features/saved_view/cubit/saved_view_cubit.freezed.dart new file mode 100644 index 0000000..64fd200 --- /dev/null +++ b/lib/features/saved_view/cubit/saved_view_cubit.freezed.dart @@ -0,0 +1,1382 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'saved_view_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$SavedViewState { + Map get correspondents => + throw _privateConstructorUsedError; + Map get documentTypes => + throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get storagePaths => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + initial, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loading, + required TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loaded, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult? Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_SavedViewIntialState value) initial, + required TResult Function(_SavedViewLoadingState value) loading, + required TResult Function(_SavedViewLoadedState value) loaded, + required TResult Function(_SavedViewErrorState value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SavedViewIntialState value)? initial, + TResult? Function(_SavedViewLoadingState value)? loading, + TResult? Function(_SavedViewLoadedState value)? loaded, + TResult? Function(_SavedViewErrorState value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SavedViewIntialState value)? initial, + TResult Function(_SavedViewLoadingState value)? loading, + TResult Function(_SavedViewLoadedState value)? loaded, + TResult Function(_SavedViewErrorState value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $SavedViewStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SavedViewStateCopyWith<$Res> { + factory $SavedViewStateCopyWith( + SavedViewState value, $Res Function(SavedViewState) then) = + _$SavedViewStateCopyWithImpl<$Res, SavedViewState>; + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class _$SavedViewStateCopyWithImpl<$Res, $Val extends SavedViewState> + implements $SavedViewStateCopyWith<$Res> { + _$SavedViewStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_value.copyWith( + correspondents: null == correspondents + ? _value.correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value.documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value.storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_SavedViewIntialStateCopyWith<$Res> + implements $SavedViewStateCopyWith<$Res> { + factory _$$_SavedViewIntialStateCopyWith(_$_SavedViewIntialState value, + $Res Function(_$_SavedViewIntialState) then) = + __$$_SavedViewIntialStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_SavedViewIntialStateCopyWithImpl<$Res> + extends _$SavedViewStateCopyWithImpl<$Res, _$_SavedViewIntialState> + implements _$$_SavedViewIntialStateCopyWith<$Res> { + __$$_SavedViewIntialStateCopyWithImpl(_$_SavedViewIntialState _value, + $Res Function(_$_SavedViewIntialState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_SavedViewIntialState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_SavedViewIntialState implements _SavedViewIntialState { + const _$_SavedViewIntialState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'SavedViewState.initial(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SavedViewIntialState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SavedViewIntialStateCopyWith<_$_SavedViewIntialState> get copyWith => + __$$_SavedViewIntialStateCopyWithImpl<_$_SavedViewIntialState>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + initial, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loading, + required TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loaded, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + error, + }) { + return initial(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult? Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + }) { + return initial?.call(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(correspondents, documentTypes, tags, storagePaths); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SavedViewIntialState value) initial, + required TResult Function(_SavedViewLoadingState value) loading, + required TResult Function(_SavedViewLoadedState value) loaded, + required TResult Function(_SavedViewErrorState value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SavedViewIntialState value)? initial, + TResult? Function(_SavedViewLoadingState value)? loading, + TResult? Function(_SavedViewLoadedState value)? loaded, + TResult? Function(_SavedViewErrorState value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SavedViewIntialState value)? initial, + TResult Function(_SavedViewLoadingState value)? loading, + TResult Function(_SavedViewLoadedState value)? loaded, + TResult Function(_SavedViewErrorState value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _SavedViewIntialState implements SavedViewState { + const factory _SavedViewIntialState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) = + _$_SavedViewIntialState; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_SavedViewIntialStateCopyWith<_$_SavedViewIntialState> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_SavedViewLoadingStateCopyWith<$Res> + implements $SavedViewStateCopyWith<$Res> { + factory _$$_SavedViewLoadingStateCopyWith(_$_SavedViewLoadingState value, + $Res Function(_$_SavedViewLoadingState) then) = + __$$_SavedViewLoadingStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_SavedViewLoadingStateCopyWithImpl<$Res> + extends _$SavedViewStateCopyWithImpl<$Res, _$_SavedViewLoadingState> + implements _$$_SavedViewLoadingStateCopyWith<$Res> { + __$$_SavedViewLoadingStateCopyWithImpl(_$_SavedViewLoadingState _value, + $Res Function(_$_SavedViewLoadingState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_SavedViewLoadingState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_SavedViewLoadingState implements _SavedViewLoadingState { + const _$_SavedViewLoadingState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'SavedViewState.loading(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SavedViewLoadingState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SavedViewLoadingStateCopyWith<_$_SavedViewLoadingState> get copyWith => + __$$_SavedViewLoadingStateCopyWithImpl<_$_SavedViewLoadingState>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + initial, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loading, + required TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loaded, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + error, + }) { + return loading(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult? Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + }) { + return loading?.call(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(correspondents, documentTypes, tags, storagePaths); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SavedViewIntialState value) initial, + required TResult Function(_SavedViewLoadingState value) loading, + required TResult Function(_SavedViewLoadedState value) loaded, + required TResult Function(_SavedViewErrorState value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SavedViewIntialState value)? initial, + TResult? Function(_SavedViewLoadingState value)? loading, + TResult? Function(_SavedViewLoadedState value)? loaded, + TResult? Function(_SavedViewErrorState value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SavedViewIntialState value)? initial, + TResult Function(_SavedViewLoadingState value)? loading, + TResult Function(_SavedViewLoadedState value)? loaded, + TResult Function(_SavedViewErrorState value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _SavedViewLoadingState implements SavedViewState { + const factory _SavedViewLoadingState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) = + _$_SavedViewLoadingState; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_SavedViewLoadingStateCopyWith<_$_SavedViewLoadingState> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_SavedViewLoadedStateCopyWith<$Res> + implements $SavedViewStateCopyWith<$Res> { + factory _$$_SavedViewLoadedStateCopyWith(_$_SavedViewLoadedState value, + $Res Function(_$_SavedViewLoadedState) then) = + __$$_SavedViewLoadedStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_SavedViewLoadedStateCopyWithImpl<$Res> + extends _$SavedViewStateCopyWithImpl<$Res, _$_SavedViewLoadedState> + implements _$$_SavedViewLoadedStateCopyWith<$Res> { + __$$_SavedViewLoadedStateCopyWithImpl(_$_SavedViewLoadedState _value, + $Res Function(_$_SavedViewLoadedState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? savedViews = null, + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_SavedViewLoadedState( + savedViews: null == savedViews + ? _value._savedViews + : savedViews // ignore: cast_nullable_to_non_nullable + as Map, + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_SavedViewLoadedState implements _SavedViewLoadedState { + const _$_SavedViewLoadedState( + {required final Map savedViews, + required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) + : _savedViews = savedViews, + _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _savedViews; + @override + Map get savedViews { + if (_savedViews is EqualUnmodifiableMapView) return _savedViews; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_savedViews); + } + + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'SavedViewState.loaded(savedViews: $savedViews, correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SavedViewLoadedState && + const DeepCollectionEquality() + .equals(other._savedViews, _savedViews) && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_savedViews), + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SavedViewLoadedStateCopyWith<_$_SavedViewLoadedState> get copyWith => + __$$_SavedViewLoadedStateCopyWithImpl<_$_SavedViewLoadedState>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + initial, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loading, + required TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loaded, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + error, + }) { + return loaded( + savedViews, correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult? Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + }) { + return loaded?.call( + savedViews, correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded( + savedViews, correspondents, documentTypes, tags, storagePaths); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SavedViewIntialState value) initial, + required TResult Function(_SavedViewLoadingState value) loading, + required TResult Function(_SavedViewLoadedState value) loaded, + required TResult Function(_SavedViewErrorState value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SavedViewIntialState value)? initial, + TResult? Function(_SavedViewLoadingState value)? loading, + TResult? Function(_SavedViewLoadedState value)? loaded, + TResult? Function(_SavedViewErrorState value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SavedViewIntialState value)? initial, + TResult Function(_SavedViewLoadingState value)? loading, + TResult Function(_SavedViewLoadedState value)? loaded, + TResult Function(_SavedViewErrorState value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _SavedViewLoadedState implements SavedViewState { + const factory _SavedViewLoadedState( + {required final Map savedViews, + required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) = + _$_SavedViewLoadedState; + + Map get savedViews; + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_SavedViewLoadedStateCopyWith<_$_SavedViewLoadedState> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$_SavedViewErrorStateCopyWith<$Res> + implements $SavedViewStateCopyWith<$Res> { + factory _$$_SavedViewErrorStateCopyWith(_$_SavedViewErrorState value, + $Res Function(_$_SavedViewErrorState) then) = + __$$_SavedViewErrorStateCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths}); +} + +/// @nodoc +class __$$_SavedViewErrorStateCopyWithImpl<$Res> + extends _$SavedViewStateCopyWithImpl<$Res, _$_SavedViewErrorState> + implements _$$_SavedViewErrorStateCopyWith<$Res> { + __$$_SavedViewErrorStateCopyWithImpl(_$_SavedViewErrorState _value, + $Res Function(_$_SavedViewErrorState) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? correspondents = null, + Object? documentTypes = null, + Object? tags = null, + Object? storagePaths = null, + }) { + return _then(_$_SavedViewErrorState( + correspondents: null == correspondents + ? _value._correspondents + : correspondents // ignore: cast_nullable_to_non_nullable + as Map, + documentTypes: null == documentTypes + ? _value._documentTypes + : documentTypes // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + storagePaths: null == storagePaths + ? _value._storagePaths + : storagePaths // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$_SavedViewErrorState implements _SavedViewErrorState { + const _$_SavedViewErrorState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) + : _correspondents = correspondents, + _documentTypes = documentTypes, + _tags = tags, + _storagePaths = storagePaths; + + final Map _correspondents; + @override + Map get correspondents { + if (_correspondents is EqualUnmodifiableMapView) return _correspondents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_correspondents); + } + + final Map _documentTypes; + @override + Map get documentTypes { + if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_documentTypes); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _storagePaths; + @override + Map get storagePaths { + if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_storagePaths); + } + + @override + String toString() { + return 'SavedViewState.error(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_SavedViewErrorState && + const DeepCollectionEquality() + .equals(other._correspondents, _correspondents) && + const DeepCollectionEquality() + .equals(other._documentTypes, _documentTypes) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality() + .equals(other._storagePaths, _storagePaths)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_correspondents), + const DeepCollectionEquality().hash(_documentTypes), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_storagePaths)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_SavedViewErrorStateCopyWith<_$_SavedViewErrorState> get copyWith => + __$$_SavedViewErrorStateCopyWithImpl<_$_SavedViewErrorState>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + initial, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loading, + required TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + loaded, + required TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths) + error, + }) { + return error(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult? Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult? Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + }) { + return error?.call(correspondents, documentTypes, tags, storagePaths); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + initial, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loading, + TResult Function( + Map savedViews, + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + loaded, + TResult Function( + Map correspondents, + Map documentTypes, + Map tags, + Map storagePaths)? + error, + required TResult orElse(), + }) { + if (error != null) { + return error(correspondents, documentTypes, tags, storagePaths); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_SavedViewIntialState value) initial, + required TResult Function(_SavedViewLoadingState value) loading, + required TResult Function(_SavedViewLoadedState value) loaded, + required TResult Function(_SavedViewErrorState value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_SavedViewIntialState value)? initial, + TResult? Function(_SavedViewLoadingState value)? loading, + TResult? Function(_SavedViewLoadedState value)? loaded, + TResult? Function(_SavedViewErrorState value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_SavedViewIntialState value)? initial, + TResult Function(_SavedViewLoadingState value)? loading, + TResult Function(_SavedViewLoadedState value)? loaded, + TResult Function(_SavedViewErrorState value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _SavedViewErrorState implements SavedViewState { + const factory _SavedViewErrorState( + {required final Map correspondents, + required final Map documentTypes, + required final Map tags, + required final Map storagePaths}) = + _$_SavedViewErrorState; + + @override + Map get correspondents; + @override + Map get documentTypes; + @override + Map get tags; + @override + Map get storagePaths; + @override + @JsonKey(ignore: true) + _$$_SavedViewErrorStateCopyWith<_$_SavedViewErrorState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/saved_view/cubit/saved_view_state.dart b/lib/features/saved_view/cubit/saved_view_state.dart index b1f6343..b886840 100644 --- a/lib/features/saved_view/cubit/saved_view_state.dart +++ b/lib/features/saved_view/cubit/saved_view_state.dart @@ -1,29 +1,33 @@ part of 'saved_view_cubit.dart'; -class SavedViewState extends Equatable { - final bool hasLoaded; - final Map value; +@freezed +class SavedViewState with _$SavedViewState { + const factory SavedViewState.initial({ + required Map correspondents, + required Map documentTypes, + required Map tags, + required Map storagePaths, + }) = _SavedViewIntialState; - const SavedViewState({ - this.value = const {}, - this.hasLoaded = false, - }); + const factory SavedViewState.loading({ + required Map correspondents, + required Map documentTypes, + required Map tags, + required Map storagePaths, + }) = _SavedViewLoadingState; - @override - List get props => [ - hasLoaded, - value, - ]; + const factory SavedViewState.loaded({ + required Map savedViews, + required Map correspondents, + required Map documentTypes, + required Map tags, + required Map storagePaths, + }) = _SavedViewLoadedState; - SavedViewState copyWith({ - Map? value, - int? selectedSavedViewId, - bool overwriteSelectedSavedViewId = false, - bool? hasLoaded, - }) { - return SavedViewState( - value: value ?? this.value, - hasLoaded: hasLoaded ?? this.hasLoaded, - ); - } + const factory SavedViewState.error({ + required Map correspondents, + required Map documentTypes, + required Map tags, + required Map storagePaths, + }) = _SavedViewErrorState; } diff --git a/lib/features/saved_view/view/add_saved_view_page.dart b/lib/features/saved_view/view/add_saved_view_page.dart index 23de7ad..a19475b 100644 --- a/lib/features/saved_view/view/add_saved_view_page.dart +++ b/lib/features/saved_view/view/add_saved_view_page.dart @@ -62,7 +62,7 @@ class _AddSavedViewPageState extends State { ], ), ), - Divider(), + const Divider(), Text( "Review filter", style: Theme.of(context).textTheme.bodyLarge, @@ -80,42 +80,6 @@ class _AddSavedViewPageState extends State { ); } - Padding _buildOld(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - FormBuilder( - key: _savedViewFormKey, - child: Expanded( - child: ListView( - children: [ - FormBuilderTextField( - name: fkName, - validator: FormBuilderValidators.required(), - decoration: InputDecoration( - label: Text(S.of(context)!.name), - ), - ), - FormBuilderCheckbox( - name: fkShowOnDashboard, - initialValue: false, - title: Text(S.of(context)!.showOnDashboard), - ), - FormBuilderCheckbox( - name: fkShowInSidebar, - initialValue: false, - title: Text(S.of(context)!.showInSidebar), - ), - ], - ), - ), - ), - ], - ), - ); - } - void _onCreate(BuildContext context) { if (_savedViewFormKey.currentState?.saveAndValidate() ?? false) { Navigator.pop( diff --git a/lib/features/saved_view/view/saved_view_list.dart b/lib/features/saved_view/view/saved_view_list.dart index aa0d639..f57e311 100644 --- a/lib/features/saved_view/view/saved_view_list.dart +++ b/lib/features/saved_view/view/saved_view_list.dart @@ -12,52 +12,70 @@ class SavedViewList extends StatelessWidget { @override Widget build(BuildContext context) { - final savedViewCubit = context.read(); return BlocBuilder( builder: (context, connectivity) { return BlocBuilder( builder: (context, state) { - if (state.value.isEmpty) { - return SliverToBoxAdapter( - child: HintCard( - hintText: - S.of(context)!.createViewsToQuicklyFilterYourDocuments, - ), - ); - } - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final view = state.value.values.elementAt(index); - return ListTile( - enabled: connectivity.isConnected, - title: Text(view.name), - subtitle: Text( - S.of(context)!.nFiltersSet(view.filterRules.length), + return state.when( + initial: (correspondents, documentTypes, tags, storagePaths) => + Container(), + loading: (correspondents, documentTypes, tags, storagePaths) => + Center( + child: Text("Saved views loading..."), + ), + loaded: (savedViews, correspondents, documentTypes, tags, + storagePaths) { + if (savedViews.isEmpty) { + return SliverToBoxAdapter( + child: HintCard( + hintText: S + .of(context)! + .createViewsToQuicklyFilterYourDocuments, ), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => SavedViewDetailsCubit( - context.read(), - context.read(), - savedView: view, + ); + } + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final view = savedViews.values.elementAt(index); + return ListTile( + enabled: connectivity.isConnected, + title: Text(view.name), + subtitle: Text( + S.of(context)!.nFiltersSet(view.filterRules.length), + ), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SavedViewDetailsCubit( + context.read(), + context.read(), + savedView: view, + ), + ), + ], + child: SavedViewDetailsPage( + onDelete: + context.read().remove, ), ), - ], - child: SavedViewDetailsPage( - onDelete: savedViewCubit.remove, ), - ), - ), + ); + }, ); }, - ); - }, - childCount: state.value.length, + childCount: savedViews.length, + ), + ); + }, + error: (correspondents, documentTypes, tags, storagePaths) => + Center( + child: Text( + "An error occurred while trying to load the saved views.", + ), ), ); }, diff --git a/lib/features/settings/view/dialogs/account_settings_dialog.dart b/lib/features/settings/view/dialogs/account_settings_dialog.dart index 62d5f8c..52a7109 100644 --- a/lib/features/settings/view/dialogs/account_settings_dialog.dart +++ b/lib/features/settings/view/dialogs/account_settings_dialog.dart @@ -82,10 +82,7 @@ class AccountSettingsDialog extends StatelessWidget { try { await context.read().logout(); await context.read().clear(); - await context.read>().clear(); - await context.read>().clear(); - await context.read>().clear(); - await context.read>().clear(); + await context.read().clear(); await context.read().clear(); await HydratedBloc.storage.clear(); } on PaperlessServerException catch (error, stackTrace) { diff --git a/lib/main.dart b/lib/main.dart index 8ac517e..d69ae8c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,17 +21,12 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; -import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart'; -import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart'; import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart'; -import 'package:paperless_mobile/core/repository/impl/storage_path_repository_impl.dart'; -import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/security/session_manager.dart'; import 'package:paperless_mobile/core/service/connectivity_status_service.dart'; import 'package:paperless_mobile/core/service/dio_file_service.dart'; -import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart'; import 'package:paperless_mobile/features/home/view/home_page.dart'; import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart'; @@ -105,10 +100,7 @@ void main() async { await connectivityCubit.initialize(); // Create repositories - final tagRepository = TagRepositoryImpl(labelsApi); - final correspondentRepository = CorrespondentRepositoryImpl(labelsApi); - final documentTypeRepository = DocumentTypeRepositoryImpl(labelsApi); - final storagePathRepository = StoragePathRepositoryImpl(labelsApi); + final labelRepository = LabelRepository(labelsApi); final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi); //Create cubits/blocs @@ -163,17 +155,8 @@ void main() async { ], child: MultiRepositoryProvider( providers: [ - RepositoryProvider>.value( - value: tagRepository, - ), - RepositoryProvider>.value( - value: correspondentRepository, - ), - RepositoryProvider>.value( - value: documentTypeRepository, - ), - RepositoryProvider>.value( - value: storagePathRepository, + RepositoryProvider.value( + value: labelRepository, ), RepositoryProvider.value( value: savedViewRepository, diff --git a/lib/routes/document_details_route.dart b/lib/routes/document_details_route.dart index 7bde498..0d484a4 100644 --- a/lib/routes/document_details_route.dart +++ b/lib/routes/document_details_route.dart @@ -1,7 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; @@ -19,9 +18,11 @@ class DocumentDetailsRoute extends StatelessWidget { context.read(), context.read(), context.read(), + context.read(), initialDocument: args.document, ), - child: LabelRepositoriesProvider( + child: RepositoryProvider.value( + value: context.read(), child: DocumentDetailsPage( allowEdit: args.allowEdit, isLabelClickable: args.isLabelClickable, diff --git a/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api.dart b/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api.dart index 5b83073..8628cdd 100644 --- a/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api.dart +++ b/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api.dart @@ -1,7 +1,7 @@ import 'package:paperless_api/src/models/saved_view_model.dart'; abstract class PaperlessSavedViewsApi { - Future find(int id); + Future find(int id); Future> findAll([Iterable? ids]); Future save(SavedView view); diff --git a/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api_impl.dart b/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api_impl.dart index 609f750..4ad183c 100644 --- a/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api_impl.dart +++ b/packages/paperless_api/lib/src/modules/saved_views_api/paperless_saved_views_api_impl.dart @@ -60,7 +60,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi { } @override - Future find(int id) { + Future find(int id) { return getSingleResult( "/api/saved_views/$id/", SavedView.fromJson, diff --git a/packages/paperless_api/lib/src/request_utils.dart b/packages/paperless_api/lib/src/request_utils.dart index 0972fcc..6c0fcfa 100644 --- a/packages/paperless_api/lib/src/request_utils.dart +++ b/packages/paperless_api/lib/src/request_utils.dart @@ -4,7 +4,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:paperless_api/src/models/paperless_server_exception.dart'; -Future getSingleResult( +Future getSingleResult( String url, T Function(Map) fromJson, ErrorCode errorCode, { diff --git a/pubspec.lock b/pubspec.lock index c604197..a25ce42 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -731,6 +731,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.4.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: e819441678f1679b719008ff2ff0ef045d66eed9f9ec81166ca0d9b02a187454 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 + url: "https://pub.dev" + source: hosted + version: "2.2.0" frontend_server_client: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4ee13ce..661c1fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,6 +91,7 @@ dependencies: dynamic_color: ^1.5.4 flutter_html: ^3.0.0-alpha.6 in_app_review: ^2.0.6 + freezed_annotation: ^2.2.0 dev_dependencies: integration_test: @@ -105,6 +106,7 @@ dev_dependencies: json_serializable: ^6.5.4 dart_code_metrics: ^5.4.0 auto_route_generator: ^5.0.3 + freezed: ^2.3.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec