Add migration scripts, remove dependencies to codegen, and cleanups

This commit is contained in:
Anton Stubenbord
2023-10-06 14:27:42 +02:00
parent 29b7c8bdc0
commit 5dcae3f0b3
32 changed files with 738 additions and 583 deletions

View File

@@ -19,6 +19,14 @@ class HiveBoxes {
static const localUserAccount = 'localUserAccount'; static const localUserAccount = 'localUserAccount';
static const localUserAppState = 'localUserAppState'; static const localUserAppState = 'localUserAppState';
static const hosts = 'hosts'; static const hosts = 'hosts';
static List<String> get all => [
globalSettings,
localUserCredentials,
localUserAccount,
localUserAppState,
hosts,
];
} }
class HiveTypeIds { class HiveTypeIds {

View File

@@ -200,19 +200,13 @@ class LabelRepository extends PersistentRepository<LabelRepositoryState> {
return updated; return updated;
} }
@override // @override
Future<void> clear() async { // LabelRepositoryState? fromJson(Map<String, dynamic> json) {
await super.clear(); // return LabelRepositoryState.fromJson(json);
emit(const LabelRepositoryState()); // }
}
@override // @override
LabelRepositoryState? fromJson(Map<String, dynamic> json) { // Map<String, dynamic>? toJson(LabelRepositoryState state) {
return LabelRepositoryState.fromJson(json); // return state.toJson();
} // }
@override
Map<String, dynamic>? toJson(LabelRepositoryState state) {
return state.toJson();
}
} }

View File

@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
abstract class PersistentRepository<T> extends HydratedCubit<T> { abstract class PersistentRepository<T> extends Cubit<T> {
final Map<Object, StreamSubscription> _subscribers = {}; final Map<Object, StreamSubscription> _subscribers = {};
PersistentRepository(T initialState) : super(initialState); PersistentRepository(T initialState) : super(initialState);

View File

@@ -76,20 +76,20 @@ class SavedViewRepository
return found; return found;
} }
@override // @override
Future<void> clear() async { // Future<void> clear() async {
await _initialized.future; // await _initialized.future;
await super.clear(); // await super.clear();
emit(const SavedViewRepositoryState.initial()); // emit(const SavedViewRepositoryState.initial());
} // }
@override // @override
SavedViewRepositoryState? fromJson(Map<String, dynamic> json) { // SavedViewRepositoryState? fromJson(Map<String, dynamic> json) {
return SavedViewRepositoryState.fromJson(json); // return SavedViewRepositoryState.fromJson(json);
} // }
@override // @override
Map<String, dynamic>? toJson(SavedViewRepositoryState state) { // Map<String, dynamic>? toJson(SavedViewRepositoryState state) {
return state.toJson(); // return state.toJson();
} // }
} }

View File

