mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-15 02:12:25 -06:00
Add migration scripts, remove dependencies to codegen, and cleanups
This commit is contained in:
@@ -19,6 +19,14 @@ class HiveBoxes {
|
||||
static const localUserAccount = 'localUserAccount';
|
||||
static const localUserAppState = 'localUserAppState';
|
||||
static const hosts = 'hosts';
|
||||
|
||||
static List<String> get all => [
|
||||
globalSettings,
|
||||
localUserCredentials,
|
||||
localUserAccount,
|
||||
localUserAppState,
|
||||
hosts,
|
||||
];
|
||||
}
|
||||
|
||||
class HiveTypeIds {
|
||||
|
||||
@@ -200,19 +200,13 @@ class LabelRepository extends PersistentRepository<LabelRepositoryState> {
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await super.clear();
|
||||
emit(const LabelRepositoryState());
|
||||
}
|
||||
// @override
|
||||
// LabelRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
// return LabelRepositoryState.fromJson(json);
|
||||
// }
|
||||
|
||||
@override
|
||||
LabelRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
return LabelRepositoryState.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(LabelRepositoryState state) {
|
||||
return state.toJson();
|
||||
}
|
||||
// @override
|
||||
// Map<String, dynamic>? toJson(LabelRepositoryState state) {
|
||||
// return state.toJson();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
|
||||
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 = {};
|
||||
|
||||
PersistentRepository(T initialState) : super(initialState);
|
||||
|
||||
@@ -76,20 +76,20 @@ class SavedViewRepository
|
||||
return found;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await _initialized.future;
|
||||
await super.clear();
|
||||
emit(const SavedViewRepositoryState.initial());
|
||||
}
|
||||
// @override
|
||||
// Future<void> clear() async {
|
||||
// await _initialized.future;
|
||||
// await super.clear();
|
||||
// emit(const SavedViewRepositoryState.initial());
|
||||
// }
|
||||
|
||||
@override
|
||||
SavedViewRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
return SavedViewRepositoryState.fromJson(json);
|
||||
}
|
||||
// @override
|
||||
// SavedViewRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
// return SavedViewRepositoryState.fromJson(json);
|
||||
// }
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(SavedViewRepositoryState state) {
|
||||
return state.toJson();
|
||||
}
|
||||
// @override
|
||||
// Map<String, dynamic>? toJson(SavedViewRepositoryState state) {
|
||||
// return state.toJson();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
part of 'saved_view_repository.dart';
|
||||
|
||||
|
||||
|
||||
@freezed
|
||||
class SavedViewRepositoryState with _$SavedViewRepositoryState {
|
||||
const factory SavedViewRepositoryState.initial({
|
||||
|
||||
@@ -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<UserRepositoryState> {
|
||||
@@ -28,13 +27,13 @@ class UserRepository extends PersistentRepository<UserRepositoryState> {
|
||||
return user;
|
||||
}
|
||||
|
||||
@override
|
||||
UserRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
return UserRepositoryState.fromJson(json);
|
||||
}
|
||||
// @override
|
||||
// UserRepositoryState? fromJson(Map<String, dynamic> json) {
|
||||
// return UserRepositoryState.fromJson(json);
|
||||
// }
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(UserRepositoryState state) {
|
||||
return state.toJson();
|
||||
}
|
||||
// @override
|
||||
// Map<String, dynamic>? toJson(UserRepositoryState state) {
|
||||
// return state.toJson();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
part of 'user_repository.dart';
|
||||
|
||||
@freezed
|
||||
class UserRepositoryState with _$UserRepositoryState {
|
||||
const factory UserRepositoryState({
|
||||
@Default({}) Map<int, UserModel> users,
|
||||
}) = _UserRepositoryState;
|
||||
class UserRepositoryState with EquatableMixin {
|
||||
final Map<int, UserModel> users;
|
||||
const UserRepositoryState({
|
||||
this.users = const {},
|
||||
});
|
||||
|
||||
factory UserRepositoryState.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserRepositoryStateFromJson(json);
|
||||
UserRepositoryState copyWith({
|
||||
Map<int, UserModel>? users,
|
||||
}) {
|
||||
return UserRepositoryState(
|
||||
users: users ?? this.users,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [users];
|
||||
}
|
||||
|
||||
@@ -109,11 +109,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
.watch<DocumentEditCubit>()
|
||||
.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<DocumentEditPage> {
|
||||
_formKey.currentState
|
||||
?.fields[fkCorrespondent]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
SetIdQueryParameter(id: itemData),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -161,11 +162,11 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
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<DocumentEditPage> {
|
||||
onPressed: () => _formKey.currentState
|
||||
?.fields[fkDocumentType]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
SetIdQueryParameter(id: itemData),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -211,9 +212,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
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<DocumentEditPage> {
|
||||
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<DocumentEditPage> {
|
||||
?.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<DocumentEditPage> {
|
||||
Future<void> _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],
|
||||
);
|
||||
|
||||
|
||||
@@ -344,20 +344,29 @@ class _DocumentUploadPreparationPageState
|
||||
final cubit = context.read<DocumentUploadCubit>();
|
||||
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 <int>[],
|
||||
};
|
||||
|
||||
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(
|
||||
|
||||
@@ -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<DocumentsState>
|
||||
class DocumentsCubit extends Cubit<DocumentsState>
|
||||
with DocumentPagingBlocMixin {
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
@@ -135,13 +134,13 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||
await _userState.save();
|
||||
}
|
||||
|
||||
@override
|
||||
DocumentsState? fromJson(Map<String, dynamic> json) {
|
||||
return DocumentsState.fromJson(json);
|
||||
}
|
||||
// @override
|
||||
// DocumentsState? fromJson(Map<String, dynamic> json) {
|
||||
// return DocumentsState.fromJson(json);
|
||||
// }
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(DocumentsState state) {
|
||||
return state.toJson();
|
||||
}
|
||||
// @override
|
||||
// Map<String, dynamic>? toJson(DocumentsState state) {
|
||||
// return state.toJson();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
part of 'documents_cubit.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class DocumentsState extends DocumentPagingState {
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final List<DocumentModel> selection;
|
||||
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final Map<int, Correspondent> correspondents;
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final Map<int, Tag> tags;
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
final ViewType viewType;
|
||||
@@ -85,9 +78,4 @@ class DocumentsState extends DocumentPagingState {
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
|
||||
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
|
||||
_$DocumentsStateFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
|
||||
}
|
||||
|
||||
@@ -252,7 +252,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
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<DocumentsPage> {
|
||||
void _addTagToFilter(int tagId) {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
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<DocumentsPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
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<DocumentsPage> {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
|
||||
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<DocumentsPage> {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
|
||||
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<DocumentsPage> {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
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<InboxState>
|
||||
emitLoading: false,
|
||||
filter: DocumentFilter(
|
||||
sortField: SortField.added,
|
||||
tags: TagsQuery.ids(include: inboxTags.toList()),
|
||||
tags: IdsTagsQuery(include: inboxTags.toList()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,16 +116,16 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
LabelTabView<Correspondent>(
|
||||
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<LabelsPage>
|
||||
LabelTabView<DocumentType>(
|
||||
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<LabelsPage>
|
||||
LabelTabView<Tag>(
|
||||
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<LabelsPage>
|
||||
EditLabelRoute(label).push(context);
|
||||
},
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: IdQueryParameter.fromId(label.id!),
|
||||
storagePath: SetIdQueryParameter(id: label.id!),
|
||||
),
|
||||
canEdit: user.canEditStoragePaths,
|
||||
canAddNew: user.canCreateStoragePaths,
|
||||
|
||||
@@ -32,11 +32,10 @@ class FullscreenLabelForm<T extends Label> 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<T extends Label>
|
||||
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<T extends Label>
|
||||
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<T extends Label>
|
||||
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<T extends Label>
|
||||
option.id == initialValue.id) {
|
||||
continue;
|
||||
}
|
||||
yield IdQueryParameter.fromId(option.id!);
|
||||
yield SetIdQueryParameter(id: option.id!);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -216,77 +214,76 @@ class _FullscreenLabelFormState<T extends Label>
|
||||
.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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,13 +47,13 @@ class LabelFormField<T extends Label> 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<T extends Label> 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<T extends Label> 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<T extends Label> extends StatelessWidget {
|
||||
child: ActionChip(
|
||||
label: Text(suggestion.name),
|
||||
onPressed: () => field.didChange(
|
||||
IdQueryParameter.fromId(suggestion.id!),
|
||||
SetIdQueryParameter(id: suggestion.id!),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -44,7 +44,8 @@ class _LandingPageState extends State<LandingPage> {
|
||||
SliverToBoxAdapter(
|
||||
child: Text(
|
||||
S.of(context)!.welcomeUser(
|
||||
currentUser.fullName ?? currentUser.username),
|
||||
currentUser.fullName ?? currentUser.username,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
||||
@@ -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<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 {
|
||||
await Hive.initFlutter();
|
||||
|
||||
await performMigrations();
|
||||
registerHiveAdapters();
|
||||
|
||||
// await getApplicationDocumentsDirectory().then((value) => value.deleteSync(recursive: true));
|
||||
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
@@ -81,6 +115,7 @@ Future<void> _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<GlobalSettings>(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<GlobalSettings>(HiveBoxes.globalSettings);
|
||||
final globalSettings = globalSettingsBox.getValue()!;
|
||||
|
||||
await findSystemLocale();
|
||||
|
||||
final connectivityStatusService = ConnectivityStatusServiceImpl(
|
||||
Connectivity(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user