Replace autocomplete
This commit is contained in:
parent
237fb8f428
commit
e0ee07a276
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,35 +475,41 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
|
||||||
key: ValueKey(
|
key: ValueKey(
|
||||||
'__line_item_${index}_${lineItems[index].createdAt}__'),
|
'__line_item_${index}_${lineItems[index].createdAt}__'),
|
||||||
children: [
|
children: [
|
||||||
/*
|
Focus(
|
||||||
Padding(
|
onFocusChange: (hasFocus) {
|
||||||
|
print('## onFocusChange - hasFocus: $hasFocus');
|
||||||
|
_autocompleteFocusIndex = index;
|
||||||
|
_onFocusChange();
|
||||||
|
},
|
||||||
|
skipTraversal: true,
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: kTableColumnGap),
|
padding: const EdgeInsets.only(right: kTableColumnGap),
|
||||||
child: Autocomplete<ProductEntity>(
|
child: RawAutocomplete<ProductEntity>(
|
||||||
//key: ValueKey('__line_item_${index}_name__'),
|
key: ValueKey('__line_item_${index}_name__'),
|
||||||
|
textEditingController: _textEditingController,
|
||||||
|
initialValue: TextEditingValue(
|
||||||
|
text: lineItems[index].productKey),
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
print(
|
||||||
|
'## optionsBuilder - filter: ${textEditingValue.text}');
|
||||||
final options = productIds
|
final options = productIds
|
||||||
.map((productId) => productState.map[productId])
|
.map((productId) => productState.map[productId])
|
||||||
.where((product) => product.productKey
|
.where((product) => product.productKey
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(_filter.toLowerCase()))
|
.contains(
|
||||||
|
textEditingValue.text.toLowerCase()))
|
||||||
.toList();
|
.toList();
|
||||||
/*
|
|
||||||
if (options.length == 1 &&
|
|
||||||
options[0].productKey.toLowerCase() ==
|
|
||||||
_filter.toLowerCase()) {
|
|
||||||
return <ProductEntity>[];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
displayStringForOption: (product) => product.productKey,
|
displayStringForOption: (product) =>
|
||||||
|
product.productKey,
|
||||||
onSelected: (product) {
|
onSelected: (product) {
|
||||||
final item = lineItems[index];
|
final item = lineItems[index];
|
||||||
final client =
|
final client =
|
||||||
state.clientState.get(invoice.clientId);
|
state.clientState.get(invoice.clientId);
|
||||||
final currency =
|
final currency = state
|
||||||
state.staticState.currencyMap[client.currencyId];
|
.staticState.currencyMap[client.currencyId];
|
||||||
|
|
||||||
double cost = product.price;
|
double cost = product.price;
|
||||||
if (company.convertProductExchangeRate &&
|
if (company.convertProductExchangeRate &&
|
||||||
|
|
@ -511,8 +522,10 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
|
||||||
final updatedItem = item.rebuild((b) => b
|
final updatedItem = item.rebuild((b) => b
|
||||||
..productKey = product.productKey
|
..productKey = product.productKey
|
||||||
..notes = item.isTask ? item.notes : product.notes
|
..notes = item.isTask ? item.notes : product.notes
|
||||||
..cost =
|
..productCost = item.productCost
|
||||||
item.isTask && item.cost != 0 ? item.cost : cost
|
..cost = item.isTask && item.cost != 0
|
||||||
|
? item.cost
|
||||||
|
: cost
|
||||||
..quantity = item.isTask || item.quantity != 0
|
..quantity = item.isTask || item.quantity != 0
|
||||||
? item.quantity
|
? item.quantity
|
||||||
: viewModel.state.company.defaultQuantity
|
: viewModel.state.company.defaultQuantity
|
||||||
|
|
@ -522,193 +535,6 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
|
||||||
..customValue2 = product.customValue2
|
..customValue2 = product.customValue2
|
||||||
..customValue3 = product.customValue3
|
..customValue3 = product.customValue3
|
||||||
..customValue4 = product.customValue4
|
..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(
|
|
||||||
onFocusChange: (hasFocus) {
|
|
||||||
_autocompleteFocusIndex = index;
|
|
||||||
_onFocusChange();
|
|
||||||
},
|
|
||||||
skipTraversal: true,
|
|
||||||
child: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(right: kTableColumnGap),
|
|
||||||
child: TypeAheadFormField<String>(
|
|
||||||
key: ValueKey('__line_item_${index}_name__'),
|
|
||||||
initialValue: lineItems[index].productKey,
|
|
||||||
noItemsFoundBuilder: (context) => SizedBox(),
|
|
||||||
suggestionsCallback: (pattern) {
|
|
||||||
return productIds
|
|
||||||
.where((productId) => productState
|
|
||||||
.map[productId].productKey
|
|
||||||
.toLowerCase()
|
|
||||||
.contains(pattern.toLowerCase()))
|
|
||||||
.toList();
|
|
||||||
/*
|
|
||||||
return productIds
|
|
||||||
.where((productId) => productState.map[productId]
|
|
||||||
.matchesFilter(pattern))
|
|
||||||
.toList();
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
itemBuilder: (context, productId) {
|
|
||||||
// TODO fix this
|
|
||||||
/*
|
|
||||||
return ListTile(
|
|
||||||
title: Text(productState.map[suggestion].productKey),
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
return Listener(
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).cardColor,
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(
|
|
||||||
productState.map[productId].productKey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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
|
..taxName1 = company.numberOfItemTaxRates >= 1
|
||||||
? product.taxName1
|
? product.taxName1
|
||||||
: ''
|
: ''
|
||||||
|
|
@ -730,17 +556,72 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
|
||||||
_onChanged(updatedItem, index, debounce: false);
|
_onChanged(updatedItem, index, debounce: false);
|
||||||
_updateTable();
|
_updateTable();
|
||||||
},
|
},
|
||||||
textFieldConfiguration:
|
fieldViewBuilder: (BuildContext context,
|
||||||
TextFieldConfiguration(onChanged: (value) {
|
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(
|
_onChanged(
|
||||||
lineItems[index]
|
lineItems[index]
|
||||||
.rebuild((b) => b..productKey = value),
|
.rebuild((b) => b..productKey = value),
|
||||||
index);
|
index);
|
||||||
}),
|
},
|
||||||
autoFlipDirection: true,
|
);
|
||||||
animationStart: 1,
|
},
|
||||||
debounceDuration: Duration(seconds: 0),
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Focus(
|
Focus(
|
||||||
onFocusChange: (hasFocus) => _onFocusChange(),
|
onFocusChange: (hasFocus) => _onFocusChange(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue