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

View File

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

View File

@@ -121,6 +121,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
expand: false,
snap: true,
initialChildSize: .9,
maxChildSize: .9,
builder: (context, controller) => LabelsBlocProvider(
child: DocumentFilterPanel(
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:intl/intl.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/features/documents/view/widgets/search/query_type_form_field.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,
),
).padded(),
_buildCreatedDateRangePickerFormField(),
_buildAddedDateRangePickerFormField(),
FormBuilderExtendedDateRangePicker(
name: DocumentModel.createdKey,
initialValue: widget.initialFilter.created,
labelText: S.of(context).documentCreatedPropertyLabel,
).padded(),
// _buildCreatedDateRangePickerFormField(),
// _buildAddedDateRangePickerFormField(),
_buildCorrespondentFormField().padded(),
_buildDocumentTypeFormField().padded(),
_buildStoragePathFormField().padded(),
@@ -129,14 +135,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildDocumentTypeFormField() {
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>(
return LabelFormField<DocumentType>(
formBuilderState: _formKey.currentState,
name: fkDocumentType,
state: state.labels,
label: S.of(context).documentDocumentTypePropertyLabel,
labelOptions: state.labels,
textFieldLabel: S.of(context).documentDocumentTypePropertyLabel,
initialValue: widget.initialFilter.documentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
prefixIcon: const Icon(Icons.description_outlined),
);
},
@@ -146,14 +150,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildCorrespondentFormField() {
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>(
return LabelFormField<Correspondent>(
formBuilderState: _formKey.currentState,
name: fkCorrespondent,
state: state.labels,
label: S.of(context).documentCorrespondentPropertyLabel,
labelOptions: state.labels,
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
initialValue: widget.initialFilter.correspondent,
queryParameterIdBuilder: CorrespondentQuery.fromId,
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
prefixIcon: const Icon(Icons.person_outline),
);
},
@@ -163,14 +165,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
Widget _buildStoragePathFormField() {
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>(
return LabelFormField<StoragePath>(
formBuilderState: _formKey.currentState,
name: fkStoragePath,
state: state.labels,
label: S.of(context).documentStoragePathPropertyLabel,
labelOptions: state.labels,
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
initialValue: widget.initialFilter.storagePath,
queryParameterIdBuilder: StoragePathQuery.fromId,
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
prefixIcon: const Icon(Icons.folder_outlined),
);
},
@@ -209,187 +209,99 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
);
}
Widget _buildDateRangePickerHelper(String formFieldKey) {
const spacer = SizedBox(width: 8.0);
return SizedBox(
height: 64,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
spacer,
ActionChip(
label: Text(
S.of(context).documentFilterDateRangeLastSevenDaysLabel,
),
onPressed: () {
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateUtils.addDaysToDate(DateTime.now(), -7),
end: DateTime.now(),
),
);
},
),
spacer,
ActionChip(
label: Text(
S.of(context).documentFilterDateRangeLastMonthLabel,
),
onPressed: () {
final now = DateTime.now();
final firstDayOfLastMonth =
DateUtils.addMonthsToMonthDate(now, -1);
_formKey.currentState?.fields[formFieldKey]?.didChange(
DateTimeRange(
start: DateTime(firstDayOfLastMonth.year,
firstDayOfLastMonth.month, now.day),
end: DateTime.now(),
),
);
},
),
spacer,
ActionChip(
label: Text(
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() {
// return Column(
// children: [
// FormBuilderDateRangePicker(
// initialValue: _dateTimeRangeOfNullable(
// widget.initialFilter.createdDateAfter,
// widget.initialFilter.createdDateBefore,
// ),
// // 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(Localizations.localeOf(context).toString()),
// fieldStartLabelText:
// S.of(context).documentFilterDateRangeFieldStartLabel,
// fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel,
// firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
// lastDate: DateTime.now(),
// name: fkCreatedAt,
// decoration: InputDecoration(
// prefixIcon: const Icon(Icons.calendar_month_outlined),
// labelText: S.of(context).documentCreatedPropertyLabel,
// suffixIcon: IconButton(
// icon: const Icon(Icons.clear),
// onPressed: () {
// _formKey.currentState?.fields[fkCreatedAt]?.didChange(null);
// },
// ),
// ),
// ).paddedSymmetrically(horizontal: 8, vertical: 4.0),
// ],
// );
// }
Widget _buildCreatedDateRangePickerFormField() {
return Column(
children: [
FormBuilderDateRangePicker(
initialValue: _dateTimeRangeOfNullable(
widget.initialFilter.createdDateAfter,
widget.initialFilter.createdDateBefore,
),
// 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(Localizations.localeOf(context).toString()),
fieldStartLabelText:
S.of(context).documentFilterDateRangeFieldStartLabel,
fieldEndLabelText: S.of(context).documentFilterDateRangeFieldEndLabel,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now(),
name: fkCreatedAt,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.calendar_month_outlined),
labelText: S.of(context).documentCreatedPropertyLabel,
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null);
},
),
),
).paddedSymmetrically(horizontal: 8, vertical: 4.0),
_buildDateRangePickerHelper(fkCreatedAt),
],
);
}
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),
],
);
}
// 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 {
_formKey.currentState?.save();
@@ -408,19 +320,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
DocumentFilter _assembleFilter() {
final v = _formKey.currentState!.value;
return DocumentFilter(
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end,
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start,
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
created: (v[fkCreatedAt] as DateRangeQuery),
correspondent: v[fkCorrespondent] as IdQueryParameter? ??
DocumentFilter.initial.correspondent,
documentType: v[fkDocumentType] as DocumentTypeQuery? ??
documentType: v[fkDocumentType] as IdQueryParameter? ??
DocumentFilter.initial.documentType,
storagePath: v[fkStoragePath] as StoragePathQuery? ??
storagePath: v[fkStoragePath] as IdQueryParameter? ??
DocumentFilter.initial.storagePath,
tags:
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
queryText: v[fkQuery] as String?,
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end,
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
added: (v[fkAddedAt] as DateRangeQuery),
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
asnQuery: widget.initialFilter.asnQuery,
page: 1,

View File

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

View File

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

View File

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

View File

@@ -126,7 +126,7 @@ class _LabelsPageState extends State<LabelsPage>
),
child: LabelTabView<Correspondent>(
filterBuilder: (label) => DocumentFilter(
correspondent: CorrespondentQuery.fromId(label.id),
correspondent: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditCorrespondentPage,
@@ -146,7 +146,7 @@ class _LabelsPageState extends State<LabelsPage>
),
child: LabelTabView<DocumentType>(
filterBuilder: (label) => DocumentFilter(
documentType: DocumentTypeQuery.fromId(label.id),
documentType: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditDocumentTypePage,
@@ -194,7 +194,7 @@ class _LabelsPageState extends State<LabelsPage>
child: LabelTabView<StoragePath>(
onEdit: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter(
storagePath: StoragePathQuery.fromId(label.id),
storagePath: IdQueryParameter.fromId(label.id),
pageSize: label.documentCount ?? 0,
),
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)
/// [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>
extends StatefulWidget {
class LabelFormField<T extends Label> extends StatefulWidget {
final Widget prefixIcon;
final Map<int, T> state;
final Map<int, T> labelOptions;
final FormBuilderState? formBuilderState;
final IdQueryParameter? initialValue;
final String name;
final String label;
final String textFieldLabel;
final FormFieldValidator? validator;
final Widget Function(String)? labelCreationWidgetBuilder;
final R Function() queryParameterNotAssignedBuilder;
final R Function(int? id) queryParameterIdBuilder;
final Widget Function(String initialName)? labelCreationWidgetBuilder;
final bool notAssignedSelectable;
final void Function(R?)? onChanged;
final void Function(IdQueryParameter?)? onChanged;
const LabelFormField({
Key? key,
required this.name,
required this.state,
required this.labelOptions,
this.validator,
this.initialValue,
required this.label,
required this.textFieldLabel,
this.labelCreationWidgetBuilder,
required this.queryParameterNotAssignedBuilder,
required this.queryParameterIdBuilder,
required this.formBuilderState,
required this.prefixIcon,
this.notAssignedSelectable = true,
@@ -41,11 +36,10 @@ class LabelFormField<T extends Label, R extends IdQueryParameter>
}) : super(key: key);
@override
State<LabelFormField<T, R>> createState() => _LabelFormFieldState<T, R>();
State<LabelFormField<T>> createState() => _LabelFormFieldState<T>();
}
class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
extends State<LabelFormField<T, R>> {
class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
bool _showCreationSuffixIcon = false;
late bool _showClearSuffixIcon;
@@ -54,12 +48,13 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
@override
void initState() {
super.initState();
_showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id);
_showClearSuffixIcon =
widget.labelOptions.containsKey(widget.initialValue?.id);
_textEditingController = TextEditingController(
text: widget.state[widget.initialValue?.id]?.name ?? '',
text: widget.labelOptions[widget.initialValue?.id]?.name ?? '',
)..addListener(() {
setState(() {
_showCreationSuffixIcon = widget.state.values
_showCreationSuffixIcon = widget.labelOptions.values
.where(
(item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(),
@@ -74,7 +69,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
@override
Widget build(BuildContext context) {
final isEnabled = widget.state.values.fold<bool>(
final isEnabled = widget.labelOptions.values.fold<bool>(
false,
(previousValue, element) =>
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),
),
),
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null),
initialValue: widget.initialValue ?? const IdQueryParameter.unset(),
name: widget.name,
suggestionsBoxDecoration: SuggestionsBoxDecoration(
shape: RoundedRectangleBorder(
@@ -103,7 +98,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
),
itemBuilder: (context, suggestion) => ListTile(
title: Text(
widget.state[suggestion.id]?.name ??
widget.labelOptions[suggestion.id]?.name ??
S.of(context).labelNotAssignedText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -112,10 +107,10 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
style: ListTileStyle.list,
),
suggestionsCallback: (pattern) {
final List<IdQueryParameter> suggestions = widget.state.entries
final List<IdQueryParameter> suggestions = widget.labelOptions.entries
.where(
(entry) =>
widget.state[entry.key]!.name
widget.labelOptions[entry.key]!.name
.toLowerCase()
.contains(pattern.toLowerCase()) ||
pattern.isEmpty,
@@ -125,34 +120,33 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
widget.labelCreationWidgetBuilder != null ||
(entry.value.documentCount ?? 0) > 0,
)
.map((entry) => widget.queryParameterIdBuilder(entry.key))
.map((entry) => IdQueryParameter.fromId(entry.key))
.toList();
if (widget.notAssignedSelectable) {
suggestions.insert(0, widget.queryParameterNotAssignedBuilder());
suggestions.insert(0, const IdQueryParameter.notAssigned());
}
return suggestions;
},
onChanged: (value) {
setState(() => _showClearSuffixIcon = value?.isSet ?? false);
widget.onChanged?.call(value as R);
widget.onChanged?.call(value);
},
controller: _textEditingController,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
label: Text(widget.label),
label: Text(widget.textFieldLabel),
hintText: _getLocalizedHint(context),
suffixIcon: _buildSuffixIcon(context),
),
selectionToTextTransformer: (suggestion) {
if (suggestion == widget.queryParameterNotAssignedBuilder()) {
if (suggestion == const IdQueryParameter.notAssigned()) {
return S.of(context).labelNotAssignedText;
}
return widget.state[suggestion.id]?.name ?? "";
return widget.labelOptions[suggestion.id]?.name ?? "";
},
direction: AxisDirection.up,
onSuggestionSelected: (suggestion) => widget
.formBuilderState?.fields[widget.name]
?.didChange(suggestion as R),
onSuggestionSelected: (suggestion) =>
widget.formBuilderState?.fields[widget.name]?.didChange(suggestion),
);
}
@@ -161,7 +155,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
return IconButton(
onPressed: () async {
FocusScope.of(context).unfocus();
final createdLabel = await showDialog(
final createdLabel = await showDialog<T>(
context: context,
builder: (context) => widget.labelCreationWidgetBuilder!(
_textEditingController.text,
@@ -170,7 +164,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
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).
widget.formBuilderState?.fields[widget.name]
?.didChange(widget.queryParameterIdBuilder(createdLabel.id));
?.didChange(IdQueryParameter.fromId(createdLabel.id));
_textEditingController.text = createdLabel.name;
} else {
_reset();
@@ -192,7 +186,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
void _reset() {
widget.formBuilderState?.fields[widget.name]?.didChange(
widget.queryParameterIdBuilder(null), // equivalnt to IdQueryParam.unset()
const IdQueryParameter.unset(),
);
_textEditingController.clear();
}

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Použít",
"@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": {},
"documentFilterQueryOptionsExtendedLabel": "Prodloužené",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "NAHLÁSIT",
"@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": {},
"genericActionCreateLabel": "Vytvořit",
@@ -408,7 +454,7 @@
"@savedViewShowOnDashboardLabel": {},
"savedViewsLabel": "Uložené náhledy",
"@savedViewsLabel": {},
"scannerPageImagePreviewTitle": "",
"scannerPageImagePreviewTitle": "Sken",
"@scannerPageImagePreviewTitle": {},
"serverInformationPaperlessVersionText": "Verze Paperless serveru",
"@serverInformationPaperlessVersionText": {},

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Anwenden",
"@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": {},
"documentFilterQueryOptionsExtendedLabel": "Erweitert",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "MELDEN",
"@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": {},
"genericActionCreateLabel": "Erstellen",

View File

@@ -78,18 +78,6 @@
"@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Apply",
"@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": {},
"documentFilterQueryOptionsExtendedLabel": "Extended",
@@ -272,6 +260,64 @@
"@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "REPORT",
"@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": {},
"genericActionCreateLabel": "Create",

View File

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

View File

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

View File

@@ -1,12 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.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 {
static const int titleRule = 0;
@@ -57,9 +51,6 @@ class FilterRule with EquatableMixin {
}
DocumentFilter applyToFilter(final DocumentFilter filter) {
if (value == null) {
return filter;
}
//TODO: Check in profiling mode if this is inefficient enough to cause stutters...
switch (ruleType) {
case titleRule:
@@ -67,20 +58,20 @@ class FilterRule with EquatableMixin {
case documentTypeRule:
return filter.copyWith(
documentType: value == null
? const DocumentTypeQuery.notAssigned()
: DocumentTypeQuery.fromId(int.parse(value!)),
? const IdQueryParameter.notAssigned()
: IdQueryParameter.fromId(int.parse(value!)),
);
case correspondentRule:
return filter.copyWith(
correspondent: value == null
? const CorrespondentQuery.notAssigned()
: CorrespondentQuery.fromId(int.parse(value!)),
? const IdQueryParameter.notAssigned()
: IdQueryParameter.fromId(int.parse(value!)),
);
case storagePathRule:
return filter.copyWith(
storagePath: value == null
? const StoragePathQuery.notAssigned()
: StoragePathQuery.fromId(int.parse(value!)),
? const IdQueryParameter.notAssigned()
: IdQueryParameter.fromId(int.parse(value!)),
);
case hasAnyTag:
return filter.copyWith(
@@ -101,48 +92,69 @@ class FilterRule with EquatableMixin {
.withIdQueriesAdded([ExcludeTagIdQuery(int.parse(value!))]),
);
case createdBeforeRule:
if (filter.created is FixedDateRangeQuery) {
if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith(
created: (filter.created as FixedDateRangeQuery)
created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)),
);
} else {
return filter.copyWith(
created:
FixedDateRangeQuery.created(before: DateTime.parse(value!)),
created: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
);
}
case createdAfterRule:
if (filter.created is FixedDateRangeQuery) {
if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith(
created: (filter.created as FixedDateRangeQuery)
created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)),
);
} else {
return filter.copyWith(
created: FixedDateRangeQuery.created(after: DateTime.parse(value!)),
created: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
);
}
case addedBeforeRule:
if (filter.added is FixedDateRangeQuery) {
if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith(
added: (filter.added as FixedDateRangeQuery)
added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)),
);
} else {
return filter.copyWith(
added: FixedDateRangeQuery.added(before: DateTime.parse(value!)),
added: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
);
}
case addedAfterRule:
if (filter.added is FixedDateRangeQuery) {
if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith(
added: (filter.added as FixedDateRangeQuery)
added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)),
);
} else {
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:
@@ -171,7 +183,7 @@ class FilterRule with EquatableMixin {
switch (field) {
case 'created':
newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.created(
created: RelativeDateRangeQuery(
n,
DateRangeUnit.values.byName(unit),
),
@@ -179,7 +191,7 @@ class FilterRule with EquatableMixin {
break;
case 'added':
newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.added(
added: RelativeDateRangeQuery(
n,
DateRangeUnit.values.byName(unit),
),
@@ -187,7 +199,7 @@ class FilterRule with EquatableMixin {
break;
case 'modified':
newFilter = newFilter.copyWith(
created: LastNDateRangeQuery.modified(
modified: RelativeDateRangeQuery(
n,
DateRangeUnit.values.byName(unit),
),
@@ -262,7 +274,7 @@ class FilterRule with EquatableMixin {
// Parse created at
final created = filter.created;
if (created is FixedDateRangeQuery) {
if (created is AbsoluteDateRangeQuery) {
if (created.after != null) {
filterRules.add(
FilterRule(createdAfterRule, apiDateFormat.format(created.after!)),
@@ -273,15 +285,16 @@ class FilterRule with EquatableMixin {
FilterRule(createdBeforeRule, apiDateFormat.format(created.before!)),
);
}
} else if (created is LastNDateRangeQuery) {
} else if (created is RelativeDateRangeQuery) {
filterRules.add(
FilterRule(extendedRule, created.toQueryParameter().values.first),
FilterRule(extendedRule,
created.toQueryParameter(DateRangeQueryField.created).values.first),
);
}
// Parse added at
final added = filter.added;
if (added is FixedDateRangeQuery) {
if (added is AbsoluteDateRangeQuery) {
if (added.after != null) {
filterRules.add(
FilterRule(addedAfterRule, apiDateFormat.format(added.after!)),
@@ -292,15 +305,16 @@ class FilterRule with EquatableMixin {
FilterRule(addedBeforeRule, apiDateFormat.format(added.before!)),
);
}
} else if (added is LastNDateRangeQuery) {
} else if (added is RelativeDateRangeQuery) {
filterRules.add(
FilterRule(extendedRule, added.toQueryParameter().values.first),
FilterRule(extendedRule,
added.toQueryParameter(DateRangeQueryField.added).values.first),
);
}
// Parse modified at
final modified = filter.added;
if (modified is FixedDateRangeQuery) {
final modified = filter.modified;
if (modified is AbsoluteDateRangeQuery) {
if (modified.after != null) {
filterRules.add(
FilterRule(modifiedAfterRule, apiDateFormat.format(modified.after!)),
@@ -312,9 +326,14 @@ class FilterRule with EquatableMixin {
modifiedBeforeRule, apiDateFormat.format(modified.before!)),
);
}
} else if (modified is LastNDateRangeQuery) {
} else if (modified is RelativeDateRangeQuery) {
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/storage_path_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/query_type.dart';
export 'query_parameters/sort_field.dart';
export 'query_parameters/sort_order.dart';
export 'query_parameters/storage_path_query.dart';
export 'query_parameters/tags_query.dart';
export 'query_parameters/date_range_query.dart';
export 'bulk_edit_model.dart';
export 'document_filter.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 {
const DateRangeQuery();
Map<String, String> toQueryParameter();
Map<String, String> toQueryParameter(DateRangeQueryField field);
}
class UnsetDateRangeQuery extends DateRangeQuery {
@@ -12,87 +12,74 @@ class UnsetDateRangeQuery extends DateRangeQuery {
List<Object?> get props => [];
@override
Map<String, String> toQueryParameter() => const {};
Map<String, String> toQueryParameter(DateRangeQueryField field) => const {};
}
class FixedDateRangeQuery extends DateRangeQuery {
final String _querySuffix;
class AbsoluteDateRangeQuery extends DateRangeQuery {
final DateTime? after;
final DateTime? before;
const FixedDateRangeQuery._(this._querySuffix, {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);
const AbsoluteDateRangeQuery({this.after, this.before});
@override
List<Object?> get props => [_querySuffix, after, before];
List<Object?> get props => [after, before];
@override
Map<String, String> toQueryParameter() {
Map<String, String> toQueryParameter(DateRangeQueryField field) {
final Map<String, String> params = {};
// Add/subtract one day in the following because paperless uses gt/lt not gte/lte
if (after != null) {
params.putIfAbsent('${_querySuffix}__date__gt',
params.putIfAbsent('${field.name}__date__gt',
() => apiDateFormat.format(after!.subtract(const Duration(days: 1))));
}
if (before != null) {
params.putIfAbsent('${_querySuffix}__date__lt',
params.putIfAbsent('${field.name}__date__lt',
() => apiDateFormat.format(before!.add(const Duration(days: 1))));
}
return params;
}
FixedDateRangeQuery copyWith({
AbsoluteDateRangeQuery copyWith({
DateTime? before,
DateTime? after,
}) {
return FixedDateRangeQuery._(
_querySuffix,
return AbsoluteDateRangeQuery(
before: before ?? this.before,
after: after ?? this.after,
);
}
}
class LastNDateRangeQuery extends DateRangeQuery {
class RelativeDateRangeQuery extends DateRangeQuery {
final int offset;
final DateRangeUnit unit;
final int n;
final String _field;
const LastNDateRangeQuery._(
this._field, {
required this.n,
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);
const RelativeDateRangeQuery(
this.offset,
this.unit,
);
@override
// TODO: implement props
List<Object?> get props => [_field, n, unit];
List<Object?> get props => [offset, unit];
@override
Map<String, String> toQueryParameter() {
Map<String, String> toQueryParameter(DateRangeQueryField field) {
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 {
@@ -101,3 +88,9 @@ enum DateRangeUnit {
month,
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';
abstract class IdQueryParameter extends Equatable {
class IdQueryParameter extends Equatable {
final int? _assignmentStatus;
final int? _id;
@@ -28,16 +28,14 @@ abstract class IdQueryParameter extends Equatable {
int? get id => _id;
String get queryParameterKey;
Map<String, String> toQueryParameter() {
Map<String, String> toQueryParameter(String field) {
final Map<String, String> params = {};
if (onlyNotAssigned || onlyAssigned) {
params.putIfAbsent(
'${queryParameterKey}__isnull', () => _assignmentStatus!.toString());
'${field}__isnull', () => _assignmentStatus!.toString());
}
if (isSet) {
params.putIfAbsent("${queryParameterKey}__id", () => id!.toString());
params.putIfAbsent("${field}__id", () => id!.toString());
}
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:flutter/foundation.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/models/bulk_edit_model.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/paged_search_result.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_order.dart';
import 'package:paperless_api/src/models/similar_document_model.dart';
@@ -139,7 +139,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
final filterParams = filter.toQueryParameters();
final response = await baseClient.get(
Uri(
path: "/api/documents/?$filterParams",
path: "/api/documents/",
queryParameters: filterParams,
),
);
@@ -190,7 +190,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
const DocumentFilter asnQueryFilter = DocumentFilter(
sortField: SortField.archiveSerialNumber,
sortOrder: SortOrder.descending,
asnQuery: AsnQuery.anyAssigned(),
asnQuery: IdQueryParameter.anyAssigned(),
page: 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:paperless_api/paperless_api.dart';
void main() {
group('Validate parsing logic from [SavedView] to [DocumentFilter]:', () {
@@ -74,10 +65,10 @@ void main() {
}).toDocumentFilter(),
equals(
DocumentFilter.initial.copyWith(
correspondent: const CorrespondentQuery.fromId(42),
documentType: const DocumentTypeQuery.fromId(69),
storagePath: const StoragePathQuery.fromId(14),
tags: IdsTagsQuery(
correspondent: const IdQueryParameter.fromId(42),
documentType: const IdQueryParameter.fromId(69),
storagePath: const IdQueryParameter.fromId(14),
tags: const IdsTagsQuery(
[
IncludeTagIdQuery(1),
IncludeTagIdQuery(2),
@@ -85,10 +76,14 @@ void main() {
ExcludeTagIdQuery(4),
],
),
createdDateBefore: DateTime.parse("2022-10-27"),
createdDateAfter: DateTime.parse("2022-09-27"),
addedDateBefore: DateTime.parse("2022-09-26"),
addedDateAfter: DateTime.parse("2000-01-01"),
created: AbsoluteDateRangeQuery(
before: DateTime.parse("2022-10-27"),
after: DateTime.parse("2022-09-27"),
),
added: AbsoluteDateRangeQuery(
before: DateTime.parse("2022-09-26"),
after: DateTime.parse("2000-01-01"),
),
sortField: SortField.created,
sortOrder: SortOrder.descending,
queryText: "Never gonna give you up",
@@ -114,8 +109,7 @@ void main() {
});
test('Values are correctly parsed if not assigned.', () {
expect(
SavedView.fromJson({
final actual = SavedView.fromJson({
"id": 1,
"name": "test_name",
"show_on_dashboard": false,
@@ -140,13 +134,16 @@ void main() {
'value': null,
},
],
}).toDocumentFilter(),
equals(DocumentFilter.initial.copyWith(
correspondent: const CorrespondentQuery.notAssigned(),
documentType: const DocumentTypeQuery.notAssigned(),
storagePath: const StoragePathQuery.notAssigned(),
}).toDocumentFilter();
final expected = DocumentFilter.initial.copyWith(
correspondent: const IdQueryParameter.notAssigned(),
documentType: const IdQueryParameter.notAssigned(),
storagePath: const IdQueryParameter.notAssigned(),
tags: const OnlyNotAssignedTagsQuery(),
)),
);
expect(
actual,
equals(expected),
);
});
});
@@ -156,10 +153,10 @@ void main() {
expect(
SavedView.fromDocumentFilter(
DocumentFilter(
correspondent: const CorrespondentQuery.fromId(1),
documentType: const DocumentTypeQuery.fromId(2),
storagePath: const StoragePathQuery.fromId(3),
tags: IdsTagsQuery([
correspondent: const IdQueryParameter.fromId(1),
documentType: const IdQueryParameter.fromId(2),
storagePath: const IdQueryParameter.fromId(3),
tags: const IdsTagsQuery([
IncludeTagIdQuery(4),
IncludeTagIdQuery(5),
ExcludeTagIdQuery(6),
@@ -168,10 +165,14 @@ void main() {
]),
sortField: SortField.added,
sortOrder: SortOrder.ascending,
addedDateAfter: DateTime.parse("2020-01-01"),
addedDateBefore: DateTime.parse("2020-03-01"),
createdDateAfter: DateTime.parse("2020-02-01"),
createdDateBefore: DateTime.parse("2020-04-01"),
created: AbsoluteDateRangeQuery(
before: DateTime.parse("2020-04-01"),
after: DateTime.parse("2020-02-01"),
),
added: AbsoluteDateRangeQuery(
before: DateTime.parse("2020-03-01"),
after: DateTime.parse("2020-01-01"),
),
queryText: "Never gonna let you down",
queryType: QueryType.title,
),
@@ -210,16 +211,14 @@ void main() {
expect(
SavedView.fromDocumentFilter(
const DocumentFilter(
correspondent: CorrespondentQuery.unset(),
documentType: DocumentTypeQuery.unset(),
storagePath: StoragePathQuery.unset(),
correspondent: IdQueryParameter.unset(),
documentType: IdQueryParameter.unset(),
storagePath: IdQueryParameter.unset(),
tags: IdsTagsQuery(),
sortField: SortField.created,
sortOrder: SortOrder.descending,
addedDateAfter: null,
addedDateBefore: null,
createdDateAfter: null,
createdDateBefore: null,
added: UnsetDateRangeQuery(),
created: UnsetDateRangeQuery(),
queryText: null,
),
name: "test_name",
@@ -243,9 +242,9 @@ void main() {
expect(
SavedView.fromDocumentFilter(
const DocumentFilter(
correspondent: CorrespondentQuery.notAssigned(),
documentType: DocumentTypeQuery.notAssigned(),
storagePath: StoragePathQuery.notAssigned(),
correspondent: IdQueryParameter.notAssigned(),
documentType: IdQueryParameter.notAssigned(),
storagePath: IdQueryParameter.notAssigned(),
tags: OnlyNotAssignedTagsQuery(),
sortField: SortField.created,
sortOrder: SortOrder.ascending,