diff --git a/lib/data/models/client_model.dart b/lib/data/models/client_model.dart index 90446ac47..094bc689d 100644 --- a/lib/data/models/client_model.dart +++ b/lib/data/models/client_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'client_model.g.dart'; @@ -288,7 +289,10 @@ abstract class ClientEntity extends Object } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$clientEntitySerializer; } @@ -395,7 +399,10 @@ abstract class ContactEntity extends Object } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$contactEntitySerializer; } diff --git a/lib/data/models/credit_model.dart b/lib/data/models/credit_model.dart index 928736517..1a0e67361 100644 --- a/lib/data/models/credit_model.dart +++ b/lib/data/models/credit_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'credit_model.g.dart'; @@ -122,7 +123,10 @@ abstract class CreditEntity extends Object with BaseEntity implements Built 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$creditEntitySerializer; } diff --git a/lib/data/models/entities.dart b/lib/data/models/entities.dart index af6f0d369..e8afae98d 100644 --- a/lib/data/models/entities.dart +++ b/lib/data/models/entities.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/models.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'entities.g.dart'; @@ -65,7 +66,8 @@ abstract class SelectableEntity { String get listDisplayName => 'Error: listDisplayName not set'; - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + FormatNumberType get listDisplayAmountType => FormatNumberType.money; } abstract class BaseEntity extends Object with SelectableEntity { diff --git a/lib/data/models/expense_model.dart b/lib/data/models/expense_model.dart index 77ce63c00..d628e1abd 100644 --- a/lib/data/models/expense_model.dart +++ b/lib/data/models/expense_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'expense_model.g.dart'; @@ -198,7 +199,10 @@ abstract class ExpenseEntity extends Object } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$expenseEntitySerializer; } @@ -235,7 +239,10 @@ abstract class ExpenseCategoryEntity extends Object } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; String get name; diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index a14ce9286..9c3257559 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -336,7 +336,10 @@ abstract class InvoiceEntity extends Object with BaseEntity, CalculateInvoiceTot } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; bool get isPastDue { return ! isDeleted && isPublic && balance > 0 && DateTime.tryParse(dueDate).isBefore(DateTime.now()); @@ -433,7 +436,10 @@ abstract class InvoiceItemEntity extends Object with BaseEntity implements Built } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$invoiceItemEntitySerializer; } @@ -493,7 +499,10 @@ abstract class InvitationEntity extends Object with BaseEntity implements Built< } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$invitationEntitySerializer; } diff --git a/lib/data/models/payment_model.dart b/lib/data/models/payment_model.dart index a1b99b817..90174987f 100644 --- a/lib/data/models/payment_model.dart +++ b/lib/data/models/payment_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'payment_model.g.dart'; @@ -133,7 +134,10 @@ abstract class PaymentEntity extends Object with BaseEntity implements Built 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$paymentEntitySerializer; } diff --git a/lib/data/models/product_model.dart b/lib/data/models/product_model.dart index fc8ec1595..34929918f 100644 --- a/lib/data/models/product_model.dart +++ b/lib/data/models/product_model.dart @@ -3,6 +3,7 @@ import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; import 'package:invoiceninja/data/models/invoice_model.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'product_model.g.dart'; @@ -102,6 +103,9 @@ abstract class ProductEntity extends Object @override double get listDisplayAmount => cost; + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; + @override InvoiceItemEntity get asInvoiceItem { return InvoiceItemEntity().rebuild((b) => b diff --git a/lib/data/models/project_model.dart b/lib/data/models/project_model.dart index 4579e13e0..96d093122 100644 --- a/lib/data/models/project_model.dart +++ b/lib/data/models/project_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'project_model.g.dart'; @@ -131,7 +132,10 @@ abstract class ProjectEntity extends Object with BaseEntity implements Built 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$projectEntitySerializer; } diff --git a/lib/data/models/static/country_model.dart b/lib/data/models/static/country_model.dart index 5de645b6f..f48e4c1cf 100644 --- a/lib/data/models/static/country_model.dart +++ b/lib/data/models/static/country_model.dart @@ -182,7 +182,7 @@ abstract class CountryEntity extends Object with SelectableEntity implements Bui } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; static Serializer get serializer => _$countryEntitySerializer; } diff --git a/lib/data/models/static/currency_model.dart b/lib/data/models/static/currency_model.dart index a34fc867c..a99e3cb0f 100644 --- a/lib/data/models/static/currency_model.dart +++ b/lib/data/models/static/currency_model.dart @@ -113,7 +113,7 @@ abstract class CurrencyEntity extends Object with SelectableEntity implements Bu } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; static Serializer get serializer => _$currencyEntitySerializer; } \ No newline at end of file diff --git a/lib/data/models/static/language_model.dart b/lib/data/models/static/language_model.dart index 67ed2a372..9d78c1399 100644 --- a/lib/data/models/static/language_model.dart +++ b/lib/data/models/static/language_model.dart @@ -83,7 +83,7 @@ abstract class LanguageEntity extends Object with SelectableEntity implements Bu } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; static Serializer get serializer => _$languageEntitySerializer; } diff --git a/lib/data/models/task_model.dart b/lib/data/models/task_model.dart index b4db02a6f..6111af5d3 100644 --- a/lib/data/models/task_model.dart +++ b/lib/data/models/task_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'task_model.g.dart'; @@ -136,7 +137,10 @@ abstract class TaskEntity extends Object with BaseEntity implements Built 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$taskEntitySerializer; } diff --git a/lib/data/models/vendor_model.dart b/lib/data/models/vendor_model.dart index d90a8a51d..58e93b98b 100644 --- a/lib/data/models/vendor_model.dart +++ b/lib/data/models/vendor_model.dart @@ -2,6 +2,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/utils/formatting.dart'; part 'vendor_model.g.dart'; @@ -177,7 +178,10 @@ abstract class VendorEntity extends Object with BaseEntity implements Built 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$vendorEntitySerializer; } @@ -238,7 +242,10 @@ abstract class VendorContactEntity extends Object with BaseEntity implements Bui } @override - double get listDisplayAmount => 0.0; + double get listDisplayAmount => null; + + @override + FormatNumberType get listDisplayAmountType => FormatNumberType.money; static Serializer get serializer => _$vendorContactEntitySerializer; } \ No newline at end of file diff --git a/lib/redux/product/product_selectors.dart b/lib/redux/product/product_selectors.dart index e57f4f276..c1a1ae92a 100644 --- a/lib/redux/product/product_selectors.dart +++ b/lib/redux/product/product_selectors.dart @@ -3,7 +3,19 @@ import 'package:built_collection/built_collection.dart'; import 'package:invoiceninja/data/models/models.dart'; import 'package:invoiceninja/redux/ui/list_ui_state.dart'; -var memoizedProductList = memo3(( +var memoizedProductList = memo1((BuiltMap productMap) => + productList(productMap)); + +List productList(BuiltMap productMap) { + final list = productMap.keys.where((productId) => productMap[productId].isActive).toList(); + + list.sort((idA, idB) => productMap[idA].listDisplayName + .compareTo(productMap[idB].listDisplayName)); + + return list; +} + +var memoizedFilteredProductList = memo3(( BuiltMap productMap, BuiltList productList, ListUIState productListState) => visibleProductsSelector(productMap, productList, productListState) diff --git a/lib/ui/app/entity_dropdown.dart b/lib/ui/app/entity_dropdown.dart index 7797401bd..2f35044a8 100644 --- a/lib/ui/app/entity_dropdown.dart +++ b/lib/ui/app/entity_dropdown.dart @@ -1,6 +1,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:invoiceninja/data/models/models.dart'; +import 'package:invoiceninja/utils/formatting.dart'; import 'package:invoiceninja/utils/localization.dart'; class EntityDropdown extends StatefulWidget { @@ -149,6 +150,10 @@ class _EntityDropdownDialogState extends State { Expanded( child: Text(entity.listDisplayName), ), + entity.listDisplayAmount != null + ? Text(formatNumber(entity.listDisplayAmount, context, + formatNumberType: entity.listDisplayAmountType)) + : Container(), ], ), subtitle: subtitle != null ? Text(subtitle, maxLines: 2) : null, diff --git a/lib/ui/invoice/edit/invoice_edit.dart b/lib/ui/invoice/edit/invoice_edit.dart index 7433549e4..0c241e7aa 100644 --- a/lib/ui/invoice/edit/invoice_edit.dart +++ b/lib/ui/invoice/edit/invoice_edit.dart @@ -110,6 +110,7 @@ class _InvoiceEditState extends State context: context, builder: (BuildContext context) { return InvoiceItemSelector( + productMap: viewModel.productMap, onItemsSelected: viewModel.onItemsAdded, ); }); diff --git a/lib/ui/invoice/edit/invoice_edit_vm.dart b/lib/ui/invoice/edit/invoice_edit_vm.dart index 6f4ab18de..c9184e918 100644 --- a/lib/ui/invoice/edit/invoice_edit_vm.dart +++ b/lib/ui/invoice/edit/invoice_edit_vm.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:built_collection/built_collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; @@ -37,6 +38,7 @@ class InvoiceEditVM { final CompanyEntity company; final InvoiceEntity invoice; final InvoiceEntity origInvoice; + final BuiltMap productMap; final Function(BuildContext) onSavePressed; final Function(BuildContext, EntityAction) onActionSelected; final Function(List) onItemsAdded; @@ -47,6 +49,7 @@ class InvoiceEditVM { @required this.company, @required this.invoice, @required this.origInvoice, + @required this.productMap, @required this.onSavePressed, @required this.onItemsAdded, @required this.onBackPressed, @@ -61,6 +64,7 @@ class InvoiceEditVM { return InvoiceEditVM( company: state.selectedCompany, isLoading: state.isLoading, + productMap: state.selectedCompanyState.productState.map, invoice: invoice, origInvoice: store.state.invoiceState.map[invoice.id], onBackPressed: () => diff --git a/lib/ui/invoice/edit/invoice_item_selector.dart b/lib/ui/invoice/edit/invoice_item_selector.dart index 069d5da13..fac842190 100644 --- a/lib/ui/invoice/edit/invoice_item_selector.dart +++ b/lib/ui/invoice/edit/invoice_item_selector.dart @@ -1,12 +1,18 @@ +import 'package:built_collection/built_collection.dart'; import 'package:invoiceninja/data/models/invoice_model.dart'; -import 'package:invoiceninja/redux/app/app_state.dart'; +import 'package:invoiceninja/data/models/models.dart'; +import 'package:invoiceninja/redux/product/product_selectors.dart'; import 'package:flutter/material.dart'; +import 'package:invoiceninja/utils/formatting.dart'; import 'package:invoiceninja/utils/localization.dart'; class InvoiceItemSelector extends StatefulWidget { - const InvoiceItemSelector({this.state, this.onItemsSelected}); + const InvoiceItemSelector({ + @required this.productMap, + this.onItemsSelected, + }); - final AppState state; + final BuiltMap productMap; final Function(List) onItemsSelected; @override @@ -35,7 +41,7 @@ class _InvoiceItemSelectorState extends State { final List items = []; _selectedIds.forEach((entityId) { - final product = widget.state.productState.map[entityId]; + final product = widget.productMap[entityId]; items.add(product.asInvoiceItem); }); @@ -130,22 +136,20 @@ class _InvoiceItemSelectorState extends State { } Widget _entityList() { - final state = widget.state.selectedCompanyState.productState; - final matches = state.list - .where((entityId) { - final entity = state.map[entityId]; - return entity.isActive && entity.matchesSearch(_filter); - }) - .toList(); + final matches = memoizedProductList(widget.productMap).where((entityId) { + final entity = widget.productMap[entityId]; + return entity.isActive && entity.matchesSearch(_filter); + }).toList(); - matches.sort((idA, idB) => state.map[idA].compareTo(state.map[idB])); + matches.sort((idA, idB) => + widget.productMap[idA].compareTo(widget.productMap[idB])); return ListView.builder( shrinkWrap: true, itemCount: matches.length, itemBuilder: (BuildContext context, int index) { final int entityId = matches[index]; - final entity = state.map[entityId]; + final entity = widget.productMap[entityId]; final String subtitle = entity.matchesSearchValue(_filter); return ListTile( dense: true, @@ -158,7 +162,10 @@ class _InvoiceItemSelectorState extends State { Expanded( child: Text(entity.listDisplayName), ), - //Text(entity.listDisplayAmount), + entity.listDisplayAmount != null + ? Text(formatNumber(entity.listDisplayAmount, context, + formatNumberType: entity.listDisplayAmountType)) + : Container(), ], ), subtitle: subtitle != null ? Text(subtitle, maxLines: 2) : null, diff --git a/lib/ui/product/product_list_vm.dart b/lib/ui/product/product_list_vm.dart index bf5ef0de4..f83b12c44 100644 --- a/lib/ui/product/product_list_vm.dart +++ b/lib/ui/product/product_list_vm.dart @@ -64,7 +64,7 @@ class ProductListVM { } return ProductListVM( - productList: memoizedProductList(store.state.productState.map, store.state.productState.list, store.state.productListState), + productList: memoizedFilteredProductList(store.state.productState.map, store.state.productState.list, store.state.productListState), productMap: store.state.productState.map, isLoading: store.state.isLoading, isLoaded: store.state.productState.isLoaded,