TF-2198 Add contact field to quick search input form

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 7beda458912eeab026194a875a8a94a17c1cbc37)
This commit is contained in:
dab246
2023-11-06 18:39:09 +07:00
committed by Dat Vu
parent c87b944c29
commit a96b18d99a
@@ -35,7 +35,7 @@ final supportedPlatform = (kIsWeb || Platform.isAndroid || Platform.isIOS);
/// ///
/// * [TypeAheadFieldQuickSearch], A [TextField](https://docs.flutter.io/flutter/material/TextField-class.html) /// * [TypeAheadFieldQuickSearch], A [TextField](https://docs.flutter.io/flutter/material/TextField-class.html)
/// that displays a list of suggestions as the user types /// that displays a list of suggestions as the user types
class QuickSearchInputForm<T, R> extends FormField<String> { class QuickSearchInputForm<T, P, R> extends FormField<String> {
/// The configuration of the [TextField](https://docs.flutter.io/flutter/material/TextField-class.html) /// The configuration of the [TextField](https://docs.flutter.io/flutter/material/TextField-class.html)
/// that the TypeAhead widget displays /// that the TypeAhead widget displays
final QuickSearchTextFieldConfiguration textFieldConfiguration; final QuickSearchTextFieldConfiguration textFieldConfiguration;
@@ -87,6 +87,9 @@ class QuickSearchInputForm<T, R> extends FormField<String> {
BoxDecoration? decoration, BoxDecoration? decoration,
double? maxHeight, double? maxHeight,
bool isDirectionRTL = false, bool isDirectionRTL = false,
ItemBuilder<P>? contactItemBuilder,
SuggestionsCallback<P>? contactSuggestionsCallback,
SuggestionSelectionCallback<P>? onContactSuggestionSelected,
}) : assert( }) : assert(
initialValue == null || textFieldConfiguration.controller == null), initialValue == null || textFieldConfiguration.controller == null),
assert(minCharsForSuggestions >= 0), assert(minCharsForSuggestions >= 0),
@@ -101,7 +104,7 @@ class QuickSearchInputForm<T, R> extends FormField<String> {
autovalidateMode: autovalidateMode, autovalidateMode: autovalidateMode,
builder: (FormFieldState<String> field) { builder: (FormFieldState<String> field) {
final _TypeAheadFormFieldState state = final _TypeAheadFormFieldState state =
field as _TypeAheadFormFieldState<dynamic, dynamic>; field as _TypeAheadFormFieldState<dynamic, dynamic, dynamic>;
return TypeAheadFieldQuickSearch( return TypeAheadFieldQuickSearch(
getImmediateSuggestions: getImmediateSuggestions, getImmediateSuggestions: getImmediateSuggestions,
@@ -151,21 +154,24 @@ class QuickSearchInputForm<T, R> extends FormField<String> {
decoration: decoration, decoration: decoration,
maxHeight: maxHeight, maxHeight: maxHeight,
isDirectionRTL: isDirectionRTL, isDirectionRTL: isDirectionRTL,
contactItemBuilder: contactItemBuilder,
contactSuggestionsCallback: contactSuggestionsCallback,
onContactSuggestionSelected: onContactSuggestionSelected,
); );
}); });
@override @override
FormFieldState<String> createState() => _TypeAheadFormFieldState<T, R>(); FormFieldState<String> createState() => _TypeAheadFormFieldState<T, P, R>();
} }
class _TypeAheadFormFieldState<T, R> extends FormFieldState<String> { class _TypeAheadFormFieldState<T, P, R> extends FormFieldState<String> {
TextEditingController? _controller; TextEditingController? _controller;
TextEditingController? get _effectiveController => TextEditingController? get _effectiveController =>
widget.textFieldConfiguration.controller ?? _controller; widget.textFieldConfiguration.controller ?? _controller;
@override @override
QuickSearchInputForm get widget => super.widget as QuickSearchInputForm<dynamic, dynamic>; QuickSearchInputForm get widget => super.widget as QuickSearchInputForm<dynamic, dynamic, dynamic>;
@override @override
void initState() { void initState() {
@@ -239,7 +245,7 @@ class _TypeAheadFormFieldState<T, R> extends FormFieldState<String> {
/// * [QuickSearchInputForm], a [FormField](https://docs.flutter.io/flutter/widgets/FormField-class.html) /// * [QuickSearchInputForm], a [FormField](https://docs.flutter.io/flutter/widgets/FormField-class.html)
/// implementation of [TypeAheadFieldQuickSearch] that allows the value to be saved, /// implementation of [TypeAheadFieldQuickSearch] that allows the value to be saved,
/// validated, etc. /// validated, etc.
class TypeAheadFieldQuickSearch<T, R> extends StatefulWidget { class TypeAheadFieldQuickSearch<T, P, R> extends StatefulWidget {
/// Called with the search pattern to get the search suggestions. /// Called with the search pattern to get the search suggestions.
/// ///
/// This callback must not be null. It is be called by the TypeAhead widget /// This callback must not be null. It is be called by the TypeAhead widget
@@ -517,8 +523,14 @@ class TypeAheadFieldQuickSearch<T, R> extends StatefulWidget {
final BoxDecoration? decoration; final BoxDecoration? decoration;
/// Max height search input /// Max height search input
final double? maxHeight; final double? maxHeight;
/// Check direction text input
final bool isDirectionRTL; final bool isDirectionRTL;
/// Widget contact item
final ItemBuilder<P>? contactItemBuilder;
/// Get all contact callback
final SuggestionsCallback<P>? contactSuggestionsCallback;
/// On listen select contact
final SuggestionSelectionCallback<P>? onContactSuggestionSelected;
/// Creates a [TypeAheadFieldQuickSearch] /// Creates a [TypeAheadFieldQuickSearch]
const TypeAheadFieldQuickSearch( const TypeAheadFieldQuickSearch(
@@ -562,6 +574,9 @@ class TypeAheadFieldQuickSearch<T, R> extends StatefulWidget {
this.decoration, this.decoration,
this.maxHeight, this.maxHeight,
this.isDirectionRTL = false, this.isDirectionRTL = false,
this.contactItemBuilder,
this.contactSuggestionsCallback,
this.onContactSuggestionSelected,
}) : assert(animationStart >= 0.0 && animationStart <= 1.0), }) : assert(animationStart >= 0.0 && animationStart <= 1.0),
assert( assert(
direction == AxisDirection.down || direction == AxisDirection.up), direction == AxisDirection.down || direction == AxisDirection.up),
@@ -569,10 +584,10 @@ class TypeAheadFieldQuickSearch<T, R> extends StatefulWidget {
super(key: key); super(key: key);
@override @override
State<TypeAheadFieldQuickSearch<T, R>> createState() => _TypeAheadFieldQuickSearchState<T, R>(); State<TypeAheadFieldQuickSearch<T, P, R>> createState() => _TypeAheadFieldQuickSearchState<T, P, R>();
} }
class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSearch<T, R>> class _TypeAheadFieldQuickSearchState<T, P, R> extends State<TypeAheadFieldQuickSearch<T, P, R>>
with WidgetsBindingObserver { with WidgetsBindingObserver {
FocusNode? _focusNode; FocusNode? _focusNode;
TextEditingController? _textEditingController; TextEditingController? _textEditingController;
@@ -707,7 +722,7 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
void _initOverlayEntry() { void _initOverlayEntry() {
_suggestionsBox!._overlayEntry = OverlayEntry(builder: (context) { _suggestionsBox!._overlayEntry = OverlayEntry(builder: (context) {
final suggestionsList = _SuggestionsList<T, R>( final suggestionsList = _SuggestionsList<T, P, R>(
suggestionsBox: _suggestionsBox, suggestionsBox: _suggestionsBox,
decoration: widget.suggestionsBoxDecoration, decoration: widget.suggestionsBoxDecoration,
debounceDuration: widget.debounceDuration, debounceDuration: widget.debounceDuration,
@@ -754,6 +769,15 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
listActionPadding: widget.listActionPadding, listActionPadding: widget.listActionPadding,
hideSuggestionsBox: widget.hideSuggestionsBox, hideSuggestionsBox: widget.hideSuggestionsBox,
isDirectionRTL: widget.isDirectionRTL, isDirectionRTL: widget.isDirectionRTL,
contactSuggestionBuilder: widget.contactItemBuilder,
contactSuggestionCallback: widget.contactSuggestionsCallback,
onContactSuggestionSelected: (P selection) {
if (!widget.keepSuggestionsOnSuggestionSelected) {
_effectiveFocusNode!.unfocus();
_suggestionsBox!.close();
}
widget.onContactSuggestionSelected?.call(selection);
},
); );
double w = _suggestionsBox!.textBoxWidth; double w = _suggestionsBox!.textBoxWidth;
@@ -882,7 +906,7 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
} }
} }
class _SuggestionsList<T, R> extends StatefulWidget { class _SuggestionsList<T, P, R> extends StatefulWidget {
final _SuggestionsBox? suggestionsBox; final _SuggestionsBox? suggestionsBox;
final TextEditingController? controller; final TextEditingController? controller;
final bool getImmediateSuggestions; final bool getImmediateSuggestions;
@@ -915,6 +939,9 @@ class _SuggestionsList<T, R> extends StatefulWidget {
final EdgeInsets? listActionPadding; final EdgeInsets? listActionPadding;
final bool hideSuggestionsBox; final bool hideSuggestionsBox;
final bool isDirectionRTL; final bool isDirectionRTL;
final ItemBuilder<P>? contactSuggestionBuilder;
final SuggestionsCallback<P>? contactSuggestionCallback;
final SuggestionSelectionCallback<P>? onContactSuggestionSelected;
const _SuggestionsList({ const _SuggestionsList({
required this.suggestionsBox, required this.suggestionsBox,
@@ -949,16 +976,20 @@ class _SuggestionsList<T, R> extends StatefulWidget {
this.listActionPadding, this.listActionPadding,
this.hideSuggestionsBox = false, this.hideSuggestionsBox = false,
this.isDirectionRTL = false, this.isDirectionRTL = false,
this.contactSuggestionBuilder,
this.contactSuggestionCallback,
this.onContactSuggestionSelected,
}); });
@override @override
_SuggestionsListState<T, R> createState() => _SuggestionsListState<T, R>(); _SuggestionsListState<T, P, R> createState() => _SuggestionsListState<T, P, R>();
} }
class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>> class _SuggestionsListState<T, P, R> extends State<_SuggestionsList<T, P, R>>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
Iterable<T>? _suggestions; Iterable<T>? _suggestions;
Iterable<R>? _recentItems; Iterable<R>? _recentItems;
Iterable<P>? _contacts;
late bool _suggestionsValid; late bool _suggestionsValid;
late VoidCallback _controllerListener; late VoidCallback _controllerListener;
Timer? _debounceTimer; Timer? _debounceTimer;
@@ -991,6 +1022,7 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
setState(() { setState(() {
_isLoading = false; _isLoading = false;
_suggestions = null; _suggestions = null;
_contacts = null;
_recentItems = recentItems; _recentItems = recentItems;
_suggestionsValid = true; _suggestionsValid = true;
}); });
@@ -1015,7 +1047,7 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
} }
@override @override
void didUpdateWidget(_SuggestionsList<T, R> oldWidget) { void didUpdateWidget(_SuggestionsList<T, P, R> oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
widget.controller!.addListener(_controllerListener); widget.controller!.addListener(_controllerListener);
_getSuggestions(); _getSuggestions();
@@ -1067,6 +1099,7 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
Iterable<T>? suggestions; Iterable<T>? suggestions;
Iterable<R>? recentItems; Iterable<R>? recentItems;
Iterable<P>? contacts;
Object? error; Object? error;
try { try {
@@ -1074,6 +1107,9 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
if (widget.fetchRecentActionCallback != null) { if (widget.fetchRecentActionCallback != null) {
recentItems = await widget.fetchRecentActionCallback!(widget.controller!.text); recentItems = await widget.fetchRecentActionCallback!(widget.controller!.text);
} }
if (widget.contactSuggestionCallback != null) {
contacts = await widget.contactSuggestionCallback!(widget.controller!.text);
}
} catch (e) { } catch (e) {
error = e; error = e;
} }
@@ -1091,6 +1127,7 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
_isLoading = false; _isLoading = false;
_suggestions = suggestions; _suggestions = suggestions;
_recentItems = recentItems; _recentItems = recentItems;
_contacts = contacts;
}); });
} }
} }
@@ -1111,7 +1148,7 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
Widget child; Widget child;
if (_suggestions?.isNotEmpty == true && widget.controller?.text.isNotEmpty == true) { if ((_suggestions?.isNotEmpty == true || _contacts?.isNotEmpty == true) && widget.controller?.text.isNotEmpty == true) {
child = createSuggestionsWidget(); child = createSuggestionsWidget();
} else { } else {
child = createRecentWidget(); child = createRecentWidget();
@@ -1165,12 +1202,10 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
Widget createSuggestionsWidget() { Widget createSuggestionsWidget() {
final listItemSuggestionWidget = _suggestions?.map((T suggestion) { final listItemSuggestionWidget = _suggestions?.map((T suggestion) {
if ( widget.itemBuilder != null) { if (widget.itemBuilder != null) {
return InkWell( return InkWell(
child: widget.itemBuilder!(context, suggestion), child: widget.itemBuilder!(context, suggestion),
onTap: () { onTap: () => widget.onSuggestionSelected?.call(suggestion),
widget.onSuggestionSelected!(suggestion);
},
); );
} else { } else {
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -1212,6 +1247,20 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
} }
}).toList()); }).toList());
final listItemContactWidget = _contacts?.map((P contact) {
if (widget.contactSuggestionBuilder != null) {
return Material(
color: Colors.transparent,
child: InkWell(
child: widget.contactSuggestionBuilder!(context, contact),
onTap: () => widget.onContactSuggestionSelected?.call(contact),
),
);
} else {
return const SizedBox.shrink();
}
}).toList() ?? [];
Widget child = ListView( Widget child = ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
primary: false, primary: false,
@@ -1226,6 +1275,8 @@ class _SuggestionsListState<T, R> extends State<_SuggestionsList<T, R>>
), ),
if (_isLoading == true && widget.hideOnLoading == false && widget.keepSuggestionsOnLoading == false) if (_isLoading == true && widget.hideOnLoading == false && widget.keepSuggestionsOnLoading == false)
loadingWidget, loadingWidget,
if (listItemContactWidget.isNotEmpty)
... listItemContactWidget,
if (widget.buttonShowAllResult != null && widget.controller?.text.isNotEmpty == true) if (widget.buttonShowAllResult != null && widget.controller?.text.isNotEmpty == true)
widget.buttonShowAllResult!(context, widget.controller?.text), widget.buttonShowAllResult!(context, widget.controller?.text),
if (listItemSuggestionWidget.isNotEmpty) if (listItemSuggestionWidget.isNotEmpty)