@@ -1,5 +1,7 @@
part of 'saved_view_repository.dart'; part of 'saved_view_repository.dart';
@freezed @freezed
class SavedViewRepositoryState with _$SavedViewRepositoryState { class SavedViewRepositoryState with _$SavedViewRepositoryState {
const factory SavedViewRepositoryState.initial({ const factory SavedViewRepositoryState.initial({

View File

@@ -1,10 +1,9 @@
import 'package:equatable/equatable.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/persistent_repository.dart'; import 'package:paperless_mobile/core/repository/persistent_repository.dart';
part 'user_repository_state.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+) /// Repository for new users (API v3, server version 1.14.2+)
class UserRepository extends PersistentRepository<UserRepositoryState> { class UserRepository extends PersistentRepository<UserRepositoryState> {
@@ -28,13 +27,13 @@ class UserRepository extends PersistentRepository<UserRepositoryState> {
return user; return user;
} }
@override // @override
UserRepositoryState? fromJson(Map<String, dynamic> json) { // UserRepositoryState? fromJson(Map<String, dynamic> json) {
return UserRepositoryState.fromJson(json); // return UserRepositoryState.fromJson(json);
} // }
@override // @override
Map<String, dynamic>? toJson(UserRepositoryState state) { // Map<String, dynamic>? toJson(UserRepositoryState state) {
return state.toJson(); // return state.toJson();
} // }
} }

View File

@@ -1,11 +1,19 @@
part of 'user_repository.dart'; part of 'user_repository.dart';
@freezed class UserRepositoryState with EquatableMixin {
class UserRepositoryState with _$UserRepositoryState { final Map<int, UserModel> users;
const factory UserRepositoryState({ const UserRepositoryState({
@Default({}) Map<int, UserModel> users, this.users = const {},
}) = _UserRepositoryState; });
factory UserRepositoryState.fromJson(Map<String, dynamic> json) => UserRepositoryState copyWith({
_$UserRepositoryStateFromJson(json); Map<int, UserModel>? users,
}) {
return UserRepositoryState(
users: users ?? this.users,
);
}
@override
List<Object?> get props => [users];
} }

View File

@@ -109,11 +109,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
.watch<DocumentEditCubit>() .watch<DocumentEditCubit>()
.state .state
.correspondents, .correspondents,
initialValue: initialValue: state
state.document.correspondent != null .document.correspondent !=
? IdQueryParameter.fromId( null
state.document.correspondent!) ? SetIdQueryParameter(
: const IdQueryParameter.unset(), id: state.document.correspondent!)
: const UnsetIdQueryParameter(),
name: fkCorrespondent, name: fkCorrespondent,
prefixIcon: prefixIcon:
const Icon(Icons.person_outlined), const Icon(Icons.person_outlined),
@@ -135,7 +136,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
_formKey.currentState _formKey.currentState
?.fields[fkCorrespondent] ?.fields[fkCorrespondent]
?.didChange( ?.didChange(
IdQueryParameter.fromId(itemData), SetIdQueryParameter(id: itemData),
); );
}, },
), ),
@@ -161,11 +162,11 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
addLabelText: addLabelText:
S.of(context)!.addDocumentType, S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType, labelText: S.of(context)!.documentType,
initialValue: initialValue: state.document.documentType !=
state.document.documentType != null null
? IdQueryParameter.fromId( ? SetIdQueryParameter(
state.document.documentType!) id: state.document.documentType!)
: const IdQueryParameter.unset(), : const UnsetIdQueryParameter(),
options: state.documentTypes, options: state.documentTypes,
name: _DocumentEditPageState.fkDocumentType, name: _DocumentEditPageState.fkDocumentType,
prefixIcon: prefixIcon:
@@ -185,7 +186,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
onPressed: () => _formKey.currentState onPressed: () => _formKey.currentState
?.fields[fkDocumentType] ?.fields[fkDocumentType]
?.didChange( ?.didChange(
IdQueryParameter.fromId(itemData), SetIdQueryParameter(id: itemData),
), ),
), ),
), ),
@@ -211,9 +212,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
options: state.storagePaths, options: state.storagePaths,
initialValue: initialValue:
state.document.storagePath != null state.document.storagePath != null
? IdQueryParameter.fromId( ? SetIdQueryParameter(
state.document.storagePath!) id: state.document.storagePath!)
: const IdQueryParameter.unset(), : const UnsetIdQueryParameter(),
name: fkStoragePath, name: fkStoragePath,
prefixIcon: prefixIcon:
const Icon(Icons.folder_outlined), const Icon(Icons.folder_outlined),
@@ -229,7 +230,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
allowOnlySelection: true, allowOnlySelection: true,
allowCreation: true, allowCreation: true,
allowExclude: false, allowExclude: false,
initialValue: TagsQuery.ids( initialValue: IdsTagsQuery(
include: state.document.tags.toList(), include: state.document.tags.toList(),
), ),
).padded(), ).padded(),
@@ -254,15 +255,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
?.fields[fkTags]?.value as TagsQuery; ?.fields[fkTags]?.value as TagsQuery;
_formKey.currentState?.fields[fkTags] _formKey.currentState?.fields[fkTags]
?.didChange( ?.didChange(
currentTags.maybeWhen( switch (currentTags) {
ids: (include, exclude) => IdsTagsQuery(
TagsQuery.ids(include: [ include: var i,
...include, exclude: var e
itemData ) =>
], exclude: exclude), IdsTagsQuery(
orElse: () => TagsQuery.ids( include: [...i, itemData],
include: [itemData]), exclude: e,
), ),
_ => IdsTagsQuery(include: [itemData])
},
); );
}, },
); );
@@ -301,16 +304,35 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
Future<void> _onSubmit(DocumentModel document) async { Future<void> _onSubmit(DocumentModel document) async {
if (_formKey.currentState?.saveAndValidate() ?? false) { if (_formKey.currentState?.saveAndValidate() ?? false) {
final values = _formKey.currentState!.value; 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( var mergedDocument = document.copyWith(
title: values[fkTitle], title: values[fkTitle],
created: values[fkCreatedDate], created: values[fkCreatedDate],
documentType: () => (values[fkDocumentType] as IdQueryParameter?) correspondent: () => correspondent,
?.whenOrNull(fromId: (id) => id), documentType: () => documentType,
correspondent: () => (values[fkCorrespondent] as IdQueryParameter?) storagePath: () => storagePath,
?.whenOrNull(fromId: (id) => id), tags: tags,
storagePath: () => (values[fkStoragePath] as IdQueryParameter?)
?.whenOrNull(fromId: (id) => id),
tags: (values[fkTags] as IdsTagsQuery?)?.include,
content: values[fkContent], content: values[fkContent],
); );

View File

@@ -344,20 +344,29 @@ class _DocumentUploadPreparationPageState
final cubit = context.read<DocumentUploadCubit>(); final cubit = context.read<DocumentUploadCubit>();
try { try {
setState(() => _isUploadLoading = true); 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 <int>[],
};
final createdAt = fv[DocumentModel.createdKey] as DateTime?; final asn = formValues[DocumentModel.asnKey] as int?;
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 taskId = await cubit.upload( final taskId = await cubit.upload(
await widget.fileBytes, await widget.fileBytes,
filename: _padWithExtension( filename: _padWithExtension(

View File

@@ -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/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'documents_cubit.g.dart';
part 'documents_state.dart'; part 'documents_state.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState> class DocumentsCubit extends Cubit<DocumentsState>
with DocumentPagingBlocMixin { with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;
@@ -135,13 +134,13 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
await _userState.save(); await _userState.save();
} }
@override // @override
DocumentsState? fromJson(Map<String, dynamic> json) { // DocumentsState? fromJson(Map<String, dynamic> json) {
return DocumentsState.fromJson(json); // return DocumentsState.fromJson(json);
} // }
@override // @override
Map<String, dynamic>? toJson(DocumentsState state) { // Map<String, dynamic>? toJson(DocumentsState state) {
return state.toJson(); // return state.toJson();
} // }
} }

View File

@@ -1,17 +1,10 @@
part of 'documents_cubit.dart'; part of 'documents_cubit.dart';
@JsonSerializable()
class DocumentsState extends DocumentPagingState { class DocumentsState extends DocumentPagingState {
@JsonKey(includeToJson: false, includeFromJson: false)
final List<DocumentModel> selection; final List<DocumentModel> selection;
@JsonKey(includeToJson: false, includeFromJson: false)
final Map<int, Correspondent> correspondents; final Map<int, Correspondent> correspondents;
@JsonKey(includeToJson: false, includeFromJson: false)
final Map<int, DocumentType> documentTypes; final Map<int, DocumentType> documentTypes;
@JsonKey(includeToJson: false, includeFromJson: false)
final Map<int, Tag> tags; final Map<int, Tag> tags;
@JsonKey(includeToJson: false, includeFromJson: false)
final Map<int, StoragePath> storagePaths; final Map<int, StoragePath> storagePaths;
final ViewType viewType; final ViewType viewType;
@@ -85,9 +78,4 @@ class DocumentsState extends DocumentPagingState {
value: value, value: value,
); );
} }
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
_$DocumentsStateFromJson(json);
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
} }

View File

@@ -252,7 +252,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
cubit.resetSelection(); cubit.resetSelection();
return false; 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(); await _onResetFilter();
return false; return false;
} }
@@ -512,8 +513,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
void _addTagToFilter(int tagId) { void _addTagToFilter(int tagId) {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
cubit.state.filter.tags.maybeMap( switch (cubit.state.filter.tags) {
ids: (state) { case IdsTagsQuery state:
if (state.include.contains(tagId)) { if (state.include.contains(tagId)) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
@@ -541,13 +542,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
), ),
); );
} }
}, break;
orElse: () { default:
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith(tags: TagsQuery.ids(include: [tagId])), (filter) => filter.copyWith(tags: IdsTagsQuery(include: [tagId])),
); );
}, break;
); }
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }
@@ -558,27 +559,26 @@ class _DocumentsPageState extends State<DocumentsPage> {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
cubit.state.filter.correspondent.maybeWhen( switch (cubit.state.filter.correspondent) {
fromId: (id) { case SetIdQueryParameter(id: var id):
if (id == correspondentId) { if (id == correspondentId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) =>
correspondent: const IdQueryParameter.unset()), filter.copyWith(correspondent: const UnsetIdQueryParameter()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
correspondent: IdQueryParameter.fromId(correspondentId)), correspondent: SetIdQueryParameter(id: correspondentId)),
); );
} }
}, break;
orElse: () { default:
cubit.updateCurrentFilter( cubit.updateCurrentFilter((filter) => filter.copyWith(
(filter) => filter.copyWith( correspondent: SetIdQueryParameter(id: correspondentId),
correspondent: IdQueryParameter.fromId(correspondentId)), ));
); break;
}, }
);
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }
@@ -589,27 +589,27 @@ class _DocumentsPageState extends State<DocumentsPage> {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
cubit.state.filter.documentType.maybeWhen( switch (cubit.state.filter.documentType) {
fromId: (id) { case SetIdQueryParameter(id: var id):
if (id == documentTypeId) { if (id == documentTypeId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(documentType: const IdQueryParameter.unset()), filter.copyWith(documentType: const UnsetIdQueryParameter()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
documentType: IdQueryParameter.fromId(documentTypeId)), documentType: SetIdQueryParameter(id: documentTypeId)),
); );
} }
}, break;
orElse: () { default:
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
documentType: IdQueryParameter.fromId(documentTypeId)), documentType: SetIdQueryParameter(id: documentTypeId)),
); );
}, break;
); }
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }
@@ -620,27 +620,28 @@ class _DocumentsPageState extends State<DocumentsPage> {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
cubit.state.filter.storagePath.maybeWhen( switch (cubit.state.filter.storagePath){
fromId: (id) { case SetIdQueryParameter(id: var id):
if (id == pathId) { if (id == pathId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(storagePath: const IdQueryParameter.unset()), filter.copyWith(storagePath: const UnsetIdQueryParameter()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)), filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)),
); );
} }
}, break;
orElse: () { default:
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)), filter.copyWith(storagePath: SetIdQueryParameter(id: pathId)),
); );
}, break;
); }
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }

View File

@@ -118,7 +118,7 @@ class InboxCubit extends HydratedCubit<InboxState>
updateFilter( updateFilter(
filter: DocumentFilter( filter: DocumentFilter(
sortField: SortField.added, sortField: SortField.added,
tags: TagsQuery.ids(include: inboxTags.toList()), tags: IdsTagsQuery(include: inboxTags.toList()),
), ),
); );
} }
@@ -160,7 +160,7 @@ class InboxCubit extends HydratedCubit<InboxState>
emitLoading: false, emitLoading: false,
filter: DocumentFilter( filter: DocumentFilter(
sortField: SortField.added, sortField: SortField.added,
tags: TagsQuery.ids(include: inboxTags.toList()), tags: IdsTagsQuery(include: inboxTags.toList()),
), ),
); );
} }

View File

