feat: Add hive type adapters to api models, migrate to freezed

This commit is contained in:
Anton Stubenbord
2023-04-24 01:14:20 +02:00
parent 5c0ef7f853
commit 1f335119b3
67 changed files with 2075 additions and 1079 deletions

View File

@@ -1,21 +1,23 @@
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/custom_adapters/theme_mode_adapter.dart'; import 'package:paperless_mobile/core/config/hive/custom_adapters/theme_mode_adapter.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart'; import 'package:paperless_mobile/features/login/model/authentication_information.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/login/model/user_credentials.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart'; import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
class HiveBoxes { class HiveBoxes {
HiveBoxes._(); HiveBoxes._();
static const globalSettings = 'globalSettings'; static const globalSettings = 'globalSettings';
static const userSettings = 'userSettings';
static const authentication = 'authentication'; static const authentication = 'authentication';
static const userCredentials = 'userCredentials'; static const userCredentials = 'userCredentials';
static const userAccount = 'userAccount'; static const userAccount = 'userAccount';
static const userAppState = 'userAppState';
static const userSettings = 'userSettings';
} }
class HiveTypeIds { class HiveTypeIds {
@@ -28,9 +30,11 @@ class HiveTypeIds {
static const clientCertificate = 5; static const clientCertificate = 5;
static const userCredentials = 6; static const userCredentials = 6;
static const userAccount = 7; static const userAccount = 7;
static const userAppState = 8;
} }
void registerHiveAdapters() { void registerHiveAdapters() {
registerPaperlessApiHiveTypeAdapters();
Hive.registerAdapter(ColorSchemeOptionAdapter()); Hive.registerAdapter(ColorSchemeOptionAdapter());
Hive.registerAdapter(ThemeModeAdapter()); Hive.registerAdapter(ThemeModeAdapter());
Hive.registerAdapter(GlobalSettingsAdapter()); Hive.registerAdapter(GlobalSettingsAdapter());
@@ -39,7 +43,7 @@ void registerHiveAdapters() {
Hive.registerAdapter(UserSettingsAdapter()); Hive.registerAdapter(UserSettingsAdapter());
Hive.registerAdapter(UserCredentialsAdapter()); Hive.registerAdapter(UserCredentialsAdapter());
Hive.registerAdapter(UserAccountAdapter()); Hive.registerAdapter(UserAccountAdapter());
Hive.registerAdapter(DocumentFilterAdapter()); Hive.registerAdapter(UserAppStateAdapter());
} }
extension HiveSingleValueBox<T> on Box<T> { extension HiveSingleValueBox<T> on Box<T> {

View File

@@ -1,6 +1,6 @@
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
part 'user_account.g.dart'; part 'user_account.g.dart';

View File

@@ -0,0 +1,22 @@
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
part 'user_app_state.g.dart';
@HiveType(typeId: HiveTypeIds.userAppState)
class UserAppState extends HiveObject {
@HiveField(0)
final String userId;
@HiveField(1)
DocumentFilter currentDocumentFilter;
@HiveField(2)
List<String> documentSearchHistory;
UserAppState({
required this.userId,
this.currentDocumentFilter = const DocumentFilter(),
this.documentSearchHistory = const [],
});
}

View File

@@ -9,11 +9,7 @@ class UserSettings with HiveObjectMixin {
@HiveField(0) @HiveField(0)
bool isBiometricAuthenticationEnabled; bool isBiometricAuthenticationEnabled;
@HiveField(1)
DocumentFilter currentDocumentFilter;
UserSettings({ UserSettings({
this.isBiometricAuthenticationEnabled = false, this.isBiometricAuthenticationEnabled = false,
required this.currentDocumentFilter,
}); });
} }

View File

@@ -9,7 +9,7 @@ import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
import 'package:paperless_mobile/core/model/document_processing_status.dart'; import 'package:paperless_mobile/core/model/document_processing_status.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart'; import 'package:paperless_mobile/features/login/model/authentication_information.dart';
import 'package:paperless_mobile/constants.dart'; import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/features/login/model/user_credentials.dart'; import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
abstract class StatusService { abstract class StatusService {

View File

@@ -8,8 +8,7 @@ import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bu
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
typedef LabelOptionsSelector<T extends Label> = Map<int, T> Function( typedef LabelOptionsSelector<T extends Label> = Map<int, T> Function(DocumentBulkActionState state);
DocumentBulkActionState state);
class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget { class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget {
final String title; final String title;
@@ -30,19 +29,16 @@ class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget {
}); });
@override @override
State<BulkEditLabelBottomSheet<T>> createState() => State<BulkEditLabelBottomSheet<T>> createState() => _BulkEditLabelBottomSheetState<T>();
_BulkEditLabelBottomSheetState<T>();
} }
class _BulkEditLabelBottomSheetState<T extends Label> class _BulkEditLabelBottomSheetState<T extends Label> extends State<BulkEditLabelBottomSheet<T>> {
extends State<BulkEditLabelBottomSheet<T>> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>( child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
builder: (context, state) { builder: (context, state) {
return Padding( return Padding(
@@ -59,8 +55,7 @@ class _BulkEditLabelBottomSheetState<T extends Label>
FormBuilder( FormBuilder(
key: _formKey, key: _formKey,
child: LabelFormField<T>( child: LabelFormField<T>(
initialValue: initialValue: IdQueryParameter.fromId(widget.initialValue),
IdQueryParameter.fromId(widget.initialValue),
name: "labelFormField", name: "labelFormField",
options: widget.availableOptionsSelector(state), options: widget.availableOptionsSelector(state),
labelText: widget.formFieldLabel, labelText: widget.formFieldLabel,
@@ -75,12 +70,11 @@ class _BulkEditLabelBottomSheetState<T extends Label>
const SizedBox(width: 16), const SizedBox(width: 16),
FilledButton( FilledButton(
onPressed: () { onPressed: () {
if (_formKey.currentState?.saveAndValidate() ?? if (_formKey.currentState?.saveAndValidate() ?? false) {
false) { final value = _formKey.currentState?.getRawValue('labelFormField')
final value = _formKey.currentState
?.getRawValue('labelFormField')
as IdQueryParameter?; as IdQueryParameter?;
widget.onSubmit(value?.id); widget
.onSubmit(value?.maybeWhen(fromId: (id) => id, orElse: () => null));
} }
}, },
child: Text(S.of(context)!.apply), child: Text(S.of(context)!.apply),

View File

@@ -5,7 +5,7 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart'; import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';

View File

@@ -50,8 +50,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_filteredSuggestions = widget.suggestions _filteredSuggestions =
?.documentDifference(context.read<DocumentEditCubit>().state.document); widget.suggestions?.documentDifference(context.read<DocumentEditCubit>().state.document);
} }
@override @override
@@ -95,16 +95,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
ListView( ListView(
children: [ children: [
_buildTitleFormField(state.document.title).padded(), _buildTitleFormField(state.document.title).padded(),
_buildCreatedAtFormField(state.document.created) _buildCreatedAtFormField(state.document.created).padded(),
.padded(),
// Correspondent form field // Correspondent form field
Column( Column(
children: [ children: [
LabelFormField<Correspondent>( LabelFormField<Correspondent>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialValue) => addLabelPageBuilder: (initialValue) => RepositoryProvider.value(
RepositoryProvider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
child: AddCorrespondentPage( child: AddCorrespondentPage(
initialName: initialValue, initialName: initialValue,
@@ -112,30 +110,20 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
), ),
addLabelText: S.of(context)!.addCorrespondent, addLabelText: S.of(context)!.addCorrespondent,
labelText: S.of(context)!.correspondent, labelText: S.of(context)!.correspondent,
options: context options: context.watch<DocumentEditCubit>().state.correspondents,
.watch<DocumentEditCubit>()
.state
.correspondents,
initialValue: IdQueryParameter.fromId( initialValue: IdQueryParameter.fromId(
state.document.correspondent, state.document.correspondent,
), ),
name: fkCorrespondent, name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined), prefixIcon: const Icon(Icons.person_outlined),
), ),
if (_filteredSuggestions if (_filteredSuggestions?.hasSuggestedCorrespondents ?? false)
?.hasSuggestedCorrespondents ??
false)
_buildSuggestionsSkeleton<int>( _buildSuggestionsSkeleton<int>(
suggestions: suggestions: _filteredSuggestions!.correspondents,
_filteredSuggestions!.correspondents, itemBuilder: (context, itemData) => ActionChip(
itemBuilder: (context, itemData) => label: Text(state.correspondents[itemData]!.name),
ActionChip(
label: Text(
state.correspondents[itemData]!.name),
onPressed: () { onPressed: () {
_formKey _formKey.currentState?.fields[fkCorrespondent]?.didChange(
.currentState?.fields[fkCorrespondent]
?.didChange(
IdQueryParameter.fromId(itemData), IdQueryParameter.fromId(itemData),
); );
}, },
@@ -149,8 +137,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
LabelFormField<DocumentType>( LabelFormField<DocumentType>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (currentInput) => addLabelPageBuilder: (currentInput) => RepositoryProvider.value(
RepositoryProvider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
child: AddDocumentTypePage( child: AddDocumentTypePage(
initialName: currentInput, initialName: currentInput,
@@ -158,26 +145,18 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
), ),
addLabelText: S.of(context)!.addDocumentType, addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType, labelText: S.of(context)!.documentType,
initialValue: IdQueryParameter.fromId( initialValue: IdQueryParameter.fromId(state.document.documentType),
state.document.documentType),
options: state.documentTypes, options: state.documentTypes,
name: _DocumentEditPageState.fkDocumentType, name: _DocumentEditPageState.fkDocumentType,
prefixIcon: prefixIcon: const Icon(Icons.description_outlined),
const Icon(Icons.description_outlined),
), ),
if (_filteredSuggestions if (_filteredSuggestions?.hasSuggestedDocumentTypes ?? false)
?.hasSuggestedDocumentTypes ??
false)
_buildSuggestionsSkeleton<int>( _buildSuggestionsSkeleton<int>(
suggestions: suggestions: _filteredSuggestions!.documentTypes,
_filteredSuggestions!.documentTypes, itemBuilder: (context, itemData) => ActionChip(
itemBuilder: (context, itemData) => label: Text(state.documentTypes[itemData]!.name),
ActionChip( onPressed: () =>
label: Text( _formKey.currentState?.fields[fkDocumentType]?.didChange(
state.documentTypes[itemData]!.name),
onPressed: () => _formKey
.currentState?.fields[fkDocumentType]
?.didChange(
IdQueryParameter.fromId(itemData), IdQueryParameter.fromId(itemData),
), ),
), ),
@@ -190,17 +169,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
LabelFormField<StoragePath>( LabelFormField<StoragePath>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialValue) => addLabelPageBuilder: (initialValue) => RepositoryProvider.value(
RepositoryProvider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
child: AddStoragePathPage( child: AddStoragePathPage(initalName: initialValue),
initalName: initialValue),
), ),
addLabelText: S.of(context)!.addStoragePath, addLabelText: S.of(context)!.addStoragePath,
labelText: S.of(context)!.storagePath, labelText: S.of(context)!.storagePath,
options: state.storagePaths, options: state.storagePaths,
initialValue: IdQueryParameter.fromId( initialValue: IdQueryParameter.fromId(state.document.storagePath),
state.document.storagePath),
name: fkStoragePath, name: fkStoragePath,
prefixIcon: const Icon(Icons.folder_outlined), prefixIcon: const Icon(Icons.folder_outlined),
), ),
@@ -213,8 +189,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
allowOnlySelection: true, allowOnlySelection: true,
allowCreation: true, allowCreation: true,
allowExclude: false, allowExclude: false,
initialValue: IdsTagsQuery.included( initialValue: TagsQuery.ids(
state.document.tags, include: state.document.tags,
), ),
).padded(), ).padded(),
if (_filteredSuggestions?.tags if (_filteredSuggestions?.tags
@@ -223,8 +199,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
.isNotEmpty ?? .isNotEmpty ??
false) false)
_buildSuggestionsSkeleton<int>( _buildSuggestionsSkeleton<int>(
suggestions: suggestions: (_filteredSuggestions?.tags.toSet() ?? {}),
(_filteredSuggestions?.tags.toSet() ?? {}),
itemBuilder: (context, itemData) { itemBuilder: (context, itemData) {
final tag = state.tags[itemData]!; final tag = state.tags[itemData]!;
return ActionChip( return ActionChip(
@@ -234,17 +209,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
), ),
backgroundColor: tag.color, backgroundColor: tag.color,
onPressed: () { onPressed: () {
final currentTags = _formKey.currentState final currentTags =
?.fields[fkTags]?.value as TagsQuery; _formKey.currentState?.fields[fkTags]?.value as TagsQuery;
if (currentTags is IdsTagsQuery) { _formKey.currentState?.fields[fkTags]?.didChange(
_formKey.currentState?.fields[fkTags] currentTags.maybeWhen(
?.didChange((IdsTagsQuery.fromIds( ids: (include, exclude) => TagsQuery.ids(
{...currentTags.ids, itemData}))); include: [...include, itemData], exclude: exclude),
} else { orElse: () => TagsQuery.ids(include: [itemData]),
_formKey.currentState?.fields[fkTags] ),
?.didChange((IdsTagsQuery.fromIds( );
{itemData})));
}
}, },
); );
}, },
@@ -284,11 +257,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
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).id, documentType: () => (values[fkDocumentType] as SetIdQueryParameter).id,
correspondent: () => (values[fkCorrespondent] as IdQueryParameter).id, correspondent: () => (values[fkCorrespondent] as SetIdQueryParameter).id,
storagePath: () => (values[fkStoragePath] as IdQueryParameter).id, storagePath: () => (values[fkStoragePath] as SetIdQueryParameter).id,
tags: (values[fkTags] as IdsTagsQuery).includedIds, tags: (values[fkTags] as IdsTagsQuery).include,
content: values[fkContent]); content: values[fkContent],
);
setState(() { setState(() {
_isSubmitLoading = true; _isSubmitLoading = true;
}); });
@@ -342,8 +316,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
suggestions: _filteredSuggestions!.dates, suggestions: _filteredSuggestions!.dates,
itemBuilder: (context, itemData) => ActionChip( itemBuilder: (context, itemData) => ActionChip(
label: Text(DateFormat.yMMMd().format(itemData)), label: Text(DateFormat.yMMMd().format(itemData)),
onPressed: () => _formKey.currentState?.fields[fkCreatedDate] onPressed: () => _formKey.currentState?.fields[fkCreatedDate]?.didChange(itemData),
?.didChange(itemData),
), ),
), ),
], ],
@@ -372,8 +345,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
itemBuilder: (context, index) => ColoredChipWrapper( itemBuilder: (context, index) => ColoredChipWrapper(
child: itemBuilder(context, suggestions.elementAt(index)), child: itemBuilder(context, suggestions.elementAt(index)),
), ),
separatorBuilder: (BuildContext context, int index) => separatorBuilder: (BuildContext context, int index) => const SizedBox(width: 4.0),
const SizedBox(width: 4.0),
), ),
), ),
], ],

View File

@@ -3,18 +3,17 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
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/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'document_search_state.dart'; part 'document_search_state.dart';
part 'document_search_cubit.g.dart'; part 'document_search_cubit.g.dart';
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with DocumentPagingBlocMixin {
with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;
@@ -58,8 +57,7 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
state.copyWith( state.copyWith(
searchHistory: [ searchHistory: [
query, query,
...state.searchHistory ...state.searchHistory.whereNot((previousQuery) => previousQuery == query)
.whereNot((previousQuery) => previousQuery == query)
], ],
), ),
); );
@@ -72,9 +70,7 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
void removeHistoryEntry(String entry) { void removeHistoryEntry(String entry) {
emit( emit(
state.copyWith( state.copyWith(
searchHistory: state.searchHistory searchHistory: state.searchHistory.whereNot((element) => element == entry).toList(),
.whereNot((element) => element == entry)
.toList(),
), ),
); );
} }
@@ -121,6 +117,5 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
} }
@override @override
// TODO: implement account Future<void> onFilterUpdated(DocumentFilter filter) async {}
UserAccount get account => throw UnimplementedError();
} }

View File

@@ -5,10 +5,9 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart'; import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s;
as s;
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart'; import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart'; import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart';
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart'; import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
@@ -46,14 +45,10 @@ class SliverSearchBar extends StatelessWidget {
icon: GlobalSettingsBuilder( icon: GlobalSettingsBuilder(
builder: (context, settings) { builder: (context, settings) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
Hive.box<UserAccount>(HiveBoxes.userAccount)
.listenable(),
builder: (context, box, _) { builder: (context, box, _) {
final account = box.get(settings.currentLoggedInUser!)!; final account = box.get(settings.currentLoggedInUser!)!;
return UserAvatar( return UserAvatar(userId: settings.currentLoggedInUser!, account: account);
userId: settings.currentLoggedInUser!,
account: account);
}, },
); );
}, },

View File

@@ -41,12 +41,10 @@ class DocumentUploadPreparationPage extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
State<DocumentUploadPreparationPage> createState() => State<DocumentUploadPreparationPage> createState() => _DocumentUploadPreparationPageState();
_DocumentUploadPreparationPageState();
} }
class _DocumentUploadPreparationPageState class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparationPage> {
extends State<DocumentUploadPreparationPage> {
static const fkFileName = "filename"; static const fkFileName = "filename";
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss"); static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
@@ -73,8 +71,7 @@ class _DocumentUploadPreparationPageState
title: Text(S.of(context)!.prepareDocument), title: Text(S.of(context)!.prepareDocument),
bottom: _isUploadLoading bottom: _isUploadLoading
? const PreferredSize( ? const PreferredSize(
child: LinearProgressIndicator(), child: LinearProgressIndicator(), preferredSize: Size.fromHeight(4.0))
preferredSize: Size.fromHeight(4.0))
: null, : null,
), ),
floatingActionButton: Visibility( floatingActionButton: Visibility(
@@ -95,8 +92,7 @@ class _DocumentUploadPreparationPageState
FormBuilderTextField( FormBuilderTextField(
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
name: DocumentModel.titleKey, name: DocumentModel.titleKey,
initialValue: initialValue: widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
validator: (value) { validator: (value) {
if (value?.trim().isEmpty ?? true) { if (value?.trim().isEmpty ?? true) {
return S.of(context)!.thisFieldIsRequired; return S.of(context)!.thisFieldIsRequired;
@@ -108,22 +104,18 @@ class _DocumentUploadPreparationPageState
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () { onPressed: () {
_formKey.currentState?.fields[DocumentModel.titleKey] _formKey.currentState?.fields[DocumentModel.titleKey]?.didChange("");
?.didChange("");
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
_formKey.currentState?.fields[fkFileName] _formKey.currentState?.fields[fkFileName]?.didChange("");
?.didChange("");
} }
}, },
), ),
errorText: _errors[DocumentModel.titleKey], errorText: _errors[DocumentModel.titleKey],
), ),
onChanged: (value) { onChanged: (value) {
final String transformedValue = final String transformedValue = _formatFilename(value ?? '');
_formatFilename(value ?? '');
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
_formKey.currentState?.fields[fkFileName] _formKey.currentState?.fields[fkFileName]?.didChange(transformedValue);
?.didChange(transformedValue);
} }
}, },
), ),
@@ -138,12 +130,10 @@ class _DocumentUploadPreparationPageState
suffixText: widget.fileExtension, suffixText: widget.fileExtension,
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () => _formKey.currentState?.fields[fkFileName] onPressed: () => _formKey.currentState?.fields[fkFileName]?.didChange(''),
?.didChange(''),
), ),
), ),
initialValue: widget.filename ?? initialValue: widget.filename ?? "scan_${fileNameDateFormat.format(_now)}",
"scan_${fileNameDateFormat.format(_now)}",
), ),
// Synchronize title and filename // Synchronize title and filename
SwitchListTile( SwitchListTile(
@@ -153,13 +143,10 @@ class _DocumentUploadPreparationPageState
() => _syncTitleAndFilename = value, () => _syncTitleAndFilename = value,
); );
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
final String transformedValue = _formatFilename(_formKey final String transformedValue = _formatFilename(
.currentState _formKey.currentState?.fields[DocumentModel.titleKey]?.value as String);
?.fields[DocumentModel.titleKey]
?.value as String);
if (_syncTitleAndFilename) { if (_syncTitleAndFilename) {
_formKey.currentState?.fields[fkFileName] _formKey.currentState?.fields[fkFileName]?.didChange(transformedValue);
?.didChange(transformedValue);
} }
} }
}, },
@@ -184,8 +171,7 @@ class _DocumentUploadPreparationPageState
? IconButton( ? IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () { onPressed: () {
_formKey.currentState! _formKey.currentState!.fields[DocumentModel.createdKey]
.fields[DocumentModel.createdKey]
?.didChange(null); ?.didChange(null);
}, },
) )
@@ -196,8 +182,7 @@ class _DocumentUploadPreparationPageState
LabelFormField<Correspondent>( LabelFormField<Correspondent>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => addLabelPageBuilder: (initialName) => RepositoryProvider.value(
RepositoryProvider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
child: AddCorrespondentPage(initialName: initialName), child: AddCorrespondentPage(initialName: initialName),
), ),
@@ -211,8 +196,7 @@ class _DocumentUploadPreparationPageState
LabelFormField<DocumentType>( LabelFormField<DocumentType>(
showAnyAssignedOption: false, showAnyAssignedOption: false,
showNotAssignedOption: false, showNotAssignedOption: false,
addLabelPageBuilder: (initialName) => addLabelPageBuilder: (initialName) => RepositoryProvider.value(
RepositoryProvider.value(
value: context.read<LabelRepository>(), value: context.read<LabelRepository>(),
child: AddDocumentTypePage(initialName: initialName), child: AddDocumentTypePage(initialName: initialName),
), ),
@@ -252,10 +236,9 @@ class _DocumentUploadPreparationPageState
final createdAt = fv[DocumentModel.createdKey] as DateTime?; final createdAt = fv[DocumentModel.createdKey] as DateTime?;
final title = fv[DocumentModel.titleKey] as String; final title = fv[DocumentModel.titleKey] as String;
final docType = fv[DocumentModel.documentTypeKey] as IdQueryParameter; final docType = fv[DocumentModel.documentTypeKey] as SetIdQueryParameter;
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery; final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
final correspondent = final correspondent = fv[DocumentModel.correspondentKey] as SetIdQueryParameter;
fv[DocumentModel.correspondentKey] as IdQueryParameter;
final taskId = await cubit.upload( final taskId = await cubit.upload(
widget.fileBytes, widget.fileBytes,
@@ -266,7 +249,7 @@ class _DocumentUploadPreparationPageState
title: title, title: title,
documentType: docType.id, documentType: docType.id,
correspondent: correspondent.id, correspondent: correspondent.id,
tags: tags.ids, tags: tags.include,
createdAt: createdAt, createdAt: createdAt,
); );
showSnackBar( showSnackBar(
@@ -283,8 +266,7 @@ class _DocumentUploadPreparationPageState
setState(() => _errors = errors); setState(() => _errors = errors);
} catch (unknownError, stackTrace) { } catch (unknownError, stackTrace) {
debugPrint(unknownError.toString()); debugPrint(unknownError.toString());
showErrorMessage( showErrorMessage(context, const PaperlessServerException.unknown(), stackTrace);
context, const PaperlessServerException.unknown(), stackTrace);
} finally { } finally {
setState(() { setState(() {
_isUploadLoading = false; _isUploadLoading = false;

View File

@@ -4,9 +4,10 @@ import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.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_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
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';
@@ -14,8 +15,7 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'documents_cubit.g.dart'; part 'documents_cubit.g.dart';
part 'documents_state.dart'; part 'documents_state.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState> class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBlocMixin {
with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;
@@ -24,24 +24,21 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
@override @override
final DocumentChangedNotifier notifier; final DocumentChangedNotifier notifier;
@override final UserAppState _userState;
final UserAccount account;
DocumentsCubit( DocumentsCubit(
this.api, this.api,
this.notifier, this.notifier,
this._labelRepository, this._labelRepository,
this.account, this._userState,
) : super(DocumentsState(filter: account.settings.currentDocumentFilter)) { ) : super(DocumentsState(filter: _userState.currentDocumentFilter)) {
notifier.addListener( notifier.addListener(
this, this,
onUpdated: (document) { onUpdated: (document) {
replace(document); replace(document);
emit( emit(
state.copyWith( state.copyWith(
selection: state.selection selection: state.selection.map((e) => e.id == document.id ? document : e).toList(),
.map((e) => e.id == document.id ? document : e)
.toList(),
), ),
); );
}, },
@@ -49,8 +46,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
remove(document); remove(document);
emit( emit(
state.copyWith( state.copyWith(
selection: selection: state.selection.where((e) => e.id != document.id).toList(),
state.selection.where((e) => e.id != document.id).toList(),
), ),
); );
}, },
@@ -84,9 +80,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
if (state.selectedIds.contains(model.id)) { if (state.selectedIds.contains(model.id)) {
emit( emit(
state.copyWith( state.copyWith(
selection: state.selection selection: state.selection.where((element) => element.id != model.id).toList(),
.where((element) => element.id != model.id)
.toList(),
), ),
); );
} else { } else {
@@ -129,4 +123,10 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
void setViewType(ViewType viewType) { void setViewType(ViewType viewType) {
emit(state.copyWith(viewType: viewType)); emit(state.copyWith(viewType: viewType));
} }
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {
_userState.currentDocumentFilter = filter;
await _userState.save();
}
} }

View File

@@ -1,4 +1,5 @@
import 'package:badges/badges.dart' as b; import 'package:badges/badges.dart' as b;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
@@ -41,12 +42,9 @@ class DocumentsPage extends StatefulWidget {
State<DocumentsPage> createState() => _DocumentsPageState(); State<DocumentsPage> createState() => _DocumentsPageState();
} }
class _DocumentsPageState extends State<DocumentsPage> class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProviderStateMixin {
with SingleTickerProviderStateMixin { final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle searchBarHandle = final SliverOverlapAbsorberHandle tabBarHandle = SliverOverlapAbsorberHandle();
SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle tabBarHandle =
SliverOverlapAbsorberHandle();
late final TabController _tabController; late final TabController _tabController;
int _currentTab = 0; int _currentTab = 0;
@@ -83,8 +81,7 @@ class _DocumentsPageState extends State<DocumentsPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<TaskStatusCubit, TaskStatusState>( return BlocListener<TaskStatusCubit, TaskStatusState>(
listenWhen: (previous, current) => listenWhen: (previous, current) => !previous.isSuccess && current.isSuccess,
!previous.isSuccess && current.isSuccess,
listener: (context, state) { listener: (context, state) {
showSnackBar( showSnackBar(
context, context,
@@ -101,8 +98,7 @@ class _DocumentsPageState extends State<DocumentsPage>
}, },
child: BlocConsumer<ConnectivityCubit, ConnectivityState>( child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
listenWhen: (previous, current) => listenWhen: (previous, current) =>
previous != ConnectivityState.connected && previous != ConnectivityState.connected && current == ConnectivityState.connected,
current == ConnectivityState.connected,
listener: (context, state) { listener: (context, state) {
try { try {
context.read<DocumentsCubit>().reload(); context.read<DocumentsCubit>().reload();
@@ -150,11 +146,7 @@ class _DocumentsPageState extends State<DocumentsPage>
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
body: WillPopScope( body: WillPopScope(
onWillPop: () async { onWillPop: () async {
if (context if (context.read<DocumentsCubit>().state.selection.isNotEmpty) {
.read<DocumentsCubit>()
.state
.selection
.isNotEmpty) {
context.read<DocumentsCubit>().resetSelection(); context.read<DocumentsCubit>().resetSelection();
} }
return false; return false;
@@ -189,8 +181,7 @@ class _DocumentsPageState extends State<DocumentsPage>
} }
return SliverPersistentHeader( return SliverPersistentHeader(
pinned: true, pinned: true,
delegate: delegate: CustomizableSliverPersistentHeaderDelegate(
CustomizableSliverPersistentHeaderDelegate(
minExtent: kTextTabBarHeight, minExtent: kTextTabBarHeight,
maxExtent: kTextTabBarHeight, maxExtent: kTextTabBarHeight,
child: ColoredTabBar( child: ColoredTabBar(
@@ -214,22 +205,15 @@ class _DocumentsPageState extends State<DocumentsPage>
if (metrics.maxScrollExtent == 0) { if (metrics.maxScrollExtent == 0) {
return true; return true;
} }
final desiredTab = final desiredTab = (metrics.pixels / metrics.maxScrollExtent).round();
(metrics.pixels / metrics.maxScrollExtent) if (metrics.axis == Axis.horizontal && _currentTab != desiredTab) {
.round();
if (metrics.axis == Axis.horizontal &&
_currentTab != desiredTab) {
setState(() => _currentTab = desiredTab); setState(() => _currentTab = desiredTab);
} }
return false; return false;
}, },
child: TabBarView( child: TabBarView(
controller: _tabController, controller: _tabController,
physics: context physics: context.watch<DocumentsCubit>().state.selection.isNotEmpty
.watch<DocumentsCubit>()
.state
.selection
.isNotEmpty
? const NeverScrollableScrollPhysics() ? const NeverScrollableScrollPhysics()
: null, : null,
children: [ children: [
@@ -297,19 +281,13 @@ class _DocumentsPageState extends State<DocumentsPage>
final currState = context.read<DocumentsCubit>().state; final currState = context.read<DocumentsCubit>().state;
final max = notification.metrics.maxScrollExtent; final max = notification.metrics.maxScrollExtent;
if (max == 0 || if (max == 0 || _currentTab != 0 || currState.isLoading || currState.isLastPageLoaded) {
_currentTab != 0 ||
currState.isLoading ||
currState.isLastPageLoaded) {
return false; return false;
} }
final offset = notification.metrics.pixels; final offset = notification.metrics.pixels;
if (offset >= max * 0.7) { if (offset >= max * 0.7) {
context context.read<DocumentsCubit>().loadMore().onError<PaperlessServerException>(
.read<DocumentsCubit>()
.loadMore()
.onError<PaperlessServerException>(
(error, stackTrace) => showErrorMessage( (error, stackTrace) => showErrorMessage(
context, context,
error, error,
@@ -344,8 +322,7 @@ class _DocumentsPageState extends State<DocumentsPage>
return SliverAdaptiveDocumentsView( return SliverAdaptiveDocumentsView(
viewType: state.viewType, viewType: state.viewType,
onTap: _openDetails, onTap: _openDetails,
onSelected: onSelected: context.read<DocumentsCubit>().toggleDocumentSelection,
context.read<DocumentsCubit>().toggleDocumentSelection,
hasInternetConnection: connectivityState.isConnected, hasInternetConnection: connectivityState.isConnected,
onTagSelected: _addTagToFilter, onTagSelected: _addTagToFilter,
onCorrespondentSelected: _addCorrespondentToFilter, onCorrespondentSelected: _addCorrespondentToFilter,
@@ -436,8 +413,7 @@ class _DocumentsPageState extends State<DocumentsPage>
snapSizes: const [0.9, 1], snapSizes: const [0.9, 1],
initialChildSize: .9, initialChildSize: .9,
maxChildSize: 1, maxChildSize: 1,
builder: (context, controller) => builder: (context, controller) => BlocBuilder<DocumentsCubit, DocumentsState>(
BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) { builder: (context, state) {
return DocumentFilterPanel( return DocumentFilterPanel(
initialFilter: context.read<DocumentsCubit>().state.filter, initialFilter: context.read<DocumentsCubit>().state.filter,
@@ -458,9 +434,7 @@ class _DocumentsPageState extends State<DocumentsPage>
if (filterIntent.shouldReset) { if (filterIntent.shouldReset) {
await context.read<DocumentsCubit>().resetFilter(); await context.read<DocumentsCubit>().resetFilter();
} else { } else {
await context await context.read<DocumentsCubit>().updateFilter(filter: filterIntent.filter!);
.read<DocumentsCubit>()
.updateFilter(filter: filterIntent.filter!);
} }
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
@@ -480,20 +454,21 @@ class _DocumentsPageState extends State<DocumentsPage>
void _addTagToFilter(int tagId) { void _addTagToFilter(int tagId) {
try { try {
final tagsQuery = final tagsQuery = context.read<DocumentsCubit>().state.filter.tags is IdsTagsQuery
context.read<DocumentsCubit>().state.filter.tags is IdsTagsQuery
? context.read<DocumentsCubit>().state.filter.tags as IdsTagsQuery ? context.read<DocumentsCubit>().state.filter.tags as IdsTagsQuery
: const IdsTagsQuery(); : const IdsTagsQuery();
if (tagsQuery.includedIds.contains(tagId)) { if (tagsQuery.include.contains(tagId)) {
context.read<DocumentsCubit>().updateCurrentFilter( context.read<DocumentsCubit>().updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
tags: tagsQuery.withIdsRemoved([tagId]), tags: tagsQuery.copyWith(
include: tagsQuery.include.whereNot((id) => id == tagId),
exclude: tagsQuery.exclude.whereNot((id) => id == tagId)),
), ),
); );
} else { } else {
context.read<DocumentsCubit>().updateCurrentFilter( context.read<DocumentsCubit>().updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
tags: tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tagId)]), tags: tagsQuery.copyWith(include: [...tagsQuery.include, tagId]),
), ),
); );
} }
@@ -505,17 +480,18 @@ class _DocumentsPageState extends State<DocumentsPage>
void _addCorrespondentToFilter(int? correspondentId) { void _addCorrespondentToFilter(int? correspondentId) {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
if (cubit.state.filter.correspondent.id == correspondentId) { final correspondent = cubit.state.filter.correspondent;
if (correspondent is SetIdQueryParameter) {
if (correspondent.id == correspondentId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) => filter.copyWith(correspondent: const IdQueryParameter.unset()),
filter.copyWith(correspondent: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(correspondent: IdQueryParameter.fromId(correspondentId)),
correspondent: IdQueryParameter.fromId(correspondentId)),
); );
} }
}
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }
@@ -524,17 +500,18 @@ class _DocumentsPageState extends State<DocumentsPage>
void _addDocumentTypeToFilter(int? documentTypeId) { void _addDocumentTypeToFilter(int? documentTypeId) {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
if (cubit.state.filter.documentType.id == documentTypeId) { final documentType = cubit.state.filter.documentType;
if (documentType is SetIdQueryParameter) {
if (documentType.id == documentTypeId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) => filter.copyWith(documentType: const IdQueryParameter.unset()),
filter.copyWith(documentType: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(documentType: IdQueryParameter.fromId(documentTypeId)),
documentType: IdQueryParameter.fromId(documentTypeId)),
); );
} }
}
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }
@@ -543,17 +520,18 @@ class _DocumentsPageState extends State<DocumentsPage>
void _addStoragePathToFilter(int? pathId) { void _addStoragePathToFilter(int? pathId) {
final cubit = context.read<DocumentsCubit>(); final cubit = context.read<DocumentsCubit>();
try { try {
if (cubit.state.filter.correspondent.id == pathId) { final path = cubit.state.filter.documentType;
if (path is SetIdQueryParameter) {
if (path.id == pathId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) => filter.copyWith(storagePath: const IdQueryParameter.unset()),
filter.copyWith(storagePath: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) => filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
); );
} }
}
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }

View File

@@ -11,6 +11,7 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
@@ -28,7 +29,7 @@ import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart'; import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart'; import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
@@ -245,7 +246,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
Hive.box<UserAccount>(HiveBoxes.userAccount).get(userId)!, Hive.box<UserAppState>(HiveBoxes.userAppState).get(userId)!,
)..reload(), )..reload(),
), ),
BlocProvider( BlocProvider(
@@ -280,8 +281,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
listeners: [ listeners: [
BlocListener<ConnectivityCubit, ConnectivityState>( BlocListener<ConnectivityCubit, ConnectivityState>(
//Only re-initialize data if the connectivity changed from not connected to connected //Only re-initialize data if the connectivity changed from not connected to connected
listenWhen: (previous, current) => listenWhen: (previous, current) => current == ConnectivityState.connected,
current == ConnectivityState.connected,
listener: (context, state) { listener: (context, state) {
_initializeData(context); _initializeData(context);
}, },
@@ -290,9 +290,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
listener: (context, state) { listener: (context, state) {
if (state.task != null) { if (state.task != null) {
// Handle local notifications on task change (only when app is running for now). // Handle local notifications on task change (only when app is running for now).
context context.read<LocalNotificationService>().notifyTaskChanged(state.task!);
.read<LocalNotificationService>()
.notifyTaskChanged(state.task!);
} }
}, },
), ),
@@ -305,9 +303,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
children: [ children: [
NavigationRail( NavigationRail(
labelType: NavigationRailLabelType.all, labelType: NavigationRailLabelType.all,
destinations: destinations destinations: destinations.map((e) => e.toNavigationRailDestination()).toList(),
.map((e) => e.toNavigationRailDestination())
.toList(),
selectedIndex: _currentIndex, selectedIndex: _currentIndex,
onDestinationSelected: _onNavigationChanged, onDestinationSelected: _onNavigationChanged,
), ),
@@ -325,8 +321,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
elevation: 4.0, elevation: 4.0,
selectedIndex: _currentIndex, selectedIndex: _currentIndex,
onDestinationSelected: _onNavigationChanged, onDestinationSelected: _onNavigationChanged,
destinations: destinations: destinations.map((e) => e.toNavigationDestination()).toList(),
destinations.map((e) => e.toNavigationDestination()).toList(),
), ),
body: routes[_currentIndex], body: routes[_currentIndex],
); );

View File

@@ -5,7 +5,7 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -32,9 +32,7 @@ class VerifyIdentityPage extends StatelessWidget {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(S Text(S.of(context)!.useTheConfiguredBiometricFactorToAuthenticate)
.of(context)!
.useTheConfiguredBiometricFactorToAuthenticate)
.paddedSymmetrically(horizontal: 16), .paddedSymmetrically(horizontal: 16),
const Icon( const Icon(
Icons.fingerprint, Icons.fingerprint,
@@ -56,9 +54,7 @@ class VerifyIdentityPage extends StatelessWidget {
), ),
), ),
ElevatedButton( ElevatedButton(
onPressed: () => context onPressed: () => context.read<AuthenticationCubit>().restoreSessionState(),
.read<AuthenticationCubit>()
.restoreSessionState(),
child: Text(S.of(context)!.verifyIdentity), child: Text(S.of(context)!.verifyIdentity),
), ),
], ],

View File

@@ -13,8 +13,7 @@ import 'package:paperless_mobile/features/paged_document_view/cubit/document_pag
part 'inbox_cubit.g.dart'; part 'inbox_cubit.g.dart';
part 'inbox_state.dart'; part 'inbox_state.dart';
class InboxCubit extends HydratedCubit<InboxState> class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin {
with DocumentPagingBlocMixin {
final LabelRepository _labelRepository; final LabelRepository _labelRepository;
final PaperlessDocumentsApi _documentsApi; final PaperlessDocumentsApi _documentsApi;
@@ -39,10 +38,7 @@ class InboxCubit extends HydratedCubit<InboxState>
this, this,
onDeleted: remove, onDeleted: remove,
onUpdated: (document) { onUpdated: (document) {
if (document.tags if (document.tags.toSet().intersection(state.inboxTags.toSet()).isEmpty) {
.toSet()
.intersection(state.inboxTags.toSet())
.isEmpty) {
remove(document); remove(document);
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1)); emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
} else { } else {
@@ -101,7 +97,7 @@ class InboxCubit extends HydratedCubit<InboxState>
updateFilter( updateFilter(
filter: DocumentFilter( filter: DocumentFilter(
sortField: SortField.added, sortField: SortField.added,
tags: IdsTagsQuery.fromIds(inboxTags), tags: TagsQuery.ids(include: inboxTags),
), ),
); );
} }
@@ -131,7 +127,7 @@ class InboxCubit extends HydratedCubit<InboxState>
updateFilter( updateFilter(
filter: DocumentFilter( filter: DocumentFilter(
sortField: SortField.added, sortField: SortField.added,
tags: IdsTagsQuery.fromIds(inboxTags), tags: TagsQuery.ids(include: inboxTags),
), ),
); );
} }
@@ -141,8 +137,7 @@ class InboxCubit extends HydratedCubit<InboxState>
/// from the inbox. /// from the inbox.
/// ///
Future<Iterable<int>> removeFromInbox(DocumentModel document) async { Future<Iterable<int>> removeFromInbox(DocumentModel document) async {
final tagsToRemove = final tagsToRemove = document.tags.toSet().intersection(state.inboxTags.toSet());
document.tags.toSet().intersection(state.inboxTags.toSet());
final updatedTags = {...document.tags}..removeAll(tagsToRemove); final updatedTags = {...document.tags}..removeAll(tagsToRemove);
final updatedDocument = await api.update( final updatedDocument = await api.update(
@@ -196,8 +191,8 @@ class InboxCubit extends HydratedCubit<InboxState>
Future<void> assignAsn(DocumentModel document) async { Future<void> assignAsn(DocumentModel document) async {
if (document.archiveSerialNumber == null) { if (document.archiveSerialNumber == null) {
final int asn = await _documentsApi.findNextAsn(); final int asn = await _documentsApi.findNextAsn();
final updatedDocument = await _documentsApi final updatedDocument =
.update(document.copyWith(archiveSerialNumber: () => asn)); await _documentsApi.update(document.copyWith(archiveSerialNumber: () => asn));
replace(updatedDocument); replace(updatedDocument);
} }
@@ -222,4 +217,7 @@ class InboxCubit extends HydratedCubit<InboxState>
_labelRepository.removeListener(this); _labelRepository.removeListener(this);
return super.close(); return super.close();
} }
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {}
} }

View File

@@ -44,12 +44,12 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
_options = widget.options.values.toList(); _options = widget.options.values.toList();
final value = widget.initialValue; final value = widget.initialValue;
if (value is IdsTagsQuery) { if (value is IdsTagsQuery) {
_include = value.includedIds.toList(); _include = value.include.toList();
_exclude = value.excludedIds.toList(); _exclude = value.include.toList();
} else if (value is AnyAssignedTagsQuery) { } else if (value is AnyAssignedTagsQuery) {
_include = value.tagIds.toList(); _include = value.tagIds.toList();
_anyAssigned = true; _anyAssigned = true;
} else if (value is OnlyNotAssignedTagsQuery) { } else if (value is NotAssignedTagsQuery) {
_notAssigned = true; _notAssigned = true;
} }
_textEditingController.addListener(() => setState(() { _textEditingController.addListener(() => setState(() {
@@ -113,28 +113,24 @@ 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: IdsTagsQuery.included(_include)); widget.onSubmit(returnValue: TagsQuery.ids(include: _include));
return; return;
} }
late final TagsQuery query; late final TagsQuery query;
if (_notAssigned) { if (_notAssigned) {
query = const OnlyNotAssignedTagsQuery(); query = const TagsQuery.notAssigned();
} else if (_anyAssigned) { } else if (_anyAssigned) {
query = AnyAssignedTagsQuery(tagIds: _include); query = TagsQuery.anyAssigned(tagIds: _include);
} else { } else {
query = IdsTagsQuery([ query = TagsQuery.ids(include: _include, exclude: _exclude);
for (var id in _include) IncludeTagIdQuery(id),
for (var id in _exclude) ExcludeTagIdQuery(id),
]);
} }
widget.onSubmit(returnValue: query); widget.onSubmit(returnValue: query);
}, },
), ),
], ],
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: !widget.allowOnlySelection preferredSize:
? const Size.fromHeight(32) !widget.allowOnlySelection ? const Size.fromHeight(32) : const Size.fromHeight(1),
: const Size.fromHeight(1),
child: Column( child: Column(
children: [ children: [
Divider(color: theme.colorScheme.outline), Divider(color: theme.colorScheme.outline),
@@ -237,8 +233,7 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
yield _buildNotAssignedOption(); yield _buildNotAssignedOption();
} }
var matches = _options var matches = _options.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
if (matches.isEmpty && widget.allowCreation) { if (matches.isEmpty && widget.allowCreation) {
yield Text(S.of(context)!.noItemsFound); yield Text(S.of(context)!.noItemsFound);
yield TextButton( yield TextButton(
@@ -304,9 +299,7 @@ class SelectableTagWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
title: Text(tag.name), title: Text(tag.name),
trailing: excluded trailing: excluded ? const Icon(Icons.close) : (selected ? const Icon(Icons.done) : null),
? const Icon(Icons.close)
: (selected ? const Icon(Icons.done) : null),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: tag.color, backgroundColor: tag.color,
child: (tag.isInboxTag) child: (tag.isInboxTag)

View File

@@ -1,6 +1,7 @@
import 'dart:developer'; import 'dart:developer';
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
@@ -32,8 +33,8 @@ class TagsFormField extends StatelessWidget {
initialValue: initialValue, initialValue: initialValue,
builder: (field) { builder: (field) {
final values = _generateOptions(context, field.value, field).toList(); final values = _generateOptions(context, field.value, field).toList();
final isEmpty = (field.value is IdsTagsQuery && final isEmpty =
(field.value as IdsTagsQuery).ids.isEmpty) || (field.value is IdsTagsQuery && (field.value as IdsTagsQuery).include.isEmpty) ||
field.value == null; field.value == null;
bool anyAssigned = field.value is AnyAssignedTagsQuery; bool anyAssigned = field.value is AnyAssignedTagsQuery;
return OpenContainer<TagsQuery>( return OpenContainer<TagsQuery>(
@@ -59,8 +60,7 @@ class TagsFormField extends StatelessWidget {
height: 32, height: 32,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
separatorBuilder: (context, index) => separatorBuilder: (context, index) => const SizedBox(width: 4),
const SizedBox(width: 4),
itemBuilder: (context, index) => values[index], itemBuilder: (context, index) => values[index],
itemCount: values.length, itemCount: values.length,
), ),
@@ -93,33 +93,56 @@ class TagsFormField extends StatelessWidget {
) sync* { ) sync* {
if (query == null) { if (query == null) {
yield Container(); yield Container();
} else if (query is IdsTagsQuery) { } else {
for (final e in query.queries) { final widgets = query.map(
yield _buildTagIdQueryWidget(context, e, field); ids: (value) => [
} for (var inc in value.include) _buildTagIdQueryWidget(context, inc, field, false),
} else if (query is OnlyNotAssignedTagsQuery) { for (var exc in value.exclude) _buildTagIdQueryWidget(context, exc, field, true),
yield _buildNotAssignedTagWidget(context, field); ],
} else if (query is AnyAssignedTagsQuery) { anyAssigned: (value) => [
for (final e in query.tagIds) { for (var id in value.tagIds) _buildAnyAssignedTagWidget(context, id, field, value),
yield _buildAnyAssignedTagWidget(context, e, field, query); ],
notAssigned: (value) => [_buildNotAssignedTagWidget(context, field)],
);
for (var child in widgets) {
yield child;
} }
} }
} }
Widget _buildTagIdQueryWidget( Widget _buildTagIdQueryWidget(
BuildContext context, BuildContext context,
TagIdQuery e, int id,
FormFieldState<TagsQuery?> field, FormFieldState<TagsQuery?> field,
bool exclude,
) { ) {
assert(field.value is IdsTagsQuery); assert(field.value is IdsTagsQuery);
final formValue = field.value as IdsTagsQuery; final formValue = field.value as IdsTagsQuery;
final tag = options[e.id]!; final tag = options[id]!;
return QueryTagChip( return QueryTagChip(
onDeleted: () => field.didChange(formValue.withIdsRemoved([e.id])), onDeleted: () => field.didChange(formValue.copyWith(
include: formValue.include.whereNot((element) => element == id),
exclude: formValue.exclude.whereNot((element) => element == id),
)),
onSelected: allowExclude onSelected: allowExclude
? () => field.didChange(formValue.withIdQueryToggled(e.id)) ? () {
if (formValue.include.contains(id)) {
field.didChange(
formValue.copyWith(
include: formValue.include.whereNot((element) => element == id),
exclude: [...formValue.exclude, id],
),
);
} else if (formValue.exclude.contains(id)) {}
field.didChange(
formValue.copyWith(
include: [...formValue.include, id],
exclude: formValue.exclude.whereNot((element) => element == id),
),
);
}
: null, : null,
exclude: e is ExcludeTagIdQuery, exclude: exclude,
backgroundColor: tag.color, backgroundColor: tag.color,
foregroundColor: tag.textColor, foregroundColor: tag.textColor,
labelText: tag.name, labelText: tag.name,
@@ -147,9 +170,11 @@ class TagsFormField extends StatelessWidget {
) { ) {
return QueryTagChip( return QueryTagChip(
onDeleted: () { onDeleted: () {
final updatedQuery = query.withRemoved([e]); final updatedQuery = query.copyWith(
tagIds: query.tagIds.whereNot((element) => element == e),
);
if (updatedQuery.tagIds.isEmpty) { if (updatedQuery.tagIds.isEmpty) {
field.didChange(const IdsTagsQuery()); field.didChange(const TagsQuery.ids());
} else { } else {
field.didChange(updatedQuery); field.didChange(updatedQuery);
} }

View File

@@ -27,12 +27,9 @@ class LabelsPage extends StatefulWidget {
State<LabelsPage> createState() => _LabelsPageState(); State<LabelsPage> createState() => _LabelsPageState();
} }
class _LabelsPageState extends State<LabelsPage> class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateMixin {
with SingleTickerProviderStateMixin { final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle searchBarHandle = final SliverOverlapAbsorberHandle tabBarHandle = SliverOverlapAbsorberHandle();
SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle tabBarHandle =
SliverOverlapAbsorberHandle();
late final TabController _tabController; late final TabController _tabController;
int _currentIndex = 0; int _currentIndex = 0;
@@ -82,33 +79,25 @@ class _LabelsPageState extends State<LabelsPage>
Tab( Tab(
icon: Icon( icon: Icon(
Icons.person_outline, Icons.person_outline,
color: Theme.of(context) color: Theme.of(context).colorScheme.onPrimaryContainer,
.colorScheme
.onPrimaryContainer,
), ),
), ),
Tab( Tab(
icon: Icon( icon: Icon(
Icons.description_outlined, Icons.description_outlined,
color: Theme.of(context) color: Theme.of(context).colorScheme.onPrimaryContainer,
.colorScheme
.onPrimaryContainer,
), ),
), ),
Tab( Tab(
icon: Icon( icon: Icon(
Icons.label_outline, Icons.label_outline,
color: Theme.of(context) color: Theme.of(context).colorScheme.onPrimaryContainer,
.colorScheme
.onPrimaryContainer,
), ),
), ),
Tab( Tab(
icon: Icon( icon: Icon(
Icons.folder_open, Icons.folder_open,
color: Theme.of(context) color: Theme.of(context).colorScheme.onPrimaryContainer,
.colorScheme
.onPrimaryContainer,
), ),
), ),
], ],
@@ -126,20 +115,17 @@ class _LabelsPageState extends State<LabelsPage>
return true; return true;
} }
final desiredTab = final desiredTab =
((metrics.pixels / metrics.maxScrollExtent) * ((metrics.pixels / metrics.maxScrollExtent) * (_tabController.length - 1))
(_tabController.length - 1))
.round(); .round();
if (metrics.axis == Axis.horizontal && if (metrics.axis == Axis.horizontal && _currentIndex != desiredTab) {
_currentIndex != desiredTab) {
setState(() => _currentIndex = desiredTab); setState(() => _currentIndex = desiredTab);
} }
return true; return true;
}, },
child: RefreshIndicator( child: RefreshIndicator(
edgeOffset: kTextTabBarHeight, edgeOffset: kTextTabBarHeight,
notificationPredicate: (notification) => notificationPredicate: (notification) => connectedState.isConnected,
connectedState.isConnected,
onRefresh: () => [ onRefresh: () => [
context.read<LabelCubit>().reloadCorrespondents, context.read<LabelCubit>().reloadCorrespondents,
context.read<LabelCubit>().reloadDocumentTypes, context.read<LabelCubit>().reloadDocumentTypes,
@@ -157,20 +143,14 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<Correspondent>( LabelTabView<Correspondent>(
labels: context labels: context.watch<LabelCubit>().state.correspondents,
.watch<LabelCubit>()
.state
.correspondents,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
correspondent: correspondent: IdQueryParameter.fromId(label.id),
IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
onEdit: _openEditCorrespondentPage, onEdit: _openEditCorrespondentPage,
emptyStateActionButtonLabel: emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent,
S.of(context)!.addNewCorrespondent, emptyStateDescription: S.of(context)!.noCorrespondentsSetUp,
emptyStateDescription:
S.of(context)!.noCorrespondentsSetUp,
onAddNew: _openAddCorrespondentPage, onAddNew: _openAddCorrespondentPage,
), ),
], ],
@@ -184,20 +164,14 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<DocumentType>( LabelTabView<DocumentType>(
labels: context labels: context.watch<LabelCubit>().state.documentTypes,
.watch<LabelCubit>()
.state
.documentTypes,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
documentType: documentType: IdQueryParameter.fromId(label.id),
IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
onEdit: _openEditDocumentTypePage, onEdit: _openEditDocumentTypePage,
emptyStateActionButtonLabel: emptyStateActionButtonLabel: S.of(context)!.addNewDocumentType,
S.of(context)!.addNewDocumentType, emptyStateDescription: S.of(context)!.noDocumentTypesSetUp,
emptyStateDescription:
S.of(context)!.noDocumentTypesSetUp,
onAddNew: _openAddDocumentTypePage, onAddNew: _openAddDocumentTypePage,
), ),
], ],
@@ -211,10 +185,9 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<Tag>( LabelTabView<Tag>(
labels: labels: context.watch<LabelCubit>().state.tags,
context.watch<LabelCubit>().state.tags,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
tags: IdsTagsQuery.fromIds([label.id!]), tags: TagsQuery.ids(include: [label.id!]),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
onEdit: _openEditTagPage, onEdit: _openEditTagPage,
@@ -227,10 +200,8 @@ class _LabelsPageState extends State<LabelsPage>
) )
: null, : null,
), ),
emptyStateActionButtonLabel: emptyStateActionButtonLabel: S.of(context)!.addNewTag,
S.of(context)!.addNewTag, emptyStateDescription: S.of(context)!.noTagsSetUp,
emptyStateDescription:
S.of(context)!.noTagsSetUp,
onAddNew: _openAddTagPage, onAddNew: _openAddTagPage,
), ),
], ],
@@ -244,21 +215,15 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<StoragePath>( LabelTabView<StoragePath>(
labels: context labels: context.watch<LabelCubit>().state.storagePaths,
.watch<LabelCubit>()
.state
.storagePaths,
onEdit: _openEditStoragePathPage, onEdit: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
storagePath: storagePath: IdQueryParameter.fromId(label.id),
IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
contentBuilder: (path) => Text(path.path), contentBuilder: (path) => Text(path.path),
emptyStateActionButtonLabel: emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath,
S.of(context)!.addNewStoragePath, emptyStateDescription: S.of(context)!.noStoragePathsSetUp,
emptyStateDescription:
S.of(context)!.noStoragePathsSetUp,
onAddNew: _openAddStoragePathPage, onAddNew: _openAddStoragePathPage,
), ),
], ],

View File

@@ -28,10 +28,10 @@ class FullscreenLabelForm<T extends Label> extends StatefulWidget {
this.addNewLabelText, this.addNewLabelText,
this.autofocus = true, this.autofocus = true,
}) : assert( }) : assert(
!(initialValue?.onlyAssigned ?? false) || showAnyAssignedOption, !(initialValue?.isOnlyAssigned() ?? false) || showAnyAssignedOption,
), ),
assert( assert(
!(initialValue?.onlyNotAssigned ?? false) || showNotAssignedOption, !(initialValue?.isOnlyNotAssigned() ?? false) || showNotAssignedOption,
), ),
assert((addNewLabelText != null) == (onCreateNewLabel != null)); assert((addNewLabelText != null) == (onCreateNewLabel != null));
@@ -39,8 +39,7 @@ class FullscreenLabelForm<T extends Label> extends StatefulWidget {
State<FullscreenLabelForm> createState() => _FullscreenLabelFormState(); State<FullscreenLabelForm> createState() => _FullscreenLabelFormState();
} }
class _FullscreenLabelFormState<T extends Label> class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelForm<T>> {
extends State<FullscreenLabelForm<T>> {
bool _showClearIcon = false; bool _showClearIcon = false;
final _textEditingController = TextEditingController(); final _textEditingController = TextEditingController();
final _focusNode = FocusNode(); final _focusNode = FocusNode();
@@ -80,7 +79,12 @@ class _FullscreenLabelFormState<T extends Label>
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
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(returnValue: IdQueryParameter.fromId(value?.id)); widget.onSubmit(
returnValue: IdQueryParameter.fromId(
value?.whenOrNull(
fromId: (id) => id,
),
));
}, },
autofocus: true, autofocus: true,
style: theme.textTheme.bodyLarge?.apply( style: theme.textTheme.bodyLarge?.apply(
@@ -124,11 +128,9 @@ class _FullscreenLabelFormState<T extends Label>
itemCount: options.length, itemCount: options.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final option = options.elementAt(index); final option = options.elementAt(index);
final highlight = final highlight = AutocompleteHighlightedOption.of(context) == index;
AutocompleteHighlightedOption.of(context) == index;
if (highlight) { if (highlight) {
SchedulerBinding.instance SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
.addPostFrameCallback((Duration timeStamp) {
Scrollable.ensureVisible( Scrollable.ensureVisible(
context, context,
alignment: 0, alignment: 0,
@@ -183,7 +185,8 @@ class _FullscreenLabelFormState<T extends Label>
} }
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
if (option.id == widget.initialValue?.id) { final initialValue = widget.initialValue;
if (initialValue is SetIdQueryParameter && option.id == initialValue.id) {
continue; continue;
} }
yield IdQueryParameter.fromId(option.id); yield IdQueryParameter.fromId(option.id);
@@ -191,8 +194,8 @@ class _FullscreenLabelFormState<T extends Label>
} }
} else { } else {
// Show filtered options, if no matching option is found, always show not assigned and any assigned (if enabled) and proceed. // Show filtered options, if no matching option is found, always show not assigned and any assigned (if enabled) and proceed.
final matches = widget.options.values final matches =
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery)); widget.options.values.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 IdQueryParameter.fromId(match.id);
@@ -218,33 +221,18 @@ class _FullscreenLabelFormState<T extends Label>
} }
String? _buildHintText() { String? _buildHintText() {
if (widget.initialValue?.isSet ?? false) { return widget.initialValue?.when(
return widget.options[widget.initialValue!.id]!.name; unset: () => S.of(context)!.startTyping,
} notAssigned: () => S.of(context)!.notAssigned,
if (widget.initialValue?.onlyNotAssigned ?? false) { anyAssigned: () => S.of(context)!.anyAssigned,
return S.of(context)!.notAssigned; fromId: (id) => widget.options[id]!.name,
} );
if (widget.initialValue?.onlyAssigned ?? false) {
return S.of(context)!.anyAssigned;
}
return S.of(context)!.startTyping;
} }
Widget _buildOptionWidget(IdQueryParameter option, bool highlight) { Widget _buildOptionWidget(IdQueryParameter option, bool highlight) {
void onTap() => widget.onSubmit(returnValue: option); void onTap() => widget.onSubmit(returnValue: option);
late final String title;
if (option.isSet) { if (option.isUnset()) {
title = widget.options[option.id]!.name;
}
if (option.onlyNotAssigned) {
title = S.of(context)!.notAssigned;
}
if (option.onlyAssigned) {
title = S.of(context)!.anyAssigned;
}
if (option.isUnset) {
return Center( return Center(
child: Column( child: Column(
children: [ children: [
@@ -258,6 +246,12 @@ class _FullscreenLabelFormState<T extends Label>
), ),
); );
} }
final title = option.whenOrNull(
notAssigned: () => S.of(context)!.notAssigned,
anyAssigned: () => S.of(context)!.anyAssigned,
fromId: (id) => widget.options[id]!.name,
)!; // Never null, since we already return on unset before
return ListTile( return ListTile(
selected: highlight, selected: highlight,
selectedTileColor: Theme.of(context).focusColor, selectedTileColor: Theme.of(context).focusColor,

View File

@@ -45,20 +45,19 @@ 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) {
if (value?.isSet ?? false) { return value?.when(
return options[value!.id]!.name; unset: () => '',
} else if (value?.onlyNotAssigned ?? false) { notAssigned: () => S.of(context)!.notAssigned,
return S.of(context)!.notAssigned; anyAssigned: () => S.of(context)!.anyAssigned,
} else if (value?.onlyAssigned ?? false) { fromId: (id) => options[id]!.name,
return S.of(context)!.anyAssigned; ) ??
} '';
return '';
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isEnabled = options.values.any((e) => (e.documentCount ?? 0) > 0) || final isEnabled =
addLabelPageBuilder != null; options.values.any((e) => (e.documentCount ?? 0) > 0) || addLabelPageBuilder != null;
return FormBuilderField<IdQueryParameter>( return FormBuilderField<IdQueryParameter>(
name: name, name: name,
initialValue: initialValue, initialValue: initialValue,
@@ -68,8 +67,9 @@ class LabelFormField<T extends Label> extends StatelessWidget {
final controller = TextEditingController( final controller = TextEditingController(
text: _buildText(context, field.value), text: _buildText(context, field.value),
); );
final displayedSuggestions = final displayedSuggestions = suggestions
suggestions.whereNot((e) => e.id == field.value?.id).toList(); .whereNot((e) => e.id == field.value?.maybeWhen(fromId: (id) => id, orElse: () => -1))
.toList();
return Column( return Column(
children: [ children: [
@@ -93,8 +93,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
suffixIcon: controller.text.isNotEmpty suffixIcon: controller.text.isNotEmpty
? IconButton( ? IconButton(
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () => onPressed: () => field.didChange(const IdQueryParameter.unset()),
field.didChange(const IdQueryParameter.unset()),
) )
: null, : null,
), ),
@@ -107,8 +106,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
? (initialName) { ? (initialName) {
return Navigator.of(context).push<T>( return Navigator.of(context).push<T>(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => addLabelPageBuilder!(initialName),
addLabelPageBuilder!(initialName),
), ),
); );
} }
@@ -139,8 +137,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: displayedSuggestions.length, itemCount: displayedSuggestions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final suggestion = final suggestion = displayedSuggestions.elementAt(index);
displayedSuggestions.elementAt(index);
return ColoredChipWrapper( return ColoredChipWrapper(
child: ActionChip( child: ActionChip(
label: Text(suggestion.name), label: Text(suggestion.name),

View File

@@ -5,8 +5,8 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart'; import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart'; import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart'; import 'package:paperless_mobile/helpers/format_helpers.dart';
class LabelItem<T extends Label> extends StatelessWidget { class LabelItem<T extends Label> extends StatelessWidget {
@@ -46,8 +46,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
onPressed: (label.documentCount ?? 0) == 0 onPressed: (label.documentCount ?? 0) == 0
? null ? null
: () { : () {
final currentUser = final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
.getValue()! .getValue()!
.currentLoggedInUser!; .currentLoggedInUser!;
final filter = filterBuilder(label); final filter = filterBuilder(label);
@@ -60,8 +59,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
Hive.box<UserAccount>(HiveBoxes.userAccount) Hive.box<UserAccount>(HiveBoxes.userAccount).get(currentUser)!,
.get(currentUser)!,
), ),
child: const LinkedDocumentsPage(), child: const LinkedDocumentsPage(),
), ),

View File

@@ -3,7 +3,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_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
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/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
@@ -73,4 +73,7 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
Map<String, dynamic>? toJson(LinkedDocumentsState state) { Map<String, dynamic>? toJson(LinkedDocumentsState state) {
return state.toJson(); return state.toJson();
} }
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {}
} }

View File

@@ -7,17 +7,18 @@ import 'package:hive_flutter/adapters.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/security/session_manager.dart'; import 'package:paperless_mobile/core/security/session_manager.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart'; import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/login/model/user_credentials.dart'; import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart'; import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
part 'authentication_state.dart'; part 'authentication_state.dart';
@@ -58,33 +59,33 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
clientCertificate: clientCertificate, clientCertificate: clientCertificate,
authToken: token, authToken: token,
); );
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
final userId = "${credentials.username}@$serverUrl"; final userId = "${credentials.username}@$serverUrl";
// If it is first time login, create settings for this user. if (userAccountBox.containsKey(userId)) {
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount); throw Exception("User with id $userId already exists!");
}
final fullName = await _fetchFullName(); final fullName = await _fetchFullName();
if (!userAccountBox.containsKey(userId)) { // Create user account
userAccountBox.put( await userAccountBox.put(
userId, userId,
UserAccount( UserAccount(
id: userId, id: userId,
settings: UserSettings( settings: UserSettings(),
currentDocumentFilter: DocumentFilter(),
),
serverUrl: serverUrl, serverUrl: serverUrl,
username: credentials.username!, username: credentials.username!,
fullName: fullName, fullName: fullName,
), ),
); );
}
// Mark logged in user as currently active user. // Create user state
final globalSettings = await userStateBox.put(
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!; userId,
globalSettings.currentLoggedInUser = userId; UserAppState(userId: userId),
globalSettings.save(); );
// Save credentials in encrypted box // Save credentials in encrypted box
final userCredentialsBox = await _getUserCredentialsBox(); final userCredentialsBox = await _getUserCredentialsBox();
@@ -96,21 +97,25 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
), ),
); );
userCredentialsBox.close(); userCredentialsBox.close();
// Mark logged in user as currently active user.
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
globalSettings.currentLoggedInUser = userId;
await globalSettings.save();
emit( emit(
AuthenticationState( AuthenticationState(
isAuthenticated: true, isAuthenticated: true,
username: credentials.username, username: credentials.username,
userId: userId, userId: userId,
fullName: fullName, fullName: fullName,
//TODO: Query ui settings with full name and add as parameter here...
), ),
); );
} }
/// Switches to another account if it exists. /// Switches to another account if it exists.
Future<void> switchAccount(String userId) async { Future<void> switchAccount(String userId) async {
final globalSettings = final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
if (globalSettings.currentLoggedInUser == userId) { if (globalSettings.currentLoggedInUser == userId) {
return; return;
} }
@@ -124,8 +129,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
final account = userAccountBox.get(userId)!; final account = userAccountBox.get(userId)!;
if (account.settings.isBiometricAuthenticationEnabled) { if (account.settings.isBiometricAuthenticationEnabled) {
final authenticated = await _localAuthService final authenticated =
.authenticateLocalUser("Authenticate to switch your account."); await _localAuthService.authenticateLocalUser("Authenticate to switch your account.");
if (!authenticated) { if (!authenticated) {
debugPrint("User not authenticated."); debugPrint("User not authenticated.");
return; return;
@@ -172,6 +177,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
final userId = "${credentials.username}@$serverUrl"; final userId = "${credentials.username}@$serverUrl";
final userAccountsBox = Hive.box<UserAccount>(HiveBoxes.userAccount); final userAccountsBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
if (userAccountsBox.containsKey(userId)) { if (userAccountsBox.containsKey(userId)) {
throw Exception("User already exists"); throw Exception("User already exists");
@@ -202,12 +208,18 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
username: credentials.username!, username: credentials.username!,
settings: UserSettings( settings: UserSettings(
isBiometricAuthenticationEnabled: enableBiometricAuthentication, isBiometricAuthenticationEnabled: enableBiometricAuthentication,
currentDocumentFilter: DocumentFilter(),
), ),
fullName: fullName, fullName: fullName,
), ),
); );
await userStateBox.put(
userId,
UserAppState(
userId: userId,
),
);
final userCredentialsBox = await _getUserCredentialsBox(); final userCredentialsBox = await _getUserCredentialsBox();
await userCredentialsBox.put( await userCredentialsBox.put(
userId, userId,
@@ -221,14 +233,16 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
} }
Future<void> removeAccount(String userId) async { Future<void> removeAccount(String userId) async {
final globalSettings = final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
final currentUser = globalSettings.currentLoggedInUser;
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount); final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userCredentialsBox = await _getUserCredentialsBox(); final userCredentialsBox = await _getUserCredentialsBox();
final userAppStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
final currentUser = globalSettings.currentLoggedInUser;
await userAccountBox.delete(userId); await userAccountBox.delete(userId);
await userAppStateBox.delete(userId);
await userCredentialsBox.delete(userId); await userCredentialsBox.delete(userId);
await userAccountBox.close();
if (currentUser == userId) { if (currentUser == userId) {
return logout(); return logout();
@@ -239,31 +253,31 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
/// Performs a conditional hydration based on the local authentication success. /// Performs a conditional hydration based on the local authentication success.
/// ///
Future<void> restoreSessionState() async { Future<void> restoreSessionState() async {
final globalSettings = final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
final userId = globalSettings.currentLoggedInUser; final userId = globalSettings.currentLoggedInUser;
if (userId == null) { if (userId == null) {
// If there is nothing to restore, we can quit here. // If there is nothing to restore, we can quit here.
return; return;
} }
final userAccount = final userAccount = Hive.box<UserAccount>(HiveBoxes.userAccount).get(userId)!;
Hive.box<UserAccount>(HiveBoxes.userAccount).get(userId)!;
if (userAccount.settings.isBiometricAuthenticationEnabled) { if (userAccount.settings.isBiometricAuthenticationEnabled) {
final localAuthSuccess = await _localAuthService final localAuthSuccess =
.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL await _localAuthService.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
if (!localAuthSuccess) { if (!localAuthSuccess) {
emit( emit(const AuthenticationState(showBiometricAuthenticationScreen: true));
const AuthenticationState(showBiometricAuthenticationScreen: true));
return; return;
} }
} }
final userCredentialsBox = await _getUserCredentialsBox(); final userCredentialsBox = await _getUserCredentialsBox();
final authentication = userCredentialsBox.get(globalSettings.currentLoggedInUser!);
final authentication = await userCredentialsBox.close();
userCredentialsBox.get(globalSettings.currentLoggedInUser!);
if (authentication != null) { if (authentication == null) {
throw Exception("User should be authenticated but no authentication information was found.");
}
_dioWrapper.updateSettings( _dioWrapper.updateSettings(
clientCertificate: authentication.clientCertificate, clientCertificate: authentication.clientCertificate,
authToken: authentication.token, authToken: authentication.token,
@@ -277,16 +291,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
username: userAccount.username, username: userAccount.username,
), ),
); );
} else {
throw Exception(
"User should be authenticated but no authentication information was found.");
}
} }
Future<void> logout() async { Future<void> logout() async {
await _resetExternalState(); await _resetExternalState();
final globalSettings = final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
globalSettings globalSettings
..currentLoggedInUser = null ..currentLoggedInUser = null
..save(); ..save();

View File

@@ -12,7 +12,7 @@ import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart'; import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart'; import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart'; import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'widgets/login_pages/server_login_page.dart'; import 'widgets/login_pages/server_login_page.dart';

View File

@@ -2,7 +2,6 @@ import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart';
import 'paged_documents_state.dart'; import 'paged_documents_state.dart';
@@ -10,11 +9,11 @@ import 'paged_documents_state.dart';
/// Mixin which can be used on cubits that handle documents. /// Mixin which can be used on cubits that handle documents.
/// This implements all paging and filtering logic. /// This implements all paging and filtering logic.
/// ///
mixin DocumentPagingBlocMixin<State extends DocumentPagingState> mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<State> {
on BlocBase<State> {
PaperlessDocumentsApi get api; PaperlessDocumentsApi get api;
DocumentChangedNotifier get notifier; DocumentChangedNotifier get notifier;
UserAccount get account;
Future<void> onFilterUpdated(DocumentFilter filter);
Future<void> loadMore() async { Future<void> loadMore() async {
if (state.isLastPageLoaded) { if (state.isLastPageLoaded) {
@@ -30,8 +29,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
value: [...state.value, result], value: [...state.value, result],
)); ));
} finally { } finally {
account.settings.currentDocumentFilter = newFilter; await onFilterUpdated(newFilter);
account.save();
emit(state.copyWithPaged(isLoading: false)); emit(state.copyWithPaged(isLoading: false));
} }
} }
@@ -52,8 +50,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
hasLoaded: true, hasLoaded: true,
)); ));
} finally { } finally {
account.settings.currentDocumentFilter = filter; await onFilterUpdated(filter);
account.save();
emit(state.copyWithPaged(isLoading: false)); emit(state.copyWithPaged(isLoading: false));
} }
} }
@@ -66,13 +63,11 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
) async => ) async =>
updateFilter(filter: transformFn(state.filter)); updateFilter(filter: transformFn(state.filter));
Future<void> resetFilter() { Future<void> resetFilter() async {
final filter = DocumentFilter.initial.copyWith( final filter = DocumentFilter.initial.copyWith(
sortField: state.filter.sortField, sortField: state.filter.sortField,
sortOrder: state.filter.sortOrder, sortOrder: state.filter.sortOrder,
); );
account.settings.currentDocumentFilter = filter;
account.save();
return updateFilter(filter: filter); return updateFilter(filter: filter);
} }
@@ -90,8 +85,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
)); ));
} }
} finally { } finally {
account.settings.currentDocumentFilter = filter; await onFilterUpdated(filter);
account.save();
if (!isClosed) { if (!isClosed) {
emit(state.copyWithPaged(isLoading: false)); emit(state.copyWithPaged(isLoading: false));
} }
@@ -132,8 +126,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
if (index != -1) { if (index != -1) {
final foundPage = state.value[index]; final foundPage = state.value[index];
final replacementPage = foundPage.copyWith( final replacementPage = foundPage.copyWith(
results: foundPage.results results: foundPage.results..removeWhere((element) => element.id == document.id),
..removeWhere((element) => element.id == document.id),
); );
final newCount = foundPage.count - 1; final newCount = foundPage.count - 1;
emit( emit(
@@ -141,8 +134,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
value: state.value value: state.value
.mapIndexed( .mapIndexed(
(currIndex, element) => (currIndex, element) =>
(currIndex == index ? replacementPage : element) (currIndex == index ? replacementPage : element).copyWith(count: newCount),
.copyWith(count: newCount),
) )
.toList(), .toList(),
), ),
@@ -165,14 +157,11 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
if (pageIndex != -1) { if (pageIndex != -1) {
final foundPage = state.value[pageIndex]; final foundPage = state.value[pageIndex];
final replacementPage = foundPage.copyWith( final replacementPage = foundPage.copyWith(
results: foundPage.results results: foundPage.results.map((doc) => doc.id == document.id ? document : doc).toList(),
.map((doc) => doc.id == document.id ? document : doc)
.toList(),
); );
final newState = state.copyWithPaged( final newState = state.copyWithPaged(
value: state.value value: state.value
.mapIndexed((currIndex, element) => .mapIndexed((currIndex, element) => currIndex == pageIndex ? replacementPage : element)
currIndex == pageIndex ? replacementPage : element)
.toList(), .toList(),
); );
emit(newState); emit(newState);

View File

@@ -69,4 +69,7 @@ class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
Map<String, dynamic>? toJson(SavedViewDetailsState state) { Map<String, dynamic>? toJson(SavedViewDetailsState state) {
return state.toJson(); return state.toJson();
} }
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {}
} }

View File

@@ -10,7 +10,7 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart'; import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';

View File

@@ -9,9 +9,9 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart'; import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/login/view/login_page.dart'; import 'package:paperless_mobile/features/login/view/login_page.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart'; import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart';
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart'; import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
@@ -26,13 +26,11 @@ class ManageAccountsPage extends StatelessWidget {
return GlobalSettingsBuilder( return GlobalSettingsBuilder(
builder: (context, globalSettings) { builder: (context, globalSettings) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
builder: (context, box, _) { builder: (context, box, _) {
final userIds = box.keys.toList().cast<String>(); final userIds = box.keys.toList().cast<String>();
final otherAccounts = userIds final otherAccounts = userIds
.whereNot( .whereNot((element) => element == globalSettings.currentLoggedInUser)
(element) => element == globalSettings.currentLoggedInUser)
.toList(); .toList();
return SimpleDialog( return SimpleDialog(
insetPadding: EdgeInsets.all(24), insetPadding: EdgeInsets.all(24),
@@ -51,11 +49,8 @@ class ManageAccountsPage extends StatelessWidget {
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
children: [ children: [
_buildAccountTile( _buildAccountTile(context, globalSettings.currentLoggedInUser!,
context, box.get(globalSettings.currentLoggedInUser!)!, globalSettings),
globalSettings.currentLoggedInUser!,
box.get(globalSettings.currentLoggedInUser!)!,
globalSettings),
// if (otherAccounts.isNotEmpty) Text("Other accounts"), // if (otherAccounts.isNotEmpty) Text("Other accounts"),
Column( Column(
children: [ children: [
@@ -188,8 +183,7 @@ class ManageAccountsPage extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LoginPage( builder: (context) => LoginPage(
titleString: "Add account", //TODO: INTL titleString: "Add account", //TODO: INTL
onSubmit: (context, username, password, serverUrl, onSubmit: (context, username, password, serverUrl, clientCertificate) async {
clientCertificate) async {
final userId = await context.read<AuthenticationCubit>().addAccount( final userId = await context.read<AuthenticationCubit>().addAccount(
credentials: LoginFormCredentials( credentials: LoginFormCredentials(
username: username, username: username,
@@ -202,8 +196,8 @@ class ManageAccountsPage extends StatelessWidget {
); );
final shoudSwitch = await showDialog( final shoudSwitch = await showDialog(
context: context, context: context,
builder: (context) => SwitchAccountDialog( builder: (context) =>
username: username, serverUrl: serverUrl), SwitchAccountDialog(username: username, serverUrl: serverUrl),
) ?? ) ??
false; false;
if (shoudSwitch) { if (shoudSwitch) {

View File

@@ -3,8 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart'; import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@@ -7,7 +7,7 @@ import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart'; import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart'; import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart'; import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart'; import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';

View File

@@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart'; import 'package:flutter/src/widgets/placeholder.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
class GlobalSettingsBuilder extends StatelessWidget { class GlobalSettingsBuilder extends StatelessWidget {
final Widget Function(BuildContext context, GlobalSettings settings) builder; final Widget Function(BuildContext context, GlobalSettings settings) builder;

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart'; import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
class UserAvatar extends StatelessWidget { class UserAvatar extends StatelessWidget {
final String userId; final String userId;

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart'; import 'package:paperless_mobile/core/database/tables/user_settings.dart';
class UserAccountBuilder extends StatelessWidget { class UserAccountBuilder extends StatelessWidget {
final Widget Function( final Widget Function(
@@ -19,12 +19,10 @@ class UserAccountBuilder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<Box<UserAccount>>( return ValueListenableBuilder<Box<UserAccount>>(
valueListenable: valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
builder: (context, accountBox, _) { builder: (context, accountBox, _) {
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings) final currentUser =
.getValue()! Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
.currentLoggedInUser;
if (currentUser != null) { if (currentUser != null) {
final account = accountBox.get(currentUser); final account = accountBox.get(currentUser);
return builder(context, account); return builder(context, account);

View File

@@ -7,8 +7,7 @@ import 'package:paperless_mobile/features/paged_document_view/cubit/paged_docume
part 'similar_documents_state.dart'; part 'similar_documents_state.dart';
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState> class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState> with DocumentPagingBlocMixin {
with DocumentPagingBlocMixin {
final int documentId; final int documentId;
@override @override
@@ -60,4 +59,7 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
_labelRepository.removeListener(this); _labelRepository.removeListener(this);
return super.close(); return super.close();
} }
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {}
} }

View File

@@ -20,6 +20,7 @@ import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart'; import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
@@ -35,12 +36,11 @@ import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart'; import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart'; import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/login/view/login_page.dart'; import 'package:paperless_mobile/features/login/view/login_page.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/model/user_settings.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart'; import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
@@ -54,8 +54,7 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart';
String get defaultPreferredLocaleSubtag { String get defaultPreferredLocaleSubtag {
String preferredLocale = Platform.localeName.split("_").first; String preferredLocale = Platform.localeName.split("_").first;
if (!S.supportedLocales if (!S.supportedLocales.any((locale) => locale.languageCode == preferredLocale)) {
.any((locale) => locale.languageCode == preferredLocale)) {
preferredLocale = 'en'; preferredLocale = 'en';
} }
return preferredLocale; return preferredLocale;
@@ -63,14 +62,13 @@ String get defaultPreferredLocaleSubtag {
Future<void> _initHive() async { Future<void> _initHive() async {
await Hive.initFlutter(); await Hive.initFlutter();
//TODO: REMOVE! // //TODO: REMOVE!
// await getApplicationDocumentsDirectory() // await getApplicationDocumentsDirectory().then((value) => value.delete(recursive: true));
// .then((value) => value.delete(recursive: true));
registerHiveAdapters(); registerHiveAdapters();
await Hive.openBox<UserAccount>(HiveBoxes.userAccount); await Hive.openBox<UserAccount>(HiveBoxes.userAccount);
final globalSettingsBox = await Hive.openBox<UserAppState>(HiveBoxes.userAppState);
await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings); final globalSettingsBox = await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
if (!globalSettingsBox.hasValue) { if (!globalSettingsBox.hasValue) {
await globalSettingsBox.setValue( await globalSettingsBox.setValue(
@@ -155,8 +153,7 @@ void main() async {
//Update language header in interceptor on language change. //Update language header in interceptor on language change.
globalSettingsBox.listenable().addListener(() { globalSettingsBox.listenable().addListener(() {
languageHeaderInterceptor.preferredLocaleSubtag = languageHeaderInterceptor.preferredLocaleSubtag = globalSettings.preferredLocaleSubtag;
globalSettings.preferredLocaleSubtag;
}); });
runApp( runApp(
@@ -180,8 +177,7 @@ void main() async {
Provider<ConnectivityStatusService>.value( Provider<ConnectivityStatusService>.value(
value: connectivityStatusService, value: connectivityStatusService,
), ),
Provider<LocalNotificationService>.value( Provider<LocalNotificationService>.value(value: localNotificationService),
value: localNotificationService),
Provider.value(value: DocumentChangedNotifier()), Provider.value(value: DocumentChangedNotifier()),
], ],
child: MultiRepositoryProvider( child: MultiRepositoryProvider(
@@ -211,8 +207,7 @@ class PaperlessMobileEntrypoint extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
State<PaperlessMobileEntrypoint> createState() => State<PaperlessMobileEntrypoint> createState() => _PaperlessMobileEntrypointState();
_PaperlessMobileEntrypointState();
} }
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> { class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
@@ -247,8 +242,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
], ],
routes: { routes: {
DocumentDetailsRoute.routeName: (context) => DocumentDetailsRoute.routeName: (context) => const DocumentDetailsRoute(),
const DocumentDetailsRoute(),
}, },
home: const AuthenticationWrapper(), home: const AuthenticationWrapper(),
); );
@@ -283,11 +277,9 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
} }
initializeDateFormatting(); initializeDateFormatting();
// For sharing files coming from outside the app while the app is still opened // For sharing files coming from outside the app while the app is still opened
ReceiveSharingIntent.getMediaStream() ReceiveSharingIntent.getMediaStream().listen(ShareIntentQueue.instance.addAll);
.listen(ShareIntentQueue.instance.addAll);
// For sharing files coming from outside the app while the app is closed // For sharing files coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialMedia() ReceiveSharingIntent.getInitialMedia().then(ShareIntentQueue.instance.addAll);
.then(ShareIntentQueue.instance.addAll);
} }
Future<void> _setOptimalDisplayMode() async { Future<void> _setOptimalDisplayMode() async {
@@ -299,8 +291,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
.toList() .toList()
..sort((a, b) => b.refreshRate.compareTo(a.refreshRate)); ..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
final DisplayMode mostOptimalMode = final DisplayMode mostOptimalMode = sameResolution.isNotEmpty ? sameResolution.first : active;
sameResolution.isNotEmpty ? sameResolution.first : active;
debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}'); debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
await FlutterDisplayMode.setPreferredMode(mostOptimalMode); await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
@@ -351,14 +342,12 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
) async { ) async {
try { try {
await context.read<AuthenticationCubit>().login( await context.read<AuthenticationCubit>().login(
credentials: credentials: LoginFormCredentials(username: username, password: password),
LoginFormCredentials(username: username, password: password),
serverUrl: serverUrl, serverUrl: serverUrl,
clientCertificate: clientCertificate, clientCertificate: clientCertificate,
); );
// Show onboarding after first login! // Show onboarding after first login!
final globalSettings = final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
if (globalSettings.showOnboarding) { if (globalSettings.showOnboarding) {
Navigator.push( Navigator.push(
context, context,

View File

@@ -1,4 +1,49 @@
import 'package:hive/hive.dart';
import 'package:paperless_api/paperless_api.dart';
class PaperlessApiHiveTypeIds { class PaperlessApiHiveTypeIds {
PaperlessApiHiveTypeIds._(); PaperlessApiHiveTypeIds._();
static const int documentFilter = 1000; static const int documentFilter = 100;
static const int idQueryParameter = 101;
static const int tagsQuery = 102;
static const int anyAssignedTagsQuery = 103;
static const int tagIdQuery = 104;
static const int includeTagIdQuery = 105;
static const int idsTagsQuery = 106;
static const int excludeTagIdQuery = 107;
static const int sortField = 108;
static const int sortOrder = 109;
static const int absoluteDateRangeQuery = 110;
static const int relativeDateRangeQuery = 111;
static const int dateRangeUnit = 112;
static const int unsetDateRangeQuery = 113;
static const int textQuery = 114;
static const int queryType = 115;
static const int unsetIdQueryParameter = 116;
static const int notAssignedIdQueryParameter = 117;
static const int anyAssignedIdQueryParameter = 118;
static const int setIdQueryParameter = 119;
static const int notAssignedTagsQuery = 120;
}
void registerPaperlessApiHiveTypeAdapters() {
Hive.registerAdapter(DocumentFilterAdapter());
// TagsQuery
Hive.registerAdapter(AnyAssignedTagsQueryAdapter());
Hive.registerAdapter(NotAssignedTagsQueryAdapter());
Hive.registerAdapter(IdsTagsQueryAdapter());
Hive.registerAdapter(SortFieldAdapter());
Hive.registerAdapter(SortOrderAdapter());
Hive.registerAdapter(AbsoluteDateRangeQueryAdapter());
Hive.registerAdapter(RelativeDateRangeQueryAdapter());
Hive.registerAdapter(DateRangeUnitAdapter());
Hive.registerAdapter(UnsetDateRangeQueryAdapter());
Hive.registerAdapter(TextQueryAdapter());
Hive.registerAdapter(QueryTypeAdapter());
// IdQueryParameter
Hive.registerAdapter(SetIdQueryParameterAdapter());
Hive.registerAdapter(UnsetIdQueryParameterAdapter());
Hive.registerAdapter(AnyAssignedIdQueryParameterAdapter());
Hive.registerAdapter(NotAssignedIdQueryParameterAdapter());
} }

View File

@@ -3,3 +3,4 @@ library paperless_api;
export 'src/models/models.dart'; export 'src/models/models.dart';
export 'src/modules/modules.dart'; export 'src/modules/modules.dart';
export 'src/converters/converters.dart'; export 'src/converters/converters.dart';
export 'config/hive/hive_type_ids.dart';

View File

@@ -1,34 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query/any_assigned_tags_query.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query/ids_tags_query.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query/only_not_assigned_tags_query.dart';
import '../models/query_parameters/tags_query/tags_query.dart';
class TagsQueryJsonConverter
extends JsonConverter<TagsQuery, Map<String, dynamic>> {
const TagsQueryJsonConverter();
@override
TagsQuery fromJson(Map<String, dynamic> json) {
final type = json['type'] as String;
final data = json['data'] as Map<String, dynamic>;
switch (type) {
case 'OnlyNotAssignedTagsQuery':
return const OnlyNotAssignedTagsQuery();
case 'AnyAssignedTagsQuery':
return AnyAssignedTagsQuery.fromJson(data);
case 'IdsTagsQuery':
return IdsTagsQuery.fromJson(data);
default:
throw Exception('Error parsing TagsQuery: Unknown type $type');
}
}
@override
Map<String, dynamic> toJson(TagsQuery object) {
return {
'type': object.runtimeType.toString(),
'data': object.toJson(),
};
}
}

View File

@@ -4,11 +4,10 @@ import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/converters/tags_query_json_converter.dart'; import 'package:paperless_api/src/models/query_parameters/tags_query/tags_query.dart';
part 'document_filter.g.dart'; part 'document_filter.g.dart';
@TagsQueryJsonConverter()
@DateRangeQueryJsonConverter() @DateRangeQueryJsonConverter()
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
@HiveType(typeId: PaperlessApiHiveTypeIds.documentFilter) @HiveType(typeId: PaperlessApiHiveTypeIds.documentFilter)
@@ -48,8 +47,6 @@ class DocumentFilter extends Equatable {
final DateRangeQuery modified; final DateRangeQuery modified;
@HiveField(12) @HiveField(12)
final TextQuery query; final TextQuery query;
/// Query documents similar to the document with this id.
@HiveField(13) @HiveField(13)
final int? moreLike; final int? moreLike;
@@ -58,7 +55,7 @@ class DocumentFilter extends Equatable {
this.correspondent = const IdQueryParameter.unset(), this.correspondent = const IdQueryParameter.unset(),
this.storagePath = const IdQueryParameter.unset(), this.storagePath = const IdQueryParameter.unset(),
this.asnQuery = const IdQueryParameter.unset(), this.asnQuery = const IdQueryParameter.unset(),
this.tags = const IdsTagsQuery(), this.tags = const TagsQuery.ids(),
this.sortField = SortField.created, this.sortField = SortField.created,
this.sortOrder = SortOrder.descending, this.sortOrder = SortOrder.descending,
this.page = 1, this.page = 1,
@@ -107,9 +104,7 @@ class DocumentFilter extends Equatable {
final queryParams = groupBy(params, (e) => e.key).map( final queryParams = groupBy(params, (e) => e.key).map(
(key, entries) => MapEntry( (key, entries) => MapEntry(
key, key,
entries.length == 1 entries.length == 1 ? entries.first.value : entries.map((e) => e.value).join(","),
? entries.first.value
: entries.map((e) => e.value).join(","),
), ),
); );
return queryParams; return queryParams;
@@ -150,8 +145,7 @@ class DocumentFilter extends Equatable {
modified: modified ?? this.modified, modified: modified ?? this.modified,
moreLike: moreLike != null ? moreLike.call() : this.moreLike, moreLike: moreLike != null ? moreLike.call() : this.moreLike,
); );
if (query?.queryType != QueryType.extended && if (query?.queryType != QueryType.extended && newFilter.forceExtendedQuery) {
newFilter.forceExtendedQuery) {
//Prevents infinite recursion //Prevents infinite recursion
return newFilter.copyWith( return newFilter.copyWith(
query: newFilter.query.copyWith(queryType: QueryType.extended), query: newFilter.query.copyWith(queryType: QueryType.extended),
@@ -207,8 +201,7 @@ class DocumentFilter extends Equatable {
query, query,
]; ];
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

@@ -3,12 +3,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/tags_query/tags_query.dart';
import 'query_parameters/tags_query/any_assigned_tags_query.dart';
import 'query_parameters/tags_query/exclude_tag_id_query.dart';
import 'query_parameters/tags_query/ids_tags_query.dart';
import 'query_parameters/tags_query/include_tag_id_query.dart';
import 'query_parameters/tags_query/only_not_assigned_tags_query.dart';
part 'filter_rule_model.g.dart'; part 'filter_rule_model.g.dart';
@@ -77,21 +72,29 @@ class FilterRule with EquatableMixin {
); );
case hasAnyTag: case hasAnyTag:
return filter.copyWith( return filter.copyWith(
tags: value == "true" tags: value == "true" ? const TagsQuery.anyAssigned() : const TagsQuery.notAssigned(),
? const AnyAssignedTagsQuery()
: const OnlyNotAssignedTagsQuery(),
); );
case includeTagsRule: case includeTagsRule:
assert(filter.tags is IdsTagsQuery); assert(filter.tags is IdsTagsQuery);
return filter.copyWith( return filter.copyWith(
tags: (filter.tags as IdsTagsQuery) tags: filter.tags.maybeWhen(
.withIdQueriesAdded([IncludeTagIdQuery(int.parse(value!))]), ids: (include, exclude) => TagsQuery.ids(
include: [...include, int.parse(value!)],
exclude: exclude,
),
orElse: () => 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 as IdsTagsQuery) tags: filter.tags.maybeWhen(
.withIdQueriesAdded([ExcludeTagIdQuery(int.parse(value!))]), ids: (include, exclude) => TagsQuery.ids(
include: include,
exclude: [...exclude, int.parse(value!)],
),
orElse: () => filter.tags,
),
); );
case createdBeforeRule: case createdBeforeRule:
if (filter.created is AbsoluteDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
@@ -101,8 +104,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: AbsoluteDateRangeQuery( created: AbsoluteDateRangeQuery(before: _dateTimeConverter.fromJson(value!)),
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case createdAfterRule: case createdAfterRule:
@@ -113,8 +115,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: AbsoluteDateRangeQuery( created: AbsoluteDateRangeQuery(after: _dateTimeConverter.fromJson(value!)),
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case addedBeforeRule: case addedBeforeRule:
@@ -125,8 +126,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery( added: AbsoluteDateRangeQuery(before: _dateTimeConverter.fromJson(value!)),
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case addedAfterRule: case addedAfterRule:
@@ -137,8 +137,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery( added: AbsoluteDateRangeQuery(after: _dateTimeConverter.fromJson(value!)),
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case modifiedBeforeRule: case modifiedBeforeRule:
@@ -149,8 +148,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
modified: AbsoluteDateRangeQuery( modified: AbsoluteDateRangeQuery(before: _dateTimeConverter.fromJson(value!)),
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case modifiedAfterRule: case modifiedAfterRule:
@@ -161,8 +159,7 @@ class FilterRule with EquatableMixin {
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery( added: AbsoluteDateRangeQuery(after: _dateTimeConverter.fromJson(value!)),
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case titleAndContentRule: case titleAndContentRule:
@@ -236,49 +233,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 = [];
if (filter.correspondent.onlyNotAssigned) { final corrRule = filter.correspondent.whenOrNull(
filterRules.add(FilterRule(correspondentRule, null)); notAssigned: () => FilterRule(correspondentRule, null),
fromId: (id) => FilterRule(correspondentRule, id.toString()),
);
if (corrRule != null) {
filterRules.add(corrRule);
} }
if (filter.correspondent.isSet) {
filterRules.add( final docTypeRule = filter.documentType.whenOrNull(
FilterRule(correspondentRule, filter.correspondent.id!.toString())); notAssigned: () => FilterRule(documentTypeRule, null),
fromId: (id) => FilterRule(documentTypeRule, id.toString()),
);
if (docTypeRule != null) {
filterRules.add(docTypeRule);
} }
if (filter.documentType.onlyNotAssigned) {
filterRules.add(FilterRule(documentTypeRule, null)); final sPathRule = filter.documentType.whenOrNull(
} notAssigned: () => FilterRule(storagePathRule, null),
if (filter.documentType.isSet) { fromId: (id) => FilterRule(storagePathRule, id.toString()),
filterRules.add( );
FilterRule(documentTypeRule, filter.documentType.id!.toString())); if (sPathRule != null) {
} filterRules.add(sPathRule);
if (filter.storagePath.onlyNotAssigned) {
filterRules.add(FilterRule(storagePathRule, null));
}
if (filter.storagePath.isSet) {
filterRules
.add(FilterRule(storagePathRule, filter.storagePath.id!.toString()));
}
if (filter.tags is OnlyNotAssignedTagsQuery) {
filterRules.add(FilterRule(hasAnyTag, false.toString()));
}
if (filter.tags is AnyAssignedTagsQuery) {
filterRules.add(FilterRule(hasAnyTag, true.toString()));
}
if (filter.tags is IdsTagsQuery) {
filterRules.addAll((filter.tags as IdsTagsQuery)
.includedIds
.map((id) => FilterRule(includeTagsRule, id.toString())));
filterRules.addAll((filter.tags as IdsTagsQuery)
.excludedIds
.map((id) => FilterRule(excludeTagsRule, id.toString())));
} }
final tagRules = filter.tags.when(
notAssigned: () => [FilterRule(hasAnyTag, 'false')],
anyAssigned: (_) => [FilterRule(hasAnyTag, 'true')],
ids: (include, exclude) => [
...include.map((id) => FilterRule(includeTagsRule, id.toString())),
...exclude.map((id) => FilterRule(excludeTagsRule, id.toString())),
],
);
filterRules.addAll(tagRules);
if (filter.query.queryText != null) { if (filter.query.queryText != null) {
switch (filter.query.queryType) { switch (filter.query.queryType) {
case QueryType.title: case QueryType.title:
filterRules.add(FilterRule(titleRule, filter.query.queryText!)); filterRules.add(FilterRule(titleRule, filter.query.queryText!));
break; break;
case QueryType.titleAndContent: case QueryType.titleAndContent:
filterRules filterRules.add(FilterRule(titleAndContentRule, filter.query.queryText!));
.add(FilterRule(titleAndContentRule, filter.query.queryText!));
break; break;
case QueryType.extended: case QueryType.extended:
filterRules.add(FilterRule(extendedRule, filter.query.queryText!)); filterRules.add(FilterRule(extendedRule, filter.query.queryText!));
@@ -304,8 +298,8 @@ class FilterRule with EquatableMixin {
} }
} else if (created is RelativeDateRangeQuery) { } else if (created is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule(extendedRule, FilterRule(
created.toQueryParameter(DateRangeQueryField.created).values.first), extendedRule, created.toQueryParameter(DateRangeQueryField.created).values.first),
); );
} }
@@ -324,8 +318,7 @@ class FilterRule with EquatableMixin {
} }
} else if (added is RelativeDateRangeQuery) { } else if (added is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule(extendedRule, FilterRule(extendedRule, added.toQueryParameter(DateRangeQueryField.added).values.first),
added.toQueryParameter(DateRangeQueryField.added).values.first),
); );
} }
@@ -339,25 +332,19 @@ class FilterRule with EquatableMixin {
} }
if (modified.before != null) { if (modified.before != null) {
filterRules.add( filterRules.add(
FilterRule( FilterRule(modifiedBeforeRule, apiDateFormat.format(modified.before!)),
modifiedBeforeRule, apiDateFormat.format(modified.before!)),
); );
} }
} else if (modified is RelativeDateRangeQuery) { } else if (modified is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule( FilterRule(
extendedRule, extendedRule, modified.toQueryParameter(DateRangeQueryField.modified).values.first),
modified
.toQueryParameter(DateRangeQueryField.modified)
.values
.first),
); );
} }
//Join values of all extended filter rules if exist //Join values of all extended filter rules if exist
if (filterRules.isNotEmpty && if (filterRules.isNotEmpty &&
filterRules.where((e) => e.ruleType == FilterRule.extendedRule).length > filterRules.where((e) => e.ruleType == FilterRule.extendedRule).length > 1) {
1) {
final mergedExtendedRule = filterRules final mergedExtendedRule = filterRules
.where((r) => r.ruleType == FilterRule.extendedRule) .where((r) => r.ruleType == FilterRule.extendedRule)
.map((e) => e.value) .map((e) => e.value)
@@ -381,6 +368,5 @@ class FilterRule with EquatableMixin {
Map<String, dynamic> toJson() => _$FilterRuleToJson(this); Map<String, dynamic> toJson() => _$FilterRuleToJson(this);
factory FilterRule.fromJson(Map<String, dynamic> json) => factory FilterRule.fromJson(Map<String, dynamic> json) => _$FilterRuleFromJson(json);
_$FilterRuleFromJson(json);
} }

View File

@@ -8,8 +8,8 @@ export 'query_parameters/id_query_parameter.dart';
export 'query_parameters/query_type.dart'; export 'query_parameters/query_type.dart';
export 'query_parameters/sort_field.dart'; export 'query_parameters/sort_field.dart';
export 'query_parameters/sort_order.dart'; export 'query_parameters/sort_order.dart';
export 'query_parameters/tags_query/tags_queries.dart';
export 'query_parameters/date_range_queries/date_range_queries.dart'; export 'query_parameters/date_range_queries/date_range_queries.dart';
export 'query_parameters/tags_query/tags_query.dart';
export 'query_parameters/text_query.dart'; export 'query_parameters/text_query.dart';
export 'bulk_edit_model.dart'; export 'bulk_edit_model.dart';
export 'document_filter.dart'; export 'document_filter.dart';

View File

@@ -1,4 +1,6 @@
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.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';
@@ -8,11 +10,14 @@ import 'date_range_query_field.dart';
part 'absolute_date_range_query.g.dart'; part 'absolute_date_range_query.g.dart';
@JsonSerializable() @JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.absoluteDateRangeQuery)
class AbsoluteDateRangeQuery extends DateRangeQuery { class AbsoluteDateRangeQuery extends DateRangeQuery {
@LocalDateTimeJsonConverter() @LocalDateTimeJsonConverter()
@HiveField(0)
final DateTime? after; final DateTime? after;
@LocalDateTimeJsonConverter() @LocalDateTimeJsonConverter()
@HiveField(1)
final DateTime? before; final DateTime? before;
const AbsoluteDateRangeQuery({this.after, this.before}); const AbsoluteDateRangeQuery({this.after, this.before});
@@ -47,8 +52,7 @@ class AbsoluteDateRangeQuery extends DateRangeQuery {
); );
} }
factory AbsoluteDateRangeQuery.fromJson(json) => factory AbsoluteDateRangeQuery.fromJson(json) => _$AbsoluteDateRangeQueryFromJson(json);
_$AbsoluteDateRangeQueryFromJson(json);
@override @override
Map<String, dynamic> toJson() => _$AbsoluteDateRangeQueryToJson(this); Map<String, dynamic> toJson() => _$AbsoluteDateRangeQueryToJson(this);

View File

@@ -4,6 +4,7 @@ import 'date_range_query_field.dart';
abstract class DateRangeQuery extends Equatable { abstract class DateRangeQuery extends Equatable {
const DateRangeQuery(); const DateRangeQuery();
Map<String, String> toQueryParameter(DateRangeQueryField field); Map<String, String> toQueryParameter(DateRangeQueryField field);
Map<String, dynamic> toJson(); Map<String, dynamic> toJson();

View File

@@ -1,6 +1,15 @@
import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'date_range_unit.g.dart';
@HiveType(typeId: PaperlessApiHiveTypeIds.dateRangeUnit)
enum DateRangeUnit { enum DateRangeUnit {
@HiveField(0)
day, day,
@HiveField(1)
week, week,
@HiveField(2)
month, month,
@HiveField(3)
year; year;
} }

View File

@@ -1,5 +1,7 @@
import 'package:hive/hive.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
import 'date_range_query.dart'; import 'date_range_query.dart';
import 'date_range_query_field.dart'; import 'date_range_query_field.dart';
@@ -7,8 +9,11 @@ import 'date_range_unit.dart';
part 'relative_date_range_query.g.dart'; part 'relative_date_range_query.g.dart';
@JsonSerializable() @JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.relativeDateRangeQuery)
class RelativeDateRangeQuery extends DateRangeQuery { class RelativeDateRangeQuery extends DateRangeQuery {
@HiveField(0)
final int offset; final int offset;
@HiveField(1)
final DateRangeUnit unit; final DateRangeUnit unit;
const RelativeDateRangeQuery([ const RelativeDateRangeQuery([

View File

@@ -1,7 +1,11 @@
import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.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 'date_range_query.dart'; import 'date_range_query.dart';
part 'unset_date_range_query.g.dart';
@HiveType(typeId: PaperlessApiHiveTypeIds.unsetDateRangeQuery)
class UnsetDateRangeQuery extends DateRangeQuery { class UnsetDateRangeQuery extends DateRangeQuery {
const UnsetDateRangeQuery(); const UnsetDateRangeQuery();
@override @override
@@ -11,9 +15,7 @@ class UnsetDateRangeQuery extends DateRangeQuery {
Map<String, String> toQueryParameter(DateRangeQueryField field) => const {}; Map<String, String> toQueryParameter(DateRangeQueryField field) => const {};
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => const {};
return {};
}
@override @override
bool matches(DateTime dt) => true; bool matches(DateTime dt) => true;

View File

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

View File

@@ -0,0 +1,686 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'id_query_parameter.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
IdQueryParameter _$IdQueryParameterFromJson(Map<String, dynamic> json) {
switch (json['runtimeType']) {
case 'unset':
return UnsetIdQueryParameter.fromJson(json);
case 'notAssigned':
return NotAssignedIdQueryParameter.fromJson(json);
case 'anyAssigned':
return AnyAssignedIdQueryParameter.fromJson(json);
case 'fromId':
return SetIdQueryParameter.fromJson(json);
default:
throw CheckedFromJsonException(json, 'runtimeType', 'IdQueryParameter',
'Invalid union type "${json['runtimeType']}"!');
}
}
/// @nodoc
mixin _$IdQueryParameter {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(UnsetIdQueryParameter value) unset,
required TResult Function(NotAssignedIdQueryParameter value) notAssigned,
required TResult Function(AnyAssignedIdQueryParameter value) anyAssigned,
required TResult Function(SetIdQueryParameter value) fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(UnsetIdQueryParameter value)? unset,
TResult? Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult? Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult? Function(SetIdQueryParameter value)? fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(UnsetIdQueryParameter value)? unset,
TResult Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult Function(SetIdQueryParameter value)? fromId,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IdQueryParameterCopyWith<$Res> {
factory $IdQueryParameterCopyWith(
IdQueryParameter value, $Res Function(IdQueryParameter) then) =
_$IdQueryParameterCopyWithImpl<$Res, IdQueryParameter>;
}
/// @nodoc
class _$IdQueryParameterCopyWithImpl<$Res, $Val extends IdQueryParameter>
implements $IdQueryParameterCopyWith<$Res> {
_$IdQueryParameterCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$UnsetIdQueryParameterCopyWith<$Res> {
factory _$$UnsetIdQueryParameterCopyWith(_$UnsetIdQueryParameter value,
$Res Function(_$UnsetIdQueryParameter) then) =
__$$UnsetIdQueryParameterCopyWithImpl<$Res>;
}
/// @nodoc
class __$$UnsetIdQueryParameterCopyWithImpl<$Res>
extends _$IdQueryParameterCopyWithImpl<$Res, _$UnsetIdQueryParameter>
implements _$$UnsetIdQueryParameterCopyWith<$Res> {
__$$UnsetIdQueryParameterCopyWithImpl(_$UnsetIdQueryParameter _value,
$Res Function(_$UnsetIdQueryParameter) _then)
: super(_value, _then);
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.unsetIdQueryParameter)
class _$UnsetIdQueryParameter extends UnsetIdQueryParameter {
const _$UnsetIdQueryParameter({final String? $type})
: $type = $type ?? 'unset',
super._();
factory _$UnsetIdQueryParameter.fromJson(Map<String, dynamic> json) =>
_$$UnsetIdQueryParameterFromJson(json);
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'IdQueryParameter.unset()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$UnsetIdQueryParameter);
}
@JsonKey(ignore: true)
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
}) {
return unset();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
}) {
return unset?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
required TResult orElse(),
}) {
if (unset != null) {
return unset();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(UnsetIdQueryParameter value) unset,
required TResult Function(NotAssignedIdQueryParameter value) notAssigned,
required TResult Function(AnyAssignedIdQueryParameter value) anyAssigned,
required TResult Function(SetIdQueryParameter value) fromId,
}) {
return unset(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(UnsetIdQueryParameter value)? unset,
TResult? Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult? Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult? Function(SetIdQueryParameter value)? fromId,
}) {
return unset?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(UnsetIdQueryParameter value)? unset,
TResult Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult Function(SetIdQueryParameter value)? fromId,
required TResult orElse(),
}) {
if (unset != null) {
return unset(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$UnsetIdQueryParameterToJson(
this,
);
}
}
abstract class UnsetIdQueryParameter extends IdQueryParameter {
const factory UnsetIdQueryParameter() = _$UnsetIdQueryParameter;
const UnsetIdQueryParameter._() : super._();
factory UnsetIdQueryParameter.fromJson(Map<String, dynamic> json) =
_$UnsetIdQueryParameter.fromJson;
}
/// @nodoc
abstract class _$$NotAssignedIdQueryParameterCopyWith<$Res> {
factory _$$NotAssignedIdQueryParameterCopyWith(
_$NotAssignedIdQueryParameter value,
$Res Function(_$NotAssignedIdQueryParameter) then) =
__$$NotAssignedIdQueryParameterCopyWithImpl<$Res>;
}
/// @nodoc
class __$$NotAssignedIdQueryParameterCopyWithImpl<$Res>
extends _$IdQueryParameterCopyWithImpl<$Res, _$NotAssignedIdQueryParameter>
implements _$$NotAssignedIdQueryParameterCopyWith<$Res> {
__$$NotAssignedIdQueryParameterCopyWithImpl(
_$NotAssignedIdQueryParameter _value,
$Res Function(_$NotAssignedIdQueryParameter) _then)
: super(_value, _then);
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter)
class _$NotAssignedIdQueryParameter extends NotAssignedIdQueryParameter {
const _$NotAssignedIdQueryParameter({final String? $type})
: $type = $type ?? 'notAssigned',
super._();
factory _$NotAssignedIdQueryParameter.fromJson(Map<String, dynamic> json) =>
_$$NotAssignedIdQueryParameterFromJson(json);
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'IdQueryParameter.notAssigned()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$NotAssignedIdQueryParameter);
}
@JsonKey(ignore: true)
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
}) {
return notAssigned();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
}) {
return notAssigned?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
required TResult orElse(),
}) {
if (notAssigned != null) {
return notAssigned();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(UnsetIdQueryParameter value) unset,
required TResult Function(NotAssignedIdQueryParameter value) notAssigned,
required TResult Function(AnyAssignedIdQueryParameter value) anyAssigned,
required TResult Function(SetIdQueryParameter value) fromId,
}) {
return notAssigned(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(UnsetIdQueryParameter value)? unset,
TResult? Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult? Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult? Function(SetIdQueryParameter value)? fromId,
}) {
return notAssigned?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(UnsetIdQueryParameter value)? unset,
TResult Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult Function(SetIdQueryParameter value)? fromId,
required TResult orElse(),
}) {
if (notAssigned != null) {
return notAssigned(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$NotAssignedIdQueryParameterToJson(
this,
);
}
}
abstract class NotAssignedIdQueryParameter extends IdQueryParameter {
const factory NotAssignedIdQueryParameter() = _$NotAssignedIdQueryParameter;
const NotAssignedIdQueryParameter._() : super._();
factory NotAssignedIdQueryParameter.fromJson(Map<String, dynamic> json) =
_$NotAssignedIdQueryParameter.fromJson;
}
/// @nodoc
abstract class _$$AnyAssignedIdQueryParameterCopyWith<$Res> {
factory _$$AnyAssignedIdQueryParameterCopyWith(
_$AnyAssignedIdQueryParameter value,
$Res Function(_$AnyAssignedIdQueryParameter) then) =
__$$AnyAssignedIdQueryParameterCopyWithImpl<$Res>;
}
/// @nodoc
class __$$AnyAssignedIdQueryParameterCopyWithImpl<$Res>
extends _$IdQueryParameterCopyWithImpl<$Res, _$AnyAssignedIdQueryParameter>
implements _$$AnyAssignedIdQueryParameterCopyWith<$Res> {
__$$AnyAssignedIdQueryParameterCopyWithImpl(
_$AnyAssignedIdQueryParameter _value,
$Res Function(_$AnyAssignedIdQueryParameter) _then)
: super(_value, _then);
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter)
class _$AnyAssignedIdQueryParameter extends AnyAssignedIdQueryParameter {
const _$AnyAssignedIdQueryParameter({final String? $type})
: $type = $type ?? 'anyAssigned',
super._();
factory _$AnyAssignedIdQueryParameter.fromJson(Map<String, dynamic> json) =>
_$$AnyAssignedIdQueryParameterFromJson(json);
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'IdQueryParameter.anyAssigned()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AnyAssignedIdQueryParameter);
}
@JsonKey(ignore: true)
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
}) {
return anyAssigned();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
}) {
return anyAssigned?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
required TResult orElse(),
}) {
if (anyAssigned != null) {
return anyAssigned();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(UnsetIdQueryParameter value) unset,
required TResult Function(NotAssignedIdQueryParameter value) notAssigned,
required TResult Function(AnyAssignedIdQueryParameter value) anyAssigned,
required TResult Function(SetIdQueryParameter value) fromId,
}) {
return anyAssigned(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(UnsetIdQueryParameter value)? unset,
TResult? Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult? Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult? Function(SetIdQueryParameter value)? fromId,
}) {
return anyAssigned?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(UnsetIdQueryParameter value)? unset,
TResult Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult Function(SetIdQueryParameter value)? fromId,
required TResult orElse(),
}) {
if (anyAssigned != null) {
return anyAssigned(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$AnyAssignedIdQueryParameterToJson(
this,
);
}
}
abstract class AnyAssignedIdQueryParameter extends IdQueryParameter {
const factory AnyAssignedIdQueryParameter() = _$AnyAssignedIdQueryParameter;
const AnyAssignedIdQueryParameter._() : super._();
factory AnyAssignedIdQueryParameter.fromJson(Map<String, dynamic> json) =
_$AnyAssignedIdQueryParameter.fromJson;
}
/// @nodoc
abstract class _$$SetIdQueryParameterCopyWith<$Res> {
factory _$$SetIdQueryParameterCopyWith(_$SetIdQueryParameter value,
$Res Function(_$SetIdQueryParameter) then) =
__$$SetIdQueryParameterCopyWithImpl<$Res>;
@useResult
$Res call({@HiveField(0) int? id});
}
/// @nodoc
class __$$SetIdQueryParameterCopyWithImpl<$Res>
extends _$IdQueryParameterCopyWithImpl<$Res, _$SetIdQueryParameter>
implements _$$SetIdQueryParameterCopyWith<$Res> {
__$$SetIdQueryParameterCopyWithImpl(
_$SetIdQueryParameter _value, $Res Function(_$SetIdQueryParameter) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
}) {
return _then(_$SetIdQueryParameter(
freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter)
class _$SetIdQueryParameter extends SetIdQueryParameter {
const _$SetIdQueryParameter(@HiveField(0) this.id, {final String? $type})
: $type = $type ?? 'fromId',
super._();
factory _$SetIdQueryParameter.fromJson(Map<String, dynamic> json) =>
_$$SetIdQueryParameterFromJson(json);
@override
@HiveField(0)
final int? id;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'IdQueryParameter.fromId(id: $id)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SetIdQueryParameter &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SetIdQueryParameterCopyWith<_$SetIdQueryParameter> get copyWith =>
__$$SetIdQueryParameterCopyWithImpl<_$SetIdQueryParameter>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
}) {
return fromId(id);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
}) {
return fromId?.call(id);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
required TResult orElse(),
}) {
if (fromId != null) {
return fromId(id);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(UnsetIdQueryParameter value) unset,
required TResult Function(NotAssignedIdQueryParameter value) notAssigned,
required TResult Function(AnyAssignedIdQueryParameter value) anyAssigned,
required TResult Function(SetIdQueryParameter value) fromId,
}) {
return fromId(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(UnsetIdQueryParameter value)? unset,
TResult? Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult? Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult? Function(SetIdQueryParameter value)? fromId,
}) {
return fromId?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(UnsetIdQueryParameter value)? unset,
TResult Function(NotAssignedIdQueryParameter value)? notAssigned,
TResult Function(AnyAssignedIdQueryParameter value)? anyAssigned,
TResult Function(SetIdQueryParameter value)? fromId,
required TResult orElse(),
}) {
if (fromId != null) {
return fromId(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$SetIdQueryParameterToJson(
this,
);
}
}
abstract class SetIdQueryParameter extends IdQueryParameter {
const factory SetIdQueryParameter(@HiveField(0) final int? id) =
_$SetIdQueryParameter;
const SetIdQueryParameter._() : super._();
factory SetIdQueryParameter.fromJson(Map<String, dynamic> json) =
_$SetIdQueryParameter.fromJson;
@HiveField(0)
int? get id;
@JsonKey(ignore: true)
_$$SetIdQueryParameterCopyWith<_$SetIdQueryParameter> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -1,11 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
@JsonEnum()
enum TagsQueryType {
notAssigned,
anyAssigned,
ids,
id,
include,
exclude;
}

View File

@@ -1,7 +1,17 @@
import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'query_type.g.dart';
@HiveType(typeId: PaperlessApiHiveTypeIds.queryType)
enum QueryType { enum QueryType {
@HiveField(0)
title('title__icontains'), title('title__icontains'),
@HiveField(1)
titleAndContent('title_content'), titleAndContent('title_content'),
@HiveField(2)
extended('query'), extended('query'),
@HiveField(3)
asn('asn'); asn('asn');
final String queryParam; final String queryParam;

View File

@@ -1,14 +1,27 @@
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'sort_field.g.dart';
@JsonEnum(valueField: 'queryString') @JsonEnum(valueField: 'queryString')
@HiveType(typeId: PaperlessApiHiveTypeIds.sortField)
enum SortField { enum SortField {
@HiveField(0)
archiveSerialNumber("archive_serial_number"), archiveSerialNumber("archive_serial_number"),
@HiveField(1)
correspondentName("correspondent__name"), correspondentName("correspondent__name"),
@HiveField(2)
title("title"), title("title"),
@HiveField(3)
documentType("document_type__name"), documentType("document_type__name"),
@HiveField(4)
created("created"), created("created"),
@HiveField(5)
added("added"), added("added"),
@HiveField(6)
modified("modified"), modified("modified"),
@HiveField(7)
score("score"); score("score");
final String queryString; final String queryString;

View File

@@ -1,8 +1,15 @@
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'sort_order.g.dart';
@JsonEnum() @JsonEnum()
@HiveType(typeId: PaperlessApiHiveTypeIds.sortOrder)
enum SortOrder { enum SortOrder {
@HiveField(0)
ascending(""), ascending(""),
@HiveField(1)
descending("-"); descending("-");
final String queryString; final String queryString;

View File

@@ -1,46 +0,0 @@
import 'package:collection/collection.dart';
import 'package:json_annotation/json_annotation.dart';
import 'tags_query.dart';
part 'any_assigned_tags_query.g.dart';
@JsonSerializable(explicitToJson: true)
class AnyAssignedTagsQuery extends TagsQuery {
final Iterable<int> tagIds;
const AnyAssignedTagsQuery({
this.tagIds = const [],
});
@override
Map<String, String> toQueryParameter() {
if (tagIds.isEmpty) {
return {'is_tagged': '1'};
}
return {'tags__id__in': tagIds.join(',')};
}
@override
List<Object?> get props => [tagIds];
AnyAssignedTagsQuery withRemoved(Iterable<int> ids) {
return AnyAssignedTagsQuery(
tagIds: tagIds.toSet().difference(ids.toSet()).toList(),
);
}
AnyAssignedTagsQuery withAdded(Iterable<int> ids) {
return AnyAssignedTagsQuery(tagIds: [...tagIds, ...ids]);
}
@override
Map<String, dynamic> toJson() => _$AnyAssignedTagsQueryToJson(this);
factory AnyAssignedTagsQuery.fromJson(Map<String, dynamic> json) =>
_$AnyAssignedTagsQueryFromJson(json);
@override
bool matches(Iterable<int> ids) {
return ids.isNotEmpty;
}
}

View File

@@ -1,15 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/tags_query/tag_id_query.dart';
import 'include_tag_id_query.dart';
class ExcludeTagIdQuery extends TagIdQuery {
const ExcludeTagIdQuery(super.id);
@override
String get methodName => 'exclude';
@override
TagIdQuery toggle() {
return IncludeTagIdQuery(id);
}
}

View File

@@ -1,94 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:collection/collection.dart';
import 'exclude_tag_id_query.dart';
import 'include_tag_id_query.dart';
import 'tag_id_query.dart';
import 'tags_query.dart';
class IdsTagsQuery extends TagsQuery {
final Iterable<TagIdQuery> _idQueries;
const IdsTagsQuery([this._idQueries = const []]);
IdsTagsQuery.included(Iterable<int> ids)
: _idQueries = ids.map((id) => IncludeTagIdQuery(id));
IdsTagsQuery.fromIds(Iterable<int> ids) : this.included(ids);
IdsTagsQuery.excluded(Iterable<int> ids)
: _idQueries = ids.map((id) => ExcludeTagIdQuery(id));
IdsTagsQuery withIdQueriesAdded(Iterable<TagIdQuery> idQueries) {
final intersection = idQueries
.map((idQ) => idQ.id)
.toSet()
.intersection(_idQueries.map((idQ) => idQ.id).toSet());
final res = IdsTagsQuery(
[...withIdsRemoved(intersection).queries, ...idQueries],
);
return res;
}
IdsTagsQuery withIdsRemoved(Iterable<int> ids) {
return IdsTagsQuery(
_idQueries.where((idQuery) => !ids.contains(idQuery.id)),
);
}
Iterable<TagIdQuery> get queries => _idQueries;
Iterable<int> get includedIds {
return _idQueries.whereType<IncludeTagIdQuery>().map((e) => e.id);
}
Iterable<int> get excludedIds {
return _idQueries.whereType<ExcludeTagIdQuery>().map((e) => e.id);
}
///
/// Returns a new instance with the type of the given [id] toggled.
/// E.g. if the provided [id] is currently registered as a [IncludeTagIdQuery],
/// then the new isntance will contain a [ExcludeTagIdQuery] with given id.
///
IdsTagsQuery withIdQueryToggled(int id) {
return IdsTagsQuery(
_idQueries.map((idQ) => idQ.id == id ? idQ.toggle() : idQ),
);
}
Iterable<int> get ids => [...includedIds, ...excludedIds];
@override
Map<String, String> toQueryParameter() {
final Map<String, String> params = {};
if (includedIds.isNotEmpty) {
params.putIfAbsent('tags__id__all', () => includedIds.join(','));
}
if (excludedIds.isNotEmpty) {
params.putIfAbsent('tags__id__none', () => excludedIds.join(','));
}
return params;
}
@override
List<Object?> get props => [_idQueries];
@override
Map<String, dynamic> toJson() {
return {
'queries': _idQueries.map((e) => e.toJson()).toList(),
};
}
factory IdsTagsQuery.fromJson(Map<String, dynamic> json) {
return IdsTagsQuery(
(json['queries'] as List).map((e) => TagIdQuery.fromJson(e)),
);
}
@override
bool matches(Iterable<int> ids) {
return includedIds.toSet().difference(ids.toSet()).isEmpty &&
excludedIds.toSet().intersection(ids.toSet()).isEmpty;
}
}

View File

@@ -1,15 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/tags_query/tag_id_query.dart';
import 'exclude_tag_id_query.dart';
class IncludeTagIdQuery extends TagIdQuery {
const IncludeTagIdQuery(super.id);
@override
String get methodName => 'include';
@override
TagIdQuery toggle() {
return ExcludeTagIdQuery(id);
}
}

View File

@@ -1,22 +0,0 @@
import 'tags_query.dart';
class OnlyNotAssignedTagsQuery extends TagsQuery {
const OnlyNotAssignedTagsQuery();
@override
Map<String, String> toQueryParameter() {
return {'is_tagged': '0'};
}
@override
List<Object?> get props => [];
@override
Map<String, dynamic> toJson() {
return {};
}
@override
bool matches(Iterable<int> ids) {
return ids.isEmpty;
}
}

View File

@@ -1,35 +0,0 @@
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
abstract class TagIdQuery extends Equatable {
final int id;
const TagIdQuery(this.id);
String get methodName;
@override
List<Object?> get props => [id, methodName];
TagIdQuery toggle();
Map<String, dynamic> toJson() {
return {
'type': methodName,
'id': id,
};
}
factory TagIdQuery.fromJson(Map<String, dynamic> json) {
final type = json['type'] as String;
var id = json['id'];
switch (type) {
case 'include':
return IncludeTagIdQuery(id);
case 'exclude':
return ExcludeTagIdQuery(id);
default:
throw Exception('Error parsing TagIdQuery: Unknown type $type');
}
}
}

View File

@@ -1,7 +0,0 @@
export 'any_assigned_tags_query.dart';
export 'ids_tags_query.dart';
export 'tags_query.dart';
export 'exclude_tag_id_query.dart';
export 'include_tag_id_query.dart';
export 'only_not_assigned_tags_query.dart';
export 'tag_id_query.dart';

View File

@@ -1,9 +1,64 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'tags_query.freezed.dart';
part 'tags_query.g.dart';
abstract class TagsQuery extends Equatable { @freezed
const TagsQuery(); class TagsQuery with _$TagsQuery {
Map<String, String> toQueryParameter(); const TagsQuery._();
Map<String, dynamic> toJson(); @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery)
const factory TagsQuery.notAssigned() = NotAssignedTagsQuery;
bool matches(Iterable<int> ids); @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery)
const factory TagsQuery.anyAssigned({
@Default([]) Iterable<int> tagIds,
}) = AnyAssignedTagsQuery;
@HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery)
const factory TagsQuery.ids({
@Default([]) Iterable<int> include,
@Default([]) Iterable<int> exclude,
}) = IdsTagsQuery;
Map<String, String> toQueryParameter() {
return when(
anyAssigned: (tagIds) {
if (tagIds.isEmpty) {
return {'is_tagged': '1'};
}
return {'tags__id__in': tagIds.join(',')};
},
ids: (include, exclude) {
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;
},
notAssigned: () {
return {'is_tagged': '0'};
},
);
}
Map<String, dynamic> toJson() {
return {};
}
bool matches(Iterable<int> ids) {
return when(
anyAssigned: (_) => ids.isNotEmpty,
ids: (include, exclude) =>
include.toSet().difference(ids.toSet()).isEmpty &&
exclude.toSet().intersection(ids.toSet()).isEmpty,
notAssigned: () => ids.isEmpty,
);
}
factory TagsQuery.fromJson(Map<String, dynamic> json) => _$TagsQueryFromJson(json);
} }

View File

@@ -0,0 +1,566 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'tags_query.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
TagsQuery _$TagsQueryFromJson(Map<String, dynamic> json) {
switch (json['runtimeType']) {
case 'notAssigned':
return NotAssignedTagsQuery.fromJson(json);
case 'anyAssigned':
return AnyAssignedTagsQuery.fromJson(json);
case 'ids':
return IdsTagsQuery.fromJson(json);
default:
throw CheckedFromJsonException(json, 'runtimeType', 'TagsQuery',
'Invalid union type "${json['runtimeType']}"!');
}
}
/// @nodoc
mixin _$TagsQuery {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() notAssigned,
required TResult Function(Iterable<int> tagIds) anyAssigned,
required TResult Function(Iterable<int> include, Iterable<int> exclude) ids,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? notAssigned,
TResult? Function(Iterable<int> tagIds)? anyAssigned,
TResult? Function(Iterable<int> include, Iterable<int> exclude)? ids,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? notAssigned,
TResult Function(Iterable<int> tagIds)? anyAssigned,
TResult Function(Iterable<int> include, Iterable<int> exclude)? ids,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NotAssignedTagsQuery value) notAssigned,
required TResult Function(AnyAssignedTagsQuery value) anyAssigned,
required TResult Function(IdsTagsQuery value) ids,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(NotAssignedTagsQuery value)? notAssigned,
TResult? Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult? Function(IdsTagsQuery value)? ids,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NotAssignedTagsQuery value)? notAssigned,
TResult Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult Function(IdsTagsQuery value)? ids,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TagsQueryCopyWith<$Res> {
factory $TagsQueryCopyWith(TagsQuery value, $Res Function(TagsQuery) then) =
_$TagsQueryCopyWithImpl<$Res, TagsQuery>;
}
/// @nodoc
class _$TagsQueryCopyWithImpl<$Res, $Val extends TagsQuery>
implements $TagsQueryCopyWith<$Res> {
_$TagsQueryCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$NotAssignedTagsQueryCopyWith<$Res> {
factory _$$NotAssignedTagsQueryCopyWith(_$NotAssignedTagsQuery value,
$Res Function(_$NotAssignedTagsQuery) then) =
__$$NotAssignedTagsQueryCopyWithImpl<$Res>;
}
/// @nodoc
class __$$NotAssignedTagsQueryCopyWithImpl<$Res>
extends _$TagsQueryCopyWithImpl<$Res, _$NotAssignedTagsQuery>
implements _$$NotAssignedTagsQueryCopyWith<$Res> {
__$$NotAssignedTagsQueryCopyWithImpl(_$NotAssignedTagsQuery _value,
$Res Function(_$NotAssignedTagsQuery) _then)
: super(_value, _then);
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedTagsQuery)
class _$NotAssignedTagsQuery extends NotAssignedTagsQuery {
const _$NotAssignedTagsQuery({final String? $type})
: $type = $type ?? 'notAssigned',
super._();
factory _$NotAssignedTagsQuery.fromJson(Map<String, dynamic> json) =>
_$$NotAssignedTagsQueryFromJson(json);
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'TagsQuery.notAssigned()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$NotAssignedTagsQuery);
}
@JsonKey(ignore: true)
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() notAssigned,
required TResult Function(Iterable<int> tagIds) anyAssigned,
required TResult Function(Iterable<int> include, Iterable<int> exclude) ids,
}) {
return notAssigned();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? notAssigned,
TResult? Function(Iterable<int> tagIds)? anyAssigned,
TResult? Function(Iterable<int> include, Iterable<int> exclude)? ids,
}) {
return notAssigned?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? notAssigned,
TResult Function(Iterable<int> tagIds)? anyAssigned,
TResult Function(Iterable<int> include, Iterable<int> exclude)? ids,
required TResult orElse(),
}) {
if (notAssigned != null) {
return notAssigned();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NotAssignedTagsQuery value) notAssigned,
required TResult Function(AnyAssignedTagsQuery value) anyAssigned,
required TResult Function(IdsTagsQuery value) ids,
}) {
return notAssigned(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(NotAssignedTagsQuery value)? notAssigned,
TResult? Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult? Function(IdsTagsQuery value)? ids,
}) {
return notAssigned?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NotAssignedTagsQuery value)? notAssigned,
TResult Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult Function(IdsTagsQuery value)? ids,
required TResult orElse(),
}) {
if (notAssigned != null) {
return notAssigned(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$NotAssignedTagsQueryToJson(
this,
);
}
}
abstract class NotAssignedTagsQuery extends TagsQuery {
const factory NotAssignedTagsQuery() = _$NotAssignedTagsQuery;
const NotAssignedTagsQuery._() : super._();
factory NotAssignedTagsQuery.fromJson(Map<String, dynamic> json) =
_$NotAssignedTagsQuery.fromJson;
}
/// @nodoc
abstract class _$$AnyAssignedTagsQueryCopyWith<$Res> {
factory _$$AnyAssignedTagsQueryCopyWith(_$AnyAssignedTagsQuery value,
$Res Function(_$AnyAssignedTagsQuery) then) =
__$$AnyAssignedTagsQueryCopyWithImpl<$Res>;
@useResult
$Res call({Iterable<int> tagIds});
}
/// @nodoc
class __$$AnyAssignedTagsQueryCopyWithImpl<$Res>
extends _$TagsQueryCopyWithImpl<$Res, _$AnyAssignedTagsQuery>
implements _$$AnyAssignedTagsQueryCopyWith<$Res> {
__$$AnyAssignedTagsQueryCopyWithImpl(_$AnyAssignedTagsQuery _value,
$Res Function(_$AnyAssignedTagsQuery) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? tagIds = null,
}) {
return _then(_$AnyAssignedTagsQuery(
tagIds: null == tagIds
? _value.tagIds
: tagIds // ignore: cast_nullable_to_non_nullable
as Iterable<int>,
));
}
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery)
class _$AnyAssignedTagsQuery extends AnyAssignedTagsQuery {
const _$AnyAssignedTagsQuery({this.tagIds = const [], final String? $type})
: $type = $type ?? 'anyAssigned',
super._();
factory _$AnyAssignedTagsQuery.fromJson(Map<String, dynamic> json) =>
_$$AnyAssignedTagsQueryFromJson(json);
@override
@JsonKey()
final Iterable<int> tagIds;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'TagsQuery.anyAssigned(tagIds: $tagIds)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AnyAssignedTagsQuery &&
const DeepCollectionEquality().equals(other.tagIds, tagIds));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(tagIds));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AnyAssignedTagsQueryCopyWith<_$AnyAssignedTagsQuery> get copyWith =>
__$$AnyAssignedTagsQueryCopyWithImpl<_$AnyAssignedTagsQuery>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() notAssigned,
required TResult Function(Iterable<int> tagIds) anyAssigned,
required TResult Function(Iterable<int> include, Iterable<int> exclude) ids,
}) {
return anyAssigned(tagIds);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? notAssigned,
TResult? Function(Iterable<int> tagIds)? anyAssigned,
TResult? Function(Iterable<int> include, Iterable<int> exclude)? ids,
}) {
return anyAssigned?.call(tagIds);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? notAssigned,
TResult Function(Iterable<int> tagIds)? anyAssigned,
TResult Function(Iterable<int> include, Iterable<int> exclude)? ids,
required TResult orElse(),
}) {
if (anyAssigned != null) {
return anyAssigned(tagIds);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NotAssignedTagsQuery value) notAssigned,
required TResult Function(AnyAssignedTagsQuery value) anyAssigned,
required TResult Function(IdsTagsQuery value) ids,
}) {
return anyAssigned(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(NotAssignedTagsQuery value)? notAssigned,
TResult? Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult? Function(IdsTagsQuery value)? ids,
}) {
return anyAssigned?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NotAssignedTagsQuery value)? notAssigned,
TResult Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult Function(IdsTagsQuery value)? ids,
required TResult orElse(),
}) {
if (anyAssigned != null) {
return anyAssigned(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$AnyAssignedTagsQueryToJson(
this,
);
}
}
abstract class AnyAssignedTagsQuery extends TagsQuery {
const factory AnyAssignedTagsQuery({final Iterable<int> tagIds}) =
_$AnyAssignedTagsQuery;
const AnyAssignedTagsQuery._() : super._();
factory AnyAssignedTagsQuery.fromJson(Map<String, dynamic> json) =
_$AnyAssignedTagsQuery.fromJson;
Iterable<int> get tagIds;
@JsonKey(ignore: true)
_$$AnyAssignedTagsQueryCopyWith<_$AnyAssignedTagsQuery> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$IdsTagsQueryCopyWith<$Res> {
factory _$$IdsTagsQueryCopyWith(
_$IdsTagsQuery value, $Res Function(_$IdsTagsQuery) then) =
__$$IdsTagsQueryCopyWithImpl<$Res>;
@useResult
$Res call({Iterable<int> include, Iterable<int> exclude});
}
/// @nodoc
class __$$IdsTagsQueryCopyWithImpl<$Res>
extends _$TagsQueryCopyWithImpl<$Res, _$IdsTagsQuery>
implements _$$IdsTagsQueryCopyWith<$Res> {
__$$IdsTagsQueryCopyWithImpl(
_$IdsTagsQuery _value, $Res Function(_$IdsTagsQuery) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? include = null,
Object? exclude = null,
}) {
return _then(_$IdsTagsQuery(
include: null == include
? _value.include
: include // ignore: cast_nullable_to_non_nullable
as Iterable<int>,
exclude: null == exclude
? _value.exclude
: exclude // ignore: cast_nullable_to_non_nullable
as Iterable<int>,
));
}
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery)
class _$IdsTagsQuery extends IdsTagsQuery {
const _$IdsTagsQuery(
{this.include = const [], this.exclude = const [], final String? $type})
: $type = $type ?? 'ids',
super._();
factory _$IdsTagsQuery.fromJson(Map<String, dynamic> json) =>
_$$IdsTagsQueryFromJson(json);
@override
@JsonKey()
final Iterable<int> include;
@override
@JsonKey()
final Iterable<int> exclude;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'TagsQuery.ids(include: $include, exclude: $exclude)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IdsTagsQuery &&
const DeepCollectionEquality().equals(other.include, include) &&
const DeepCollectionEquality().equals(other.exclude, exclude));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(include),
const DeepCollectionEquality().hash(exclude));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IdsTagsQueryCopyWith<_$IdsTagsQuery> get copyWith =>
__$$IdsTagsQueryCopyWithImpl<_$IdsTagsQuery>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() notAssigned,
required TResult Function(Iterable<int> tagIds) anyAssigned,
required TResult Function(Iterable<int> include, Iterable<int> exclude) ids,
}) {
return ids(include, exclude);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? notAssigned,
TResult? Function(Iterable<int> tagIds)? anyAssigned,
TResult? Function(Iterable<int> include, Iterable<int> exclude)? ids,
}) {
return ids?.call(include, exclude);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? notAssigned,
TResult Function(Iterable<int> tagIds)? anyAssigned,
TResult Function(Iterable<int> include, Iterable<int> exclude)? ids,
required TResult orElse(),
}) {
if (ids != null) {
return ids(include, exclude);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NotAssignedTagsQuery value) notAssigned,
required TResult Function(AnyAssignedTagsQuery value) anyAssigned,
required TResult Function(IdsTagsQuery value) ids,
}) {
return ids(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(NotAssignedTagsQuery value)? notAssigned,
TResult? Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult? Function(IdsTagsQuery value)? ids,
}) {
return ids?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NotAssignedTagsQuery value)? notAssigned,
TResult Function(AnyAssignedTagsQuery value)? anyAssigned,
TResult Function(IdsTagsQuery value)? ids,
required TResult orElse(),
}) {
if (ids != null) {
return ids(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$IdsTagsQueryToJson(
this,
);
}
}
abstract class IdsTagsQuery extends TagsQuery {
const factory IdsTagsQuery(
{final Iterable<int> include,
final Iterable<int> exclude}) = _$IdsTagsQuery;
const IdsTagsQuery._() : super._();
factory IdsTagsQuery.fromJson(Map<String, dynamic> json) =
_$IdsTagsQuery.fromJson;
Iterable<int> get include;
Iterable<int> get exclude;
@JsonKey(ignore: true)
_$$IdsTagsQueryCopyWith<_$IdsTagsQuery> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -1,13 +1,18 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart';
import 'query_type.dart'; import 'query_type.dart';
part 'text_query.g.dart'; part 'text_query.g.dart';
@HiveType(typeId: PaperlessApiHiveTypeIds.textQuery)
@JsonSerializable() @JsonSerializable()
class TextQuery extends Equatable { class TextQuery extends Equatable {
@HiveField(0)
final QueryType queryType; final QueryType queryType;
@HiveField(1)
final String? queryText; final String? queryText;
const TextQuery({ const TextQuery({
@@ -17,8 +22,7 @@ class TextQuery extends Equatable {
const TextQuery.title(this.queryText) : queryType = QueryType.title; const TextQuery.title(this.queryText) : queryType = QueryType.title;
const TextQuery.titleAndContent(this.queryText) const TextQuery.titleAndContent(this.queryText) : queryType = QueryType.titleAndContent;
: queryType = QueryType.titleAndContent;
const TextQuery.extended(this.queryText) : queryType = QueryType.extended; const TextQuery.extended(this.queryText) : queryType = QueryType.extended;
@@ -68,8 +72,7 @@ class TextQuery extends Equatable {
case QueryType.title: case QueryType.title:
return title.contains(queryText!); return title.contains(queryText!);
case QueryType.titleAndContent: case QueryType.titleAndContent:
return title.contains(queryText!) || return title.contains(queryText!) || (content?.contains(queryText!) ?? false);
(content?.contains(queryText!) ?? false);
case QueryType.extended: case QueryType.extended:
//TODO: Implement. Might be too complex... //TODO: Implement. Might be too complex...
return true; return true;
@@ -80,8 +83,7 @@ class TextQuery extends Equatable {
Map<String, dynamic> toJson() => _$TextQueryToJson(this); Map<String, dynamic> toJson() => _$TextQueryToJson(this);
factory TextQuery.fromJson(Map<String, dynamic> json) => factory TextQuery.fromJson(Map<String, dynamic> json) => _$TextQueryFromJson(json);
_$TextQueryFromJson(json);
@override @override
List<Object?> get props => [queryType, queryText]; List<Object?> get props => [queryType, queryText];

View File

@@ -1,6 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query/include_tag_id_query.dart';
import 'package:paperless_api/src/models/query_parameters/text_query.dart'; import 'package:paperless_api/src/models/query_parameters/text_query.dart';
void main() { void main() {
@@ -70,13 +69,9 @@ void main() {
correspondent: const IdQueryParameter.fromId(42), correspondent: const IdQueryParameter.fromId(42),
documentType: const IdQueryParameter.fromId(69), documentType: const IdQueryParameter.fromId(69),
storagePath: const IdQueryParameter.fromId(14), storagePath: const IdQueryParameter.fromId(14),
tags: const IdsTagsQuery( tags: const TagsQuery.ids(
[ include: [1, 2],
IncludeTagIdQuery(1), exclude: [3, 4],
IncludeTagIdQuery(2),
ExcludeTagIdQuery(3),
ExcludeTagIdQuery(4),
],
), ),
created: AbsoluteDateRangeQuery( created: AbsoluteDateRangeQuery(
before: DateTime.parse("2022-10-27"), before: DateTime.parse("2022-10-27"),
@@ -140,7 +135,7 @@ void main() {
correspondent: const IdQueryParameter.notAssigned(), correspondent: const IdQueryParameter.notAssigned(),
documentType: const IdQueryParameter.notAssigned(), documentType: const IdQueryParameter.notAssigned(),
storagePath: const IdQueryParameter.notAssigned(), storagePath: const IdQueryParameter.notAssigned(),
tags: const OnlyNotAssignedTagsQuery(), tags: const TagsQuery.notAssigned(),
); );
expect( expect(
actual, actual,
@@ -157,13 +152,10 @@ void main() {
correspondent: const IdQueryParameter.fromId(1), correspondent: const IdQueryParameter.fromId(1),
documentType: const IdQueryParameter.fromId(2), documentType: const IdQueryParameter.fromId(2),
storagePath: const IdQueryParameter.fromId(3), storagePath: const IdQueryParameter.fromId(3),
tags: const IdsTagsQuery([ tags: const TagsQuery.ids(
IncludeTagIdQuery(4), include: [4, 5],
IncludeTagIdQuery(5), exclude: [6, 7, 8],
ExcludeTagIdQuery(6), ),
ExcludeTagIdQuery(7),
ExcludeTagIdQuery(8),
]),
sortField: SortField.added, sortField: SortField.added,
sortOrder: SortOrder.ascending, sortOrder: SortOrder.ascending,
created: AbsoluteDateRangeQuery( created: AbsoluteDateRangeQuery(
@@ -241,11 +233,11 @@ void main() {
test('Values are correctly parsed if not assigned.', () { test('Values are correctly parsed if not assigned.', () {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
DocumentFilter( const DocumentFilter(
correspondent: const IdQueryParameter.notAssigned(), correspondent: IdQueryParameter.notAssigned(),
documentType: const IdQueryParameter.notAssigned(), documentType: IdQueryParameter.notAssigned(),
storagePath: const IdQueryParameter.notAssigned(), storagePath: IdQueryParameter.notAssigned(),
tags: const OnlyNotAssignedTagsQuery(), tags: TagsQuery.notAssigned(),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.ascending, sortOrder: SortOrder.ascending,
), ),