WIP - Refactoring date range picker dialog

This commit is contained in:
Anton Stubenbord
2022-12-19 03:03:11 +01:00
parent f77ccf50c1
commit 901d646ec2
27 changed files with 971 additions and 556 deletions

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_relative_date_range_field.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
class ExtendedDateRangeDialog extends StatefulWidget {
final DateRangeQuery initialValue;
final String Function(DateRangeQuery query) stringTransformer;
const ExtendedDateRangeDialog({
super.key,
required this.initialValue,
required this.stringTransformer,
});
@override
State<ExtendedDateRangeDialog> createState() =>
_ExtendedDateRangeDialogState();
}
class _ExtendedDateRangeDialogState extends State<ExtendedDateRangeDialog> {
static const String _fkAbsoluteBefore = 'absoluteBefore';
static const String _fkAbsoluteAfter = 'absoluteAfter';
static const String _fkRelative = 'relative';
final _formKey = GlobalKey<FormBuilderState>();
late DateRangeType _selectedDateRangeType;
@override
void initState() {
super.initState();
_selectedDateRangeType = (widget.initialValue is RelativeDateRangeQuery)
? DateRangeType.relative
: DateRangeType.absolute;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text("Select date range"),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Hint: You can either specify absolute values by selecting concrete dates, or you can specify a time range relative to today.",
style: Theme.of(context).textTheme.caption,
),
_buildDateRangeQueryTypeSelection(),
const SizedBox(height: 16),
Builder(
builder: (context) {
switch (_selectedDateRangeType) {
case DateRangeType.absolute:
return _buildAbsoluteDateRangeForm();
case DateRangeType.relative:
return FormBuilderRelativeDateRangePicker(
initialValue:
widget.initialValue is RelativeDateRangeQuery
? widget.initialValue as RelativeDateRangeQuery
: const RelativeDateRangeQuery(
1,
DateRangeUnit.month,
),
name: _fkRelative,
);
}
},
),
],
),
),
actions: [
TextButton(
child: Text(S.of(context).genericActionCancelLabel),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: Text(S.of(context).genericActionSaveLabel),
onPressed: () {
_formKey.currentState?.save();
if (_formKey.currentState?.validate() ?? false) {
final values = _formKey.currentState!.value;
final query = _buildQuery(values);
Navigator.pop(context, query);
}
},
),
],
);
}
Widget _buildDateRangeQueryTypeSelection() {
return Row(
children: [
ChoiceChip(
label: Text('Absolute'),
selected: _selectedDateRangeType == DateRangeType.absolute,
onSelected: (value) =>
setState(() => _selectedDateRangeType = DateRangeType.absolute),
).paddedOnly(right: 8.0),
ChoiceChip(
label: Text('Relative'),
selected: _selectedDateRangeType == DateRangeType.relative,
onSelected: (value) =>
setState(() => _selectedDateRangeType = DateRangeType.relative),
),
],
);
}
Widget _buildAbsoluteDateRangeForm() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FormBuilderDateTimePicker(
name: _fkAbsoluteAfter,
initialDate: widget.initialValue is AbsoluteDateRangeQuery
? (widget.initialValue as AbsoluteDateRangeQuery).after
: null,
decoration: InputDecoration(
labelText: S.of(context).extendedDateRangePickerAfterLabel,
),
inputType: InputType.date,
),
FormBuilderDateTimePicker(
name: _fkAbsoluteBefore,
initialDate: widget.initialValue is AbsoluteDateRangeQuery
? (widget.initialValue as AbsoluteDateRangeQuery).before
: null,
inputType: InputType.date,
decoration: InputDecoration(
labelText: S.of(context).extendedDateRangePickerBeforeLabel,
),
),
],
);
}
DateRangeQuery? _buildQuery(Map<String, dynamic> values) {
if (_selectedDateRangeType == DateRangeType.absolute) {
return AbsoluteDateRangeQuery(
after: values[_fkAbsoluteAfter],
before: values[_fkAbsoluteBefore],
);
} else {
return values[_fkRelative] as RelativeDateRangeQuery;
}
}
}
enum DateRangeType {
absolute,
relative;
}

View File

@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_dialog.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class FormBuilderExtendedDateRangePicker extends StatefulWidget {
final String name;
final String labelText;
final DateRangeQuery initialValue;
const FormBuilderExtendedDateRangePicker({
super.key,
required this.name,
required this.labelText,
required this.initialValue,
});
@override
State<FormBuilderExtendedDateRangePicker> createState() =>
_FormBuilderExtendedDateRangePickerState();
}
class _FormBuilderExtendedDateRangePickerState
extends State<FormBuilderExtendedDateRangePicker> {
late final TextEditingController _textEditingController;
@override
void initState() {
super.initState();
_textEditingController = TextEditingController(
text: _dateRangeQueryToString(widget.initialValue));
}
@override
Widget build(BuildContext context) {
return FormBuilderField<DateRangeQuery>(
name: widget.name,
initialValue: widget.initialValue,
onChanged: (query) {
_textEditingController.text =
_dateRangeQueryToString(query ?? const UnsetDateRangeQuery());
},
builder: (field) {
return Column(
children: [
TextFormField(
controller: _textEditingController,
readOnly: true,
onTap: () => _showExtendedDateRangePicker(field),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.date_range),
labelText: widget.labelText,
),
),
_buildExtendedQueryOptions(field),
],
);
},
);
}
Widget _buildExtendedQueryOptions(FormFieldState<DateRangeQuery> field) {
return SizedBox(
height: 64,
child: ListView.separated(
itemCount: _options.length,
separatorBuilder: (context, index) => const SizedBox(width: 8.0),
itemBuilder: (context, index) {
final option = _options[index];
return FilterChip(
label: Text(option.title),
onSelected: (isSelected) => isSelected
? field.didChange(option.value)
: field.didChange(const UnsetDateRangeQuery()),
selected: field.value == option.value,
);
},
scrollDirection: Axis.horizontal,
),
);
}
List<_ExtendedDateRangeQueryOption> get _options => [
_ExtendedDateRangeQueryOption(
S.of(context).extendedDateRangePickerLastWeeksLabel(1),
const RelativeDateRangeQuery(1, DateRangeUnit.week),
),
_ExtendedDateRangeQueryOption(
S.of(context).extendedDateRangePickerLastMonthsLabel(1),
const RelativeDateRangeQuery(1, DateRangeUnit.month),
),
_ExtendedDateRangeQueryOption(
S.of(context).extendedDateRangePickerLastMonthsLabel(3),
const RelativeDateRangeQuery(3, DateRangeUnit.month),
),
_ExtendedDateRangeQueryOption(
S.of(context).extendedDateRangePickerLastYearsLabel(1),
const RelativeDateRangeQuery(1, DateRangeUnit.year),
),
];
String _dateRangeQueryToString(DateRangeQuery query) {
if (query is UnsetDateRangeQuery) {
return '';
} else if (query is AbsoluteDateRangeQuery) {
if (query.before != null && query.after != null) {
return '${DateFormat.yMd(query.after)} ${DateFormat.yMd(query.before)}';
}
if (query.before != null) {
return '${S.of(context).extendedDateRangePickerBeforeLabel} ${DateFormat.yMd(query.before)}';
}
if (query.after != null) {
return '${S.of(context).extendedDateRangePickerAfterLabel} ${DateFormat.yMd(query.after)}';
}
} else if (query is RelativeDateRangeQuery) {
switch (query.unit) {
case DateRangeUnit.day:
return S
.of(context)
.extendedDateRangePickerLastDaysLabel(query.offset);
case DateRangeUnit.week:
return S
.of(context)
.extendedDateRangePickerLastWeeksLabel(query.offset);
case DateRangeUnit.month:
return S
.of(context)
.extendedDateRangePickerLastMonthsLabel(query.offset);
case DateRangeUnit.year:
return S
.of(context)
.extendedDateRangePickerLastYearsLabel(query.offset);
default:
}
}
return '';
}
void _showExtendedDateRangePicker(
FormFieldState<DateRangeQuery> field,
) async {
final query = await showDialog<DateRangeQuery>(
context: context,
builder: (context) => ExtendedDateRangeDialog(
initialValue: field.value!,
stringTransformer: _dateRangeQueryToString,
),
);
if (query != null) {
field.didChange(query);
}
}
}
class _ExtendedDateRangeQueryOption {
final String title;
final RelativeDateRangeQuery value;
_ExtendedDateRangeQueryOption(this.title, this.value);
}

View File

@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class FormBuilderRelativeDateRangePicker extends StatefulWidget {
final String name;
final RelativeDateRangeQuery initialValue;
const FormBuilderRelativeDateRangePicker({
super.key,
required this.name,
required this.initialValue,
});
@override
State<FormBuilderRelativeDateRangePicker> createState() =>
_FormBuilderRelativeDateRangePickerState();
}
class _FormBuilderRelativeDateRangePickerState
extends State<FormBuilderRelativeDateRangePicker> {
late int _offset;
@override
void initState() {
super.initState();
_offset = widget.initialValue.offset;
}
@override
Widget build(BuildContext context) {
return FormBuilderField<RelativeDateRangeQuery>(
name: widget.name,
initialValue: widget.initialValue,
builder: (field) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Last"),
SizedBox(
width: 70,
child: TextFormField(
decoration: InputDecoration(
labelText: "Offset",
),
initialValue: widget.initialValue.offset.toString(),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r"[1-9][0-9]*"))
],
validator: FormBuilderValidators.numeric(),
keyboardType: TextInputType.number,
onChanged: (value) {
final parsed = int.parse(value);
setState(() {
_offset = parsed;
});
field.didChange(field.value!.copyWith(offset: parsed));
},
),
),
SizedBox(
width: 120,
child: DropdownButtonFormField<DateRangeUnit?>(
value: field.value?.unit,
items: DateRangeUnit.values
.map(
(unit) => DropdownMenuItem(
child: Text(
_dateRangeUnitToLocalizedString(
unit,
_offset,
),
),
value: unit,
),
)
.toList(),
onChanged: (value) =>
field.didChange(field.value!.copyWith(unit: value)),
decoration: InputDecoration(
labelText: "Amount",
),
),
)
],
),
);
}
String _dateRangeUnitToLocalizedString(DateRangeUnit unit, int? count) {
switch (unit) {
case DateRangeUnit.day:
return S.of(context).extendedDateRangePickerDayText(count ?? 1);
case DateRangeUnit.week:
return S.of(context).extendedDateRangePickerWeekText(count ?? 1);
case DateRangeUnit.month:
return S.of(context).extendedDateRangePickerMonthText(count ?? 1);
case DateRangeUnit.year:
return S.of(context).extendedDateRangePickerYearText(count ?? 1);
}
}
}

