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:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user