You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
4.9 KiB
Dart
145 lines
4.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:test_sa/extensions/int_extensions.dart';
|
|
import 'package:test_sa/extensions/text_extensions.dart';
|
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
|
import 'package:test_sa/views/app_style/sizing.dart';
|
|
|
|
class AutoCompleteGenericField<T extends Object> extends StatefulWidget {
|
|
final String? label;
|
|
final String initialValue;
|
|
final bool clearAfterPick;
|
|
final Future<List<T>> Function(String query) onSearch;
|
|
final String Function(T item) displayString;
|
|
final Function(T item) onPick;
|
|
|
|
const AutoCompleteGenericField({
|
|
Key? key,
|
|
this.label,
|
|
required this.initialValue,
|
|
required this.onSearch,
|
|
required this.displayString,
|
|
required this.onPick,
|
|
this.clearAfterPick = true,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<AutoCompleteGenericField<T>> createState() => _AutoCompleteGenericFieldState<T>();
|
|
}
|
|
|
|
class _AutoCompleteGenericFieldState<T extends Object> extends State<AutoCompleteGenericField<T>> {
|
|
late TextEditingController _controller;
|
|
List<T> _options = [];
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
_controller = TextEditingController(text: widget.initialValue);
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant AutoCompleteGenericField<T> oldWidget) {
|
|
if (widget.initialValue != oldWidget.initialValue) {
|
|
_controller.text = widget.initialValue;
|
|
}
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
Future<void> _search(String query) async {
|
|
if (query.isEmpty) {
|
|
setState(() => _options = []);
|
|
return;
|
|
}
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
final results = await widget.onSearch(query);
|
|
setState(() => _options = results);
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10));
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: AppColor.background(context),
|
|
borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)),
|
|
),
|
|
child: RawAutocomplete<T>(
|
|
// textEditingController: _controller,
|
|
optionsBuilder: (TextEditingValue textEditingValue) => _options,
|
|
displayStringForOption: widget.displayString,
|
|
fieldViewBuilder: (context, fieldTextEditingController, fieldFocusNode, onFieldSubmitted) {
|
|
return TextField(
|
|
controller: _controller,
|
|
focusNode: fieldFocusNode,
|
|
style: AppTextStyles.bodyText.copyWith(color: AppColor.black10),
|
|
textAlign: TextAlign.start,
|
|
decoration: InputDecoration(
|
|
border: border,
|
|
disabledBorder: border,
|
|
focusedBorder: border,
|
|
enabledBorder: border,
|
|
errorBorder: border,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth),
|
|
// suffixIcon: _isLoading
|
|
// ? const Padding(
|
|
// padding: EdgeInsets.all(8.0),
|
|
// child: SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)),
|
|
// )
|
|
// : const Icon(Icons.search, size: 18),
|
|
filled: true,
|
|
fillColor: AppColor.fieldBgColor(context),
|
|
labelText: widget.label,
|
|
labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)),
|
|
),
|
|
textInputAction: TextInputAction.search,
|
|
onChanged: (text) => _search(text),
|
|
onSubmitted: (_) => onFieldSubmitted(),
|
|
);
|
|
},
|
|
onSelected: (T selection) {
|
|
if (widget.clearAfterPick) {
|
|
_controller.clear();
|
|
} else {
|
|
_controller.text = widget.displayString(selection);
|
|
}
|
|
widget.onPick(selection);
|
|
},
|
|
optionsViewBuilder: (context, onSelected, options) {
|
|
return Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
elevation: 4,
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxHeight: 200, minWidth: 200),
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.zero,
|
|
itemCount: options.length,
|
|
itemBuilder: (context, index) {
|
|
final option = options.elementAt(index);
|
|
return ListTile(
|
|
title: Text(widget.displayString(option)),
|
|
onTap: () => onSelected(option),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|