Files
paperless-mobile/lib/features/labels/view/widgets/label_form_field.dart

174 lines
6.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart';
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:form_builder_extra_fields/form_builder_extra_fields.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 {
final Widget prefixIcon;
final Map<int, T> state;
final FormBuilderState? formBuilderState;
final IdQueryParameter? initialValue;
final String name;
final String label;
final FormFieldValidator? validator;
final Widget Function(String)? labelCreationWidgetBuilder;
final R Function() queryParameterNotAssignedBuilder;
final R Function(int? id) queryParameterIdBuilder;
final bool notAssignedSelectable;
final void Function(R?)? onChanged;
const LabelFormField({
Key? key,
required this.name,
required this.state,
this.validator,
this.initialValue,
required this.label,
this.labelCreationWidgetBuilder,
required this.queryParameterNotAssignedBuilder,
required this.queryParameterIdBuilder,
required this.formBuilderState,
required this.prefixIcon,
this.notAssignedSelectable = true,
this.onChanged,
}) : super(key: key);
@override
State<LabelFormField<T, R>> createState() => _LabelFormFieldState<T, R>();
}
class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
extends State<LabelFormField<T, R>> {
bool _showCreationSuffixIcon = false;
late bool _showClearSuffixIcon;
late final TextEditingController _textEditingController;
@override
void initState() {
super.initState();
_showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id);
_textEditingController =
TextEditingController(text: widget.state[widget.initialValue?.id]?.name ?? '')
..addListener(() {
setState(() {
_showCreationSuffixIcon = widget.state.values
.where((item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(),
))
.isEmpty;
});
setState(() => _showClearSuffixIcon = _textEditingController.text.isNotEmpty);
});
}
@override
Widget build(BuildContext context) {
return FormBuilderTypeAhead<IdQueryParameter>(
noItemsFoundBuilder: (context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
S.of(context).labelFormFieldNoItemsFoundText,
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
),
),
initialValue: widget.initialValue ?? widget.queryParameterIdBuilder(null),
name: widget.name,
itemBuilder: (context, suggestion) => ListTile(
title: Text(widget.state[suggestion.id]?.name ?? S.of(context).labelNotAssignedText),
),
suggestionsCallback: (pattern) {
final List<IdQueryParameter> suggestions = widget.state.keys
.where((item) =>
widget.state[item]!.name.toLowerCase().startsWith(pattern.toLowerCase()) ||
pattern.isEmpty)
.map((id) => widget.queryParameterIdBuilder(id))
.toList();
if (widget.notAssignedSelectable) {
suggestions.insert(0, widget.queryParameterNotAssignedBuilder());
}
return suggestions;
},
onChanged: (value) {
setState(() => _showClearSuffixIcon = value?.isSet ?? false);
widget.onChanged?.call(value as R);
},
controller: _textEditingController,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
label: Text(widget.label),
hintText: _getLocalizedHint(context),
suffixIcon: _buildSuffixIcon(context),
),
selectionToTextTransformer: (suggestion) {
if (suggestion == widget.queryParameterNotAssignedBuilder()) {
return S.of(context).labelNotAssignedText;
}
return widget.state[suggestion.id]?.name ?? "";
},
direction: AxisDirection.up,
onSuggestionSelected: (suggestion) =>
widget.formBuilderState?.fields[widget.name]?.didChange(suggestion as R),
);
}
Widget? _buildSuffixIcon(BuildContext context) {
if (_showCreationSuffixIcon && widget.labelCreationWidgetBuilder != null) {
return IconButton(
onPressed: () => Navigator.of(context)
.push<T>(MaterialPageRoute(
builder: (context) =>
widget.labelCreationWidgetBuilder!(_textEditingController.text)))
.then((value) {
if (value != 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(value.id));
_textEditingController.text = value.name;
FocusScope.of(context).unfocus();
} else {
_reset();
}
}),
icon: const Icon(
Icons.new_label,
),
);
}
if (_showClearSuffixIcon) {
return IconButton(
icon: const Icon(Icons.clear),
onPressed: _reset,
);
}
return null;
}
void _reset() {
widget.formBuilderState?.fields[widget.name]?.didChange(widget.queryParameterIdBuilder(null));
_textEditingController.clear();
}
String _getLocalizedHint(BuildContext context) {
if (T == Correspondent) {
return S.of(context).correspondentFormFieldSearchHintText;
} else if (T == DocumentType) {
return S.of(context).documentTypeFormFieldSearchHintText;
} else {
return S
.of(context)
.tagFormFieldSearchHintText; //TODO: Update tag form field once there is multi selection support.
}
}
}