This commit is contained in:
unknown 2018-07-02 12:54:29 +03:00
parent 39a73558c3
commit d171ef9b11
16 changed files with 190 additions and 204 deletions

View File

@ -42,6 +42,7 @@ analyzer:
linter:
rules:
# - prefer_const_literals_to_create_immutables
# these rules are documented on and in the same order as
# the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
@ -117,7 +118,6 @@ linter:
# - prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
# - prefer_constructors_over_static_methods # not yet tested
- prefer_contains
# - prefer_equal_for_default_values # not supported by built_value

View File

@ -5,22 +5,26 @@ import 'package:invoiceninja/data/models/entities.dart';
part 'client_model.g.dart';
abstract class ClientListResponse implements Built<ClientListResponse, ClientListResponseBuilder> {
abstract class ClientListResponse
implements Built<ClientListResponse, ClientListResponseBuilder> {
BuiltList<ClientEntity> get data;
ClientListResponse._();
factory ClientListResponse([updates(ClientListResponseBuilder b)]) = _$ClientListResponse;
static Serializer<ClientListResponse> get serializer => _$clientListResponseSerializer;
factory ClientListResponse([updates(ClientListResponseBuilder b)]) =
_$ClientListResponse;
static Serializer<ClientListResponse> get serializer =>
_$clientListResponseSerializer;
}
abstract class ClientItemResponse implements Built<ClientItemResponse, ClientItemResponseBuilder> {
abstract class ClientItemResponse
implements Built<ClientItemResponse, ClientItemResponseBuilder> {
ClientEntity get data;
ClientItemResponse._();
factory ClientItemResponse([updates(ClientItemResponseBuilder b)]) = _$ClientItemResponse;
static Serializer<ClientItemResponse> get serializer => _$clientItemResponseSerializer;
factory ClientItemResponse([updates(ClientItemResponseBuilder b)]) =
_$ClientItemResponse;
static Serializer<ClientItemResponse> get serializer =>
_$clientItemResponseSerializer;
}
class ClientFields {
@ -36,9 +40,9 @@ class ClientFields {
static const String workPhone = 'workPhone';
}
abstract class ClientEntity extends Object with BaseEntity implements Built<ClientEntity, ClientEntityBuilder> {
abstract class ClientEntity extends Object
with BaseEntity
implements Built<ClientEntity, ClientEntityBuilder> {
@override
EntityType get entityType {
return EntityType.client;
@ -48,47 +52,46 @@ abstract class ClientEntity extends Object with BaseEntity implements Built<Clie
factory ClientEntity() {
return _$ClientEntity._(
id: --ClientEntity.counter,
name: '',
displayName: '',
balance: 0.0,
paidToDate: 0.0,
address1: '',
address2: '',
city: '',
state: '',
postalCode: '',
countryId: 0,
workPhone: '',
privateNotes: '',
publicNotes: '',
website: '',
industryId: 0,
sizeId: 0,
paymentTerms: 0,
vatNumber: '',
idNumber: '',
languageId: 0,
currencyId: 0,
invoiceNumberCounter: 0,
quoteNumberCounter: 0,
taskRate: 0.0,
shippingAddress1: '',
shippingAddress2: '',
shippingCity: '',
shippingState: '',
shippingPostalCode: '',
shippingCountryId: 0,
showTasksInPortal: false,
sendReminders: false,
creditNumberCounter: 0,
customValue1: '',
customValue2: '',
contacts: BuiltList<ContactEntity>(),
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
id: --ClientEntity.counter,
name: '',
displayName: '',
balance: 0.0,
paidToDate: 0.0,
address1: '',
address2: '',
city: '',
state: '',
postalCode: '',
countryId: 0,
workPhone: '',
privateNotes: '',
publicNotes: '',
website: '',
industryId: 0,
sizeId: 0,
paymentTerms: 0,
vatNumber: '',
idNumber: '',
languageId: 0,
currencyId: 0,
invoiceNumberCounter: 0,
quoteNumberCounter: 0,
taskRate: 0.0,
shippingAddress1: '',
shippingAddress2: '',
shippingCity: '',
shippingState: '',
shippingPostalCode: '',
shippingCountryId: 0,
showTasksInPortal: false,
sendReminders: false,
creditNumberCounter: 0,
customValue1: '',
customValue2: '',
contacts: BuiltList<ContactEntity>(),
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
);
}
@ -200,12 +203,13 @@ abstract class ClientEntity extends Object with BaseEntity implements Built<Clie
return displayName;
}
bool get hasEmailAddress => contacts.where((contact) => contact.email?.isNotEmpty).length > 0;
bool get hasEmailAddress =>
contacts.where((contact) => contact.email?.isNotEmpty).length > 0;
int compareTo(ClientEntity client, String sortField, bool sortAscending) {
int response = 0;
ClientEntity clientA = sortAscending ? this : client;
ClientEntity clientB = sortAscending ? client: this;
ClientEntity clientB = sortAscending ? client : this;
switch (sortField) {
case ClientFields.balance:
@ -213,7 +217,9 @@ abstract class ClientEntity extends Object with BaseEntity implements Built<Clie
}
if (response == 0) {
return clientA.displayName.toLowerCase().compareTo(clientB.displayName.toLowerCase());
return clientA.displayName
.toLowerCase()
.compareTo(clientB.displayName.toLowerCase());
} else {
return response;
}
@ -242,34 +248,9 @@ abstract class ClientEntity extends Object with BaseEntity implements Built<Clie
return false;
}
String matchesSearchField(String search) {
if (search == null || search.isEmpty) {
return null;
}
search = search.toLowerCase();
if (displayName.toLowerCase().contains(search)) {
return null;
}
if (vatNumber.toLowerCase().contains(search)) {
return ClientFields.vatNumber;
}
if (idNumber.toLowerCase().contains(search)) {
return ClientFields.idNumber;
}
if (workPhone.toLowerCase().contains(search)) {
return ClientFields.workPhone;
}
var contact = contacts.where((contact) => contact.matchesSearch(search)).first;
if (contact != null) {
return contact.matchesSearchField(search);
}
return null;
}
String matchesSearchValue(String search) {
if (search == null || search.isEmpty) {
return null;
return '';
}
search = search.toLowerCase();
@ -282,19 +263,20 @@ abstract class ClientEntity extends Object with BaseEntity implements Built<Clie
if (workPhone.toLowerCase().contains(search)) {
return workPhone;
}
var contact = contacts.where((contact) => contact.matchesSearch(search)).first;
final contact = contacts.firstWhere(
(contact) => contact.matchesSearch(search),
orElse: () => null);
if (contact != null) {
return contact.matchesSearchValue(search);
}
return null;
return '';
}
ClientEntity._();
static Serializer<ClientEntity> get serializer => _$clientEntitySerializer;
}
class ContactFields {
static const String firstName = 'firstName';
static const String lastName = 'lastName';
@ -302,24 +284,24 @@ class ContactFields {
static const String phone = 'phone';
}
abstract class ContactEntity extends Object with BaseEntity implements Built<ContactEntity, ContactEntityBuilder> {
abstract class ContactEntity extends Object
with BaseEntity
implements Built<ContactEntity, ContactEntityBuilder> {
static int counter = 0;
factory ContactEntity() {
return _$ContactEntity._(
id: --ContactEntity.counter,
firstName: '',
lastName: '',
email: '',
phone: '',
contactKey: '',
isPrimary: false,
customValue1: '',
customValue2: '',
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
id: --ContactEntity.counter,
firstName: '',
lastName: '',
email: '',
phone: '',
contactKey: '',
isPrimary: false,
customValue1: '',
customValue2: '',
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
);
}
@ -345,7 +327,7 @@ abstract class ContactEntity extends Object with BaseEntity implements Built<Con
@BuiltValueField(wireName: 'custom_value2')
String get customValue2;
String fullName () {
String fullName() {
return (firstName + ' ' + lastName).trim();
}
@ -369,22 +351,6 @@ abstract class ContactEntity extends Object with BaseEntity implements Built<Con
return false;
}
String matchesSearchField(String search) {
if (search == null || search.isEmpty) {
return null;
}
search = search.toLowerCase();
if (fullName().toLowerCase().contains(search)) {
return ClientFields.contact;
} else if (email.toLowerCase().contains(search)) {
return ContactFields.email;
} else if (phone.toLowerCase().contains(search)) {
return ContactFields.phone;
}
return null;
}
String matchesSearchValue(String search) {
if (search == null || search.isEmpty) {
return null;

View File

@ -89,10 +89,6 @@ abstract class BaseEntity {
return true;
}
String matchesSearchField(String search) {
return null;
}
String matchesSearchValue(String search) {
return null;
}

View File

@ -8,22 +8,26 @@ import 'package:invoiceninja/utils/formatting.dart';
part 'product_model.g.dart';
abstract class ProductListResponse implements Built<ProductListResponse, ProductListResponseBuilder> {
abstract class ProductListResponse
implements Built<ProductListResponse, ProductListResponseBuilder> {
BuiltList<ProductEntity> get data;
ProductListResponse._();
factory ProductListResponse([updates(ProductListResponseBuilder b)]) = _$ProductListResponse;
static Serializer<ProductListResponse> get serializer => _$productListResponseSerializer;
factory ProductListResponse([updates(ProductListResponseBuilder b)]) =
_$ProductListResponse;
static Serializer<ProductListResponse> get serializer =>
_$productListResponseSerializer;
}
abstract class ProductItemResponse implements Built<ProductItemResponse, ProductItemResponseBuilder> {
abstract class ProductItemResponse
implements Built<ProductItemResponse, ProductItemResponseBuilder> {
ProductEntity get data;
ProductItemResponse._();
factory ProductItemResponse([updates(ProductItemResponseBuilder b)]) = _$ProductItemResponse;
static Serializer<ProductItemResponse> get serializer => _$productItemResponseSerializer;
factory ProductItemResponse([updates(ProductItemResponseBuilder b)]) =
_$ProductItemResponse;
static Serializer<ProductItemResponse> get serializer =>
_$productItemResponseSerializer;
}
class ProductFields {
@ -33,10 +37,13 @@ class ProductFields {
static const String updatedAt = 'updatedAt';
static const String archivedAt = 'archivedAt';
static const String isDeleted = 'isDeleted';
static const String customValue1 = 'customValue1';
static const String customValue2 = 'customValue2';
}
abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceItem implements Built<ProductEntity, ProductEntityBuilder> {
abstract class ProductEntity extends Object
with BaseEntity, ConvertToInvoiceItem
implements Built<ProductEntity, ProductEntityBuilder> {
@override
EntityType get entityType {
return EntityType.product;
@ -45,44 +52,44 @@ abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceIte
static int counter = 0;
factory ProductEntity() {
return _$ProductEntity._(
id: --ProductEntity.counter,
productKey: '',
notes: '',
cost: 0.0,
taxName1: '',
taxRate1: 0.0,
taxName2: '',
taxRate2: 0.0,
customValue1: '',
customValue2: '',
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
id: --ProductEntity.counter,
productKey: '',
notes: '',
cost: 0.0,
taxName1: '',
taxRate1: 0.0,
taxName2: '',
taxRate2: 0.0,
customValue1: '',
customValue2: '',
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
);
}
@BuiltValueField(wireName: 'product_key')
String get productKey;
String get notes;
double get cost;
@BuiltValueField(wireName: 'tax_name1')
String get taxName1;
@BuiltValueField(wireName: 'tax_rate1')
double get taxRate1;
@BuiltValueField(wireName: 'tax_name2')
String get taxName2;
@BuiltValueField(wireName: 'tax_rate2')
double get taxRate2;
@BuiltValueField(wireName: 'custom_value1')
String get customValue1;
@BuiltValueField(wireName: 'custom_value2')
String get customValue2;
@ -103,14 +110,14 @@ abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceIte
..taxName1 = taxName1
..taxRate1 = taxRate1
..taxName2 = taxName2
..taxRate2 = taxRate2
);
..taxRate2 = taxRate2);
}
int compareTo(ProductEntity product, [String sortField, bool sortAscending = true]) {
int compareTo(ProductEntity product,
[String sortField, bool sortAscending = true]) {
int response = 0;
ProductEntity productA = sortAscending ? this : product;
ProductEntity productB = sortAscending ? product: this;
ProductEntity productB = sortAscending ? product : this;
switch (sortField) {
case ProductFields.cost:
@ -118,7 +125,9 @@ abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceIte
}
if (response == 0) {
return productA.productKey.toLowerCase().compareTo(productB.productKey.toLowerCase());
return productA.productKey
.toLowerCase()
.compareTo(productB.productKey.toLowerCase());
} else {
return response;
}
@ -130,29 +139,22 @@ abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceIte
}
search = search.toLowerCase();
if (productKey.toLowerCase().contains(search)) {
return true;
}
if (notes.toLowerCase().contains(search)) {
} else if (notes.toLowerCase().contains(search)) {
return true;
} else if (customValue1.isNotEmpty &&
customValue1.toLowerCase().contains(search)) {
return true;
} else if (customValue2.isNotEmpty &&
customValue2.toLowerCase().contains(search)) {
return true;
}
return false;
}
String matchesSearchField(String search) {
if (search == null || search.isEmpty) {
return null;
}
search = search.toLowerCase();
if (notes.toLowerCase().contains(search)) {
return ProductFields.notes;
}
return null;
}
String matchesSearchValue(String search) {
if (search == null || search.isEmpty) {
return null;
@ -161,6 +163,12 @@ abstract class ProductEntity extends Object with BaseEntity, ConvertToInvoiceIte
search = search.toLowerCase();
if (notes.toLowerCase().contains(search)) {
return notes;
} else if (customValue1.isNotEmpty &&
customValue1.toLowerCase().contains(search)) {
return customValue1;
} else if (customValue2.isNotEmpty &&
customValue2.toLowerCase().contains(search)) {
return customValue2;
}
return null;
}

View File

@ -125,7 +125,7 @@ class AppDrawer extends StatelessWidget {
DrawerTile(
icon: FontAwesomeIcons.filePdfO,
title: AppLocalization.of(context).invoices,
onTap: () => store.dispatch(ViewProductList(context)),
onTap: () => store.dispatch(ViewInvoiceList(context)),
onCreateTap: () {
navigator.pop();
store.dispatch(

View File

@ -88,13 +88,7 @@ class _EntityDropdownState extends State<EntityDropdown> {
final entity = widget.entityMap[entityId];
final filter =
store.state.getUIState(widget.entityType).dropdownFilter;
String subtitle;
final matchField = entity.matchesSearchField(filter);
if (matchField != null) {
final field = localization.lookup(matchField);
final value = entity.matchesSearchValue(filter);
subtitle = '$field: $value';
}
final String subtitle = entity.matchesSearchValue(filter);
return ListTile(
dense: true,
title: Text(entity.listDisplayName),

View File

@ -14,6 +14,7 @@ class ClientItem extends StatelessWidget {
final GestureTapCallback onTap;
//final ValueChanged<bool> onCheckboxChanged;
final ClientEntity client;
final String filter;
static final clientItemKey = (int id) => Key('__client_item_${id}__');
@ -23,6 +24,7 @@ class ClientItem extends StatelessWidget {
@required this.onTap,
//@required this.onCheckboxChanged,
@required this.client,
@required this.filter,
});
@override
@ -47,7 +49,18 @@ class ClientItem extends StatelessWidget {
style: Theme.of(context).textTheme.title)
],
),
subtitle: EntityStateLabel(client),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
filter != null && filter.isNotEmpty
? client.matchesSearchValue(filter) : '',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
EntityStateLabel(client),
],
),
),
);
}

View File

@ -31,6 +31,7 @@ class ClientList extends StatelessWidget {
var client = viewModel.clientMap[clientId];
return Column(children: <Widget>[
ClientItem(
filter: viewModel.filter,
state: viewModel.state,
client: client,
onDismissed: (DismissDirection direction) =>

View File

@ -35,6 +35,7 @@ class ClientListVM {
final AppState state;
final List<int> clientList;
final BuiltMap<int, ClientEntity> clientMap;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, ClientEntity) onClientTap;
@ -47,6 +48,7 @@ class ClientListVM {
@required this.clientMap,
@required this.isLoading,
@required this.isLoaded,
@required this.filter,
@required this.onClientTap,
@required this.onDismissed,
@required this.onRefreshed,
@ -70,6 +72,7 @@ class ClientListVM {
clientMap: store.state.clientState.map,
isLoading: store.state.isLoading,
isLoaded: store.state.clientState.isLoaded,
filter: store.state.clientListState.search,
onClientTap: (context, client) {
store.dispatch(ViewClient(clientId: client.id, context: context));
},

View File

@ -145,11 +145,7 @@ class _InvoiceItemSelectorState extends State<InvoiceItemSelector> {
itemBuilder: (BuildContext context, int index) {
int entityId = matches[index];
var entity = state.map[entityId];
var subtitle = null;
var matchField = entity.matchesSearchField(_filter);
if (matchField != null) {
subtitle = entity.matchesSearchValue(_filter);
}
final String subtitle = entity.matchesSearchValue(_filter);
return ListTile(
dense: true,
leading: Checkbox(

View File

@ -14,6 +14,7 @@ class InvoiceItem extends StatelessWidget {
final InvoiceEntity invoice;
final ClientEntity client;
final AppState state;
final String filter;
InvoiceItem({
@required this.onDismissed,
@ -22,6 +23,7 @@ class InvoiceItem extends StatelessWidget {
@required this.invoice,
@required this.client,
@required this.state,
@required this.filter,
});
@override
@ -62,16 +64,13 @@ class InvoiceItem extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(invoice.invoiceNumber),
/*
invoice.notes.isNotEmpty
? Text(
invoice.notes,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
*/
Text(
filter != null && filter.isNotEmpty
? invoice.matchesSearchValue(filter) ?? ''
: invoice.privateNotes ?? '',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
EntityStateLabel(invoice),
],
),

View File

@ -32,6 +32,7 @@ class InvoiceList extends StatelessWidget {
var invoice = viewModel.invoiceMap[invoiceId];
return Column(children: <Widget>[
InvoiceItem(
filter: viewModel.filter,
invoice: invoice,
client: viewModel.clientMap[invoice.clientId],
onDismissed: (DismissDirection direction) =>

View File

@ -36,6 +36,7 @@ class InvoiceListVM {
final List<int> invoiceList;
final BuiltMap<int, InvoiceEntity> invoiceMap;
final BuiltMap<int, ClientEntity> clientMap;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, InvoiceEntity) onInvoiceTap;
@ -49,6 +50,7 @@ class InvoiceListVM {
@required this.clientMap,
@required this.isLoading,
@required this.isLoaded,
@required this.filter,
@required this.onInvoiceTap,
@required this.onDismissed,
@required this.onRefreshed,
@ -80,6 +82,7 @@ class InvoiceListVM {
isLoading: state.isLoading,
isLoaded: state.invoiceState.isLoaded &&
state.clientState.isLoaded,
filter: state.invoiceListState.search,
onInvoiceTap: (context, invoice) {
store.dispatch(ViewInvoice(invoiceId: invoice.id, context: context));
},

View File

@ -13,7 +13,8 @@ class ProductItem extends StatelessWidget {
final GestureTapCallback onTap;
//final ValueChanged<bool> onCheckboxChanged;
final ProductEntity product;
final String filter;
static final productItemKey = (int id) => Key('__product_item_${id}__');
ProductItem({
@ -22,6 +23,7 @@ class ProductItem extends StatelessWidget {
@required this.onTap,
//@required this.onCheckboxChanged,
@required this.product,
@required this.filter,
});
@override
@ -61,13 +63,13 @@ class ProductItem extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
product.notes.isNotEmpty
? Text(
product.notes,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
Text(
filter != null && filter.isNotEmpty
? product.matchesSearchValue(filter) ?? ''
: product.notes ?? '',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
EntityStateLabel(product),
],
),

View File

@ -32,6 +32,7 @@ class ProductList extends StatelessWidget {
var product = viewModel.productMap[productId];
return Column(children: <Widget>[
ProductItem(
filter: viewModel.filter,
state: viewModel.state,
product: product,
onDismissed: (DismissDirection direction) =>

View File

@ -34,6 +34,7 @@ class ProductListVM {
final AppState state;
final List<int> productList;
final BuiltMap<int, ProductEntity> productMap;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, ProductEntity) onProductTap;
@ -44,6 +45,7 @@ class ProductListVM {
@required this.state,
@required this.productList,
@required this.productMap,
@required this.filter,
@required this.isLoading,
@required this.isLoaded,
@required this.onProductTap,
@ -69,6 +71,7 @@ class ProductListVM {
productMap: store.state.productState.map,
isLoading: store.state.isLoading,
isLoaded: store.state.productState.isLoaded,
filter: store.state.productUIState.listUIState.search,
onProductTap: (context, product) {
store.dispatch(EditProduct(product: product, context: context));
},