From 5dcae3f0b3096840609021a7c955b6ac5437270e Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Fri, 6 Oct 2023 14:27:42 +0200 Subject: [PATCH] Add migration scripts, remove dependencies to codegen, and cleanups --- lib/core/config/hive/hive_config.dart | 8 + lib/core/repository/label_repository.dart | 24 +-- .../repository/persistent_repository.dart | 2 +- .../repository/saved_view_repository.dart | 28 ++-- .../saved_view_repository_state.dart | 2 + lib/core/repository/user_repository.dart | 19 +-- .../repository/user_repository_state.dart | 22 ++- .../view/document_edit_page.dart | 86 ++++++---- .../document_upload_preparation_page.dart | 33 ++-- .../documents/cubit/documents_cubit.dart | 19 +-- .../documents/cubit/documents_state.dart | 12 -- .../documents/view/pages/documents_page.dart | 83 ++++----- lib/features/inbox/cubit/inbox_cubit.dart | 4 +- .../view/widgets/fullscreen_tags_form.dart | 8 +- .../tags/view/widgets/tags_form_field.dart | 26 ++- .../labels/view/pages/labels_page.dart | 8 +- .../view/widgets/fullscreen_label_form.dart | 125 +++++++------- .../labels/view/widgets/label_form_field.dart | 29 ++-- lib/features/landing/view/landing_page.dart | 3 +- lib/main.dart | 50 +++++- .../lib/config/hive/hive_type_ids.dart | 18 +- .../lib/src/models/document_filter.dart | 65 ++++---- .../lib/src/models/filter_rule_model.dart | 96 ++++++----- .../user_permission_extension.dart | 50 +++--- .../query_parameters/id_query_parameter.dart | 157 +++++++----------- .../tags_query/tags_query.dart | 102 +++++++----- .../lib/src/models/user_model.dart | 136 +++++++++------ .../paperless_documents_api_impl.dart | 2 +- .../labels_api/paperless_labels_api.dart | 1 - .../paperless_api/test/saved_view_test.dart | 38 ++--- pubspec.lock | 56 +++++++ pubspec.yaml | 1 + 32 files changed, 734 insertions(+), 579 deletions(-) diff --git a/lib/core/config/hive/hive_config.dart b/lib/core/config/hive/hive_config.dart index 6557f20a..66288fe3 100644 --- a/lib/core/config/hive/hive_config.dart +++ b/lib/core/config/hive/hive_config.dart @@ -19,6 +19,14 @@ class HiveBoxes { static const localUserAccount = 'localUserAccount'; static const localUserAppState = 'localUserAppState'; static const hosts = 'hosts'; + + static List get all => [ + globalSettings, + localUserCredentials, + localUserAccount, + localUserAppState, + hosts, + ]; } class HiveTypeIds { diff --git a/lib/core/repository/label_repository.dart b/lib/core/repository/label_repository.dart index 563c6663..e7b73dc8 100644 --- a/lib/core/repository/label_repository.dart +++ b/lib/core/repository/label_repository.dart @@ -200,19 +200,13 @@ class LabelRepository extends PersistentRepository { return updated; } - @override - Future clear() async { - await super.clear(); - emit(const LabelRepositoryState()); - } - - @override - LabelRepositoryState? fromJson(Map json) { - return LabelRepositoryState.fromJson(json); - } - - @override - Map? toJson(LabelRepositoryState state) { - return state.toJson(); - } + // @override + // LabelRepositoryState? fromJson(Map json) { + // return LabelRepositoryState.fromJson(json); + // } + + // @override + // Map? toJson(LabelRepositoryState state) { + // return state.toJson(); + // } } diff --git a/lib/core/repository/persistent_repository.dart b/lib/core/repository/persistent_repository.dart index 8df0084f..1738e453 100644 --- a/lib/core/repository/persistent_repository.dart +++ b/lib/core/repository/persistent_repository.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -abstract class PersistentRepository extends HydratedCubit { +abstract class PersistentRepository extends Cubit { final Map _subscribers = {}; PersistentRepository(T initialState) : super(initialState); diff --git a/lib/core/repository/saved_view_repository.dart b/lib/core/repository/saved_view_repository.dart index 09fb82d1..ebc64ed3 100644 --- a/lib/core/repository/saved_view_repository.dart +++ b/lib/core/repository/saved_view_repository.dart @@ -76,20 +76,20 @@ class SavedViewRepository return found; } - @override - Future clear() async { - await _initialized.future; - await super.clear(); - emit(const SavedViewRepositoryState.initial()); - } + // @override + // Future clear() async { + // await _initialized.future; + // await super.clear(); + // emit(const SavedViewRepositoryState.initial()); + // } - @override - SavedViewRepositoryState? fromJson(Map json) { - return SavedViewRepositoryState.fromJson(json); - } + // @override + // SavedViewRepositoryState? fromJson(Map json) { + // return SavedViewRepositoryState.fromJson(json); + // } - @override - Map? toJson(SavedViewRepositoryState state) { - return state.toJson(); - } + // @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 index bf720587..24989911 100644 --- a/lib/core/repository/saved_view_repository_state.dart +++ b/lib/core/repository/saved_view_repository_state.dart @@ -1,5 +1,7 @@ part of 'saved_view_repository.dart'; + + @freezed class SavedViewRepositoryState with _$SavedViewRepositoryState { const factory SavedViewRepositoryState.initial({ diff --git a/lib/core/repository/user_repository.dart b/lib/core/repository/user_repository.dart index b1748c3e..990c85e1 100644 --- a/lib/core/repository/user_repository.dart +++ b/lib/core/repository/user_repository.dart @@ -1,10 +1,9 @@ +import 'package:equatable/equatable.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/persistent_repository.dart'; part 'user_repository_state.dart'; -part 'user_repository.freezed.dart'; -part 'user_repository.g.dart'; /// Repository for new users (API v3, server version 1.14.2+) class UserRepository extends PersistentRepository { @@ -28,13 +27,13 @@ class UserRepository extends PersistentRepository { return user; } - @override - UserRepositoryState? fromJson(Map json) { - return UserRepositoryState.fromJson(json); - } + // @override + // UserRepositoryState? fromJson(Map json) { + // return UserRepositoryState.fromJson(json); + // } - @override - Map? toJson(UserRepositoryState state) { - return state.toJson(); - } + // @override + // Map? toJson(UserRepositoryState state) { + // return state.toJson(); + // } } diff --git a/lib/core/repository/user_repository_state.dart b/lib/core/repository/user_repository_state.dart index 3cd32acb..ceb06a2c 100644 --- a/lib/core/repository/user_repository_state.dart +++ b/lib/core/repository/user_repository_state.dart @@ -1,11 +1,19 @@ part of 'user_repository.dart'; -@freezed -class UserRepositoryState with _$UserRepositoryState { - const factory UserRepositoryState({ - @Default({}) Map users, - }) = _UserRepositoryState; +class UserRepositoryState with EquatableMixin { + final Map users; + const UserRepositoryState({ + this.users = const {}, + }); - factory UserRepositoryState.fromJson(Map json) => - _$UserRepositoryStateFromJson(json); + UserRepositoryState copyWith({ + Map? users, + }) { + return UserRepositoryState( + users: users ?? this.users, + ); + } + + @override + List get props => [users]; } diff --git a/lib/features/document_edit/view/document_edit_page.dart b/lib/features/document_edit/view/document_edit_page.dart index f974eb74..d660e76e 100644 --- a/lib/features/document_edit/view/document_edit_page.dart +++ b/lib/features/document_edit/view/document_edit_page.dart @@ -109,11 +109,12 @@ class _DocumentEditPageState extends State { .watch() .state .correspondents, - initialValue: - state.document.correspondent != null - ? IdQueryParameter.fromId( - state.document.correspondent!) - : const IdQueryParameter.unset(), + initialValue: state + .document.correspondent != + null + ? SetIdQueryParameter( + id: state.document.correspondent!) + : const UnsetIdQueryParameter(), name: fkCorrespondent, prefixIcon: const Icon(Icons.person_outlined), @@ -135,7 +136,7 @@ class _DocumentEditPageState extends State { _formKey.currentState ?.fields[fkCorrespondent] ?.didChange( - IdQueryParameter.fromId(itemData), + SetIdQueryParameter(id: itemData), ); }, ), @@ -161,11 +162,11 @@ class _DocumentEditPageState extends State { addLabelText: S.of(context)!.addDocumentType, labelText: S.of(context)!.documentType, - initialValue: - state.document.documentType != null - ? IdQueryParameter.fromId( - state.document.documentType!) - : const IdQueryParameter.unset(), + initialValue: state.document.documentType != + null + ? SetIdQueryParameter( + id: state.document.documentType!) + : const UnsetIdQueryParameter(), options: state.documentTypes, name: _DocumentEditPageState.fkDocumentType, prefixIcon: @@ -185,7 +186,7 @@ class _DocumentEditPageState extends State { onPressed: () => _formKey.currentState ?.fields[fkDocumentType] ?.didChange( - IdQueryParameter.fromId(itemData), + SetIdQueryParameter(id: itemData), ), ), ), @@ -211,9 +212,9 @@ class _DocumentEditPageState extends State { options: state.storagePaths, initialValue: state.document.storagePath != null - ? IdQueryParameter.fromId( - state.document.storagePath!) - : const IdQueryParameter.unset(), + ? SetIdQueryParameter( + id: state.document.storagePath!) + : const UnsetIdQueryParameter(), name: fkStoragePath, prefixIcon: const Icon(Icons.folder_outlined), @@ -229,7 +230,7 @@ class _DocumentEditPageState extends State { allowOnlySelection: true, allowCreation: true, allowExclude: false, - initialValue: TagsQuery.ids( + initialValue: IdsTagsQuery( include: state.document.tags.toList(), ), ).padded(), @@ -254,15 +255,17 @@ class _DocumentEditPageState extends State { ?.fields[fkTags]?.value as TagsQuery; _formKey.currentState?.fields[fkTags] ?.didChange( - currentTags.maybeWhen( - ids: (include, exclude) => - TagsQuery.ids(include: [ - ...include, - itemData - ], exclude: exclude), - orElse: () => TagsQuery.ids( - include: [itemData]), - ), + switch (currentTags) { + IdsTagsQuery( + include: var i, + exclude: var e + ) => + IdsTagsQuery( + include: [...i, itemData], + exclude: e, + ), + _ => IdsTagsQuery(include: [itemData]) + }, ); }, ); @@ -301,16 +304,35 @@ class _DocumentEditPageState extends State { Future _onSubmit(DocumentModel document) async { if (_formKey.currentState?.saveAndValidate() ?? false) { final values = _formKey.currentState!.value; + + final correspondentParam = values[fkCorrespondent] as IdQueryParameter?; + final documentTypeParam = values[fkDocumentType] as IdQueryParameter?; + final storagePathParam = values[fkStoragePath] as IdQueryParameter?; + final tagsParam = values[fkTags] as TagsQuery?; + + final correspondent = switch (correspondentParam) { + SetIdQueryParameter(id: var id) => id, + _ => null, + }; + final documentType = switch (documentTypeParam) { + SetIdQueryParameter(id: var id) => id, + _ => null, + }; + final storagePath = switch (storagePathParam) { + SetIdQueryParameter(id: var id) => id, + _ => null, + }; + final tags = switch (tagsParam) { + IdsTagsQuery(include: var i) => i, + _ => null, + }; var mergedDocument = document.copyWith( title: values[fkTitle], created: values[fkCreatedDate], - documentType: () => (values[fkDocumentType] as IdQueryParameter?) - ?.whenOrNull(fromId: (id) => id), - correspondent: () => (values[fkCorrespondent] as IdQueryParameter?) - ?.whenOrNull(fromId: (id) => id), - storagePath: () => (values[fkStoragePath] as IdQueryParameter?) - ?.whenOrNull(fromId: (id) => id), - tags: (values[fkTags] as IdsTagsQuery?)?.include, + correspondent: () => correspondent, + documentType: () => documentType, + storagePath: () => storagePath, + tags: tags, content: values[fkContent], ); diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart index a2113d52..ca244d09 100644 --- a/lib/features/document_upload/view/document_upload_preparation_page.dart +++ b/lib/features/document_upload/view/document_upload_preparation_page.dart @@ -344,20 +344,29 @@ class _DocumentUploadPreparationPageState final cubit = context.read(); try { setState(() => _isUploadLoading = true); + final formValues = _formKey.currentState!.value; - final fv = _formKey.currentState!.value; + final correspondentParam = + formValues[DocumentModel.correspondentKey] as IdQueryParameter?; + final docTypeParam = + formValues[DocumentModel.documentTypeKey] as IdQueryParameter?; + final tagsParam = formValues[DocumentModel.tagsKey] as TagsQuery?; + final createdAt = formValues[DocumentModel.createdKey] as DateTime?; + final title = formValues[DocumentModel.titleKey] as String; + final correspondent = switch (correspondentParam) { + SetIdQueryParameter(id: var id) => id, + _ => null, + }; + final docType = switch (docTypeParam) { + SetIdQueryParameter(id: var id) => id, + _ => null, + }; + final tags = switch (tagsParam) { + IdsTagsQuery(include: var ids) => ids, + _ => const [], + }; - final createdAt = fv[DocumentModel.createdKey] as DateTime?; - final title = fv[DocumentModel.titleKey] as String; - final docType = (fv[DocumentModel.documentTypeKey] as IdQueryParameter?) - ?.whenOrNull(fromId: (id) => id); - final tags = (fv[DocumentModel.tagsKey] as TagsQuery?) - ?.whenOrNull(ids: (include, exclude) => include) ?? - []; - final correspondent = - (fv[DocumentModel.correspondentKey] as IdQueryParameter?) - ?.whenOrNull(fromId: (id) => id); - final asn = fv[DocumentModel.asnKey] as int?; + final asn = formValues[DocumentModel.asnKey] as int?; final taskId = await cubit.upload( await widget.fileBytes, filename: _padWithExtension( diff --git a/lib/features/documents/cubit/documents_cubit.dart b/lib/features/documents/cubit/documents_cubit.dart index da1add14..b5fb1a86 100644 --- a/lib/features/documents/cubit/documents_cubit.dart +++ b/lib/features/documents/cubit/documents_cubit.dart @@ -12,10 +12,9 @@ import 'package:paperless_mobile/features/paged_document_view/cubit/document_pag import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart'; -part 'documents_cubit.g.dart'; part 'documents_state.dart'; -class DocumentsCubit extends HydratedCubit +class DocumentsCubit extends Cubit with DocumentPagingBlocMixin { @override final PaperlessDocumentsApi api; @@ -135,13 +134,13 @@ class DocumentsCubit extends HydratedCubit await _userState.save(); } - @override - DocumentsState? fromJson(Map json) { - return DocumentsState.fromJson(json); - } + // @override + // DocumentsState? fromJson(Map json) { + // return DocumentsState.fromJson(json); + // } - @override - Map? toJson(DocumentsState state) { - return state.toJson(); - } + // @override + // Map? toJson(DocumentsState state) { + // return state.toJson(); + // } } diff --git a/lib/features/documents/cubit/documents_state.dart b/lib/features/documents/cubit/documents_state.dart index f93756e2..87a050a4 100644 --- a/lib/features/documents/cubit/documents_state.dart +++ b/lib/features/documents/cubit/documents_state.dart @@ -1,17 +1,10 @@ part of 'documents_cubit.dart'; -@JsonSerializable() class DocumentsState extends DocumentPagingState { - @JsonKey(includeToJson: false, includeFromJson: false) final List selection; - - @JsonKey(includeToJson: false, includeFromJson: false) final Map correspondents; - @JsonKey(includeToJson: false, includeFromJson: false) final Map documentTypes; - @JsonKey(includeToJson: false, includeFromJson: false) final Map tags; - @JsonKey(includeToJson: false, includeFromJson: false) final Map storagePaths; final ViewType viewType; @@ -85,9 +78,4 @@ class DocumentsState extends DocumentPagingState { value: value, ); } - - factory DocumentsState.fromJson(Map json) => - _$DocumentsStateFromJson(json); - - Map toJson() => _$DocumentsStateToJson(this); } diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index 9689a44f..62f89da3 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -252,7 +252,8 @@ class _DocumentsPageState extends State { cubit.resetSelection(); return false; } - if (cubit.state.filter.appliedFiltersCount > 0 || cubit.state.filter.selectedView != null) { + if (cubit.state.filter.appliedFiltersCount > 0 || + cubit.state.filter.selectedView != null) { await _onResetFilter(); return false; } @@ -512,8 +513,8 @@ class _DocumentsPageState extends State { void _addTagToFilter(int tagId) { final cubit = context.read(); try { - cubit.state.filter.tags.maybeMap( - ids: (state) { + switch (cubit.state.filter.tags) { + case IdsTagsQuery state: if (state.include.contains(tagId)) { cubit.updateCurrentFilter( (filter) => filter.copyWith( @@ -541,13 +542,13 @@ class _DocumentsPageState extends State { ), ); } - }, - orElse: () { + break; + default: cubit.updateCurrentFilter( - (filter) => filter.copyWith(tags: TagsQuery.ids(include: [tagId])), + (filter) => filter.copyWith(tags: IdsTagsQuery(include: [tagId])), ); - }, - ); + break; + } } on PaperlessApiException catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } @@ -558,27 +559,26 @@ class _DocumentsPageState extends State { final cubit = context.read(); try { - cubit.state.filter.correspondent.maybeWhen( - fromId: (id) { + switch (cubit.state.filter.correspondent) { + case SetIdQueryParameter(id: var id): if (id == correspondentId) { cubit.updateCurrentFilter( - (filter) => filter.copyWith( - correspondent: const IdQueryParameter.unset()), + (filter) => + filter.copyWith(correspondent: const UnsetIdQueryParameter()), ); } else { cubit.updateCurrentFilter( (filter) => filter.copyWith( - correspondent: IdQueryParameter.fromId(correspondentId)), + correspondent: SetIdQueryParameter(id: correspondentId)), ); } - }, - orElse: () { - cubit.updateCurrentFilter( - (filter) => filter.copyWith( - correspondent: IdQueryParameter.fromId(correspondentId)), - ); - }, - ); + break; + default: + cubit.updateCurrentFilter((filter) => filter.copyWith( + correspondent: SetIdQueryParameter(id: correspondentId), + )); + break; + } } on PaperlessApiException catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } @@ -589,27 +589,27 @@ class _DocumentsPageState extends State { final cubit = context.read(); try { - cubit.state.filter.documentType.maybeWhen( - fromId: (id) { + switch (cubit.state.filter.documentType) { + case SetIdQueryParameter(id: var id): if (id == documentTypeId) { cubit.updateCurrentFilter( (filter) => - filter.copyWith(documentType: const IdQueryParameter.unset()), + filter.copyWith(documentType: const UnsetIdQueryParameter()), ); } else { cubit.updateCurrentFilter( (filter) => filter.copyWith( - documentType: IdQueryParameter.fromId(documentTypeId)), + documentType: SetIdQueryParameter(id: documentTypeId)), ); } - }, - orElse: () { + break; + default: cubit.updateCurrentFilter( (filter) => filter.copyWith( - documentType: IdQueryParameter.fromId(documentTypeId)), + documentType: SetIdQueryParameter(id: documentTypeId)), ); - }, - ); + break; + } } on PaperlessApiException catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } @@ -620,27 +620,28 @@ class _DocumentsPageState extends State { final cubit = context.read(); try { - cubit.state.filter.storagePath.maybeWhen( - fromId: (id) { - if (id == pathId) { + switch (cubit.state.filter.storagePath){ + case SetIdQueryParameter(id: var id): + if (id == pathId) { cubit.updateCurrentFilter( (filter) => - filter.copyWith(storagePath: const IdQueryParameter.unset()), + filter.copyWith(storagePath: const UnsetIdQueryParameter()), ); } else { cubit.updateCurrentFilter( (filter) => - filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)), + filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)), ); } - }, - orElse: () { - cubit.updateCurrentFilter( + break; + default: + cubit.updateCurrentFilter( (filter) => - filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)), + filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)), ); - }, - ); + break; + } + } on PaperlessApiException catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index fa4465b4..7ab9bb85 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -118,7 +118,7 @@ class InboxCubit extends HydratedCubit updateFilter( filter: DocumentFilter( sortField: SortField.added, - tags: TagsQuery.ids(include: inboxTags.toList()), + tags: IdsTagsQuery(include: inboxTags.toList()), ), ); } @@ -160,7 +160,7 @@ class InboxCubit extends HydratedCubit emitLoading: false, filter: DocumentFilter( sortField: SortField.added, - tags: TagsQuery.ids(include: inboxTags.toList()), + tags: IdsTagsQuery(include: inboxTags.toList()), ), ); } diff --git a/lib/features/labels/tags/view/widgets/fullscreen_tags_form.dart b/lib/features/labels/tags/view/widgets/fullscreen_tags_form.dart index 4f6c74ae..f9811ecc 100644 --- a/lib/features/labels/tags/view/widgets/fullscreen_tags_form.dart +++ b/lib/features/labels/tags/view/widgets/fullscreen_tags_form.dart @@ -116,16 +116,16 @@ class _FullscreenTagsFormState extends State { icon: const Icon(Icons.done), onPressed: () { if (widget.allowOnlySelection) { - widget.onSubmit(returnValue: TagsQuery.ids(include: _include)); + widget.onSubmit(returnValue: IdsTagsQuery(include: _include)); return; } late final TagsQuery query; if (_notAssigned) { - query = const TagsQuery.notAssigned(); + query = const NotAssignedTagsQuery(); } else if (_anyAssigned) { - query = TagsQuery.anyAssigned(tagIds: _include); + query = AnyAssignedTagsQuery(tagIds: _include); } else { - query = TagsQuery.ids(include: _include, exclude: _exclude); + query = IdsTagsQuery(include: _include, exclude: _exclude); } widget.onSubmit(returnValue: query); }, 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 7ec30960..f85b5903 100644 --- a/lib/features/labels/tags/view/widgets/tags_form_field.dart +++ b/lib/features/labels/tags/view/widgets/tags_form_field.dart @@ -96,19 +96,17 @@ class TagsFormField extends StatelessWidget { if (query == null) { yield Container(); } else { - final widgets = query.map( - ids: (value) => [ - for (var inc in value.include) - _buildTagIdQueryWidget(context, inc, field, false), - for (var exc in value.exclude) - _buildTagIdQueryWidget(context, exc, field, true), - ], - anyAssigned: (value) => [ - for (var id in value.tagIds) - _buildAnyAssignedTagWidget(context, id, field, value), - ], - notAssigned: (value) => [_buildNotAssignedTagWidget(context, field)], - ); + final widgets = switch (query) { + IdsTagsQuery(include: var inc, exclude: var exc) => [ + for (var i in inc) _buildTagIdQueryWidget(context, i, field, false), + for (var e in exc) _buildTagIdQueryWidget(context, e, field, true), + ], + AnyAssignedTagsQuery query => [ + for (var id in query.tagIds) + _buildAnyAssignedTagWidget(context, id, field, query), + ], + NotAssignedTagsQuery() => [_buildNotAssignedTagWidget(context, field)], + }; for (var child in widgets) { yield child; } @@ -185,7 +183,7 @@ class TagsFormField extends StatelessWidget { tagIds: query.tagIds.whereNot((element) => element == e).toList(), ); if (updatedQuery.tagIds.isEmpty) { - field.didChange(const TagsQuery.ids()); + field.didChange(const IdsTagsQuery()); } else { field.didChange(updatedQuery); } diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart index 213299e3..bbe5cd2d 100644 --- a/lib/features/labels/view/pages/labels_page.dart +++ b/lib/features/labels/view/pages/labels_page.dart @@ -259,7 +259,7 @@ class _LabelsPageState extends State LabelTabView( labels: state.correspondents, filterBuilder: (label) => DocumentFilter( - correspondent: IdQueryParameter.fromId(label.id!), + correspondent: SetIdQueryParameter(id: label.id!), ), canEdit: user.canEditCorrespondents, canAddNew: user.canCreateCorrespondents, @@ -287,7 +287,7 @@ class _LabelsPageState extends State LabelTabView( labels: state.documentTypes, filterBuilder: (label) => DocumentFilter( - documentType: IdQueryParameter.fromId(label.id!), + documentType: SetIdQueryParameter(id: label.id!), ), canEdit: user.canEditDocumentTypes, canAddNew: user.canCreateDocumentTypes, @@ -315,7 +315,7 @@ class _LabelsPageState extends State LabelTabView( labels: state.tags, filterBuilder: (label) => DocumentFilter( - tags: TagsQuery.ids(include: [label.id!]), + tags: IdsTagsQuery(include: [label.id!]), ), canEdit: user.canEditTags, canAddNew: user.canCreateTags, @@ -354,7 +354,7 @@ class _LabelsPageState extends State EditLabelRoute(label).push(context); }, filterBuilder: (label) => DocumentFilter( - storagePath: IdQueryParameter.fromId(label.id!), + storagePath: SetIdQueryParameter(id: label.id!), ), canEdit: user.canEditStoragePaths, canAddNew: user.canCreateStoragePaths, diff --git a/lib/features/labels/view/widgets/fullscreen_label_form.dart b/lib/features/labels/view/widgets/fullscreen_label_form.dart index c4c649e8..bdc26e2e 100644 --- a/lib/features/labels/view/widgets/fullscreen_label_form.dart +++ b/lib/features/labels/view/widgets/fullscreen_label_form.dart @@ -32,11 +32,10 @@ class FullscreenLabelForm extends StatefulWidget { this.allowSelectUnassigned = true, required this.canCreateNewLabel, }) : assert( - !(initialValue?.isOnlyAssigned() ?? false) || showAnyAssignedOption, + !(initialValue?.isOnlyAssigned ?? false) || showAnyAssignedOption, ), assert( - !(initialValue?.isOnlyNotAssigned() ?? false) || - showNotAssignedOption, + !(initialValue?.isOnlyNotAssigned ?? false) || showNotAssignedOption, ), assert((addNewLabelText != null) == (onCreateNewLabel != null)); @@ -87,11 +86,10 @@ class _FullscreenLabelFormState final index = AutocompleteHighlightedOption.of(context); final value = index.isNegative ? null : options.elementAt(index); widget.onSubmit( - returnValue: value?.maybeWhen( - fromId: (id) => IdQueryParameter.fromId(id), - orElse: () => const IdQueryParameter.unset(), - ) ?? - const IdQueryParameter.unset(), + returnValue: switch (value) { + SetIdQueryParameter query => query, + _ => const UnsetIdQueryParameter(), + }, ); }, autofocus: true, @@ -169,7 +167,7 @@ class _FullscreenLabelFormState final label = await widget.onCreateNewLabel!(_textEditingController.text); if (label?.id != null) { widget.onSubmit( - returnValue: IdQueryParameter.fromId(label!.id!), + returnValue: SetIdQueryParameter(id: label!.id!), ); } } @@ -184,21 +182,21 @@ class _FullscreenLabelFormState if (widget.initialValue == null) { // If nothing is selected yet, show all options first. for (final option in widget.options.values) { - yield IdQueryParameter.fromId(option.id!); + yield SetIdQueryParameter(id: option.id!); } if (widget.showNotAssignedOption) { - yield const IdQueryParameter.notAssigned(); + yield const NotAssignedIdQueryParameter(); } if (widget.showAnyAssignedOption) { - yield const IdQueryParameter.anyAssigned(); + yield const AnyAssignedIdQueryParameter(); } } else { // If an initial value is given, show not assigned first, which will be selected by default when pressing "done" on keyboard. if (widget.showNotAssignedOption) { - yield const IdQueryParameter.notAssigned(); + yield const NotAssignedIdQueryParameter(); } if (widget.showAnyAssignedOption) { - yield const IdQueryParameter.anyAssigned(); + yield const AnyAssignedIdQueryParameter(); } for (final option in widget.options.values) { // Don't include the initial value in the selection @@ -207,7 +205,7 @@ class _FullscreenLabelFormState option.id == initialValue.id) { continue; } - yield IdQueryParameter.fromId(option.id!); + yield SetIdQueryParameter(id: option.id!); } } } else { @@ -216,77 +214,76 @@ class _FullscreenLabelFormState .where((e) => e.name.trim().toLowerCase().contains(normalizedQuery)); if (matches.isNotEmpty) { for (final match in matches) { - yield IdQueryParameter.fromId(match.id!); + yield SetIdQueryParameter(id: match.id!); } if (widget.showNotAssignedOption) { - yield const IdQueryParameter.notAssigned(); + yield const NotAssignedIdQueryParameter(); } if (widget.showAnyAssignedOption) { - yield const IdQueryParameter.anyAssigned(); + yield const AnyAssignedIdQueryParameter(); } } else { if (widget.showNotAssignedOption) { - yield const IdQueryParameter.notAssigned(); + yield const NotAssignedIdQueryParameter(); } if (widget.showAnyAssignedOption) { - yield const IdQueryParameter.anyAssigned(); + yield const AnyAssignedIdQueryParameter(); } if (!(widget.showAnyAssignedOption || widget.showNotAssignedOption)) { - yield const IdQueryParameter.unset(); + yield const UnsetIdQueryParameter(); } } } } String? _buildHintText() { - return widget.initialValue?.when( - unset: () => S.of(context)!.startTyping, - notAssigned: () => S.of(context)!.notAssigned, - anyAssigned: () => S.of(context)!.anyAssigned, - fromId: (id) => widget.options[id]?.name ?? S.of(context)!.startTyping, - ); + return switch (widget.initialValue) { + UnsetIdQueryParameter() => S.of(context)!.startTyping, + NotAssignedIdQueryParameter() => S.of(context)!.notAssigned, + AnyAssignedIdQueryParameter() => S.of(context)!.anyAssigned, + SetIdQueryParameter(id: var id) => + widget.options[id]?.name ?? S.of(context)!.startTyping, + _ => null, + }; } Widget _buildOptionWidget(IdQueryParameter option, bool highlight) { void onTap() => widget.onSubmit(returnValue: option); - if (option.isUnset()) { - return Center( - child: Column( - children: [ - Text(S.of(context)!.noItemsFound).padded(), - if (widget.onCreateNewLabel != null) - TextButton( - child: Text(widget.addNewLabelText!), - onPressed: _onCreateNewLabel, - ), - ], + return switch (option) { + NotAssignedIdQueryParameter() => ListTile( + selected: highlight, + selectedTileColor: Theme.of(context).focusColor, + title: Text(S.of(context)!.notAssigned), + onTap: onTap, ), - ); - } - - return option.whenOrNull( - notAssigned: () => ListTile( - selected: highlight, - selectedTileColor: Theme.of(context).focusColor, - title: Text(S.of(context)!.notAssigned), - onTap: onTap, - ), - anyAssigned: () => ListTile( - selected: highlight, - selectedTileColor: Theme.of(context).focusColor, - title: Text(S.of(context)!.anyAssigned), - onTap: onTap, - ), - fromId: (id) => ListTile( - selected: highlight, - selectedTileColor: Theme.of(context).focusColor, - title: Text(widget.options[id]!.name), - onTap: onTap, - enabled: widget.allowSelectUnassigned - ? true - : widget.options[id]!.documentCount != 0, - ), - )!; // Never null, since we already return on unset before + AnyAssignedIdQueryParameter() => ListTile( + selected: highlight, + selectedTileColor: Theme.of(context).focusColor, + title: Text(S.of(context)!.anyAssigned), + onTap: onTap, + ), + SetIdQueryParameter(id: var id) => ListTile( + selected: highlight, + selectedTileColor: Theme.of(context).focusColor, + title: Text(widget.options[id]!.name), + onTap: onTap, + enabled: widget.allowSelectUnassigned + ? true + : widget.options[id]!.documentCount != 0, + ), + UnsetIdQueryParameter() => Center( + child: Column( + children: [ + Text(S.of(context)!.noItemsFound).padded(), + if (widget.onCreateNewLabel != null) + TextButton( + child: Text(widget.addNewLabelText!), + onPressed: _onCreateNewLabel, + ), + ], + ), + ), + }; } } diff --git a/lib/features/labels/view/widgets/label_form_field.dart b/lib/features/labels/view/widgets/label_form_field.dart index e8aba561..d2fff42d 100644 --- a/lib/features/labels/view/widgets/label_form_field.dart +++ b/lib/features/labels/view/widgets/label_form_field.dart @@ -47,13 +47,13 @@ class LabelFormField extends StatelessWidget { }) : super(key: key); String _buildText(BuildContext context, IdQueryParameter? value) { - return value?.when( - unset: () => '', - notAssigned: () => S.of(context)!.notAssigned, - anyAssigned: () => S.of(context)!.anyAssigned, - fromId: (id) => options[id]?.name, - ) ?? - ''; + return switch (value) { + UnsetIdQueryParameter() => '', + NotAssignedIdQueryParameter() => S.of(context)!.notAssigned, + AnyAssignedIdQueryParameter() => S.of(context)!.anyAssigned, + SetIdQueryParameter(id: var id) => options[id]?.name ?? '', + _ => '', + }; } @override @@ -70,9 +70,14 @@ class LabelFormField extends StatelessWidget { text: _buildText(context, field.value), ); final displayedSuggestions = suggestions - .whereNot((e) => - e.id == - field.value?.maybeWhen(fromId: (id) => id, orElse: () => -1)) + .whereNot( + (e) => + e.id == + switch (field.value) { + SetIdQueryParameter(id: var id) => id, + _ => -1, + }, + ) .toList(); return Column( @@ -98,7 +103,7 @@ class LabelFormField extends StatelessWidget { ? IconButton( icon: const Icon(Icons.clear), onPressed: () => - field.didChange(const IdQueryParameter.unset()), + field.didChange(const UnsetIdQueryParameter()), ) : null, ), @@ -151,7 +156,7 @@ class LabelFormField extends StatelessWidget { child: ActionChip( label: Text(suggestion.name), onPressed: () => field.didChange( - IdQueryParameter.fromId(suggestion.id!), + SetIdQueryParameter(id: suggestion.id!), ), ), ); diff --git a/lib/features/landing/view/landing_page.dart b/lib/features/landing/view/landing_page.dart index 0f5d18f7..b7d1df72 100644 --- a/lib/features/landing/view/landing_page.dart +++ b/lib/features/landing/view/landing_page.dart @@ -44,7 +44,8 @@ class _LandingPageState extends State { SliverToBoxAdapter( child: Text( S.of(context)!.welcomeUser( - currentUser.fullName ?? currentUser.username), + currentUser.fullName ?? currentUser.username, + ), textAlign: TextAlign.center, style: Theme.of(context) .textTheme diff --git a/lib/main.dart b/lib/main.dart index c336dc8c..4f420bdb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -52,6 +52,7 @@ import 'package:paperless_mobile/routes/typed/top_level/settings_route.dart'; import 'package:paperless_mobile/theme.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; String get defaultPreferredLocaleSubtag { String preferredLocale = Platform.localeName.split("_").first; @@ -62,9 +63,42 @@ String get defaultPreferredLocaleSubtag { return preferredLocale; } +Map Function()> _migrations = { + '3.0.0': () { + // Remove all stored data due to updates in schema + return Future.wait([ + for (var box in HiveBoxes.all) Hive.deleteBoxFromDisk(box), + ]); + }, +}; + +Future performMigrations() async { + final sp = await SharedPreferences.getInstance(); + final currentVersion = packageInfo.version; + final migrationExists = _migrations.containsKey(currentVersion); + if (!migrationExists) { + return; + } + final migrationProcedure = _migrations[currentVersion]!; + final performedMigrations = sp.getStringList("performed_migrations") ?? []; + final requiresMigrationForCurrentVersion = + !performedMigrations.contains(currentVersion); + if (requiresMigrationForCurrentVersion) { + debugPrint("Applying migration scripts for version $currentVersion"); + await migrationProcedure(); + await sp.setStringList( + 'performed_migrations', + [...performedMigrations, currentVersion], + ); + } +} + Future _initHive() async { await Hive.initFlutter(); + + await performMigrations(); registerHiveAdapters(); + // await getApplicationDocumentsDirectory().then((value) => value.deleteSync(recursive: true)); await Hive.openBox(HiveBoxes.localUserAccount); await Hive.openBox(HiveBoxes.localUserAppState); @@ -81,6 +115,7 @@ Future _initHive() async { void main() async { runZonedGuarded(() async { + WidgetsFlutterBinding.ensureInitialized(); Paint.enableDithering = true; // if (kDebugMode) { // // URL: http://localhost:3131 @@ -93,13 +128,6 @@ void main() async { // ) // .start(); // } - await _initHive(); - final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - final globalSettingsBox = - Hive.box(HiveBoxes.globalSettings); - final globalSettings = globalSettingsBox.getValue()!; - - await findSystemLocale(); packageInfo = await PackageInfo.fromPlatform(); if (Platform.isAndroid) { @@ -108,6 +136,14 @@ void main() async { if (Platform.isIOS) { iosInfo = await DeviceInfoPlugin().iosInfo; } + await _initHive(); + final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + final globalSettingsBox = + Hive.box(HiveBoxes.globalSettings); + final globalSettings = globalSettingsBox.getValue()!; + + await findSystemLocale(); + final connectivityStatusService = ConnectivityStatusServiceImpl( Connectivity(), ); diff --git a/packages/paperless_api/lib/config/hive/hive_type_ids.dart b/packages/paperless_api/lib/config/hive/hive_type_ids.dart index a0248e48..a931473c 100644 --- a/packages/paperless_api/lib/config/hive/hive_type_ids.dart +++ b/packages/paperless_api/lib/config/hive/hive_type_ids.dart @@ -36,9 +36,9 @@ class PaperlessApiHiveTypeIds { void registerPaperlessApiHiveTypeAdapters() { Hive.registerAdapter(DocumentFilterAdapter()); // TagsQuery - Hive.registerAdapter(AnyAssignedTagsQueryImplAdapter()); - Hive.registerAdapter(NotAssignedTagsQueryImplAdapter()); - Hive.registerAdapter(IdsTagsQueryImplAdapter()); + Hive.registerAdapter(AnyAssignedTagsQueryAdapter()); + Hive.registerAdapter(NotAssignedTagsQueryAdapter()); + Hive.registerAdapter(IdsTagsQueryAdapter()); Hive.registerAdapter(SortFieldAdapter()); Hive.registerAdapter(SortOrderAdapter()); @@ -49,13 +49,13 @@ void registerPaperlessApiHiveTypeAdapters() { Hive.registerAdapter(TextQueryAdapter()); Hive.registerAdapter(QueryTypeAdapter()); // IdQueryParameter - Hive.registerAdapter(SetIdQueryParameterImplAdapter()); - Hive.registerAdapter(UnsetIdQueryParameterImplAdapter()); - Hive.registerAdapter(AnyAssignedIdQueryParameterImplAdapter()); - Hive.registerAdapter(NotAssignedIdQueryParameterImplAdapter()); + Hive.registerAdapter(SetIdQueryParameterAdapter()); + Hive.registerAdapter(UnsetIdQueryParameterAdapter()); + Hive.registerAdapter(AnyAssignedIdQueryParameterAdapter()); + Hive.registerAdapter(NotAssignedIdQueryParameterAdapter()); // Users and permissions - Hive.registerAdapter(UserModelV3ImplAdapter()); - Hive.registerAdapter(UserModelV2ImplAdapter()); + Hive.registerAdapter(UserModelV3Adapter()); + Hive.registerAdapter(UserModelV2Adapter()); Hive.registerAdapter(GroupModelAdapter()); Hive.registerAdapter(PermissionsAdapter()); } diff --git a/packages/paperless_api/lib/src/models/document_filter.dart b/packages/paperless_api/lib/src/models/document_filter.dart index 19ceea3c..58cf9fa4 100644 --- a/packages/paperless_api/lib/src/models/document_filter.dart +++ b/packages/paperless_api/lib/src/models/document_filter.dart @@ -3,13 +3,12 @@ import 'package:equatable/equatable.dart'; import 'package:hive/hive.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; -import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_query.dart'; import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_query_field.dart'; part 'document_filter.g.dart'; @DateRangeQueryJsonConverter() -@JsonSerializable(explicitToJson: true) +// @JsonSerializable(explicitToJson: true) @HiveType(typeId: PaperlessApiHiveTypeIds.documentFilter) class DocumentFilter extends Equatable { static const DocumentFilter initial = DocumentFilter(); @@ -67,11 +66,11 @@ class DocumentFilter extends Equatable { final int? selectedView; const DocumentFilter({ - this.documentType = const IdQueryParameter.unset(), - this.correspondent = const IdQueryParameter.unset(), - this.storagePath = const IdQueryParameter.unset(), - this.asnQuery = const IdQueryParameter.unset(), - this.tags = const TagsQuery.ids(), + this.documentType = const UnsetIdQueryParameter(), + this.correspondent = const UnsetIdQueryParameter(), + this.storagePath = const UnsetIdQueryParameter(), + this.asnQuery = const UnsetIdQueryParameter(), + this.tags = const IdsTagsQuery(), this.sortField = SortField.created, this.sortOrder = SortOrder.descending, this.page = 1, @@ -164,7 +163,8 @@ class DocumentFilter extends Equatable { created: created ?? this.created, modified: modified ?? this.modified, moreLike: moreLike != null ? moreLike.call() : this.moreLike, - selectedView: selectedView != null ? selectedView.call() : this.selectedView, + selectedView: + selectedView != null ? selectedView.call() : this.selectedView, ); if (query?.queryType != QueryType.extended && newFilter.forceExtendedQuery) { @@ -195,24 +195,23 @@ class DocumentFilter extends Equatable { } int get appliedFiltersCount => [ - documentType.maybeWhen( - unset: () => 0, - orElse: () => 1, - ), - correspondent.maybeWhen( - unset: () => 0, - orElse: () => 1, - ), - storagePath.maybeWhen( - unset: () => 0, - orElse: () => 1, - ), - tags.maybeWhen( - ids: (include, exclude) => include.length + exclude.length, - anyAssigned: (tagIds) => tagIds.length, - notAssigned: () => 1, - orElse: () => 0, - ), + switch (documentType) { + UnsetIdQueryParameter() => 0, + _ => 1, + }, + switch (correspondent) { + UnsetIdQueryParameter() => 0, + _ => 1, + }, + switch (storagePath) { + UnsetIdQueryParameter() => 0, + _ => 1, + }, + switch (tags) { + NotAssignedTagsQuery() => 1, + AnyAssignedTagsQuery(tagIds: var tags) => tags.length, + IdsTagsQuery(include: var i, exclude: var e) => e.length + i.length, + }, switch (added) { RelativeDateRangeQuery() => 1, AbsoluteDateRangeQuery() => 1, @@ -228,10 +227,10 @@ class DocumentFilter extends Equatable { AbsoluteDateRangeQuery() => 1, UnsetDateRangeQuery() => 0, }, - asnQuery.maybeWhen( - unset: () => 0, - orElse: () => 1, - ), + switch (asnQuery) { + UnsetIdQueryParameter() => 0, + _ => 1, + }, (query.queryText?.isNotEmpty ?? false) ? 1 : 0, ].fold(0, (previousValue, element) => previousValue + element); @@ -254,8 +253,8 @@ class DocumentFilter extends Equatable { selectedView, ]; - factory DocumentFilter.fromJson(Map json) => - _$DocumentFilterFromJson(json); + // factory DocumentFilter.fromJson(Map json) => + // _$DocumentFilterFromJson(json); - Map toJson() => _$DocumentFilterToJson(this); + // Map toJson() => _$DocumentFilterToJson(this); } diff --git a/packages/paperless_api/lib/src/models/filter_rule_model.dart b/packages/paperless_api/lib/src/models/filter_rule_model.dart index 450b81ef..f9743511 100644 --- a/packages/paperless_api/lib/src/models/filter_rule_model.dart +++ b/packages/paperless_api/lib/src/models/filter_rule_model.dart @@ -5,9 +5,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/src/constants.dart'; import 'package:paperless_api/src/converters/local_date_time_json_converter.dart'; -import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_query.dart'; import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_query_field.dart'; -import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_unit.dart'; part 'filter_rule_model.g.dart'; @@ -59,48 +57,49 @@ class FilterRule with EquatableMixin { case documentTypeRule: return filter.copyWith( documentType: value == null - ? const IdQueryParameter.notAssigned() - : IdQueryParameter.fromId(int.parse(value!)), + ? const NotAssignedIdQueryParameter() + : SetIdQueryParameter(id: int.parse(value!)), ); case correspondentRule: return filter.copyWith( correspondent: value == null - ? const IdQueryParameter.notAssigned() - : IdQueryParameter.fromId(int.parse(value!)), + ? const NotAssignedIdQueryParameter() + : SetIdQueryParameter(id: int.parse(value!)), ); case storagePathRule: return filter.copyWith( storagePath: value == null - ? const IdQueryParameter.notAssigned() - : IdQueryParameter.fromId(int.parse(value!)), + ? const NotAssignedIdQueryParameter() + : SetIdQueryParameter(id: int.parse(value!)), ); case hasAnyTag: return filter.copyWith( tags: value == "true" - ? const TagsQuery.anyAssigned() - : const TagsQuery.notAssigned(), + ? const AnyAssignedTagsQuery() + : const NotAssignedTagsQuery(), ); case includeTagsRule: assert(filter.tags is IdsTagsQuery); return filter.copyWith( - tags: filter.tags.maybeWhen( - ids: (include, exclude) => TagsQuery.ids( - include: [...include, int.parse(value!)], - exclude: exclude, - ), - orElse: () => filter.tags, - ), + tags: switch (filter.tags) { + // TODO: Handle this case. + IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery( + include: [...i, int.parse(value!)], + exclude: e, + ), + _ => filter.tags, + }, ); case excludeTagsRule: assert(filter.tags is IdsTagsQuery); return filter.copyWith( - tags: filter.tags.maybeWhen( - ids: (include, exclude) => TagsQuery.ids( - include: include, - exclude: [...exclude, int.parse(value!)], - ), - orElse: () => filter.tags, - ), + tags: switch (filter.tags) { + IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery( + include: i, + exclude: [...e, int.parse(value!)], + ), + _ => filter.tags, + }, ); case createdBeforeRule: if (filter.created is AbsoluteDateRangeQuery) { @@ -245,37 +244,46 @@ class FilterRule with EquatableMixin { /// static List fromFilter(final DocumentFilter filter) { List filterRules = []; - final corrRule = filter.correspondent.whenOrNull( - notAssigned: () => FilterRule(correspondentRule, null), - fromId: (id) => FilterRule(correspondentRule, id.toString()), - ); + final corrRule = switch (filter.correspondent) { + NotAssignedIdQueryParameter() => FilterRule(correspondentRule, null), + SetIdQueryParameter(id: var id) => + FilterRule(correspondentRule, id.toString()), + _ => null, + }; if (corrRule != null) { filterRules.add(corrRule); } - final docTypeRule = filter.documentType.whenOrNull( - notAssigned: () => FilterRule(documentTypeRule, null), - fromId: (id) => FilterRule(documentTypeRule, id.toString()), - ); + final docTypeRule = switch (filter.documentType) { + NotAssignedIdQueryParameter() => FilterRule(documentTypeRule, null), + SetIdQueryParameter(id: var id) => + FilterRule(documentTypeRule, id.toString()), + _ => null, + }; + if (docTypeRule != null) { filterRules.add(docTypeRule); } - final sPathRule = filter.storagePath.whenOrNull( - notAssigned: () => FilterRule(storagePathRule, null), - fromId: (id) => FilterRule(storagePathRule, id.toString()), - ); + final sPathRule = switch (filter.storagePath) { + NotAssignedIdQueryParameter() => FilterRule(storagePathRule, null), + SetIdQueryParameter(id: var id) => + FilterRule(storagePathRule, id.toString()), + _ => null, + }; + if (sPathRule != null) { filterRules.add(sPathRule); } - final tagRules = filter.tags.when( - notAssigned: () => [FilterRule(hasAnyTag, 'false')], - anyAssigned: (_) => [FilterRule(hasAnyTag, 'true')], - ids: (include, exclude) => [ - ...include.map((id) => FilterRule(includeTagsRule, id.toString())), - ...exclude.map((id) => FilterRule(excludeTagsRule, id.toString())), - ], - ); + final tagRules = switch (filter.tags) { + NotAssignedTagsQuery() => [FilterRule(hasAnyTag, 'false')], + AnyAssignedTagsQuery() => [FilterRule(hasAnyTag, 'true')], + IdsTagsQuery(include: var i, exclude: var e) => [ + ...i.map((id) => FilterRule(includeTagsRule, id.toString())), + ...e.map((id) => FilterRule(excludeTagsRule, id.toString())), + ], + }; + filterRules.addAll(tagRules); if (filter.query.queryText != null) { diff --git a/packages/paperless_api/lib/src/models/permissions/user_permission_extension.dart b/packages/paperless_api/lib/src/models/permissions/user_permission_extension.dart index d6a01864..e14a025e 100644 --- a/packages/paperless_api/lib/src/models/permissions/user_permission_extension.dart +++ b/packages/paperless_api/lib/src/models/permissions/user_permission_extension.dart @@ -2,32 +2,36 @@ import 'package:paperless_api/paperless_api.dart'; extension UserPermissionExtension on UserModel { bool hasPermission(PermissionAction action, PermissionTarget target) { - return map( - v3: (user) { - final permission = [action.value, target.value].join("_"); - return user.userPermissions.any((element) => element == permission) || - user.inheritedPermissions - .any((element) => element.split(".").last == permission); - }, - v2: (_) => true, - ); + final permission = [action.value, target.value].join("_"); + return switch (this) { + UserModelV2() => true, + UserModelV3( + userPermissions: var userPermissions, + inheritedPermissions: var inheritedPermissions, + ) => + userPermissions.any((p) => p == permission) || + inheritedPermissions.any((p) => p.split(".").last == permission) + }; } bool hasPermissions( - List actions, List targets) { - return map( - v3: (user) { - final permissions = [ - for (var action in actions) - for (var target in targets) [action, target].join("_") - ]; - return permissions.every((requestedPermission) => - user.userPermissions.contains(requestedPermission) || - user.inheritedPermissions.any( - (element) => element.split(".").last == requestedPermission)); - }, - v2: (_) => true, - ); + List actions, + List targets, + ) { + final permissions = [ + for (var action in actions) + for (var target in targets) [action, target].join("_") + ]; + return switch (this) { + UserModelV2() => true, + UserModelV3( + userPermissions: var userPermissions, + inheritedPermissions: var inheritedPermissions, + ) => + permissions.every((p) => + userPermissions.contains(p) || + inheritedPermissions.any((ip) => ip.split(".").last == p)) + }; } bool get canViewDocuments => diff --git a/packages/paperless_api/lib/src/models/query_parameters/id_query_parameter.dart b/packages/paperless_api/lib/src/models/query_parameters/id_query_parameter.dart index 6fae3bf7..76fa5714 100644 --- a/packages/paperless_api/lib/src/models/query_parameters/id_query_parameter.dart +++ b/packages/paperless_api/lib/src/models/query_parameters/id_query_parameter.dart @@ -1,110 +1,79 @@ +import 'dart:isolate'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:hive/hive.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'id_query_parameter.freezed.dart'; part 'id_query_parameter.g.dart'; -@freezed -class IdQueryParameter with _$IdQueryParameter { - const IdQueryParameter._(); - @HiveType(typeId: PaperlessApiHiveTypeIds.unsetIdQueryParameter) - const factory IdQueryParameter.unset() = UnsetIdQueryParameter; - @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter) - const factory IdQueryParameter.notAssigned() = NotAssignedIdQueryParameter; - @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter) - const factory IdQueryParameter.anyAssigned() = AnyAssignedIdQueryParameter; - @HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter) - const factory IdQueryParameter.fromId(@HiveField(0) int id) = SetIdQueryParameter; - - Map toQueryParameter(String field) { - return when( - unset: () => {}, - notAssigned: () => { - '${field}__isnull': '1', - }, - anyAssigned: () => { - '${field}__isnull': '0', - }, - fromId: (id) { - return {'${field}__id': '$id'}; - }, - ); - } +sealed class IdQueryParameter { + const IdQueryParameter(); + Map toQueryParameter(String field); + bool matches(int? id); - bool isOnlyNotAssigned() => this is NotAssignedIdQueryParameter; - - bool isOnlyAssigned() => this is AnyAssignedIdQueryParameter; - - bool isSet() => this is SetIdQueryParameter; + bool get isUnset => this is UnsetIdQueryParameter; + bool get isSet => this is SetIdQueryParameter; + bool get isOnlyNotAssigned => this is NotAssignedIdQueryParameter; + bool get isOnlyAssigned => this is AnyAssignedIdQueryParameter; +} - bool isUnset() => this is UnsetIdQueryParameter; +@HiveType(typeId: PaperlessApiHiveTypeIds.unsetIdQueryParameter) +@Freezed(toJson: false, fromJson: false) +class UnsetIdQueryParameter extends IdQueryParameter + with _$UnsetIdQueryParameter { + const UnsetIdQueryParameter._(); + const factory UnsetIdQueryParameter() = _UnsetIdQueryParameter; + @override + Map toQueryParameter(String field) => {}; + + @override + bool matches(int? id) => true; +} - bool matches(int? id) { - return when( - unset: () => true, - notAssigned: () => id == null, - anyAssigned: () => id != null, - fromId: (id_) => id == id_, - ); +@HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter) +@Freezed(toJson: false, fromJson: false) +class NotAssignedIdQueryParameter extends IdQueryParameter + with _$NotAssignedIdQueryParameter { + const NotAssignedIdQueryParameter._(); + const factory NotAssignedIdQueryParameter() = _NotAssignedIdQueryParameter; + @override + Map toQueryParameter(String field) { + return {'${field}__isnull': '1'}; } - factory IdQueryParameter.fromJson(Map json) => _$IdQueryParameterFromJson(json); + @override + bool matches(int? id) => id == null; } -// @JsonSerializable() -// @HiveType(typeId: PaperlessApiHiveTypeIds.idQueryParameter) -// class IdQueryParameter extends Equatable { -// @HiveField(0) -// final int? assignmentStatus; -// @HiveField(1) -// final int? id; - -// @Deprecated("Use named constructors, this is only meant for code generation") -// const IdQueryParameter(this.assignmentStatus, this.id); - -// const IdQueryParameter.notAssigned() -// : assignmentStatus = 1, -// id = null; - -// const IdQueryParameter.anyAssigned() -// : assignmentStatus = 0, -// id = null; - -// const IdQueryParameter.fromId(this.id) : assignmentStatus = null; - -// const IdQueryParameter.unset() : this.fromId(null); - -// bool get isUnset => id == null && assignmentStatus == null; - -// bool get isSet => id != null && assignmentStatus == null; - -// bool get onlyNotAssigned => assignmentStatus == 1; - -// bool get onlyAssigned => assignmentStatus == 0; - -// Map toQueryParameter(String field) { -// final Map params = {}; -// if (onlyNotAssigned || onlyAssigned) { -// params.putIfAbsent('${field}__isnull', () => assignmentStatus!.toString()); -// } -// if (isSet) { -// params.putIfAbsent("${field}__id", () => id!.toString()); -// } -// return params; -// } - -// bool matches(int? id) { -// return onlyAssigned && id != null || -// onlyNotAssigned && id == null || -// isSet && id == this.id || -// isUnset; -// } - -// @override -// List get props => [assignmentStatus, id]; +@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter) +@Freezed(toJson: false, fromJson: false) +class AnyAssignedIdQueryParameter extends IdQueryParameter + with _$AnyAssignedIdQueryParameter { + const factory AnyAssignedIdQueryParameter() = _AnyAssignedIdQueryParameter; + const AnyAssignedIdQueryParameter._(); + @override + Map toQueryParameter(String field) { + return {'${field}__isnull': '0'}; + } -// Map toJson() => _$IdQueryParameterToJson(this); + @override + bool matches(int? id) => id != null; +} -// factory IdQueryParameter.fromJson(Map json) => _$IdQueryParameterFromJson(json); -// } +@HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter) +@Freezed(toJson: false, fromJson: false) +class SetIdQueryParameter extends IdQueryParameter with _$SetIdQueryParameter { + const SetIdQueryParameter._(); + const factory SetIdQueryParameter({ + @HiveField(0) required int id, + }) = _SetIdQueryParameter; + @override + Map toQueryParameter(String field) { + return {'${field}__id': '$id'}; + } + @override + bool matches(int? id) => id == this.id; +} diff --git a/packages/paperless_api/lib/src/models/query_parameters/tags_query/tags_query.dart b/packages/paperless_api/lib/src/models/query_parameters/tags_query/tags_query.dart index 46022041..ff84f6f8 100644 --- a/packages/paperless_api/lib/src/models/query_parameters/tags_query/tags_query.dart +++ b/packages/paperless_api/lib/src/models/query_parameters/tags_query/tags_query.dart @@ -4,54 +4,68 @@ import 'package:paperless_api/config/hive/hive_type_ids.dart'; part 'tags_query.freezed.dart'; part 'tags_query.g.dart'; -@freezed -class TagsQuery with _$TagsQuery { - const TagsQuery._(); - @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery) - const factory TagsQuery.notAssigned() = NotAssignedTagsQuery; - @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery) - const factory TagsQuery.anyAssigned({ - @Default([]) List tagIds, - }) = AnyAssignedTagsQuery; - @HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery) - const factory TagsQuery.ids({ - @Default([]) List include, - @Default([]) List exclude, - }) = IdsTagsQuery; +sealed class TagsQuery { + const TagsQuery(); + Map toQueryParameter(); + bool matches(Iterable ids); +} +@HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery) +@Freezed(toJson: false, fromJson: false) +class NotAssignedTagsQuery extends TagsQuery with _$NotAssignedTagsQuery { + const NotAssignedTagsQuery._(); + const factory NotAssignedTagsQuery() = _NotAssignedTagsQuery; + @override Map toQueryParameter() { - return when( - anyAssigned: (tagIds) { - if (tagIds.isEmpty) { - return {'is_tagged': '1'}; - } - return {'tags__id__in': tagIds.join(',')}; - }, - ids: (include, exclude) { - final Map params = {}; - if (include.isNotEmpty) { - params.putIfAbsent('tags__id__all', () => include.join(',')); - } - if (exclude.isNotEmpty) { - params.putIfAbsent('tags__id__none', () => exclude.join(',')); - } - return params; - }, - notAssigned: () { - return {'is_tagged': '0'}; - }, - ); + return {'is_tagged': '0'}; } - bool matches(Iterable ids) { - return when( - anyAssigned: (_) => ids.isNotEmpty, - ids: (include, exclude) => - include.toSet().difference(ids.toSet()).isEmpty && - exclude.toSet().intersection(ids.toSet()).isEmpty, - notAssigned: () => ids.isEmpty, - ); + @override + bool matches(Iterable ids) => ids.isEmpty; +} + +@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery) +@Freezed(toJson: false, fromJson: false) +class AnyAssignedTagsQuery extends TagsQuery with _$AnyAssignedTagsQuery { + const AnyAssignedTagsQuery._(); + const factory AnyAssignedTagsQuery({ + @HiveField(0) @Default([]) List tagIds, + }) = _AnyAssignedTagsQuery; + @override + Map toQueryParameter() { + if (tagIds.isEmpty) { + return {'is_tagged': '1'}; + } + return {'tags__id__in': tagIds.join(',')}; + } + + @override + bool matches(Iterable ids) => ids.isNotEmpty; +} + +@HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery) +@Freezed(toJson: false, fromJson: false) +class IdsTagsQuery extends TagsQuery with _$IdsTagsQuery { + const IdsTagsQuery._(); + const factory IdsTagsQuery({ + @HiveField(0) @Default([]) List include, + @HiveField(1) @Default([]) List exclude, + }) = _IdsTagsQuery; + @override + Map toQueryParameter() { + final Map params = {}; + if (include.isNotEmpty) { + params.putIfAbsent('tags__id__all', () => include.join(',')); + } + if (exclude.isNotEmpty) { + params.putIfAbsent('tags__id__none', () => exclude.join(',')); + } + return params; } - factory TagsQuery.fromJson(Map json) => _$TagsQueryFromJson(json); + @override + bool matches(Iterable ids) { + return include.toSet().difference(ids.toSet()).isEmpty && + exclude.toSet().intersection(ids.toSet()).isEmpty; + } } diff --git a/packages/paperless_api/lib/src/models/user_model.dart b/packages/paperless_api/lib/src/models/user_model.dart index f358b941..3436ed32 100644 --- a/packages/paperless_api/lib/src/models/user_model.dart +++ b/packages/paperless_api/lib/src/models/user_model.dart @@ -4,55 +4,93 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart'; -part 'user_model.freezed.dart'; part 'user_model.g.dart'; -@freezed -class UserModel with _$UserModel { - const UserModel._(); - - @JsonSerializable(fieldRename: FieldRename.snake) - @HiveType(typeId: PaperlessApiHiveTypeIds.userModelv3) - const factory UserModel.v3({ - @HiveField(0) required int id, - @HiveField(1) required String username, - @HiveField(2) String? email, - @HiveField(3) String? firstName, - @HiveField(4) String? lastName, - @HiveField(5) DateTime? dateJoined, - @HiveField(6) @Default(true) bool isStaff, - @HiveField(7) @Default(true) bool isActive, - @HiveField(8) @Default(true) bool isSuperuser, - @HiveField(9) @Default([]) List groups, - @HiveField(10) @Default([]) List userPermissions, - @HiveField(11) @Default([]) List inheritedPermissions, - }) = UserModelV3; - - @JsonSerializable(fieldRename: FieldRename.snake) - @HiveType(typeId: PaperlessApiHiveTypeIds.userModelv2) - const factory UserModel.v2({ - @HiveField(0) @JsonKey(name: "user_id") required int id, - @HiveField(1) required String username, - @HiveField(2) String? displayName, - }) = UserModelV2; - - factory UserModel.fromJson(Map json) => - _$UserModelFromJson(json); - - String? get fullName => map( - v2: (value) => value.displayName, - v3: (value) { - bool hasFirstName = value.firstName?.trim().isNotEmpty ?? false; - bool hasLastName = value.lastName?.trim().isNotEmpty ?? false; - if (hasFirstName && hasLastName) { - return "${value.firstName!} ${value.lastName!}"; - } else if (hasFirstName) { - return value.firstName!; - } else if (hasLastName) { - return value.lastName; - } else { - return null; - } - }, - ); +sealed class UserModel { + @HiveField(0) + final int id; + @HiveField(1) + final String username; + const UserModel({ + required this.id, + required this.username, + }); + + String? get fullName; +} + +@JsonSerializable(fieldRename: FieldRename.snake) +@HiveType(typeId: PaperlessApiHiveTypeIds.userModelv2) +class UserModelV2 extends UserModel { + @HiveField(2) + final String? displayName; + const UserModelV2({ + required super.id, + required super.username, + this.displayName, + }); + Map toJson() => _$UserModelV2ToJson(this); + factory UserModelV2.fromJson(Map json) => + _$UserModelV2FromJson(json); + + @override + String? get fullName => displayName; +} + +@JsonSerializable(fieldRename: FieldRename.snake) +@HiveType(typeId: PaperlessApiHiveTypeIds.userModelv3) +class UserModelV3 extends UserModel { + @HiveField(2) + final String? email; + @HiveField(3) + final String? firstName; + @HiveField(4) + final String? lastName; + @HiveField(5) + final DateTime? dateJoined; + @HiveField(6) + final bool isStaff; + @HiveField(7) + final bool isActive; + @HiveField(8) + final bool isSuperuser; + @HiveField(9) + final List groups; + @HiveField(10) + final List userPermissions; + @HiveField(11) + final List inheritedPermissions; + + @override + String? get fullName { + bool hasFirstName = firstName?.trim().isNotEmpty ?? false; + bool hasLastName = lastName?.trim().isNotEmpty ?? false; + if (hasFirstName && hasLastName) { + return "${firstName!} ${lastName!}"; + } else if (hasFirstName) { + return firstName!; + } else if (hasLastName) { + return lastName; + } + return null; + } + + const UserModelV3({ + required super.id, + required super.username, + this.email, + this.firstName, + this.lastName, + this.dateJoined, + required this.isStaff, + required this.isActive, + required this.isSuperuser, + required this.groups, + required this.userPermissions, + required this.inheritedPermissions, + }); + + Map toJson() => _$UserModelV3ToJson(this); + factory UserModelV3.fromJson(Map json) => + _$UserModelV3FromJson(json); } diff --git a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart index 25aabaa3..cbc62b22 100644 --- a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart +++ b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart @@ -162,7 +162,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi { const DocumentFilter asnQueryFilter = DocumentFilter( sortField: SortField.archiveSerialNumber, sortOrder: SortOrder.descending, - asnQuery: IdQueryParameter.anyAssigned(), + asnQuery: AnyAssignedIdQueryParameter(), page: 1, pageSize: 1, ); diff --git a/packages/paperless_api/lib/src/modules/labels_api/paperless_labels_api.dart b/packages/paperless_api/lib/src/modules/labels_api/paperless_labels_api.dart index eb5f5434..61755e38 100644 --- a/packages/paperless_api/lib/src/modules/labels_api/paperless_labels_api.dart +++ b/packages/paperless_api/lib/src/modules/labels_api/paperless_labels_api.dart @@ -1,4 +1,3 @@ - import 'package:paperless_api/src/models/models.dart'; /// diff --git a/packages/paperless_api/test/saved_view_test.dart b/packages/paperless_api/test/saved_view_test.dart index adabfe36..0e1abf0b 100644 --- a/packages/paperless_api/test/saved_view_test.dart +++ b/packages/paperless_api/test/saved_view_test.dart @@ -65,10 +65,10 @@ void main() { }).toDocumentFilter(), equals( DocumentFilter.initial.copyWith( - correspondent: const IdQueryParameter.fromId(42), - documentType: const IdQueryParameter.fromId(69), - storagePath: const IdQueryParameter.fromId(14), - tags: const TagsQuery.ids( + correspondent: const SetIdQueryParameter(id: 42), + documentType: const SetIdQueryParameter(id: 69), + storagePath: const SetIdQueryParameter(id: 14), + tags: const IdsTagsQuery( include: [1, 2], exclude: [3, 4], ), @@ -131,10 +131,10 @@ void main() { ], }).toDocumentFilter(); final expected = DocumentFilter.initial.copyWith( - correspondent: const IdQueryParameter.notAssigned(), - documentType: const IdQueryParameter.notAssigned(), - storagePath: const IdQueryParameter.notAssigned(), - tags: const TagsQuery.notAssigned(), + correspondent: const NotAssignedIdQueryParameter(), + documentType: const NotAssignedIdQueryParameter(), + storagePath: const NotAssignedIdQueryParameter(), + tags: const NotAssignedTagsQuery(), ); expect( actual, @@ -148,10 +148,10 @@ void main() { expect( SavedView.fromDocumentFilter( DocumentFilter( - correspondent: const IdQueryParameter.fromId(1), - documentType: const IdQueryParameter.fromId(2), - storagePath: const IdQueryParameter.fromId(3), - tags: const TagsQuery.ids( + correspondent: const SetIdQueryParameter(id: 1), + documentType: const SetIdQueryParameter(id: 2), + storagePath: const SetIdQueryParameter(id: 3), + tags: const IdsTagsQuery( include: [4, 5], exclude: [6, 7, 8], ), @@ -202,9 +202,9 @@ void main() { expect( SavedView.fromDocumentFilter( const DocumentFilter( - correspondent: IdQueryParameter.unset(), - documentType: IdQueryParameter.unset(), - storagePath: IdQueryParameter.unset(), + correspondent: UnsetIdQueryParameter(), + documentType: UnsetIdQueryParameter(), + storagePath: UnsetIdQueryParameter(), tags: IdsTagsQuery(), sortField: SortField.created, sortOrder: SortOrder.descending, @@ -233,10 +233,10 @@ void main() { expect( SavedView.fromDocumentFilter( const DocumentFilter( - correspondent: IdQueryParameter.notAssigned(), - documentType: IdQueryParameter.notAssigned(), - storagePath: IdQueryParameter.notAssigned(), - tags: TagsQuery.notAssigned(), + correspondent: NotAssignedIdQueryParameter(), + documentType: NotAssignedIdQueryParameter(), + storagePath: NotAssignedIdQueryParameter(), + tags: NotAssignedTagsQuery(), sortField: SortField.created, sortOrder: SortOrder.ascending, ), diff --git a/pubspec.lock b/pubspec.lock index 212a30b9..c12da938 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1459,6 +1459,62 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f + url: "https://pub.dev" + source: hosted + version: "2.3.1" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1e5f8564..82e8daf8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,7 @@ dependencies: defer_pointer: ^0.0.2 transparent_image: ^2.0.1 flutter_animate: ^4.2.0+1 + shared_preferences: ^2.2.1 dependency_overrides: intl: ^0.18.1