Replace autocomplete

This commit is contained in:
Hillel Coren 2021-11-21 19:39:21 +02:00
parent 237fb8f428
commit e0ee07a276
2 changed files with 151 additions and 270 deletions

View File

@ -302,7 +302,7 @@ class _EntityDropdownState extends State<EntityDropdown> {
? kDefaultDarkSelectedColor ? kDefaultDarkSelectedColor
: kDefaultLightSelectedColor) : kDefaultLightSelectedColor)
: Theme.of(context).cardColor, : Theme.of(context).cardColor,
child: _EntityListTile( child: EntityAutocompleteListTile(
onTap: (entity) => onSelected(entity), onTap: (entity) => onSelected(entity),
entity: options.elementAt(index), entity: options.elementAt(index),
filter: _filter, filter: _filter,
@ -456,7 +456,7 @@ class _EntityDropdownDialogState extends State<EntityDropdownDialog> {
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final entityId = matches[index]; final entityId = matches[index];
final entity = widget.entityMap[entityId]; final entity = widget.entityMap[entityId];
return _EntityListTile( return EntityAutocompleteListTile(
entity: entity, entity: entity,
filter: _filter, filter: _filter,
onTap: (entity) => _selectEntity(entity), onTap: (entity) => _selectEntity(entity),
@ -482,12 +482,12 @@ class _EntityDropdownDialogState extends State<EntityDropdownDialog> {
} }
} }
class _EntityListTile extends StatelessWidget { class EntityAutocompleteListTile extends StatelessWidget {
const _EntityListTile({ const EntityAutocompleteListTile({
@required this.entity, @required this.entity,
@required this.filter, this.filter,
@required this.overrideSuggestedLabel, this.overrideSuggestedLabel,
@required this.overrideSuggestedAmount, this.overrideSuggestedAmount,
this.onTap, this.onTap,
}); });

View File

@ -4,7 +4,10 @@ import 'package:flutter/material.dart';
// Package imports: // Package imports:
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:invoiceninja_flutter/data/models/product_model.dart';
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
// Project imports: // Project imports:
@ -45,6 +48,7 @@ class InvoiceEditItemsDesktop extends StatefulWidget {
class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> { class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
final _debouncer = Debouncer(); final _debouncer = Debouncer();
TextEditingController _textEditingController;
bool _isReordering = false; bool _isReordering = false;
int _updatedAt; int _updatedAt;
int _autocompleteFocusIndex = -1; int _autocompleteFocusIndex = -1;
@ -94,6 +98,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final theme = Theme.of(context);
final viewModel = widget.viewModel; final viewModel = widget.viewModel;
final state = viewModel.state; final state = viewModel.state;
final company = state.company; final company = state.company;
@ -470,277 +475,153 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
key: ValueKey( key: ValueKey(
'__line_item_${index}_${lineItems[index].createdAt}__'), '__line_item_${index}_${lineItems[index].createdAt}__'),
children: [ children: [
/*
Padding(
padding: const EdgeInsets.only(right: kTableColumnGap),
child: Autocomplete<ProductEntity>(
//key: ValueKey('__line_item_${index}_name__'),
optionsBuilder: (TextEditingValue textEditingValue) {
final options = productIds
.map((productId) => productState.map[productId])
.where((product) => product.productKey
.toLowerCase()
.contains(_filter.toLowerCase()))
.toList();
/*
if (options.length == 1 &&
options[0].productKey.toLowerCase() ==
_filter.toLowerCase()) {
return <ProductEntity>[];
}
*/
return options;
},
displayStringForOption: (product) => product.productKey,
onSelected: (product) {
final item = lineItems[index];
final client =
state.clientState.get(invoice.clientId);
final currency =
state.staticState.currencyMap[client.currencyId];
double cost = product.price;
if (company.convertProductExchangeRate &&
invoice.clientId != null &&
client.currencyId != company.currencyId) {
cost = round(cost * invoice.exchangeRate,
currency.precision);
}
final updatedItem = item.rebuild((b) => b
..productKey = product.productKey
..notes = item.isTask ? item.notes : product.notes
..cost =
item.isTask && item.cost != 0 ? item.cost : cost
..quantity = item.isTask || item.quantity != 0
? item.quantity
: viewModel.state.company.defaultQuantity
? 1
: product.quantity
..customValue1 = product.customValue1
..customValue2 = product.customValue2
..customValue3 = product.customValue3
..customValue4 = product.customValue4
..taxName1 = company.numberOfItemTaxRates >= 1 ? product.taxName1 : ''
..taxRate1 = company.numberOfItemTaxRates >= 1 ? product.taxRate1 : 0
..taxName2 = company.numberOfItemTaxRates >= 2 ? product.taxName2 : ''
..taxRate2 = company.numberOfItemTaxRates >= 2 ? product.taxRate2 : 0
..taxName3 = company.numberOfItemTaxRates >= 3 ? product.taxName3 : ''
..taxRate3 = company.numberOfItemTaxRates >= 3 ? product.taxRate3 : 0);
_onChanged(updatedItem, index);
_updateTable();
},
fieldViewBuilder: (BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
return DecoratedFormField(
showClear: false,
autofocus: false,
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
onChanged: (value) {
_filter = value;
_onChanged(
lineItems[index]
.rebuild((b) => b..productKey = value),
index);
},
);
},
),
),
*/
Focus( Focus(
onFocusChange: (hasFocus) { onFocusChange: (hasFocus) {
print('## onFocusChange - hasFocus: $hasFocus');
_autocompleteFocusIndex = index; _autocompleteFocusIndex = index;
_onFocusChange(); _onFocusChange();
}, },
skipTraversal: true, skipTraversal: true,
child: Padding( child: Padding(
padding: padding: const EdgeInsets.only(right: kTableColumnGap),
const EdgeInsets.only(right: kTableColumnGap), child: RawAutocomplete<ProductEntity>(
child: TypeAheadFormField<String>( key: ValueKey('__line_item_${index}_name__'),
key: ValueKey('__line_item_${index}_name__'), textEditingController: _textEditingController,
initialValue: lineItems[index].productKey, initialValue: TextEditingValue(
noItemsFoundBuilder: (context) => SizedBox(), text: lineItems[index].productKey),
suggestionsCallback: (pattern) { optionsBuilder: (TextEditingValue textEditingValue) {
return productIds print(
.where((productId) => productState '## optionsBuilder - filter: ${textEditingValue.text}');
.map[productId].productKey final options = productIds
.toLowerCase() .map((productId) => productState.map[productId])
.contains(pattern.toLowerCase())) .where((product) => product.productKey
.toList(); .toLowerCase()
/* .contains(
return productIds textEditingValue.text.toLowerCase()))
.where((productId) => productState.map[productId] .toList();
.matchesFilter(pattern))
.toList(); return options;
*/ },
}, displayStringForOption: (product) =>
itemBuilder: (context, productId) { product.productKey,
// TODO fix this onSelected: (product) {
/* final item = lineItems[index];
return ListTile( final client =
title: Text(productState.map[suggestion].productKey), state.clientState.get(invoice.clientId);
); final currency = state
*/ .staticState.currencyMap[client.currencyId];
return Listener(
child: Container( double cost = product.price;
color: Theme.of(context).cardColor, if (company.convertProductExchangeRate &&
child: ListTile( invoice.clientId != null &&
title: Text( client.currencyId != company.currencyId) {
productState.map[productId].productKey), cost = round(cost * invoice.exchangeRate,
currency.precision);
}
final updatedItem = item.rebuild((b) => b
..productKey = product.productKey
..notes = item.isTask ? item.notes : product.notes
..productCost = item.productCost
..cost = item.isTask && item.cost != 0
? item.cost
: cost
..quantity = item.isTask || item.quantity != 0
? item.quantity
: viewModel.state.company.defaultQuantity
? 1
: product.quantity
..customValue1 = product.customValue1
..customValue2 = product.customValue2
..customValue3 = product.customValue3
..customValue4 = product.customValue4
..taxName1 = company.numberOfItemTaxRates >= 1
? product.taxName1
: ''
..taxRate1 = company.numberOfItemTaxRates >= 1
? product.taxRate1
: 0
..taxName2 = company.numberOfItemTaxRates >= 2
? product.taxName2
: ''
..taxRate2 = company.numberOfItemTaxRates >= 2
? product.taxRate2
: 0
..taxName3 = company.numberOfItemTaxRates >= 3
? product.taxName3
: ''
..taxRate3 = company.numberOfItemTaxRates >= 3
? product.taxRate3
: 0);
_onChanged(updatedItem, index, debounce: false);
_updateTable();
},
fieldViewBuilder: (BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
return DecoratedFormField(
showClear: false,
autofocus: false,
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
onChanged: (value) {
print('## Set filter to $value');
_onChanged(
lineItems[index]
.rebuild((b) => b..productKey = value),
index);
},
);
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<ProductEntity> onSelected,
Iterable<SelectableEntity> options) {
final highlightedIndex =
AutocompleteHighlightedOption.of(context);
return Theme(
data: theme,
child: Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4,
child: AppBorder(
child: Container(
color: Theme.of(context).cardColor,
width: 250,
constraints:
BoxConstraints(maxHeight: 270),
child: ScrollableListViewBuilder(
itemCount: options.length,
itemBuilder:
(BuildContext context, int index) {
return Container(
color: highlightedIndex == index
? convertHexStringToColor(state
.prefState
.enableDarkMode
? kDefaultDarkSelectedColor
: kDefaultLightSelectedColor)
: Theme.of(context).cardColor,
child: EntityAutocompleteListTile(
onTap: (entity) =>
onSelected(entity),
entity: options.elementAt(index),
),
);
},
),
),
), ),
), ),
onPointerDown: (_) { ),
if (!kIsWeb) { );
return; },
} ),
),
final item = lineItems[index];
final product = productState.map[productId];
final client =
state.clientState.get(invoice.clientId);
final currency = state.staticState
.currencyMap[client.currencyId];
double cost = product.price;
if (company.convertProductExchangeRate &&
invoice.clientId != null &&
client.currencyId != company.currencyId) {
cost = round(cost * invoice.exchangeRate,
currency.precision);
}
final updatedItem = item.rebuild((b) => b
..productKey = product.productKey
..notes =
item.isTask || !company.fillProducts
? item.notes
: product.notes
..cost = (item.isTask && item.cost != 0) ||
!company.fillProducts
? item.cost
: cost
..productCost = product.cost
..quantity =
item.isTask || item.quantity != 0
? item.quantity
: product.quantity == 0
? 1
: product.quantity
..customValue1 = product.customValue1
..customValue2 = product.customValue2
..customValue3 = product.customValue3
..customValue4 = product.customValue4
..taxName1 =
company.numberOfItemTaxRates >= 1
? product.taxName1
: ''
..taxRate1 =
company.numberOfItemTaxRates >= 1
? product.taxRate1
: 0
..taxName2 =
company.numberOfItemTaxRates >= 2
? product.taxName2
: ''
..taxRate2 =
company.numberOfItemTaxRates >= 2
? product.taxRate2
: 0
..taxName3 =
company.numberOfItemTaxRates >= 3
? product.taxName3
: ''
..taxRate3 =
company.numberOfItemTaxRates >= 3
? product.taxRate3
: 0);
_onChanged(updatedItem, index,
debounce: false);
_updateTable();
},
);
},
onSuggestionSelected: (suggestion) {
if (kIsWeb) {
return;
}
final item = lineItems[index];
final product = productState.map[suggestion];
final client =
state.clientState.get(invoice.clientId);
double cost = product.price;
if (company.convertProductExchangeRate &&
invoice.clientId != null &&
client.currencyId != company.currencyId) {
cost = round(
cost * invoice.exchangeRate,
state
.staticState
.currencyMap[client?.currencyId ??
company.currencyId]
.precision);
}
final updatedItem = item.rebuild((b) => b
..productKey = product.productKey
..notes = item.isTask || !company.fillProducts
? item.notes
: product.notes
..cost = (item.isTask && item.cost != 0) ||
!company.fillProducts
? item.cost
: cost
..productCost = product.cost
..quantity = item.isTask || item.quantity != 0
? item.quantity
: product.quantity == 0
? 1
: product.quantity
..customValue1 = product.customValue1
..customValue2 = product.customValue2
..customValue3 = product.customValue3
..customValue4 = product.customValue4
..taxName1 = company.numberOfItemTaxRates >= 1
? product.taxName1
: ''
..taxRate1 = company.numberOfItemTaxRates >= 1
? product.taxRate1
: 0
..taxName2 = company.numberOfItemTaxRates >= 2
? product.taxName2
: ''
..taxRate2 = company.numberOfItemTaxRates >= 2
? product.taxRate2
: 0
..taxName3 = company.numberOfItemTaxRates >= 3
? product.taxName3
: ''
..taxRate3 = company.numberOfItemTaxRates >= 3
? product.taxRate3
: 0);
_onChanged(updatedItem, index, debounce: false);
_updateTable();
},
textFieldConfiguration:
TextFieldConfiguration(onChanged: (value) {
_onChanged(
lineItems[index]
.rebuild((b) => b..productKey = value),
index);
}),
autoFlipDirection: true,
animationStart: 1,
debounceDuration: Duration(seconds: 0),
)),
), ),
Focus( Focus(
onFocusChange: (hasFocus) => _onFocusChange(), onFocusChange: (hasFocus) => _onFocusChange(),