@@ -116,16 +116,16 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
icon: const Icon(Icons.done), icon: const Icon(Icons.done),
onPressed: () { onPressed: () {
if (widget.allowOnlySelection) { if (widget.allowOnlySelection) {
widget.onSubmit(returnValue: TagsQuery.ids(include: _include)); widget.onSubmit(returnValue: IdsTagsQuery(include: _include));
return; return;
} }
late final TagsQuery query; late final TagsQuery query;
if (_notAssigned) { if (_notAssigned) {
query = const TagsQuery.notAssigned(); query = const NotAssignedTagsQuery();
} else if (_anyAssigned) { } else if (_anyAssigned) {
query = TagsQuery.anyAssigned(tagIds: _include); query = AnyAssignedTagsQuery(tagIds: _include);
} else { } else {
query = TagsQuery.ids(include: _include, exclude: _exclude); query = IdsTagsQuery(include: _include, exclude: _exclude);
} }
widget.onSubmit(returnValue: query); widget.onSubmit(returnValue: query);
}, },

View File

@@ -96,19 +96,17 @@ class TagsFormField extends StatelessWidget {
if (query == null) { if (query == null) {
yield Container(); yield Container();
} else { } else {
final widgets = query.map( final widgets = switch (query) {
ids: (value) => [ IdsTagsQuery(include: var inc, exclude: var exc) => [
for (var inc in value.include) for (var i in inc) _buildTagIdQueryWidget(context, i, field, false),
_buildTagIdQueryWidget(context, inc, field, false), for (var e in exc) _buildTagIdQueryWidget(context, e, field, true),
for (var exc in value.exclude) ],
_buildTagIdQueryWidget(context, exc, field, true), AnyAssignedTagsQuery query => [
], for (var id in query.tagIds)
anyAssigned: (value) => [ _buildAnyAssignedTagWidget(context, id, field, query),
for (var id in value.tagIds) ],
_buildAnyAssignedTagWidget(context, id, field, value), NotAssignedTagsQuery() => [_buildNotAssignedTagWidget(context, field)],
], };
notAssigned: (value) => [_buildNotAssignedTagWidget(context, field)],
);
for (var child in widgets) { for (var child in widgets) {
yield child; yield child;
} }
@@ -185,7 +183,7 @@ class TagsFormField extends StatelessWidget {
tagIds: query.tagIds.whereNot((element) => element == e).toList(), tagIds: query.tagIds.whereNot((element) => element == e).toList(),
); );
if (updatedQuery.tagIds.isEmpty) { if (updatedQuery.tagIds.isEmpty) {
field.didChange(const TagsQuery.ids()); field.didChange(const IdsTagsQuery());
} else { } else {
field.didChange(updatedQuery); field.didChange(updatedQuery);
} }

View File

@@ -259,7 +259,7 @@ class _LabelsPageState extends State<LabelsPage>
LabelTabView<Correspondent>( LabelTabView<Correspondent>(
labels: state.correspondents, labels: state.correspondents,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
correspondent: IdQueryParameter.fromId(label.id!), correspondent: SetIdQueryParameter(id: label.id!),
), ),
canEdit: user.canEditCorrespondents, canEdit: user.canEditCorrespondents,
canAddNew: user.canCreateCorrespondents, canAddNew: user.canCreateCorrespondents,
@@ -287,7 +287,7 @@ class _LabelsPageState extends State<LabelsPage>
LabelTabView<DocumentType>( LabelTabView<DocumentType>(
labels: state.documentTypes, labels: state.documentTypes,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
documentType: IdQueryParameter.fromId(label.id!), documentType: SetIdQueryParameter(id: label.id!),
), ),
canEdit: user.canEditDocumentTypes, canEdit: user.canEditDocumentTypes,
canAddNew: user.canCreateDocumentTypes, canAddNew: user.canCreateDocumentTypes,
@@ -315,7 +315,7 @@ class _LabelsPageState extends State<LabelsPage>
LabelTabView<Tag>( LabelTabView<Tag>(
labels: state.tags, labels: state.tags,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
tags: TagsQuery.ids(include: [label.id!]), tags: IdsTagsQuery(include: [label.id!]),
), ),
canEdit: user.canEditTags, canEdit: user.canEditTags,
canAddNew: user.canCreateTags, canAddNew: user.canCreateTags,
@@ -354,7 +354,7 @@ class _LabelsPageState extends State<LabelsPage>
EditLabelRoute(label).push(context); EditLabelRoute(label).push(context);
}, },
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
storagePath: IdQueryParameter.fromId(label.id!), storagePath: SetIdQueryParameter(id: label.id!),
), ),
canEdit: user.canEditStoragePaths, canEdit: user.canEditStoragePaths,
canAddNew: user.canCreateStoragePaths, canAddNew: user.canCreateStoragePaths,

View File