View File

@@ -162,7 +162,7 @@ class _DocumentUploadPreparationPageState
S.of(context).documentCreatedPropertyLabel + " *", S.of(context).documentCreatedPropertyLabel + " *",
), ),
), ),
LabelFormField<DocumentType, DocumentTypeQuery>( LabelFormField<DocumentType>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) => labelCreationWidgetBuilder: (initialName) =>
@@ -172,15 +172,13 @@ class _DocumentUploadPreparationPageState
), ),
child: AddDocumentTypePage(initialName: initialName), child: AddDocumentTypePage(initialName: initialName),
), ),
label: S.of(context).documentDocumentTypePropertyLabel + " *", textFieldLabel:
S.of(context).documentDocumentTypePropertyLabel + " *",
name: DocumentModel.documentTypeKey, name: DocumentModel.documentTypeKey,
state: state.documentTypes, labelOptions: state.documentTypes,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder:
DocumentTypeQuery.notAssigned,
prefixIcon: const Icon(Icons.description_outlined), prefixIcon: const Icon(Icons.description_outlined),
), ),
LabelFormField<Correspondent, CorrespondentQuery>( LabelFormField<Correspondent>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) => labelCreationWidgetBuilder: (initialName) =>
@@ -191,13 +189,10 @@ class _DocumentUploadPreparationPageState
), ),
child: AddCorrespondentPage(initialName: initialName), child: AddCorrespondentPage(initialName: initialName),
), ),
label: textFieldLabel:
S.of(context).documentCorrespondentPropertyLabel + " *", S.of(context).documentCorrespondentPropertyLabel + " *",
name: DocumentModel.correspondentKey, name: DocumentModel.correspondentKey,
state: state.correspondents, labelOptions: state.correspondents,
queryParameterIdBuilder: CorrespondentQuery.fromId,
queryParameterNotAssignedBuilder:
CorrespondentQuery.notAssigned,
prefixIcon: const Icon(Icons.person_outline), prefixIcon: const Icon(Icons.person_outline),
), ),
TagFormField( TagFormField(

View File

@@ -96,26 +96,24 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
Widget _buildStoragePathFormField( Widget _buildStoragePathFormField(
int? initialId, Map<int, StoragePath> options) { int? initialId, Map<int, StoragePath> options) {
return LabelFormField<StoragePath, StoragePathQuery>( return LabelFormField<StoragePath>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value( labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context), value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
child: AddStoragePathPage(initalValue: initialValue), child: AddStoragePathPage(initalValue: initialValue),
), ),
label: S.of(context).documentStoragePathPropertyLabel, textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
state: options, labelOptions: options,
initialValue: StoragePathQuery.fromId(initialId), initialValue: IdQueryParameter.fromId(initialId),
name: fkStoragePath, name: fkStoragePath,
queryParameterIdBuilder: StoragePathQuery.fromId,
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
prefixIcon: const Icon(Icons.folder_outlined), prefixIcon: const Icon(Icons.folder_outlined),
); );
} }
Widget _buildCorrespondentFormField( Widget _buildCorrespondentFormField(
int? initialId, Map<int, Correspondent> options) { int? initialId, Map<int, Correspondent> options) {
return LabelFormField<Correspondent, CorrespondentQuery>( return LabelFormField<Correspondent>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value( labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
@@ -124,19 +122,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
), ),
child: AddCorrespondentPage(initialName: initialValue), child: AddCorrespondentPage(initialName: initialValue),
), ),
label: S.of(context).documentCorrespondentPropertyLabel, textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
state: options, labelOptions: options,
initialValue: CorrespondentQuery.fromId(initialId), initialValue: IdQueryParameter.fromId(initialId),
name: fkCorrespondent, name: fkCorrespondent,
queryParameterIdBuilder: CorrespondentQuery.fromId,
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
prefixIcon: const Icon(Icons.person_outlined), prefixIcon: const Icon(Icons.person_outlined),
); );
} }
Widget _buildDocumentTypeFormField( Widget _buildDocumentTypeFormField(
int? initialId, Map<int, DocumentType> options) { int? initialId, Map<int, DocumentType> options) {
return LabelFormField<DocumentType, DocumentTypeQuery>( return LabelFormField<DocumentType>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value( labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value(
@@ -147,12 +143,10 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
initialName: currentInput, initialName: currentInput,
), ),
), ),
label: S.of(context).documentDocumentTypePropertyLabel, textFieldLabel: S.of(context).documentDocumentTypePropertyLabel,
initialValue: DocumentTypeQuery.fromId(initialId), initialValue: IdQueryParameter.fromId(initialId),
state: options, labelOptions: options,
name: fkDocumentType, name: fkDocumentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
prefixIcon: const Icon(Icons.description_outlined), prefixIcon: const Icon(Icons.description_outlined),
); );
} }

View File

