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);
}
}
}