@@ -32,11 +32,10 @@ class FullscreenLabelForm<T extends Label> extends StatefulWidget {
this.allowSelectUnassigned = true, this.allowSelectUnassigned = true,
required this.canCreateNewLabel, required this.canCreateNewLabel,
}) : assert( }) : assert(
!(initialValue?.isOnlyAssigned() ?? false) || showAnyAssignedOption, !(initialValue?.isOnlyAssigned ?? false) || showAnyAssignedOption,
), ),
assert( assert(
!(initialValue?.isOnlyNotAssigned() ?? false) || !(initialValue?.isOnlyNotAssigned ?? false) || showNotAssignedOption,
showNotAssignedOption,
), ),
assert((addNewLabelText != null) == (onCreateNewLabel != null)); assert((addNewLabelText != null) == (onCreateNewLabel != null));
@@ -87,11 +86,10 @@ class _FullscreenLabelFormState<T extends Label>
final index = AutocompleteHighlightedOption.of(context); final index = AutocompleteHighlightedOption.of(context);
final value = index.isNegative ? null : options.elementAt(index); final value = index.isNegative ? null : options.elementAt(index);
widget.onSubmit( widget.onSubmit(
returnValue: value?.maybeWhen( returnValue: switch (value) {
fromId: (id) => IdQueryParameter.fromId(id), SetIdQueryParameter query => query,
orElse: () => const IdQueryParameter.unset(), _ => const UnsetIdQueryParameter(),
) ?? },
const IdQueryParameter.unset(),
); );
}, },
autofocus: true, autofocus: true,
@@ -169,7 +167,7 @@ class _FullscreenLabelFormState<T extends Label>
final label = await widget.onCreateNewLabel!(_textEditingController.text); final label = await widget.onCreateNewLabel!(_textEditingController.text);
if (label?.id != null) { if (label?.id != null) {
widget.onSubmit( widget.onSubmit(
returnValue: IdQueryParameter.fromId(label!.id!), returnValue: SetIdQueryParameter(id: label!.id!),
); );
} }
} }
@@ -184,21 +182,21 @@ class _FullscreenLabelFormState<T extends Label>
if (widget.initialValue == null) { if (widget.initialValue == null) {
// If nothing is selected yet, show all options first. // If nothing is selected yet, show all options first.
for (final option in widget.options.values) { for (final option in widget.options.values) {
yield IdQueryParameter.fromId(option.id!); yield SetIdQueryParameter(id: option.id!);
} }
if (widget.showNotAssignedOption) { if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned(); yield const NotAssignedIdQueryParameter();
} }
if (widget.showAnyAssignedOption) { if (widget.showAnyAssignedOption) {
yield const IdQueryParameter.anyAssigned(); yield const AnyAssignedIdQueryParameter();
} }
} else { } else {
// If an initial value is given, show not assigned first, which will be selected by default when pressing "done" on keyboard. // If an initial value is given, show not assigned first, which will be selected by default when pressing "done" on keyboard.
if (widget.showNotAssignedOption) { if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned(); yield const NotAssignedIdQueryParameter();
} }
if (widget.showAnyAssignedOption) { if (widget.showAnyAssignedOption) {
yield const IdQueryParameter.anyAssigned(); yield const AnyAssignedIdQueryParameter();
} }
for (final option in widget.options.values) { for (final option in widget.options.values) {
// Don't include the initial value in the selection // Don't include the initial value in the selection
@@ -207,7 +205,7 @@ class _FullscreenLabelFormState<T extends Label>
option.id == initialValue.id) { option.id == initialValue.id) {
continue; continue;
} }
yield IdQueryParameter.fromId(option.id!); yield SetIdQueryParameter(id: option.id!);
} }
} }
} else { } else {
@@ -216,77 +214,76 @@ class _FullscreenLabelFormState<T extends Label>
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery)); .where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
if (matches.isNotEmpty) { if (matches.isNotEmpty) {
for (final match in matches) { for (final match in matches) {
yield IdQueryParameter.fromId(match.id!); yield SetIdQueryParameter(id: match.id!);
} }
if (widget.showNotAssignedOption) { if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned(); yield const NotAssignedIdQueryParameter();
} }
if (widget.showAnyAssignedOption) { if (widget.showAnyAssignedOption) {
yield const IdQueryParameter.anyAssigned(); yield const AnyAssignedIdQueryParameter();
} }
} else { } else {
if (widget.showNotAssignedOption) { if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned(); yield const NotAssignedIdQueryParameter();
} }
if (widget.showAnyAssignedOption) { if (widget.showAnyAssignedOption) {
yield const IdQueryParameter.anyAssigned(); yield const AnyAssignedIdQueryParameter();
} }
if (!(widget.showAnyAssignedOption || widget.showNotAssignedOption)) { if (!(widget.showAnyAssignedOption || widget.showNotAssignedOption)) {
yield const IdQueryParameter.unset(); yield const UnsetIdQueryParameter();
} }
} }
} }
} }
String? _buildHintText() { String? _buildHintText() {
return widget.initialValue?.when( return switch (widget.initialValue) {
unset: () => S.of(context)!.startTyping, UnsetIdQueryParameter() => S.of(context)!.startTyping,
notAssigned: () => S.of(context)!.notAssigned, NotAssignedIdQueryParameter() => S.of(context)!.notAssigned,
anyAssigned: () => S.of(context)!.anyAssigned, AnyAssignedIdQueryParameter() => S.of(context)!.anyAssigned,
fromId: (id) => widget.options[id]?.name ?? S.of(context)!.startTyping, SetIdQueryParameter(id: var id) =>
); widget.options[id]?.name ?? S.of(context)!.startTyping,
_ => null,
};
} }
Widget _buildOptionWidget(IdQueryParameter option, bool highlight) { Widget _buildOptionWidget(IdQueryParameter option, bool highlight) {
void onTap() => widget.onSubmit(returnValue: option); void onTap() => widget.onSubmit(returnValue: option);
if (option.isUnset()) { return switch (option) {
return Center( NotAssignedIdQueryParameter() => ListTile(
child: Column( selected: highlight,
children: [ selectedTileColor: Theme.of(context).focusColor,
Text(S.of(context)!.noItemsFound).padded(), title: Text(S.of(context)!.notAssigned),
if (widget.onCreateNewLabel != null) onTap: onTap,
TextButton(
child: Text(widget.addNewLabelText!),
onPressed: _onCreateNewLabel,
),
],
), ),
); AnyAssignedIdQueryParameter() => ListTile(
} selected: highlight,
selectedTileColor: Theme.of(context).focusColor,
return option.whenOrNull( title: Text(S.of(context)!.anyAssigned),
notAssigned: () => ListTile( onTap: onTap,
selected: highlight, ),
selectedTileColor: Theme.of(context).focusColor, SetIdQueryParameter(id: var id) => ListTile(
title: Text(S.of(context)!.notAssigned), selected: highlight,
onTap: onTap, selectedTileColor: Theme.of(context).focusColor,
), title: Text(widget.options[id]!.name),
anyAssigned: () => ListTile( onTap: onTap,
selected: highlight, enabled: widget.allowSelectUnassigned
selectedTileColor: Theme.of(context).focusColor, ? true
title: Text(S.of(context)!.anyAssigned), : widget.options[id]!.documentCount != 0,
onTap: onTap, ),
), UnsetIdQueryParameter() => Center(
fromId: (id) => ListTile( child: Column(
selected: highlight, children: [
selectedTileColor: Theme.of(context).focusColor, Text(S.of(context)!.noItemsFound).padded(),
title: Text(widget.options[id]!.name), if (widget.onCreateNewLabel != null)
onTap: onTap, TextButton(
enabled: widget.allowSelectUnassigned child: Text(widget.addNewLabelText!),
? true onPressed: _onCreateNewLabel,
: widget.options[id]!.documentCount != 0, ),
), ],
)!; // Never null, since we already return on unset before ),
),
};
} }
} }

View File

@@ -47,13 +47,13 @@ class LabelFormField<T extends Label> extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
String _buildText(BuildContext context, IdQueryParameter? value) { String _buildText(BuildContext context, IdQueryParameter? value) {
return value?.when( return switch (value) {
unset: () => '', UnsetIdQueryParameter() => '',
notAssigned: () => S.of(context)!.notAssigned, NotAssignedIdQueryParameter() => S.of(context)!.notAssigned,
anyAssigned: () => S.of(context)!.anyAssigned, AnyAssignedIdQueryParameter() => S.of(context)!.anyAssigned,
fromId: (id) => options[id]?.name, SetIdQueryParameter(id: var id) => options[id]?.name ?? '',
) ?? _ => '',
''; };
} }
@override @override
@@ -70,9 +70,14 @@ class LabelFormField<T extends Label> extends StatelessWidget {
text: _buildText(context, field.value), text: _buildText(context, field.value),
); );
final displayedSuggestions = suggestions final displayedSuggestions = suggestions
.whereNot((e) => .whereNot(
e.id == (e) =>
field.value?.maybeWhen(fromId: (id) => id, orElse: () => -1)) e.id ==
switch (field.value) {
SetIdQueryParameter(id: var id) => id,
_ => -1,
},
)
.toList(); .toList();
return Column( return Column(
@@ -98,7 +103,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
? IconButton( ? IconButton(
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () => onPressed: () =>
field.didChange(const IdQueryParameter.unset()), field.didChange(const UnsetIdQueryParameter()),
) )
: null, : null,
), ),
@@ -151,7 +156,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
child: ActionChip( child: ActionChip(
label: Text(suggestion.name), label: Text(suggestion.name),
onPressed: () => field.didChange( onPressed: () => field.didChange(
IdQueryParameter.fromId(suggestion.id!), SetIdQueryParameter(id: suggestion.id!),
), ),
), ),
); );

View File