@@ -121,6 +121,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
expand: false, expand: false,
snap: true, snap: true,
initialChildSize: .9, initialChildSize: .9,
maxChildSize: .9,
builder: (context, controller) => LabelsBlocProvider( builder: (context, controller) => LabelsBlocProvider(
child: DocumentFilterPanel( child: DocumentFilterPanel(
initialFilter: _documentsCubit.state.filter, initialFilter: _documentsCubit.state.filter,

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_extended_date_range_picker.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart'; import 'package:paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
@@ -95,8 +96,13 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.caption,
), ),
).padded(), ).padded(),
_buildCreatedDateRangePickerFormField(), FormBuilderExtendedDateRangePicker(
_buildAddedDateRangePickerFormField(), name: DocumentModel.createdKey,
initialValue: widget.initialFilter.created,
labelText: S.of(context).documentCreatedPropertyLabel,
).padded(),
// _buildCreatedDateRangePickerFormField(),
// _buildAddedDateRangePickerFormField(),
_buildCorrespondentFormField().padded(), _buildCorrespondentFormField().padded(),
_buildDocumentTypeFormField().padded(), _buildDocumentTypeFormField().padded(),
_buildStoragePathFormField().padded(), _buildStoragePathFormField().padded(),
@@ -129,14 +135,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildDocumentTypeFormField() { Widget _buildDocumentTypeFormField() {
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>( return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>( return LabelFormField<DocumentType>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkDocumentType, name: fkDocumentType,
state: state.labels, labelOptions: state.labels,
label: S.of(context).documentDocumentTypePropertyLabel, textFieldLabel: S.of(context).documentDocumentTypePropertyLabel,
initialValue: widget.initialFilter.documentType, initialValue: widget.initialFilter.documentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
prefixIcon: const Icon(Icons.description_outlined), prefixIcon: const Icon(Icons.description_outlined),
); );
}, },
@@ -146,14 +150,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildCorrespondentFormField() { Widget _buildCorrespondentFormField() {
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>( return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>( return LabelFormField<Correspondent>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkCorrespondent, name: fkCorrespondent,
state: state.labels, labelOptions: state.labels,
label: S.of(context).documentCorrespondentPropertyLabel, textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
initialValue: widget.initialFilter.correspondent, initialValue: widget.initialFilter.correspondent,
queryParameterIdBuilder: CorrespondentQuery.fromId,
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
prefixIcon: const Icon(Icons.person_outline), prefixIcon: const Icon(Icons.person_outline),
); );
}, },
@@ -163,14 +165,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildStoragePathFormField() { Widget _buildStoragePathFormField() {
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>( return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>( return LabelFormField<StoragePath>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkStoragePath, name: fkStoragePath,
state: state.labels, labelOptions: state.labels,
label: S.of(context).documentStoragePathPropertyLabel, textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
initialValue: widget.initialFilter.storagePath, initialValue: widget.initialFilter.storagePath,
queryParameterIdBuilder: StoragePathQuery.fromId,
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
prefixIcon: const Icon(Icons.folder_outlined), prefixIcon: const Icon(Icons.folder_outlined),
); );
}, },
@@ -209,187 +209,99 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
); );
} }
Widget _buildDateRangePickerHelper(String formFieldKey) { // Widget _buildCreatedDateRangePickerFormField() {
const spacer = SizedBox(width: 8.0); // return Column(
return SizedBox( // children: [
height: 64, // FormBuilderDateRangePicker(
child: ListView( // initialValue: _dateTimeRangeOfNullable(
scrollDirection: Axis.horizontal, // widget.initialFilter.createdDateAfter,
children: [ // widget.initialFilter.createdDateBefore,
spacer, // ),
ActionChip( // // Workaround for theme data not being correctly passed to daterangepicker, see
label: Text( // // https://github.com/flutter/flutter/issues/87580
S.of(context).documentFilterDateRangeLastSevenDaysLabel, // pickerBuilder: (context, Widget? child) => Theme(
), // data: Theme.of(context).copyWith(
onPressed: () { // dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
_formKey.currentState?.fields[formFieldKey]?.didChange( // appBarTheme: Theme.of(context).appBarTheme.copyWith(
DateTimeRange( // iconTheme:
start: DateUtils.addDaysToDate(DateTime.now(), -7), // IconThemeData(color: Theme.of(context).primaryColor),
end: DateTime.now(), // ),
), // colorScheme: Theme.of(context).colorScheme.copyWith(
); // onPrimary: Theme.of(context).primaryColor,
}, // primary: Theme.of(context).colorScheme.primary,
), // ),
spacer, // ),
ActionChip( // child: child!,
label: Text( // ),
S.of(context).documentFilterDateRangeLastMonthLabel, // format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
), // fieldStartLabelText:
onPressed: () { // S.of(context).documentFilterDateRangeFieldStartLabel,
final now = DateTime.now(); // fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel,
final firstDayOfLastMonth = // firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
DateUtils.addMonthsToMonthDate(now, -1); // lastDate: DateTime.now(),
_formKey.currentState?.fields[formFieldKey]?.didChange( // name: fkCreatedAt,
DateTimeRange( // decoration: InputDecoration(
start: DateTime(firstDayOfLastMonth.year, // prefixIcon: const Icon(Icons.calendar_month_outlined),
firstDayOfLastMonth.month, now.day), // labelText: S.of(context).documentCreatedPropertyLabel,
end: DateTime.now(), // suffixIcon: IconButton(
), // icon: const Icon(Icons.clear),
); // onPressed: () {
}, // _formKey.currentState?.fields[fkCreatedAt]?.didChange(null);
), // },
spacer, // ),
ActionChip( // ),
label: Text( // ).paddedSymmetrically(horizontal: 8, vertical: 4.0),
S.of(context).documentFilterDateRangeLastThreeMonthsLabel, // ],
), // );
onPressed: () { // }
final now = DateTime.now();
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -3);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(
firstDayOfLastMonth.year,
firstDayOfLastMonth.month,
now.day,
),
end: DateTime.now(),
),
);
},
),
spacer,
ActionChip(
label: Text(
S.of(context).documentFilterDateRangeLastYearLabel,
),
onPressed: () {
final now = DateTime.now();
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -12);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(
firstDayOfLastMonth.year,
firstDayOfLastMonth.month,
now.day,
),
end: DateTime.now(),
),
);
},
),
spacer,
],
),
);
}
Widget _buildCreatedDateRangePickerFormField() { // Widget _buildAddedDateRangePickerFormField() {
return Column( // return Column(
children: [ // children: [
FormBuilderDateRangePicker( // FormBuilderDateRangePicker(
initialValue: _dateTimeRangeOfNullable( // initialValue: _dateTimeRangeOfNullable(
widget.initialFilter.createdDateAfter, // widget.initialFilter.addedDateAfter,
widget.initialFilter.createdDateBefore, // widget.initialFilter.addedDateBefore,
), // ),
// Workaround for theme data not being correctly passed to daterangepicker, see // // Workaround for theme data not being correctly passed to daterangepicker, see
// https://github.com/flutter/flutter/issues/87580 // // https://github.com/flutter/flutter/issues/87580
pickerBuilder: (context, Widget? child) => Theme( // pickerBuilder: (context, Widget? child) => Theme(
data: Theme.of(context).copyWith( // data: Theme.of(context).copyWith(
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor, // dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBarTheme: Theme.of(context).appBarTheme.copyWith( // appBarTheme: Theme.of(context).appBarTheme.copyWith(
iconTheme: // iconTheme:
IconThemeData(color: Theme.of(context).primaryColor), // IconThemeData(color: Theme.of(context).primaryColor),
), // ),
colorScheme: Theme.of(context).colorScheme.copyWith( // colorScheme: Theme.of(context).colorScheme.copyWith(
onPrimary: Theme.of(context).primaryColor, // onPrimary: Theme.of(context).primaryColor,
primary: Theme.of(context).colorScheme.primary, // primary: Theme.of(context).colorScheme.primary,
), // ),
), // ),
child: child!, // child: child!,
), // ),
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()), // format: DateFormat.yMMMd(),
fieldStartLabelText: // fieldStartLabelText:
S.of(context).documentFilterDateRangeFieldStartLabel, // S.of(context).documentFilterDateRangeFieldStartLabel,
fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel, // fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0), // firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now(), // lastDate: DateTime.now(),
name: fkCreatedAt, // name: fkAddedAt,
decoration: InputDecoration( // decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month_outlined), // prefixIcon: const Icon(Icons.calendar_month_outlined),
labelText: S.of(context).documentCreatedPropertyLabel, // labelText: S.of(context).documentAddedPropertyLabel,
suffixIcon: IconButton( // suffixIcon: IconButton(
icon: const Icon(Icons.clear), // icon: const Icon(Icons.clear),
onPressed: () { // onPressed: () {
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null); // _formKey.currentState?.fields[fkAddedAt]?.didChange(null);
}, // },
), // ),
), // ),
).paddedSymmetrically(horizontal: 8, vertical: 4.0), // ).paddedSymmetrically(horizontal: 8),
_buildDateRangePickerHelper(fkCreatedAt), // const SizedBox(height: 4.0),
], // _buildDateRangePickerHelper(fkAddedAt),
); // ],
} // );
// }
Widget _buildAddedDateRangePickerFormField() {
return Column(
children: [
FormBuilderDateRangePicker(
initialValue: _dateTimeRangeOfNullable(
widget.initialFilter.addedDateAfter,
widget.initialFilter.addedDateBefore,
),
// Workaround for theme data not being correctly passed to daterangepicker, see
// https://github.com/flutter/flutter/issues/87580
pickerBuilder: (context, Widget? child) => Theme(
data: Theme.of(context).copyWith(
dialogBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBarTheme: Theme.of(context).appBarTheme.copyWith(
iconTheme:
IconThemeData(color: Theme.of(context).primaryColor),
),
colorScheme: Theme.of(context).colorScheme.copyWith(
onPrimary: Theme.of(context).primaryColor,
primary: Theme.of(context).colorScheme.primary,
),
),
child: child!,
),
format: DateFormat.yMMMd(),
fieldStartLabelText:
S.of(context).documentFilterDateRangeFieldStartLabel,
fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now(),
name: fkAddedAt,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month_outlined),
labelText: S.of(context).documentAddedPropertyLabel,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_formKey.currentState?.fields[fkAddedAt]?.didChange(null);
},
),
),
).paddedSymmetrically(horizontal: 8),
const SizedBox(height: 4.0),
_buildDateRangePickerHelper(fkAddedAt),
],
);
}
void _onApplyFilter() async { void _onApplyFilter() async {
_formKey.currentState?.save(); _formKey.currentState?.save();
@@ -408,19 +320,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
DocumentFilter _assembleFilter() { DocumentFilter _assembleFilter() {
final v = _formKey.currentState!.value; final v = _formKey.currentState!.value;
return DocumentFilter( return DocumentFilter(
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end, created: (v[fkCreatedAt] as DateRangeQuery),
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start, correspondent: v[fkCorrespondent] as IdQueryParameter? ??
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
DocumentFilter.initial.correspondent, DocumentFilter.initial.correspondent,
documentType: v[fkDocumentType] as DocumentTypeQuery? ?? documentType: v[fkDocumentType] as IdQueryParameter? ??
DocumentFilter.initial.documentType, DocumentFilter.initial.documentType,
storagePath: v[fkStoragePath] as StoragePathQuery? ?? storagePath: v[fkStoragePath] as IdQueryParameter? ??
DocumentFilter.initial.storagePath, DocumentFilter.initial.storagePath,
tags: tags:
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags, v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
queryText: v[fkQuery] as String?, queryText: v[fkQuery] as String?,
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end, added: (v[fkAddedAt] as DateRangeQuery),
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
queryType: v[QueryTypeFormField.fkQueryType] as QueryType, queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
asnQuery: widget.initialFilter.asnQuery, asnQuery: widget.initialFilter.asnQuery,
page: 1, page: 1,

View File

@@ -52,12 +52,12 @@ class CorrespondentWidget extends StatelessWidget {
if (cubit.state.filter.correspondent.id == correspondentId) { if (cubit.state.filter.correspondent.id == correspondentId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(correspondent: const CorrespondentQuery.unset()), filter.copyWith(correspondent: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
correspondent: CorrespondentQuery.fromId(correspondentId)), correspondent: IdQueryParameter.fromId(correspondentId)),
); );
} }
afterSelected?.call(); afterSelected?.call();

View File

@@ -51,12 +51,12 @@ class DocumentTypeWidget extends StatelessWidget {
if (cubit.state.filter.documentType.id == documentTypeId) { if (cubit.state.filter.documentType.id == documentTypeId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(documentType: const DocumentTypeQuery.unset()), filter.copyWith(documentType: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => filter.copyWith( (filter) => filter.copyWith(
documentType: DocumentTypeQuery.fromId(documentTypeId)), documentType: IdQueryParameter.fromId(documentTypeId)),
); );
} }
afterSelected?.call(); afterSelected?.call();

View File

@@ -54,12 +54,12 @@ class StoragePathWidget extends StatelessWidget {
if (cubit.state.filter.correspondent.id == pathId) { if (cubit.state.filter.correspondent.id == pathId) {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(storagePath: const StoragePathQuery.unset()), filter.copyWith(storagePath: const IdQueryParameter.unset()),
); );
} else { } else {
cubit.updateCurrentFilter( cubit.updateCurrentFilter(
(filter) => (filter) =>
filter.copyWith(storagePath: StoragePathQuery.fromId(pathId)), filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
); );
} }
afterSelected?.call(); afterSelected?.call();

View File

@@ -126,7 +126,7 @@ class _LabelsPageState extends State<LabelsPage>
), ),
child: LabelTabView<Correspondent>( child: LabelTabView<Correspondent>(
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
correspondent: CorrespondentQuery.fromId(label.id), correspondent: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
onEdit: _openEditCorrespondentPage, onEdit: _openEditCorrespondentPage,
@@ -146,7 +146,7 @@ class _LabelsPageState extends State<LabelsPage>
), ),
child: LabelTabView<DocumentType>( child: LabelTabView<DocumentType>(
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
documentType: DocumentTypeQuery.fromId(label.id), documentType: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
onEdit: _openEditDocumentTypePage, onEdit: _openEditDocumentTypePage,
@@ -194,7 +194,7 @@ class _LabelsPageState extends State<LabelsPage>
child: LabelTabView<StoragePath>( child: LabelTabView<StoragePath>(
onEdit: _openEditStoragePathPage, onEdit: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
storagePath: StoragePathQuery.fromId(label.id), storagePath: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
), ),
contentBuilder: (path) => Text(path.path ?? ""), contentBuilder: (path) => Text(path.path ?? ""),

View File

@@ -9,31 +9,26 @@ import 'package:paperless_mobile/generated/l10n.dart';
/// Form field allowing to select labels (i.e. correspondent, documentType) /// Form field allowing to select labels (i.e. correspondent, documentType)
/// [T] is the label type (e.g. [DocumentType], [Correspondent], ...), [R] is the return type (e.g. [CorrespondentQuery], ...). /// [T] is the label type (e.g. [DocumentType], [Correspondent], ...), [R] is the return type (e.g. [CorrespondentQuery], ...).
/// ///
class LabelFormField<T extends Label, R extends IdQueryParameter> class LabelFormField<T extends Label> extends StatefulWidget {
extends StatefulWidget {
final Widget prefixIcon; final Widget prefixIcon;
final Map<int, T> state; final Map<int, T> labelOptions;
final FormBuilderState? formBuilderState; final FormBuilderState? formBuilderState;
final IdQueryParameter? initialValue; final IdQueryParameter? initialValue;
final String name; final String name;
final String label; final String textFieldLabel;
final FormFieldValidator? validator; final FormFieldValidator? validator;
final Widget Function(String)? labelCreationWidgetBuilder; final Widget Function(String initialName)? labelCreationWidgetBuilder;
final R Function() queryParameterNotAssignedBuilder;
final R Function(int? id) queryParameterIdBuilder;
final bool notAssignedSelectable; final bool notAssignedSelectable;
final void Function(R?)? onChanged; final void Function(IdQueryParameter?)? onChanged;
const LabelFormField({ const LabelFormField({
Key? key, Key? key,
required this.name, required this.name,
required this.state, required this.labelOptions,
this.validator, this.validator,
this.initialValue, this.initialValue,
required this.label, required this.textFieldLabel,
this.labelCreationWidgetBuilder, this.labelCreationWidgetBuilder,
required this.queryParameterNotAssignedBuilder,
required this.queryParameterIdBuilder,
required this.formBuilderState, required this.formBuilderState,
required this.prefixIcon, required this.prefixIcon,
this.notAssignedSelectable = true, this.notAssignedSelectable = true,
@@ -41,11 +36,10 @@ class LabelFormField<T extends Label, R extends IdQueryParameter>
}) : super(key: key); }) : super(key: key);
@override @override
State<LabelFormField<T, R>> createState() => _LabelFormFieldState<T, R>(); State<LabelFormField<T>> createState() => _LabelFormFieldState<T>();
} }
class _LabelFormFieldState<T extends Label, R extends IdQueryParameter> class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
extends State<LabelFormField<T, R>> {
bool _showCreationSuffixIcon = false; bool _showCreationSuffixIcon = false;
late bool _showClearSuffixIcon; late bool _showClearSuffixIcon;
@@ -54,12 +48,13 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id); _showClearSuffixIcon =
widget.labelOptions.containsKey(widget.initialValue?.id);
_textEditingController = TextEditingController( _textEditingController = TextEditingController(
text: widget.state[widget.initialValue?.id]?.name ?? '', text: widget.labelOptions[widget.initialValue?.id]?.name ?? '',
)..addListener(() { )..addListener(() {
setState(() { setState(() {
_showCreationSuffixIcon = widget.state.values _showCreationSuffixIcon = widget.labelOptions.values
.where( .where(
(item) => item.name.toLowerCase().startsWith( (item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(), _textEditingController.text.toLowerCase(),
@@ -74,7 +69,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isEnabled = widget.state.values.fold<bool>( final isEnabled = widget.labelOptions.values.fold<bool>(
false, false,
(previousValue, element) => (previousValue, element) =>
previousValue || (element.documentCount ?? 0) > 0) || previousValue || (element.documentCount ?? 0) > 0) ||
@@ -90,7 +85,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0), TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
), ),
), ),
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null), initialValue: widget.initialValue ?? const IdQueryParameter.unset(),
name: widget.name, name: widget.name,
suggestionsBoxDecoration: SuggestionsBoxDecoration( suggestionsBoxDecoration: SuggestionsBoxDecoration(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -103,7 +98,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
), ),
itemBuilder: (context, suggestion) => ListTile( itemBuilder: (context, suggestion) => ListTile(
title: Text( title: Text(
widget.state[suggestion.id]?.name ?? widget.labelOptions[suggestion.id]?.name ??
S.of(context).labelNotAssignedText, S.of(context).labelNotAssignedText,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -112,10 +107,10 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
style: ListTileStyle.list, style: ListTileStyle.list,
), ),
suggestionsCallback: (pattern) { suggestionsCallback: (pattern) {
final List<IdQueryParameter> suggestions = widget.state.entries final List<IdQueryParameter> suggestions = widget.labelOptions.entries
.where( .where(
(entry) => (entry) =>
widget.state[entry.key]!.name widget.labelOptions[entry.key]!.name
.toLowerCase() .toLowerCase()
.contains(pattern.toLowerCase()) || .contains(pattern.toLowerCase()) ||
pattern.isEmpty, pattern.isEmpty,
@@ -125,34 +120,33 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
widget.labelCreationWidgetBuilder != null || widget.labelCreationWidgetBuilder != null ||
(entry.value.documentCount ?? 0) > 0, (entry.value.documentCount ?? 0) > 0,
) )
.map((entry) => widget.queryParameterIdBuilder(entry.key)) .map((entry) => IdQueryParameter.fromId(entry.key))
.toList(); .toList();
if (widget.notAssignedSelectable) { if (widget.notAssignedSelectable) {
suggestions.insert(0, widget.queryParameterNotAssignedBuilder()); suggestions.insert(0, const IdQueryParameter.notAssigned());
} }
return suggestions; return suggestions;
}, },
onChanged: (value) { onChanged: (value) {
setState(() => _showClearSuffixIcon = value?.isSet ?? false); setState(() => _showClearSuffixIcon = value?.isSet ?? false);
widget.onChanged?.call(value as R); widget.onChanged?.call(value);
}, },
controller: _textEditingController, controller: _textEditingController,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: widget.prefixIcon, prefixIcon: widget.prefixIcon,
label: Text(widget.label), label: Text(widget.textFieldLabel),
hintText: _getLocalizedHint(context), hintText: _getLocalizedHint(context),
suffixIcon: _buildSuffixIcon(context), suffixIcon: _buildSuffixIcon(context),
), ),
selectionToTextTransformer: (suggestion) { selectionToTextTransformer: (suggestion) {
if (suggestion == widget.queryParameterNotAssignedBuilder()) { if (suggestion == const IdQueryParameter.notAssigned()) {
return S.of(context).labelNotAssignedText; return S.of(context).labelNotAssignedText;
} }
return widget.state[suggestion.id]?.name ?? ""; return widget.labelOptions[suggestion.id]?.name ?? "";
}, },
direction: AxisDirection.up, direction: AxisDirection.up,
onSuggestionSelected: (suggestion) => widget onSuggestionSelected: (suggestion) =>
.formBuilderState?.fields[widget.name] widget.formBuilderState?.fields[widget.name]?.didChange(suggestion),
?.didChange(suggestion as R),
); );
} }
@@ -161,7 +155,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
final createdLabel = await showDialog( final createdLabel = await showDialog<T>(
context: context, context: context,
builder: (context) => widget.labelCreationWidgetBuilder!( builder: (context) => widget.labelCreationWidgetBuilder!(
_textEditingController.text, _textEditingController.text,
@@ -170,7 +164,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
if (createdLabel != null) { if (createdLabel != null) {
// If new label has been created, set form field value and text of this form field and unfocus keyboard (we assume user is done). // If new label has been created, set form field value and text of this form field and unfocus keyboard (we assume user is done).
widget.formBuilderState?.fields[widget.name] widget.formBuilderState?.fields[widget.name]
?.didChange(widget.queryParameterIdBuilder(createdLabel.id)); ?.didChange(IdQueryParameter.fromId(createdLabel.id));
_textEditingController.text = createdLabel.name; _textEditingController.text = createdLabel.name;
} else { } else {
_reset(); _reset();
@@ -192,7 +186,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
void _reset() { void _reset() {
widget.formBuilderState?.fields[widget.name]?.didChange( widget.formBuilderState?.fields[widget.name]?.didChange(
widget.queryParameterIdBuilder(null), // equivalnt to IdQueryParam.unset() const IdQueryParameter.unset(),
); );
_textEditingController.clear(); _textEditingController.clear();
} }

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {}, "@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Použít", "documentFilterApplyFilterLabel": "Použít",
"@documentFilterApplyFilterLabel": {}, "@documentFilterApplyFilterLabel": {},
"documentFilterDateRangeFieldEndLabel": "Do",
"@documentFilterDateRangeFieldEndLabel": {},
"documentFilterDateRangeFieldStartLabel": "Od",
"@documentFilterDateRangeFieldStartLabel": {},
"documentFilterDateRangeLastMonthLabel": "Minulý měsíc",
"@documentFilterDateRangeLastMonthLabel": {},
"documentFilterDateRangeLastSevenDaysLabel": "Posledních 7 dní",
"@documentFilterDateRangeLastSevenDaysLabel": {},
"documentFilterDateRangeLastThreeMonthsLabel": "Poslední 3 měsíce",
"@documentFilterDateRangeLastThreeMonthsLabel": {},
"documentFilterDateRangeLastYearLabel": "Minulý rok",
"@documentFilterDateRangeLastYearLabel": {},
"documentFilterQueryOptionsAsnLabel": "ASČ", "documentFilterQueryOptionsAsnLabel": "ASČ",
"@documentFilterQueryOptionsAsnLabel": {}, "@documentFilterQueryOptionsAsnLabel": {},
"documentFilterQueryOptionsExtendedLabel": "Prodloužené", "documentFilterQueryOptionsExtendedLabel": "Prodloužené",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {}, "@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "NAHLÁSIT", "errorReportLabel": "NAHLÁSIT",
"@errorReportLabel": {}, "@errorReportLabel": {},
"extendedDateRangePickerAfterLabel": "",
"@extendedDateRangePickerAfterLabel": {},
"extendedDateRangePickerBeforeLabel": "",
"@extendedDateRangePickerBeforeLabel": {},
"extendedDateRangePickerDayText": "{count, plural, other{}}",
"@extendedDateRangePickerDayText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerFromLabel": "Od",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{} few{} many{} other{Posledních 7 dní}}",
"@extendedDateRangePickerLastDaysLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastMonthsLabel": "{count, plural, zero{} one{} few{} many{} other{Minulý měsíc}}",
"@extendedDateRangePickerLastMonthsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastText": "",
"@extendedDateRangePickerLastText": {},
"extendedDateRangePickerLastWeeksLabel": "{count, plural, other{}}",
"@extendedDateRangePickerLastWeeksLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastYearsLabel": "{count, plural, zero{} one{} few{} many{} other{Minulý rok}}",
"@extendedDateRangePickerLastYearsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerMonthText": "{count, plural, other{}}",
"@extendedDateRangePickerMonthText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerToLabel": "Do",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, other{}}",
"@extendedDateRangePickerWeekText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerYearText": "{count, plural, other{}}",
"@extendedDateRangePickerYearText": {
"placeholders": {
"count": {}
}
},
"genericActionCancelLabel": "Zrušit", "genericActionCancelLabel": "Zrušit",
"@genericActionCancelLabel": {}, "@genericActionCancelLabel": {},
"genericActionCreateLabel": "Vytvořit", "genericActionCreateLabel": "Vytvořit",
@@ -408,7 +454,7 @@
"@savedViewShowOnDashboardLabel": {}, "@savedViewShowOnDashboardLabel": {},
"savedViewsLabel": "Uložené náhledy", "savedViewsLabel": "Uložené náhledy",
"@savedViewsLabel": {}, "@savedViewsLabel": {},
"scannerPageImagePreviewTitle": "", "scannerPageImagePreviewTitle": "Sken",
"@scannerPageImagePreviewTitle": {}, "@scannerPageImagePreviewTitle": {},
"serverInformationPaperlessVersionText": "Verze Paperless serveru", "serverInformationPaperlessVersionText": "Verze Paperless serveru",
"@serverInformationPaperlessVersionText": {}, "@serverInformationPaperlessVersionText": {},

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {}, "@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Anwenden", "documentFilterApplyFilterLabel": "Anwenden",
"@documentFilterApplyFilterLabel": {}, "@documentFilterApplyFilterLabel": {},
"documentFilterDateRangeFieldEndLabel": "Bis",
"@documentFilterDateRangeFieldEndLabel": {},
"documentFilterDateRangeFieldStartLabel": "Von",
"@documentFilterDateRangeFieldStartLabel": {},
"documentFilterDateRangeLastMonthLabel": "Letzter Monat",
"@documentFilterDateRangeLastMonthLabel": {},
"documentFilterDateRangeLastSevenDaysLabel": "Letzte 7 Tage",
"@documentFilterDateRangeLastSevenDaysLabel": {},
"documentFilterDateRangeLastThreeMonthsLabel": "Letzte 3 Monate",
"@documentFilterDateRangeLastThreeMonthsLabel": {},
"documentFilterDateRangeLastYearLabel": "Letztes Jahr",
"@documentFilterDateRangeLastYearLabel": {},
"documentFilterQueryOptionsAsnLabel": "ASN", "documentFilterQueryOptionsAsnLabel": "ASN",
"@documentFilterQueryOptionsAsnLabel": {}, "@documentFilterQueryOptionsAsnLabel": {},
"documentFilterQueryOptionsExtendedLabel": "Erweitert", "documentFilterQueryOptionsExtendedLabel": "Erweitert",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {}, "@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "MELDEN", "errorReportLabel": "MELDEN",
"@errorReportLabel": {}, "@errorReportLabel": {},
"extendedDateRangePickerAfterLabel": "Nach",
"@extendedDateRangePickerAfterLabel": {},
"extendedDateRangePickerBeforeLabel": "Vor",
"@extendedDateRangePickerBeforeLabel": {},
"extendedDateRangePickerDayText": "{count, plural, zero{} one{Tag} other{Tage}}",
"@extendedDateRangePickerDayText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerFromLabel": "Von",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Gestern} other{Letzte {count} Tage}}",
"@extendedDateRangePickerLastDaysLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastMonthsLabel": "{count, plural, zero{} one{Letzter Monat} other{Letzte {count} Monate}}",
"@extendedDateRangePickerLastMonthsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastText": "Letzte",
"@extendedDateRangePickerLastText": {},
"extendedDateRangePickerLastWeeksLabel": "{count, plural, zero{} one{Letzte Woche} other{Letzte {count} Wochen}}",
"@extendedDateRangePickerLastWeeksLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastYearsLabel": "{count, plural, zero{} one{Letztes Jahr} other{Letzte {count} Jahre}}",
"@extendedDateRangePickerLastYearsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerMonthText": "{count, plural, zero{} one{Monat} other{Monate}}",
"@extendedDateRangePickerMonthText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerToLabel": "Bis",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{Woche} other{Wochen}}",
"@extendedDateRangePickerWeekText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerYearText": "{count, plural, zero{} one{Jahr} other{Jahre}}",
"@extendedDateRangePickerYearText": {
"placeholders": {
"count": {}
}
},
"genericActionCancelLabel": "Abbrechen", "genericActionCancelLabel": "Abbrechen",
"@genericActionCancelLabel": {}, "@genericActionCancelLabel": {},
"genericActionCreateLabel": "Erstellen", "genericActionCreateLabel": "Erstellen",

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {}, "@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Apply", "documentFilterApplyFilterLabel": "Apply",
"@documentFilterApplyFilterLabel": {}, "@documentFilterApplyFilterLabel": {},
"documentFilterDateRangeFieldEndLabel": "To",
"@documentFilterDateRangeFieldEndLabel": {},
"documentFilterDateRangeFieldStartLabel": "From",
"@documentFilterDateRangeFieldStartLabel": {},
"documentFilterDateRangeLastMonthLabel": "Last Month",
"@documentFilterDateRangeLastMonthLabel": {},
"documentFilterDateRangeLastSevenDaysLabel": "Last 7 Days",
"@documentFilterDateRangeLastSevenDaysLabel": {},
"documentFilterDateRangeLastThreeMonthsLabel": "Last 3 Months",
"@documentFilterDateRangeLastThreeMonthsLabel": {},
"documentFilterDateRangeLastYearLabel": "Last Year",
"@documentFilterDateRangeLastYearLabel": {},
"documentFilterQueryOptionsAsnLabel": "ASN", "documentFilterQueryOptionsAsnLabel": "ASN",
"@documentFilterQueryOptionsAsnLabel": {}, "@documentFilterQueryOptionsAsnLabel": {},
"documentFilterQueryOptionsExtendedLabel": "Extended", "documentFilterQueryOptionsExtendedLabel": "Extended",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {}, "@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "REPORT", "errorReportLabel": "REPORT",
"@errorReportLabel": {}, "@errorReportLabel": {},
"extendedDateRangePickerAfterLabel": "After",
"@extendedDateRangePickerAfterLabel": {},
"extendedDateRangePickerBeforeLabel": "Before",
"@extendedDateRangePickerBeforeLabel": {},
"extendedDateRangePickerDayText": "{count, plural, zero{} one{day} other{days}}",
"@extendedDateRangePickerDayText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerFromLabel": "From",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Yesterday} other{Last {count} days}}",
"@extendedDateRangePickerLastDaysLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastMonthsLabel": "{count, plural, zero{} one{Last month} other{Last {count} months}}",
"@extendedDateRangePickerLastMonthsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastText": "Last",
"@extendedDateRangePickerLastText": {},
"extendedDateRangePickerLastWeeksLabel": "{count, plural, zero{} one{Last week} other{Last {count} weeks}}",
"@extendedDateRangePickerLastWeeksLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastYearsLabel": "{count, plural, zero{} one{Last year} other{Last {count} years}}",
"@extendedDateRangePickerLastYearsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerMonthText": "{count, plural, zero{} one{month} other{months}}",
"@extendedDateRangePickerMonthText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerToLabel": "To",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{week} other{weeks}}",
"@extendedDateRangePickerWeekText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerYearText": "{count, plural, zero{} one{year} other{years}}",
"@extendedDateRangePickerYearText": {
"placeholders": {
"count": {}
}
},
"genericActionCancelLabel": "Cancel", "genericActionCancelLabel": "Cancel",
"@genericActionCancelLabel": {}, "@genericActionCancelLabel": {},
"genericActionCreateLabel": "Create", "genericActionCreateLabel": "Create",

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@@ -1,14 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:paperless_api/src/constants.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/models/query_parameters/asn_query.dart';
import 'package:paperless_api/src/models/query_parameters/correspondent_query.dart';
import 'package:paperless_api/src/models/query_parameters/date_range_query.dart';
import 'package:paperless_api/src/models/query_parameters/document_type_query.dart';
import 'package:paperless_api/src/models/query_parameters/query_type.dart';
import 'package:paperless_api/src/models/query_parameters/sort_field.dart';
import 'package:paperless_api/src/models/query_parameters/sort_order.dart';
import 'package:paperless_api/src/models/query_parameters/storage_path_query.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query.dart';
class DocumentFilter extends Equatable { class DocumentFilter extends Equatable {
static const _oneDay = Duration(days: 1); static const _oneDay = Duration(days: 1);
@@ -23,23 +14,24 @@ class DocumentFilter extends Equatable {
final int pageSize; final int pageSize;
final int page; final int page;
final DocumentTypeQuery documentType; final IdQueryParameter documentType;
final CorrespondentQuery correspondent; final IdQueryParameter correspondent;
final StoragePathQuery storagePath; final IdQueryParameter storagePath;
final AsnQuery asnQuery; final IdQueryParameter asnQuery;
final TagsQuery tags; final TagsQuery tags;
final SortField sortField; final SortField sortField;
final SortOrder sortOrder; final SortOrder sortOrder;
final DateRangeQuery added;
final DateRangeQuery created; final DateRangeQuery created;
final DateRangeQuery added;
final DateRangeQuery modified;
final QueryType queryType; final QueryType queryType;
final String? queryText; final String? queryText;
const DocumentFilter({ const DocumentFilter({
this.documentType = const DocumentTypeQuery.unset(), this.documentType = const IdQueryParameter.unset(),
this.correspondent = const CorrespondentQuery.unset(), this.correspondent = const IdQueryParameter.unset(),
this.storagePath = const StoragePathQuery.unset(), this.storagePath = const IdQueryParameter.unset(),
this.asnQuery = const AsnQuery.unset(), this.asnQuery = const IdQueryParameter.unset(),
this.tags = const IdsTagsQuery(), this.tags = const IdsTagsQuery(),
this.sortField = SortField.created, this.sortField = SortField.created,
this.sortOrder = SortOrder.descending, this.sortOrder = SortOrder.descending,
@@ -49,6 +41,7 @@ class DocumentFilter extends Equatable {
this.queryText, this.queryText,
this.added = const UnsetDateRangeQuery(), this.added = const UnsetDateRangeQuery(),
this.created = const UnsetDateRangeQuery(), this.created = const UnsetDateRangeQuery(),
this.modified = const UnsetDateRangeQuery(),
}); });
Map<String, String> toQueryParameters() { Map<String, String> toQueryParameters() {
@@ -57,13 +50,14 @@ class DocumentFilter extends Equatable {
'page_size': pageSize.toString(), 'page_size': pageSize.toString(),
}; };
params.addAll(documentType.toQueryParameter()); params.addAll(documentType.toQueryParameter('document_type'));
params.addAll(correspondent.toQueryParameter()); params.addAll(correspondent.toQueryParameter('correspondent'));
params.addAll(storagePath.toQueryParameter('storage_path'));
params.addAll(asnQuery.toQueryParameter('archive_serial_number'));
params.addAll(tags.toQueryParameter()); params.addAll(tags.toQueryParameter());
params.addAll(storagePath.toQueryParameter()); params.addAll(added.toQueryParameter(DateRangeQueryField.added));
params.addAll(asnQuery.toQueryParameter()); params.addAll(created.toQueryParameter(DateRangeQueryField.created));
params.addAll(added.toQueryParameter()); params.addAll(modified.toQueryParameter(DateRangeQueryField.modified));
params.addAll(created.toQueryParameter());
//TODO: Rework when implementing extended queries. //TODO: Rework when implementing extended queries.
if (queryText?.isNotEmpty ?? false) { if (queryText?.isNotEmpty ?? false) {
params.putIfAbsent(queryType.queryParam, () => queryText!); params.putIfAbsent(queryType.queryParam, () => queryText!);
@@ -84,15 +78,16 @@ class DocumentFilter extends Equatable {
int? pageSize, int? pageSize,
int? page, int? page,
bool? onlyNoDocumentType, bool? onlyNoDocumentType,
DocumentTypeQuery? documentType, IdQueryParameter? documentType,
CorrespondentQuery? correspondent, IdQueryParameter? correspondent,
StoragePathQuery? storagePath, IdQueryParameter? storagePath,
AsnQuery? asnQuery, IdQueryParameter? asnQuery,
TagsQuery? tags, TagsQuery? tags,
SortField? sortField, SortField? sortField,
SortOrder? sortOrder, SortOrder? sortOrder,
DateRangeQuery? added, DateRangeQuery? added,
DateRangeQuery? created, DateRangeQuery? created,
DateRangeQuery? modified,
QueryType? queryType, QueryType? queryType,
String? queryText, String? queryText,
}) { }) {
@@ -105,11 +100,12 @@ class DocumentFilter extends Equatable {
tags: tags ?? this.tags, tags: tags ?? this.tags,
sortField: sortField ?? this.sortField, sortField: sortField ?? this.sortField,
sortOrder: sortOrder ?? this.sortOrder, sortOrder: sortOrder ?? this.sortOrder,
added: added ?? this.added,
queryType: queryType ?? this.queryType, queryType: queryType ?? this.queryType,
queryText: queryText ?? this.queryText, queryText: queryText ?? this.queryText,
asnQuery: asnQuery ?? this.asnQuery, asnQuery: asnQuery ?? this.asnQuery,
added: added ?? this.added,
created: created ?? this.created, created: created ?? this.created,
modified: modified ?? this.modified,
); );
} }
@@ -139,8 +135,9 @@ class DocumentFilter extends Equatable {
correspondent != initial.correspondent, correspondent != initial.correspondent,
storagePath != initial.storagePath, storagePath != initial.storagePath,
tags != initial.tags, tags != initial.tags,
(added != initial.added), added != initial.added,
(created != initial.created), created != initial.created,
modified != initial.modified,
asnQuery != initial.asnQuery, asnQuery != initial.asnQuery,
(queryType != initial.queryType || queryText != initial.queryText), (queryType != initial.queryType || queryText != initial.queryText),
].fold(0, (previousValue, element) => previousValue += element ? 1 : 0); ].fold(0, (previousValue, element) => previousValue += element ? 1 : 0);
@@ -158,6 +155,7 @@ class DocumentFilter extends Equatable {
sortOrder, sortOrder,
added, added,
created, created,
modified,
queryType, queryType,
queryText, queryText,
]; ];

View File

@@ -1,12 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.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/models/document_filter.dart';
import 'package:paperless_api/src/models/query_parameters/correspondent_query.dart';
import 'package:paperless_api/src/models/query_parameters/date_range_query.dart';
import 'package:paperless_api/src/models/query_parameters/document_type_query.dart';
import 'package:paperless_api/src/models/query_parameters/query_type.dart';
import 'package:paperless_api/src/models/query_parameters/storage_path_query.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query.dart';
class FilterRule with EquatableMixin { class FilterRule with EquatableMixin {
static const int titleRule = 0; static const int titleRule = 0;
@@ -57,9 +51,6 @@ class FilterRule with EquatableMixin {
} }
DocumentFilter applyToFilter(final DocumentFilter filter) { DocumentFilter applyToFilter(final DocumentFilter filter) {
if (value == null) {
return filter;
}
//TODO: Check in profiling mode if this is inefficient enough to cause stutters... //TODO: Check in profiling mode if this is inefficient enough to cause stutters...
switch (ruleType) { switch (ruleType) {
case titleRule: case titleRule:
@@ -67,20 +58,20 @@ class FilterRule with EquatableMixin {
case documentTypeRule: case documentTypeRule:
return filter.copyWith( return filter.copyWith(
documentType: value == null documentType: value == null
? const DocumentTypeQuery.notAssigned() ? const IdQueryParameter.notAssigned()
: DocumentTypeQuery.fromId(int.parse(value!)), : IdQueryParameter.fromId(int.parse(value!)),
); );
case correspondentRule: case correspondentRule:
return filter.copyWith( return filter.copyWith(
correspondent: value == null correspondent: value == null
? const CorrespondentQuery.notAssigned() ? const IdQueryParameter.notAssigned()
: CorrespondentQuery.fromId(int.parse(value!)), : IdQueryParameter.fromId(int.parse(value!)),
); );
case storagePathRule: case storagePathRule:
return filter.copyWith( return filter.copyWith(
storagePath: value == null storagePath: value == null
? const StoragePathQuery.notAssigned() ? const IdQueryParameter.notAssigned()
: StoragePathQuery.fromId(int.parse(value!)), : IdQueryParameter.fromId(int.parse(value!)),
); );
case hasAnyTag: case hasAnyTag:
return filter.copyWith( return filter.copyWith(
@@ -101,48 +92,69 @@ class FilterRule with EquatableMixin {
.withIdQueriesAdded([ExcludeTagIdQuery(int.parse(value!))]), .withIdQueriesAdded([ExcludeTagIdQuery(int.parse(value!))]),
); );
case createdBeforeRule: case createdBeforeRule:
if (filter.created is FixedDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
created: (filter.created as FixedDateRangeQuery) created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)), .copyWith(before: DateTime.parse(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: created: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
FixedDateRangeQuery.created(before: DateTime.parse(value!)),
); );
} }
case createdAfterRule: case createdAfterRule:
if (filter.created is FixedDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
created: (filter.created as FixedDateRangeQuery) created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)), .copyWith(after: DateTime.parse(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: FixedDateRangeQuery.created(after: DateTime.parse(value!)), created: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
); );
} }
case addedBeforeRule: case addedBeforeRule:
if (filter.added is FixedDateRangeQuery) { if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
added: (filter.added as FixedDateRangeQuery) added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)), .copyWith(before: DateTime.parse(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: FixedDateRangeQuery.added(before: DateTime.parse(value!)), added: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
); );
} }
case addedAfterRule: case addedAfterRule:
if (filter.added is FixedDateRangeQuery) { if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
added: (filter.added as FixedDateRangeQuery) added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)), .copyWith(after: DateTime.parse(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: FixedDateRangeQuery.added(after: DateTime.parse(value!)), added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
);
}
case modifiedBeforeRule:
if (filter.modified is AbsoluteDateRangeQuery) {
return filter.copyWith(
modified: (filter.modified as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)),
);
} else {
return filter.copyWith(
modified: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
);
}
case modifiedAfterRule:
if (filter.modified is AbsoluteDateRangeQuery) {
return filter.copyWith(
modified: (filter.modified as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)),
);
} else {
return filter.copyWith(
added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
); );
} }
case titleAndContentRule: case titleAndContentRule:
@@ -171,7 +183,7 @@ class FilterRule with EquatableMixin {
switch (field) { switch (field) {
case 'created': case 'created':
newFilter = newFilter.copyWith( newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.created( created: RelativeDateRangeQuery(
n, n,
DateRangeUnit.values.byName(unit), DateRangeUnit.values.byName(unit),
), ),
@@ -179,7 +191,7 @@ class FilterRule with EquatableMixin {
break; break;
case 'added': case 'added':
newFilter = newFilter.copyWith( newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.added( added: RelativeDateRangeQuery(
n, n,
DateRangeUnit.values.byName(unit), DateRangeUnit.values.byName(unit),
), ),
@@ -187,7 +199,7 @@ class FilterRule with EquatableMixin {
break; break;
case 'modified': case 'modified':
newFilter = newFilter.copyWith( newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.modified( modified: RelativeDateRangeQuery(
n, n,
DateRangeUnit.values.byName(unit), DateRangeUnit.values.byName(unit),
), ),
@@ -262,7 +274,7 @@ class FilterRule with EquatableMixin {
// Parse created at // Parse created at
final created = filter.created; final created = filter.created;
if (created is FixedDateRangeQuery) { if (created is AbsoluteDateRangeQuery) {
if (created.after != null) { if (created.after != null) {
filterRules.add( filterRules.add(
FilterRule(createdAfterRule, apiDateFormat.format(created.after!)), FilterRule(createdAfterRule, apiDateFormat.format(created.after!)),
@@ -273,15 +285,16 @@ class FilterRule with EquatableMixin {
FilterRule(createdBeforeRule, apiDateFormat.format(created.before!)), FilterRule(createdBeforeRule, apiDateFormat.format(created.before!)),
); );
} }
} else if (created is LastNDateRangeQuery) { } else if (created is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule(extendedRule, created.toQueryParameter().values.first), FilterRule(extendedRule,
created.toQueryParameter(DateRangeQueryField.created).values.first),
); );
} }
// Parse added at // Parse added at
final added = filter.added; final added = filter.added;
if (added is FixedDateRangeQuery) { if (added is AbsoluteDateRangeQuery) {
if (added.after != null) { if (added.after != null) {
filterRules.add( filterRules.add(
FilterRule(addedAfterRule, apiDateFormat.format(added.after!)), FilterRule(addedAfterRule, apiDateFormat.format(added.after!)),
@@ -292,15 +305,16 @@ class FilterRule with EquatableMixin {
FilterRule(addedBeforeRule, apiDateFormat.format(added.before!)), FilterRule(addedBeforeRule, apiDateFormat.format(added.before!)),
); );
} }
} else if (added is LastNDateRangeQuery) { } else if (added is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule(extendedRule, added.toQueryParameter().values.first), FilterRule(extendedRule,
added.toQueryParameter(DateRangeQueryField.added).values.first),
); );
} }
// Parse modified at // Parse modified at
final modified = filter.added; final modified = filter.modified;
if (modified is FixedDateRangeQuery) { if (modified is AbsoluteDateRangeQuery) {
if (modified.after != null) { if (modified.after != null) {
filterRules.add( filterRules.add(
FilterRule(modifiedAfterRule, apiDateFormat.format(modified.after!)), FilterRule(modifiedAfterRule, apiDateFormat.format(modified.after!)),
@@ -312,9 +326,14 @@ class FilterRule with EquatableMixin {
modifiedBeforeRule, apiDateFormat.format(modified.before!)), modifiedBeforeRule, apiDateFormat.format(modified.before!)),
); );
} }
} else if (modified is LastNDateRangeQuery) { } else if (modified is RelativeDateRangeQuery) {
filterRules.add( filterRules.add(
FilterRule(extendedRule, modified.toQueryParameter().values.first), FilterRule(
extendedRule,
modified
.toQueryParameter(DateRangeQueryField.modified)
.values
.first),
); );
} }

View File

@@ -4,15 +4,12 @@ export 'labels/label_model.dart';
export 'labels/matching_algorithm.dart'; export 'labels/matching_algorithm.dart';
export 'labels/storage_path_model.dart'; export 'labels/storage_path_model.dart';
export 'labels/tag_model.dart'; export 'labels/tag_model.dart';
export 'query_parameters/asn_query.dart';
export 'query_parameters/correspondent_query.dart';
export 'query_parameters/document_type_query.dart';
export 'query_parameters/id_query_parameter.dart'; 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/storage_path_query.dart';
export 'query_parameters/tags_query.dart'; export 'query_parameters/tags_query.dart';
export 'query_parameters/date_range_query.dart';
export 'bulk_edit_model.dart'; export 'bulk_edit_model.dart';
export 'document_filter.dart'; export 'document_filter.dart';
export 'document_meta_data_model.dart'; export 'document_meta_data_model.dart';

View File

@@ -1,11 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/id_query_parameter.dart';
class AsnQuery extends IdQueryParameter {
const AsnQuery.fromId(super.id) : super.fromId();
const AsnQuery.unset() : super.unset();
const AsnQuery.notAssigned() : super.notAssigned();
const AsnQuery.anyAssigned() : super.anyAssigned();
@override
String get queryParameterKey => 'archive_serial_number';
}

View File

@@ -1,11 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/id_query_parameter.dart';
class CorrespondentQuery extends IdQueryParameter {
const CorrespondentQuery.fromId(super.id) : super.fromId();
const CorrespondentQuery.unset() : super.unset();
const CorrespondentQuery.notAssigned() : super.notAssigned();
const CorrespondentQuery.anyAssigned() : super.anyAssigned();
@override
String get queryParameterKey => 'correspondent';
}

View File

@@ -3,7 +3,7 @@ import 'package:paperless_api/src/constants.dart';
abstract class DateRangeQuery extends Equatable { abstract class DateRangeQuery extends Equatable {
const DateRangeQuery(); const DateRangeQuery();
Map<String, String> toQueryParameter(); Map<String, String> toQueryParameter(DateRangeQueryField field);
} }
class UnsetDateRangeQuery extends DateRangeQuery { class UnsetDateRangeQuery extends DateRangeQuery {
@@ -12,87 +12,74 @@ class UnsetDateRangeQuery extends DateRangeQuery {
List<Object?> get props => []; List<Object?> get props => [];
@override @override
Map<String, String> toQueryParameter() => const {}; Map<String, String> toQueryParameter(DateRangeQueryField field) => const {};
} }
class FixedDateRangeQuery extends DateRangeQuery { class AbsoluteDateRangeQuery extends DateRangeQuery {
final String _querySuffix;
final DateTime? after; final DateTime? after;
final DateTime? before; final DateTime? before;
const FixedDateRangeQuery._(this._querySuffix, {this.after, this.before}) const AbsoluteDateRangeQuery({this.after, this.before});
: assert(after != null || before != null);
const FixedDateRangeQuery.created({DateTime? after, DateTime? before})
: this._('created', after: after, before: before);
const FixedDateRangeQuery.added({DateTime? after, DateTime? before})
: this._('added', after: after, before: before);
const FixedDateRangeQuery.modified({DateTime? after, DateTime? before})
: this._('modified', after: after, before: before);
@override @override
List<Object?> get props => [_querySuffix, after, before]; List<Object?> get props => [after, before];
@override @override
Map<String, String> toQueryParameter() { Map<String, String> toQueryParameter(DateRangeQueryField field) {
final Map<String, String> params = {}; final Map<String, String> params = {};
// Add/subtract one day in the following because paperless uses gt/lt not gte/lte // Add/subtract one day in the following because paperless uses gt/lt not gte/lte
if (after != null) { if (after != null) {
params.putIfAbsent('${_querySuffix}__date__gt', params.putIfAbsent('${field.name}__date__gt',
() => apiDateFormat.format(after!.subtract(const Duration(days: 1)))); () => apiDateFormat.format(after!.subtract(const Duration(days: 1))));
} }
if (before != null) { if (before != null) {
params.putIfAbsent('${_querySuffix}__date__lt', params.putIfAbsent('${field.name}__date__lt',
() => apiDateFormat.format(before!.add(const Duration(days: 1)))); () => apiDateFormat.format(before!.add(const Duration(days: 1))));
} }
return params; return params;
} }
FixedDateRangeQuery copyWith({ AbsoluteDateRangeQuery copyWith({
DateTime? before, DateTime? before,
DateTime? after, DateTime? after,
}) { }) {
return FixedDateRangeQuery._( return AbsoluteDateRangeQuery(
_querySuffix,
before: before ?? this.before, before: before ?? this.before,
after: after ?? this.after, after: after ?? this.after,
); );
} }
} }
class LastNDateRangeQuery extends DateRangeQuery { class RelativeDateRangeQuery extends DateRangeQuery {
final int offset;
final DateRangeUnit unit; final DateRangeUnit unit;
final int n;
final String _field;
const LastNDateRangeQuery._( const RelativeDateRangeQuery(
this._field, { this.offset,
required this.n, this.unit,
required this.unit, );
});
const LastNDateRangeQuery.created(int n, DateRangeUnit unit)
: this._('created', unit: unit, n: n);
const LastNDateRangeQuery.added(int n, DateRangeUnit unit)
: this._('added', unit: unit, n: n);
const LastNDateRangeQuery.modified(int n, DateRangeUnit unit)
: this._('modified', unit: unit, n: n);
@override @override
// TODO: implement props List<Object?> get props => [offset, unit];
List<Object?> get props => [_field, n, unit];
@override @override
Map<String, String> toQueryParameter() { Map<String, String> toQueryParameter(DateRangeQueryField field) {
return { return {
'query': '[$_field:$n ${unit.name} to now]', 'query': '[${field.name}:$offset ${unit.name} to now]',
}; };
} }
RelativeDateRangeQuery copyWith({
int? offset,
DateRangeUnit? unit,
}) {
return RelativeDateRangeQuery(
offset ?? this.offset,
unit ?? this.unit,
);
}
} }
enum DateRangeUnit { enum DateRangeUnit {
@@ -101,3 +88,9 @@ enum DateRangeUnit {
month, month,
year; year;
} }
enum DateRangeQueryField {
created,
added,
modified;
}

View File

@@ -1,11 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/id_query_parameter.dart';
class DocumentTypeQuery extends IdQueryParameter {
const DocumentTypeQuery.fromId(super.id) : super.fromId();
const DocumentTypeQuery.unset() : super.unset();
const DocumentTypeQuery.notAssigned() : super.notAssigned();
const DocumentTypeQuery.anyAssigned() : super.anyAssigned();
@override
String get queryParameterKey => 'document_type';
}

View File

@@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
abstract class IdQueryParameter extends Equatable { class IdQueryParameter extends Equatable {
final int? _assignmentStatus; final int? _assignmentStatus;
final int? _id; final int? _id;
@@ -28,16 +28,14 @@ abstract class IdQueryParameter extends Equatable {
int? get id => _id; int? get id => _id;
String get queryParameterKey; Map<String, String> toQueryParameter(String field) {
Map<String, String> toQueryParameter() {
final Map<String, String> params = {}; final Map<String, String> params = {};
if (onlyNotAssigned || onlyAssigned) { if (onlyNotAssigned || onlyAssigned) {
params.putIfAbsent( params.putIfAbsent(
'${queryParameterKey}__isnull', () => _assignmentStatus!.toString()); '${field}__isnull', () => _assignmentStatus!.toString());
} }
if (isSet) { if (isSet) {
params.putIfAbsent("${queryParameterKey}__id", () => id!.toString()); params.putIfAbsent("${field}__id", () => id!.toString());
} }
return params; return params;
} }

View File

@@ -1,11 +0,0 @@
import 'package:paperless_api/src/models/query_parameters/id_query_parameter.dart';
class StoragePathQuery extends IdQueryParameter {
const StoragePathQuery.fromId(super.id) : super.fromId();
const StoragePathQuery.unset() : super.unset();
const StoragePathQuery.notAssigned() : super.notAssigned();
const StoragePathQuery.anyAssigned() : super.anyAssigned();
@override
String get queryParameterKey => 'storage_path';
}

View File

@@ -4,6 +4,7 @@ import 'dart:math';
import 'package:http/src/boundary_characters.dart'; import 'package:http/src/boundary_characters.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart'; import 'package:http/http.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/models/bulk_edit_model.dart'; import 'package:paperless_api/src/models/bulk_edit_model.dart';
import 'package:paperless_api/src/models/document_filter.dart'; import 'package:paperless_api/src/models/document_filter.dart';
@@ -11,7 +12,6 @@ import 'package:paperless_api/src/models/document_meta_data_model.dart';
import 'package:paperless_api/src/models/document_model.dart'; import 'package:paperless_api/src/models/document_model.dart';
import 'package:paperless_api/src/models/paged_search_result.dart'; import 'package:paperless_api/src/models/paged_search_result.dart';
import 'package:paperless_api/src/models/paperless_server_exception.dart'; import 'package:paperless_api/src/models/paperless_server_exception.dart';
import 'package:paperless_api/src/models/query_parameters/asn_query.dart';
import 'package:paperless_api/src/models/query_parameters/sort_field.dart'; import 'package:paperless_api/src/models/query_parameters/sort_field.dart';
import 'package:paperless_api/src/models/query_parameters/sort_order.dart'; import 'package:paperless_api/src/models/query_parameters/sort_order.dart';
import 'package:paperless_api/src/models/similar_document_model.dart'; import 'package:paperless_api/src/models/similar_document_model.dart';
@@ -139,7 +139,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
final filterParams = filter.toQueryParameters(); final filterParams = filter.toQueryParameters();
final response = await baseClient.get( final response = await baseClient.get(
Uri( Uri(
path: "/api/documents/?$filterParams", path: "/api/documents/",
queryParameters: filterParams, queryParameters: filterParams,
), ),
); );
@@ -190,7 +190,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
const DocumentFilter asnQueryFilter = DocumentFilter( const DocumentFilter asnQueryFilter = DocumentFilter(
sortField: SortField.archiveSerialNumber, sortField: SortField.archiveSerialNumber,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
asnQuery: AsnQuery.anyAssigned(), asnQuery: IdQueryParameter.anyAssigned(),
page: 1, page: 1,
pageSize: 1, pageSize: 1,
); );

View File

@@ -1,14 +1,5 @@
import 'package:paperless_api/src/models/document_filter.dart';
import 'package:paperless_api/src/models/filter_rule_model.dart';
import 'package:paperless_api/src/models/query_parameters/correspondent_query.dart';
import 'package:paperless_api/src/models/query_parameters/document_type_query.dart';
import 'package:paperless_api/src/models/query_parameters/query_type.dart';
import 'package:paperless_api/src/models/query_parameters/sort_field.dart';
import 'package:paperless_api/src/models/query_parameters/sort_order.dart';
import 'package:paperless_api/src/models/query_parameters/storage_path_query.dart';
import 'package:paperless_api/src/models/query_parameters/tags_query.dart';
import 'package:paperless_api/src/models/saved_view_model.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:paperless_api/paperless_api.dart';
void main() { void main() {
group('Validate parsing logic from [SavedView] to [DocumentFilter]:', () { group('Validate parsing logic from [SavedView] to [DocumentFilter]:', () {
@@ -74,10 +65,10 @@ void main() {
}).toDocumentFilter(), }).toDocumentFilter(),
equals( equals(
DocumentFilter.initial.copyWith( DocumentFilter.initial.copyWith(
correspondent: const CorrespondentQuery.fromId(42), correspondent: const IdQueryParameter.fromId(42),
documentType: const DocumentTypeQuery.fromId(69), documentType: const IdQueryParameter.fromId(69),
storagePath: const StoragePathQuery.fromId(14), storagePath: const IdQueryParameter.fromId(14),
tags: IdsTagsQuery( tags: const IdsTagsQuery(
[ [
IncludeTagIdQuery(1), IncludeTagIdQuery(1),
IncludeTagIdQuery(2), IncludeTagIdQuery(2),
@@ -85,10 +76,14 @@ void main() {
ExcludeTagIdQuery(4), ExcludeTagIdQuery(4),
], ],
), ),
createdDateBefore: DateTime.parse("2022-10-27"), created: AbsoluteDateRangeQuery(
createdDateAfter: DateTime.parse("2022-09-27"), before: DateTime.parse("2022-10-27"),
addedDateBefore: DateTime.parse("2022-09-26"), after: DateTime.parse("2022-09-27"),
addedDateAfter: DateTime.parse("2000-01-01"), ),
added: AbsoluteDateRangeQuery(
before: DateTime.parse("2022-09-26"),
after: DateTime.parse("2000-01-01"),
),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
queryText: "Never gonna give you up", queryText: "Never gonna give you up",
@@ -114,8 +109,7 @@ void main() {
}); });
test('Values are correctly parsed if not assigned.', () { test('Values are correctly parsed if not assigned.', () {
expect( final actual = SavedView.fromJson({
SavedView.fromJson({
"id": 1, "id": 1,
"name": "test_name", "name": "test_name",
"show_on_dashboard": false, "show_on_dashboard": false,
@@ -140,13 +134,16 @@ void main() {
'value': null, 'value': null,
}, },
], ],
}).toDocumentFilter(), }).toDocumentFilter();
equals(DocumentFilter.initial.copyWith( final expected = DocumentFilter.initial.copyWith(
correspondent: const CorrespondentQuery.notAssigned(), correspondent: const IdQueryParameter.notAssigned(),
documentType: const DocumentTypeQuery.notAssigned(), documentType: const IdQueryParameter.notAssigned(),
storagePath: const StoragePathQuery.notAssigned(), storagePath: const IdQueryParameter.notAssigned(),
tags: const OnlyNotAssignedTagsQuery(), tags: const OnlyNotAssignedTagsQuery(),
)), );
expect(
actual,
equals(expected),
); );
}); });
}); });
@@ -156,10 +153,10 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
DocumentFilter( DocumentFilter(
correspondent: const CorrespondentQuery.fromId(1), correspondent: const IdQueryParameter.fromId(1),
documentType: const DocumentTypeQuery.fromId(2), documentType: const IdQueryParameter.fromId(2),
storagePath: const StoragePathQuery.fromId(3), storagePath: const IdQueryParameter.fromId(3),
tags: IdsTagsQuery([ tags: const IdsTagsQuery([
IncludeTagIdQuery(4), IncludeTagIdQuery(4),
IncludeTagIdQuery(5), IncludeTagIdQuery(5),
ExcludeTagIdQuery(6), ExcludeTagIdQuery(6),
@@ -168,10 +165,14 @@ void main() {
]), ]),
sortField: SortField.added, sortField: SortField.added,
sortOrder: SortOrder.ascending, sortOrder: SortOrder.ascending,
addedDateAfter: DateTime.parse("2020-01-01"), created: AbsoluteDateRangeQuery(
addedDateBefore: DateTime.parse("2020-03-01"), before: DateTime.parse("2020-04-01"),
createdDateAfter: DateTime.parse("2020-02-01"), after: DateTime.parse("2020-02-01"),
createdDateBefore: DateTime.parse("2020-04-01"), ),
added: AbsoluteDateRangeQuery(
before: DateTime.parse("2020-03-01"),
after: DateTime.parse("2020-01-01"),
),
queryText: "Never gonna let you down", queryText: "Never gonna let you down",
queryType: QueryType.title, queryType: QueryType.title,
), ),
@@ -210,16 +211,14 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
const DocumentFilter( const DocumentFilter(
correspondent: CorrespondentQuery.unset(), correspondent: IdQueryParameter.unset(),
documentType: DocumentTypeQuery.unset(), documentType: IdQueryParameter.unset(),
storagePath: StoragePathQuery.unset(), storagePath: IdQueryParameter.unset(),
tags: IdsTagsQuery(), tags: IdsTagsQuery(),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
addedDateAfter: null, added: UnsetDateRangeQuery(),
addedDateBefore: null, created: UnsetDateRangeQuery(),
createdDateAfter: null,
createdDateBefore: null,
queryText: null, queryText: null,
), ),
name: "test_name", name: "test_name",
@@ -243,9 +242,9 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
const DocumentFilter( const DocumentFilter(
correspondent: CorrespondentQuery.notAssigned(), correspondent: IdQueryParameter.notAssigned(),
documentType: DocumentTypeQuery.notAssigned(), documentType: IdQueryParameter.notAssigned(),
storagePath: StoragePathQuery.notAssigned(), storagePath: IdQueryParameter.notAssigned(),
tags: OnlyNotAssignedTagsQuery(), tags: OnlyNotAssignedTagsQuery(),
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.ascending, sortOrder: SortOrder.ascending,