@@ -44,7 +44,8 @@ class _LandingPageState extends State<LandingPage> {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Text( child: Text(
S.of(context)!.welcomeUser( S.of(context)!.welcomeUser(
currentUser.fullName ?? currentUser.username), currentUser.fullName ?? currentUser.username,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme

View File

@@ -52,6 +52,7 @@ import 'package:paperless_mobile/routes/typed/top_level/settings_route.dart';
import 'package:paperless_mobile/theme.dart'; import 'package:paperless_mobile/theme.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
String get defaultPreferredLocaleSubtag { String get defaultPreferredLocaleSubtag {
String preferredLocale = Platform.localeName.split("_").first; String preferredLocale = Platform.localeName.split("_").first;
@@ -62,9 +63,42 @@ String get defaultPreferredLocaleSubtag {
return preferredLocale; return preferredLocale;
} }
Map<String, Future<void> 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<void> 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<void> _initHive() async { Future<void> _initHive() async {
await Hive.initFlutter(); await Hive.initFlutter();
await performMigrations();
registerHiveAdapters(); registerHiveAdapters();
// await getApplicationDocumentsDirectory().then((value) => value.deleteSync(recursive: true)); // await getApplicationDocumentsDirectory().then((value) => value.deleteSync(recursive: true));
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount); await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState); await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
@@ -81,6 +115,7 @@ Future<void> _initHive() async {
void main() async { void main() async {
runZonedGuarded(() async { runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
Paint.enableDithering = true; Paint.enableDithering = true;
// if (kDebugMode) { // if (kDebugMode) {
// // URL: http://localhost:3131 // // URL: http://localhost:3131
@@ -93,13 +128,6 @@ void main() async {
// ) // )
// .start(); // .start();
// } // }
await _initHive();
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
final globalSettingsBox =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings);
final globalSettings = globalSettingsBox.getValue()!;
await findSystemLocale();
packageInfo = await PackageInfo.fromPlatform(); packageInfo = await PackageInfo.fromPlatform();
if (Platform.isAndroid) { if (Platform.isAndroid) {
@@ -108,6 +136,14 @@ void main() async {
if (Platform.isIOS) { if (Platform.isIOS) {
iosInfo = await DeviceInfoPlugin().iosInfo; iosInfo = await DeviceInfoPlugin().iosInfo;
} }
await _initHive();
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
final globalSettingsBox =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings);
final globalSettings = globalSettingsBox.getValue()!;
await findSystemLocale();
final connectivityStatusService = ConnectivityStatusServiceImpl( final connectivityStatusService = ConnectivityStatusServiceImpl(
Connectivity(), Connectivity(),
); );

View File

@@ -36,9 +36,9 @@ class PaperlessApiHiveTypeIds {
void registerPaperlessApiHiveTypeAdapters() { void registerPaperlessApiHiveTypeAdapters() {
Hive.registerAdapter(DocumentFilterAdapter()); Hive.registerAdapter(DocumentFilterAdapter());
// TagsQuery // TagsQuery
Hive.registerAdapter(AnyAssignedTagsQueryImplAdapter()); Hive.registerAdapter(AnyAssignedTagsQueryAdapter());
Hive.registerAdapter(NotAssignedTagsQueryImplAdapter()); Hive.registerAdapter(NotAssignedTagsQueryAdapter());
Hive.registerAdapter(IdsTagsQueryImplAdapter()); Hive.registerAdapter(IdsTagsQueryAdapter());
Hive.registerAdapter(SortFieldAdapter()); Hive.registerAdapter(SortFieldAdapter());
Hive.registerAdapter(SortOrderAdapter()); Hive.registerAdapter(SortOrderAdapter());
@@ -49,13 +49,13 @@ void registerPaperlessApiHiveTypeAdapters() {
Hive.registerAdapter(TextQueryAdapter()); Hive.registerAdapter(TextQueryAdapter());
Hive.registerAdapter(QueryTypeAdapter()); Hive.registerAdapter(QueryTypeAdapter());
// IdQueryParameter // IdQueryParameter
Hive.registerAdapter(SetIdQueryParameterImplAdapter()); Hive.registerAdapter(SetIdQueryParameterAdapter());
Hive.registerAdapter(UnsetIdQueryParameterImplAdapter()); Hive.registerAdapter(UnsetIdQueryParameterAdapter());
Hive.registerAdapter(AnyAssignedIdQueryParameterImplAdapter()); Hive.registerAdapter(AnyAssignedIdQueryParameterAdapter());
Hive.registerAdapter(NotAssignedIdQueryParameterImplAdapter()); Hive.registerAdapter(NotAssignedIdQueryParameterAdapter());
// Users and permissions // Users and permissions
Hive.registerAdapter(UserModelV3ImplAdapter()); Hive.registerAdapter(UserModelV3Adapter());
Hive.registerAdapter(UserModelV2ImplAdapter()); Hive.registerAdapter(UserModelV2Adapter());
Hive.registerAdapter(GroupModelAdapter()); Hive.registerAdapter(GroupModelAdapter());
Hive.registerAdapter(PermissionsAdapter()); Hive.registerAdapter(PermissionsAdapter());
} }

View File

@@ -3,13 +3,12 @@ import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.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'; import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_query_field.dart';
part 'document_filter.g.dart'; part 'document_filter.g.dart';
@DateRangeQueryJsonConverter() @DateRangeQueryJsonConverter()
@JsonSerializable(explicitToJson: true) // @JsonSerializable(explicitToJson: true)
@HiveType(typeId: PaperlessApiHiveTypeIds.documentFilter) @HiveType(typeId: PaperlessApiHiveTypeIds.documentFilter)
class DocumentFilter extends Equatable { class DocumentFilter extends Equatable {
static const DocumentFilter initial = DocumentFilter(); static const DocumentFilter initial = DocumentFilter();
@@ -67,11 +66,11 @@ class DocumentFilter extends Equatable {
final int? selectedView; final int? selectedView;
const DocumentFilter({ const DocumentFilter({
this.documentType = const IdQueryParameter.unset(), this.documentType = const UnsetIdQueryParameter(),
this.correspondent = const IdQueryParameter.unset(), this.correspondent = const UnsetIdQueryParameter(),
this.storagePath = const IdQueryParameter.unset(), this.storagePath = const UnsetIdQueryParameter(),
this.asnQuery = const IdQueryParameter.unset(), this.asnQuery = const UnsetIdQueryParameter(),
this.tags = const TagsQuery.ids(), this.tags = const IdsTagsQuery(),
this.sortField = SortField.created, this.sortField = SortField.created,
this.sortOrder = SortOrder.descending, this.sortOrder = SortOrder.descending,
this.page = 1, this.page = 1,
@@ -164,7 +163,8 @@ class DocumentFilter extends Equatable {
created: created ?? this.created, created: created ?? this.created,
modified: modified ?? this.modified, modified: modified ?? this.modified,
moreLike: moreLike != null ? moreLike.call() : this.moreLike, 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 && if (query?.queryType != QueryType.extended &&
newFilter.forceExtendedQuery) { newFilter.forceExtendedQuery) {
@@ -195,24 +195,23 @@ class DocumentFilter extends Equatable {
} }
int get appliedFiltersCount => [ int get appliedFiltersCount => [
documentType.maybeWhen( switch (documentType) {
unset: () => 0, UnsetIdQueryParameter() => 0,
orElse: () => 1, _ => 1,
), },
correspondent.maybeWhen( switch (correspondent) {
unset: () => 0, UnsetIdQueryParameter() => 0,
orElse: () => 1, _ => 1,
), },
storagePath.maybeWhen( switch (storagePath) {
unset: () => 0, UnsetIdQueryParameter() => 0,
orElse: () => 1, _ => 1,
), },
tags.maybeWhen( switch (tags) {
ids: (include, exclude) => include.length + exclude.length, NotAssignedTagsQuery() => 1,
anyAssigned: (tagIds) => tagIds.length, AnyAssignedTagsQuery(tagIds: var tags) => tags.length,
notAssigned: () => 1, IdsTagsQuery(include: var i, exclude: var e) => e.length + i.length,
orElse: () => 0, },
),
switch (added) { switch (added) {
RelativeDateRangeQuery() => 1, RelativeDateRangeQuery() => 1,
AbsoluteDateRangeQuery() => 1, AbsoluteDateRangeQuery() => 1,
@@ -228,10 +227,10 @@ class DocumentFilter extends Equatable {
AbsoluteDateRangeQuery() => 1, AbsoluteDateRangeQuery() => 1,
UnsetDateRangeQuery() => 0, UnsetDateRangeQuery() => 0,
}, },
asnQuery.maybeWhen( switch (asnQuery) {
unset: () => 0, UnsetIdQueryParameter() => 0,
orElse: () => 1, _ => 1,
), },
(query.queryText?.isNotEmpty ?? false) ? 1 : 0, (query.queryText?.isNotEmpty ?? false) ? 1 : 0,
].fold(0, (previousValue, element) => previousValue + element); ].fold(0, (previousValue, element) => previousValue + element);
@@ -254,8 +253,8 @@ class DocumentFilter extends Equatable {
selectedView, selectedView,
]; ];
factory DocumentFilter.fromJson(Map<String, dynamic> json) => // factory DocumentFilter.fromJson(Map<String, dynamic> json) =>
_$DocumentFilterFromJson(json); // _$DocumentFilterFromJson(json);
Map<String, dynamic> toJson() => _$DocumentFilterToJson(this); // Map<String, dynamic> toJson() => _$DocumentFilterToJson(this);
} }

View File

@@ -5,9 +5,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/constants.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/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_query_field.dart';
import 'package:paperless_api/src/models/query_parameters/date_range_queries/date_range_unit.dart';
part 'filter_rule_model.g.dart'; part 'filter_rule_model.g.dart';
@@ -59,48 +57,49 @@ class FilterRule with EquatableMixin {
case documentTypeRule: case documentTypeRule:
return filter.copyWith( return filter.copyWith(
documentType: value == null documentType: value == null
? const IdQueryParameter.notAssigned() ? const NotAssignedIdQueryParameter()
: IdQueryParameter.fromId(int.parse(value!)), : SetIdQueryParameter(id: int.parse(value!)),
); );
case correspondentRule: case correspondentRule:
return filter.copyWith( return filter.copyWith(
correspondent: value == null correspondent: value == null
? const IdQueryParameter.notAssigned() ? const NotAssignedIdQueryParameter()
: IdQueryParameter.fromId(int.parse(value!)), : SetIdQueryParameter(id: int.parse(value!)),
); );
case storagePathRule: case storagePathRule:
return filter.copyWith( return filter.copyWith(
storagePath: value == null storagePath: value == null
? const IdQueryParameter.notAssigned() ? const NotAssignedIdQueryParameter()
: IdQueryParameter.fromId(int.parse(value!)), : SetIdQueryParameter(id: int.parse(value!)),
); );
case hasAnyTag: case hasAnyTag:
return filter.copyWith( return filter.copyWith(
tags: value == "true" tags: value == "true"
? const TagsQuery.anyAssigned() ? const AnyAssignedTagsQuery()
: const TagsQuery.notAssigned(), : const NotAssignedTagsQuery(),
); );
case includeTagsRule: case includeTagsRule:
assert(filter.tags is IdsTagsQuery); assert(filter.tags is IdsTagsQuery);
return filter.copyWith( return filter.copyWith(
tags: filter.tags.maybeWhen( tags: switch (filter.tags) {
ids: (include, exclude) => TagsQuery.ids( // TODO: Handle this case.
include: [...include, int.parse(value!)], IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery(
exclude: exclude, include: [...i, int.parse(value!)],
), exclude: e,
orElse: () => filter.tags, ),
), _ => filter.tags,
},
); );
case excludeTagsRule: case excludeTagsRule:
assert(filter.tags is IdsTagsQuery); assert(filter.tags is IdsTagsQuery);
return filter.copyWith( return filter.copyWith(
tags: filter.tags.maybeWhen( tags: switch (filter.tags) {
ids: (include, exclude) => TagsQuery.ids( IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery(
include: include, include: i,
exclude: [...exclude, int.parse(value!)], exclude: [...e, int.parse(value!)],
), ),
orElse: () => filter.tags, _ => filter.tags,
), },
); );
case createdBeforeRule: case createdBeforeRule:
if (filter.created is AbsoluteDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
@@ -245,37 +244,46 @@ class FilterRule with EquatableMixin {
/// ///
static List<FilterRule> fromFilter(final DocumentFilter filter) { static List<FilterRule> fromFilter(final DocumentFilter filter) {
List<FilterRule> filterRules = []; List<FilterRule> filterRules = [];
final corrRule = filter.correspondent.whenOrNull( final corrRule = switch (filter.correspondent) {
notAssigned: () => FilterRule(correspondentRule, null), NotAssignedIdQueryParameter() => FilterRule(correspondentRule, null),
fromId: (id) => FilterRule(correspondentRule, id.toString()), SetIdQueryParameter(id: var id) =>
); FilterRule(correspondentRule, id.toString()),
_ => null,
};
if (corrRule != null) { if (corrRule != null) {
filterRules.add(corrRule); filterRules.add(corrRule);
} }
final docTypeRule = filter.documentType.whenOrNull( final docTypeRule = switch (filter.documentType) {
notAssigned: () => FilterRule(documentTypeRule, null), NotAssignedIdQueryParameter() => FilterRule(documentTypeRule, null),
fromId: (id) => FilterRule(documentTypeRule, id.toString()), SetIdQueryParameter(id: var id) =>
); FilterRule(documentTypeRule, id.toString()),
_ => null,
};
if (docTypeRule != null) { if (docTypeRule != null) {
filterRules.add(docTypeRule); filterRules.add(docTypeRule);
} }
final sPathRule = filter.storagePath.whenOrNull( final sPathRule = switch (filter.storagePath) {
notAssigned: () => FilterRule(storagePathRule, null), NotAssignedIdQueryParameter() => FilterRule(storagePathRule, null),
fromId: (id) => FilterRule(storagePathRule, id.toString()), SetIdQueryParameter(id: var id) =>
); FilterRule(storagePathRule, id.toString()),
_ => null,
};
if (sPathRule != null) { if (sPathRule != null) {
filterRules.add(sPathRule); filterRules.add(sPathRule);
} }
final tagRules = filter.tags.when( final tagRules = switch (filter.tags) {
notAssigned: () => [FilterRule(hasAnyTag, 'false')], NotAssignedTagsQuery() => [FilterRule(hasAnyTag, 'false')],
anyAssigned: (_) => [FilterRule(hasAnyTag, 'true')], AnyAssignedTagsQuery() => [FilterRule(hasAnyTag, 'true')],
ids: (include, exclude) => [ IdsTagsQuery(include: var i, exclude: var e) => [
...include.map((id) => FilterRule(includeTagsRule, id.toString())), ...i.map((id) => FilterRule(includeTagsRule, id.toString())),
...exclude.map((id) => FilterRule(excludeTagsRule, id.toString())), ...e.map((id) => FilterRule(excludeTagsRule, id.toString())),
], ],
); };
filterRules.addAll(tagRules); filterRules.addAll(tagRules);
if (filter.query.queryText != null) { if (filter.query.queryText != null) {

View File

@@ -2,32 +2,36 @@ import 'package:paperless_api/paperless_api.dart';
extension UserPermissionExtension on UserModel { extension UserPermissionExtension on UserModel {
bool hasPermission(PermissionAction action, PermissionTarget target) { bool hasPermission(PermissionAction action, PermissionTarget target) {
return map( final permission = [action.value, target.value].join("_");
v3: (user) { return switch (this) {
final permission = [action.value, target.value].join("_"); UserModelV2() => true,
return user.userPermissions.any((element) => element == permission) || UserModelV3(
user.inheritedPermissions userPermissions: var userPermissions,
.any((element) => element.split(".").last == permission); inheritedPermissions: var inheritedPermissions,
}, ) =>
v2: (_) => true, userPermissions.any((p) => p == permission) ||
); inheritedPermissions.any((p) => p.split(".").last == permission)
};
} }
bool hasPermissions( bool hasPermissions(
List<PermissionAction> actions, List<PermissionTarget> targets) { List<PermissionAction> actions,
return map( List<PermissionTarget> targets,
v3: (user) { ) {
final permissions = [ final permissions = [
for (var action in actions) for (var action in actions)
for (var target in targets) [action, target].join("_") for (var target in targets) [action, target].join("_")
]; ];
return permissions.every((requestedPermission) => return switch (this) {
user.userPermissions.contains(requestedPermission) || UserModelV2() => true,
user.inheritedPermissions.any( UserModelV3(
(element) => element.split(".").last == requestedPermission)); userPermissions: var userPermissions,
}, inheritedPermissions: var inheritedPermissions,
v2: (_) => true, ) =>
); permissions.every((p) =>
userPermissions.contains(p) ||
inheritedPermissions.any((ip) => ip.split(".").last == p))
};
} }
bool get canViewDocuments => bool get canViewDocuments =>

View File

@@ -1,110 +1,79 @@
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'id_query_parameter.freezed.dart'; part 'id_query_parameter.freezed.dart';
part 'id_query_parameter.g.dart'; part 'id_query_parameter.g.dart';
@freezed sealed class IdQueryParameter {
class IdQueryParameter with _$IdQueryParameter { const IdQueryParameter();
const IdQueryParameter._(); Map<String, String> toQueryParameter(String field);
@HiveType(typeId: PaperlessApiHiveTypeIds.unsetIdQueryParameter) bool matches(int? id);
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<String, String> toQueryParameter(String field) { bool get isUnset => this is UnsetIdQueryParameter;
return when( bool get isSet => this is SetIdQueryParameter;
unset: () => {}, bool get isOnlyNotAssigned => this is NotAssignedIdQueryParameter;
notAssigned: () => { bool get isOnlyAssigned => this is AnyAssignedIdQueryParameter;
'${field}__isnull': '1',
},
anyAssigned: () => {
'${field}__isnull': '0',
},
fromId: (id) {
return {'${field}__id': '$id'};
},
);
}
bool isOnlyNotAssigned() => this is NotAssignedIdQueryParameter;
bool isOnlyAssigned() => this is AnyAssignedIdQueryParameter;
bool isSet() => this is SetIdQueryParameter;
bool isUnset() => this is UnsetIdQueryParameter;
bool matches(int? id) {
return when(
unset: () => true,
notAssigned: () => id == null,
anyAssigned: () => id != null,
fromId: (id_) => id == id_,
);
}
factory IdQueryParameter.fromJson(Map<String, dynamic> json) => _$IdQueryParameterFromJson(json);
} }
// @JsonSerializable() @HiveType(typeId: PaperlessApiHiveTypeIds.unsetIdQueryParameter)
// @HiveType(typeId: PaperlessApiHiveTypeIds.idQueryParameter) @Freezed(toJson: false, fromJson: false)
// class IdQueryParameter extends Equatable { class UnsetIdQueryParameter extends IdQueryParameter
// @HiveField(0) with _$UnsetIdQueryParameter {
// final int? assignmentStatus; const UnsetIdQueryParameter._();
// @HiveField(1) const factory UnsetIdQueryParameter() = _UnsetIdQueryParameter;
// final int? id; @override
Map<String, String> toQueryParameter(String field) => {};
// @Deprecated("Use named constructors, this is only meant for code generation") @override
// const IdQueryParameter(this.assignmentStatus, this.id); bool matches(int? id) => true;
}
// const IdQueryParameter.notAssigned() @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter)
// : assignmentStatus = 1, @Freezed(toJson: false, fromJson: false)
// id = null; class NotAssignedIdQueryParameter extends IdQueryParameter
with _$NotAssignedIdQueryParameter {
const NotAssignedIdQueryParameter._();
const factory NotAssignedIdQueryParameter() = _NotAssignedIdQueryParameter;
@override
Map<String, String> toQueryParameter(String field) {
return {'${field}__isnull': '1'};
}
// const IdQueryParameter.anyAssigned() @override
// : assignmentStatus = 0, bool matches(int? id) => id == null;
// id = null; }
// const IdQueryParameter.fromId(this.id) : assignmentStatus = null; @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter)
@Freezed(toJson: false, fromJson: false)
class AnyAssignedIdQueryParameter extends IdQueryParameter
with _$AnyAssignedIdQueryParameter {
const factory AnyAssignedIdQueryParameter() = _AnyAssignedIdQueryParameter;
const AnyAssignedIdQueryParameter._();
@override
Map<String, String> toQueryParameter(String field) {
return {'${field}__isnull': '0'};
}
// const IdQueryParameter.unset() : this.fromId(null); @override
bool matches(int? id) => id != null;
}
// bool get isUnset => id == null && assignmentStatus == null; @HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter)
@Freezed(toJson: false, fromJson: false)
// bool get isSet => id != null && assignmentStatus == null; class SetIdQueryParameter extends IdQueryParameter with _$SetIdQueryParameter {
const SetIdQueryParameter._();
// bool get onlyNotAssigned => assignmentStatus == 1; const factory SetIdQueryParameter({
@HiveField(0) required int id,
// bool get onlyAssigned => assignmentStatus == 0; }) = _SetIdQueryParameter;
@override
// Map<String, String> toQueryParameter(String field) { Map<String, String> toQueryParameter(String field) {
// final Map<String, String> params = {}; return {'${field}__id': '$id'};
// 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<Object?> get props => [assignmentStatus, id];
// Map<String, dynamic> toJson() => _$IdQueryParameterToJson(this);
// factory IdQueryParameter.fromJson(Map<String, dynamic> json) => _$IdQueryParameterFromJson(json);
// }
@override
bool matches(int? id) => id == this.id;
}

View File

@@ -4,54 +4,68 @@ import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'tags_query.freezed.dart'; part 'tags_query.freezed.dart';
part 'tags_query.g.dart'; part 'tags_query.g.dart';
@freezed sealed class TagsQuery {
class TagsQuery with _$TagsQuery { const TagsQuery();
const TagsQuery._(); Map<String, String> toQueryParameter();
@HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery) bool matches(Iterable<int> ids);
const factory TagsQuery.notAssigned() = NotAssignedTagsQuery; }
@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery)
const factory TagsQuery.anyAssigned({ @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery)
@Default([]) List<int> tagIds, @Freezed(toJson: false, fromJson: false)
}) = AnyAssignedTagsQuery; class NotAssignedTagsQuery extends TagsQuery with _$NotAssignedTagsQuery {
@HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery) const NotAssignedTagsQuery._();
const factory TagsQuery.ids({ const factory NotAssignedTagsQuery() = _NotAssignedTagsQuery;
@Default([]) List<int> include, @override
@Default([]) List<int> exclude, Map<String, String> toQueryParameter() {
}) = IdsTagsQuery; return {'is_tagged': '0'};
}
Map<String, String> toQueryParameter() {
return when( @override
anyAssigned: (tagIds) { bool matches(Iterable<int> ids) => ids.isEmpty;
if (tagIds.isEmpty) { }
return {'is_tagged': '1'};
} @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery)
return {'tags__id__in': tagIds.join(',')}; @Freezed(toJson: false, fromJson: false)
}, class AnyAssignedTagsQuery extends TagsQuery with _$AnyAssignedTagsQuery {
ids: (include, exclude) { const AnyAssignedTagsQuery._();
final Map<String, String> params = {}; const factory AnyAssignedTagsQuery({
if (include.isNotEmpty) { @HiveField(0) @Default([]) List<int> tagIds,
params.putIfAbsent('tags__id__all', () => include.join(',')); }) = _AnyAssignedTagsQuery;
} @override
if (exclude.isNotEmpty) { Map<String, String> toQueryParameter() {
params.putIfAbsent('tags__id__none', () => exclude.join(',')); if (tagIds.isEmpty) {
} return {'is_tagged': '1'};
return params; }
}, return {'tags__id__in': tagIds.join(',')};
notAssigned: () { }
return {'is_tagged': '0'};
}, @override
); bool matches(Iterable<int> ids) => ids.isNotEmpty;
} }
bool matches(Iterable<int> ids) { @HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery)
return when( @Freezed(toJson: false, fromJson: false)
anyAssigned: (_) => ids.isNotEmpty, class IdsTagsQuery extends TagsQuery with _$IdsTagsQuery {
ids: (include, exclude) => const IdsTagsQuery._();
include.toSet().difference(ids.toSet()).isEmpty && const factory IdsTagsQuery({
exclude.toSet().intersection(ids.toSet()).isEmpty, @HiveField(0) @Default([]) List<int> include,
notAssigned: () => ids.isEmpty, @HiveField(1) @Default([]) List<int> exclude,
); }) = _IdsTagsQuery;
} @override
Map<String, String> toQueryParameter() {
factory TagsQuery.fromJson(Map<String, dynamic> json) => _$TagsQueryFromJson(json); final Map<String, String> params = {};
if (include.isNotEmpty) {
params.putIfAbsent('tags__id__all', () => include.join(','));
}
if (exclude.isNotEmpty) {
params.putIfAbsent('tags__id__none', () => exclude.join(','));
}
return params;
}
@override
bool matches(Iterable<int> ids) {
return include.toSet().difference(ids.toSet()).isEmpty &&
exclude.toSet().intersection(ids.toSet()).isEmpty;
}
} }

View File

@@ -4,55 +4,93 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'user_model.freezed.dart';
part 'user_model.g.dart'; part 'user_model.g.dart';
@freezed sealed class UserModel {
class UserModel with _$UserModel { @HiveField(0)
const UserModel._(); final int id;
@HiveField(1)
final String username;
const UserModel({
required this.id,
required this.username,
});
@JsonSerializable(fieldRename: FieldRename.snake) String? get fullName;
@HiveType(typeId: PaperlessApiHiveTypeIds.userModelv3) }
const factory UserModel.v3({
@HiveField(0) required int id, @JsonSerializable(fieldRename: FieldRename.snake)
@HiveField(1) required String username, @HiveType(typeId: PaperlessApiHiveTypeIds.userModelv2)
@HiveField(2) String? email, class UserModelV2 extends UserModel {
@HiveField(3) String? firstName, @HiveField(2)
@HiveField(4) String? lastName, final String? displayName;
@HiveField(5) DateTime? dateJoined, const UserModelV2({
@HiveField(6) @Default(true) bool isStaff, required super.id,
@HiveField(7) @Default(true) bool isActive, required super.username,
@HiveField(8) @Default(true) bool isSuperuser, this.displayName,
@HiveField(9) @Default([]) List<int> groups, });
@HiveField(10) @Default([]) List<String> userPermissions, Map<String, dynamic> toJson() => _$UserModelV2ToJson(this);
@HiveField(11) @Default([]) List<String> inheritedPermissions, factory UserModelV2.fromJson(Map<String, dynamic> json) =>
}) = UserModelV3; _$UserModelV2FromJson(json);
@JsonSerializable(fieldRename: FieldRename.snake) @override
@HiveType(typeId: PaperlessApiHiveTypeIds.userModelv2) String? get fullName => displayName;
const factory UserModel.v2({ }
@HiveField(0) @JsonKey(name: "user_id") required int id,
@HiveField(1) required String username, @JsonSerializable(fieldRename: FieldRename.snake)
@HiveField(2) String? displayName, @HiveType(typeId: PaperlessApiHiveTypeIds.userModelv3)
}) = UserModelV2; class UserModelV3 extends UserModel {
@HiveField(2)
factory UserModel.fromJson(Map<String, dynamic> json) => final String? email;
_$UserModelFromJson(json); @HiveField(3)
final String? firstName;
String? get fullName => map( @HiveField(4)
v2: (value) => value.displayName, final String? lastName;
v3: (value) { @HiveField(5)
bool hasFirstName = value.firstName?.trim().isNotEmpty ?? false; final DateTime? dateJoined;
bool hasLastName = value.lastName?.trim().isNotEmpty ?? false; @HiveField(6)
if (hasFirstName && hasLastName) { final bool isStaff;
return "${value.firstName!} ${value.lastName!}"; @HiveField(7)
} else if (hasFirstName) { final bool isActive;
return value.firstName!; @HiveField(8)
} else if (hasLastName) { final bool isSuperuser;
return value.lastName; @HiveField(9)
} else { final List<int> groups;
return null; @HiveField(10)
} final List<String> userPermissions;
}, @HiveField(11)
); final List<String> 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<String, dynamic> toJson() => _$UserModelV3ToJson(this);
factory UserModelV3.fromJson(Map<String, dynamic> json) =>
_$UserModelV3FromJson(json);
} }

View File

@@ -162,7 +162,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
const DocumentFilter asnQueryFilter = DocumentFilter( const DocumentFilter asnQueryFilter = DocumentFilter(
sortField: SortField.archiveSerialNumber, sortField: SortField.archiveSerialNumber,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
asnQuery: IdQueryParameter.anyAssigned(), asnQuery: AnyAssignedIdQueryParameter(),
page: 1, page: 1,
pageSize: 1, pageSize: 1,
); );

View File

@@ -1,4 +1,3 @@
import 'package:paperless_api/src/models/models.dart'; import 'package:paperless_api/src/models/models.dart';
/// ///

View File

@@ -65,10 +65,10 @@ void main() {
}).toDocumentFilter(), }).toDocumentFilter(),
equals( equals(
DocumentFilter.initial.copyWith( DocumentFilter.initial.copyWith(
correspondent: const IdQueryParameter.fromId(42), correspondent: const SetIdQueryParameter(id: 42),
documentType: const IdQueryParameter.fromId(69), documentType: const SetIdQueryParameter(id: 69),
storagePath: const IdQueryParameter.fromId(14), storagePath: const SetIdQueryParameter(id: 14),
tags: const TagsQuery.ids( tags: const IdsTagsQuery(
include: [1, 2], include: [1, 2],
exclude: [3, 4], exclude: [3, 4],
), ),
@@ -131,10 +131,10 @@ void main() {
], ],
}).toDocumentFilter(); }).toDocumentFilter();
final expected = DocumentFilter.initial.copyWith( final expected = DocumentFilter.initial.copyWith(
correspondent: const IdQueryParameter.notAssigned(), correspondent: const NotAssignedIdQueryParameter(),
documentType: const IdQueryParameter.notAssigned(), documentType: const NotAssignedIdQueryParameter(),
storagePath: const IdQueryParameter.notAssigned(), storagePath: const NotAssignedIdQueryParameter(),
tags: const TagsQuery.notAssigned(), tags: const NotAssignedTagsQuery(),
); );
expect( expect(
actual, actual,
@@ -148,10 +148,10 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
DocumentFilter( DocumentFilter(
correspondent: const IdQueryParameter.fromId(1), correspondent: const SetIdQueryParameter(id: 1),
documentType: const IdQueryParameter.fromId(2), documentType: const SetIdQueryParameter(id: 2),
storagePath: const IdQueryParameter.fromId(3), storagePath: const SetIdQueryParameter(id: 3),
tags: const TagsQuery.ids( tags: const IdsTagsQuery(
include: [4, 5], include: [4, 5],
exclude: [6, 7, 8], exclude: [6, 7, 8],
), ),
@@ -202,9 +202,9 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
const DocumentFilter( const DocumentFilter(
correspondent: IdQueryParameter.unset(), correspondent: UnsetIdQueryParameter(),
documentType: IdQueryParameter.unset(), documentType: UnsetIdQueryParameter(),
storagePath: IdQueryParameter.unset(), storagePath: UnsetIdQueryParameter(),
tags: IdsTagsQuery(), tags: IdsTagsQuery(),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
@@ -233,10 +233,10 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
const DocumentFilter( const DocumentFilter(
correspondent: IdQueryParameter.notAssigned(), correspondent: NotAssignedIdQueryParameter(),
documentType: IdQueryParameter.notAssigned(), documentType: NotAssignedIdQueryParameter(),
storagePath: IdQueryParameter.notAssigned(), storagePath: NotAssignedIdQueryParameter(),
tags: TagsQuery.notAssigned(), tags: NotAssignedTagsQuery(),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.ascending, sortOrder: SortOrder.ascending,
), ),

View File

@@ -1459,6 +1459,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" 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: shelf:
dependency: transitive dependency: transitive
description: description:

View File

@@ -96,6 +96,7 @@ dependencies:
defer_pointer: ^0.0.2 defer_pointer: ^0.0.2
transparent_image: ^2.0.1 transparent_image: ^2.0.1
flutter_animate: ^4.2.0+1 flutter_animate: ^4.2.0+1
shared_preferences: ^2.2.1
dependency_overrides: dependency_overrides:
intl: ^0.18.1 intl: ^0.18.1