Quotes
This commit is contained in:
parent
29ff8f415e
commit
5a3a30dc4e
|
|
@ -367,7 +367,7 @@ abstract class ActivityEntity implements Built<ActivityEntity, ActivityEntityBui
|
||||||
//ContactEntity contact,
|
//ContactEntity contact,
|
||||||
PaymentEntity payment,
|
PaymentEntity payment,
|
||||||
CreditEntity credit,
|
CreditEntity credit,
|
||||||
//QuoteEntity quote,
|
//InvoiceEntity quote,
|
||||||
TaskEntity task,
|
TaskEntity task,
|
||||||
ExpenseEntity expense,
|
ExpenseEntity expense,
|
||||||
VendorEntity vendor,
|
VendorEntity vendor,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ export 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/task_model.dart';
|
export 'package:invoiceninja_flutter/data/models/task_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/expense_model.dart';
|
export 'package:invoiceninja_flutter/data/models/expense_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/vendor_model.dart';
|
export 'package:invoiceninja_flutter/data/models/vendor_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/quote_model.dart';
|
|
||||||
export 'package:invoiceninja_flutter/data/models/static/static_data_model.dart';
|
export 'package:invoiceninja_flutter/data/models/static/static_data_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/static/currency_model.dart';
|
export 'package:invoiceninja_flutter/data/models/static/currency_model.dart';
|
||||||
export 'package:invoiceninja_flutter/data/models/static/size_model.dart';
|
export 'package:invoiceninja_flutter/data/models/static/size_model.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,343 +0,0 @@
|
||||||
import 'package:built_value/built_value.dart';
|
|
||||||
import 'package:built_collection/built_collection.dart';
|
|
||||||
import 'package:built_value/serializer.dart';
|
|
||||||
import 'package:invoiceninja_flutter/constants.dart';
|
|
||||||
import 'package:invoiceninja_flutter/data/models/mixins/invoice_mixin.dart';
|
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
|
||||||
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
|
||||||
|
|
||||||
part 'quote_model.g.dart';
|
|
||||||
|
|
||||||
|
|
||||||
abstract class QuoteListResponse
|
|
||||||
implements Built<QuoteListResponse, QuoteListResponseBuilder> {
|
|
||||||
factory QuoteListResponse([void updates(QuoteListResponseBuilder b)]) =
|
|
||||||
_$QuoteListResponse;
|
|
||||||
|
|
||||||
QuoteListResponse._();
|
|
||||||
|
|
||||||
BuiltList<QuoteEntity> get data;
|
|
||||||
|
|
||||||
static Serializer<QuoteListResponse> get serializer =>
|
|
||||||
_$quoteListResponseSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class QuoteItemResponse
|
|
||||||
implements Built<QuoteItemResponse, QuoteItemResponseBuilder> {
|
|
||||||
factory QuoteItemResponse([void updates(QuoteItemResponseBuilder b)]) =
|
|
||||||
_$QuoteItemResponse;
|
|
||||||
|
|
||||||
QuoteItemResponse._();
|
|
||||||
|
|
||||||
QuoteEntity get data;
|
|
||||||
|
|
||||||
static Serializer<QuoteItemResponse> get serializer =>
|
|
||||||
_$quoteItemResponseSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuoteFields {
|
|
||||||
static const String amount = 'amount';
|
|
||||||
static const String clientId = 'clientId';
|
|
||||||
static const String quoteStatusId = 'quoteStatusId';
|
|
||||||
static const String quoteNumber = 'quoteNumber';
|
|
||||||
static const String discount = 'discount';
|
|
||||||
static const String poNumber = 'poNumber';
|
|
||||||
static const String quoteDate = 'quoteDate';
|
|
||||||
static const String validUntil = 'validUntil';
|
|
||||||
static const String terms = 'terms';
|
|
||||||
static const String partial = 'partial';
|
|
||||||
static const String partialDueDate = 'partialDueDate';
|
|
||||||
static const String publicNotes = 'publicNotes';
|
|
||||||
static const String privateNotes = 'privateNotes';
|
|
||||||
|
|
||||||
static const String updatedAt = 'updatedAt';
|
|
||||||
static const String archivedAt = 'archivedAt';
|
|
||||||
static const String isDeleted = 'isDeleted';
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class QuoteEntity extends Object
|
|
||||||
with BaseEntity, CalculateInvoiceTotal
|
|
||||||
implements Built<QuoteEntity, QuoteEntityBuilder> {
|
|
||||||
static int counter = 0;
|
|
||||||
|
|
||||||
factory QuoteEntity() {
|
|
||||||
return _$QuoteEntity._(
|
|
||||||
id: --QuoteEntity.counter,
|
|
||||||
amount: 0.0,
|
|
||||||
clientId: 0,
|
|
||||||
quoteStatusId: 0,
|
|
||||||
quoteNumber: '',
|
|
||||||
discount: 0.0,
|
|
||||||
poNumber: '',
|
|
||||||
quoteDate: convertDateTimeToSqlDate(),
|
|
||||||
validUntil: '',
|
|
||||||
terms: '',
|
|
||||||
publicNotes: '',
|
|
||||||
privateNotes: '',
|
|
||||||
recurringQuoteId: 0,
|
|
||||||
taxName1: '',
|
|
||||||
taxRate1: 0.0,
|
|
||||||
taxName2: '',
|
|
||||||
taxRate2: 0.0,
|
|
||||||
isAmountDiscount: false,
|
|
||||||
footer: '',
|
|
||||||
partial: 0.0,
|
|
||||||
partialDueDate: '',
|
|
||||||
customValue1: 0.0,
|
|
||||||
customValue2: 0.0,
|
|
||||||
customTaxes1: false,
|
|
||||||
customTaxes2: false,
|
|
||||||
quoteInvoiceId: 0,
|
|
||||||
customTextValue1: '',
|
|
||||||
customTextValue2: '',
|
|
||||||
isPublic: false,
|
|
||||||
filename: '',
|
|
||||||
invoiceItems: BuiltList<InvoiceItemEntity>(),
|
|
||||||
invitations: BuiltList<InvitationEntity>(),
|
|
||||||
updatedAt: 0,
|
|
||||||
archivedAt: 0,
|
|
||||||
isDeleted: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuoteEntity._();
|
|
||||||
|
|
||||||
QuoteEntity get clone => rebuild((b) => b
|
|
||||||
..id = --QuoteEntity.counter
|
|
||||||
..quoteNumber = ''
|
|
||||||
..isPublic = false);
|
|
||||||
|
|
||||||
@override
|
|
||||||
EntityType get entityType {
|
|
||||||
return EntityType.invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get amount;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'client_id')
|
|
||||||
int get clientId;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'invoice_status_id')
|
|
||||||
int get quoteStatusId;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'invoice_number')
|
|
||||||
String get quoteNumber;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get discount;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'po_number')
|
|
||||||
String get poNumber;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'invoice_date')
|
|
||||||
String get quoteDate;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'due_date')
|
|
||||||
String get validUntil;
|
|
||||||
|
|
||||||
String get terms;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'public_notes')
|
|
||||||
String get publicNotes;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'private_notes')
|
|
||||||
String get privateNotes;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'recurring_invoice_id')
|
|
||||||
int get recurringQuoteId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'tax_name1')
|
|
||||||
String get taxName1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'tax_rate1')
|
|
||||||
double get taxRate1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'tax_name2')
|
|
||||||
String get taxName2;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'tax_rate2')
|
|
||||||
double get taxRate2;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'is_amount_discount')
|
|
||||||
bool get isAmountDiscount;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'invoice_footer')
|
|
||||||
String get footer;
|
|
||||||
|
|
||||||
double get partial;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'partial_due_date')
|
|
||||||
String get partialDueDate;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'custom_value1')
|
|
||||||
double get customValue1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'custom_value2')
|
|
||||||
double get customValue2;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'custom_taxes1')
|
|
||||||
bool get customTaxes1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'custom_taxes2')
|
|
||||||
bool get customTaxes2;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'quote_invoice_id')
|
|
||||||
int get quoteInvoiceId;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'custom_text_value1')
|
|
||||||
String get customTextValue1;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'custom_text_value2')
|
|
||||||
String get customTextValue2;
|
|
||||||
|
|
||||||
@BuiltValueField(wireName: 'is_public')
|
|
||||||
bool get isPublic;
|
|
||||||
|
|
||||||
String get filename;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@BuiltValueField(wireName: 'invoice_items')
|
|
||||||
BuiltList<InvoiceItemEntity> get invoiceItems;
|
|
||||||
|
|
||||||
BuiltList<InvitationEntity> get invitations;
|
|
||||||
|
|
||||||
//String get last_login;
|
|
||||||
//String get custom_messages;
|
|
||||||
|
|
||||||
int compareTo(QuoteEntity quote, String sortField, bool sortAscending) {
|
|
||||||
int response = 0;
|
|
||||||
final QuoteEntity quoteA = sortAscending ? this : quote;
|
|
||||||
final QuoteEntity quoteB = sortAscending ? quote : this;
|
|
||||||
|
|
||||||
switch (sortField) {
|
|
||||||
case QuoteFields.amount:
|
|
||||||
response = quoteA.amount.compareTo(quoteB.amount);
|
|
||||||
break;
|
|
||||||
case QuoteFields.updatedAt:
|
|
||||||
response = quoteA.updatedAt.compareTo(quoteB.updatedAt);
|
|
||||||
break;
|
|
||||||
case QuoteFields.quoteDate:
|
|
||||||
response = quoteA.quoteDate.compareTo(quoteB.quoteDate);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response == 0) {
|
|
||||||
return quoteA.quoteNumber.compareTo(quoteB.quoteNumber);
|
|
||||||
} else {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesStatuses(BuiltList<EntityStatus> statuses) {
|
|
||||||
if (statuses.isEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final status in statuses) {
|
|
||||||
if (status.id == quoteStatusId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.id == kInvoiceStatusPastDue && isPastDue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFilter(String filter) {
|
|
||||||
if (filter == null || filter.isEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (quoteNumber.toLowerCase().contains(filter)) {
|
|
||||||
return true;
|
|
||||||
} else if (customTextValue1.isNotEmpty &&
|
|
||||||
customTextValue1.toLowerCase().contains(filter)) {
|
|
||||||
return true;
|
|
||||||
} else if (customTextValue2.isNotEmpty &&
|
|
||||||
customTextValue2.toLowerCase().contains(filter)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String matchesFilterValue(String filter) {
|
|
||||||
if (filter == null || filter.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
filter = filter.toLowerCase();
|
|
||||||
if (customTextValue1.isNotEmpty &&
|
|
||||||
customTextValue1.toLowerCase().contains(filter)) {
|
|
||||||
return customTextValue1;
|
|
||||||
} else if (customTextValue2.isNotEmpty &&
|
|
||||||
customTextValue2.toLowerCase().contains(filter)) {
|
|
||||||
return customTextValue2;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
QuoteEntity applyTax(TaxRateEntity taxRate) {
|
|
||||||
QuoteEntity invoice = rebuild((b) => b
|
|
||||||
..taxRate1 = taxRate.rate
|
|
||||||
..taxName1 = taxRate.name);
|
|
||||||
|
|
||||||
if (taxRate.isInclusive) {
|
|
||||||
invoice = invoice.rebuild((b) => b
|
|
||||||
..invoiceItems.replace(invoiceItems
|
|
||||||
.map((item) => item.rebuild(
|
|
||||||
(b) => b.cost = round(b.cost / (100 + taxRate.rate) * 100, 2)))
|
|
||||||
.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get listDisplayName {
|
|
||||||
return quoteNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get listDisplayAmount => null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
FormatNumberType get listDisplayAmountType => FormatNumberType.money;
|
|
||||||
|
|
||||||
double get requestedAmount => partial > 0 ? partial : amount;
|
|
||||||
|
|
||||||
bool get isPastDue {
|
|
||||||
if (validUntil.isEmpty) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isDeleted &&
|
|
||||||
isPublic &&
|
|
||||||
quoteStatusId != kInvoiceStatusPaid &&
|
|
||||||
DateTime.tryParse(validUntil)
|
|
||||||
.isBefore(DateTime.now().subtract(Duration(days: 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
String get invitationLink => invitations.first?.link;
|
|
||||||
|
|
||||||
String get invitationSilentLink => invitations.first?.silentLink;
|
|
||||||
|
|
||||||
String get invitationDownloadLink => invitations.first?.downloadLink;
|
|
||||||
|
|
||||||
static Serializer<QuoteEntity> get serializer => _$quoteEntitySerializer;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -21,7 +21,6 @@ import 'package:invoiceninja_flutter/redux/client/client_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
|
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
|
||||||
// STARTER: import - do not remove comment
|
// STARTER: import - do not remove comment
|
||||||
import 'package:invoiceninja_flutter/data/models/quote_model.dart';
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,7 +72,6 @@ part 'serializers.g.dart';
|
||||||
TimezoneItemResponse,
|
TimezoneItemResponse,
|
||||||
TimezoneListResponse,
|
TimezoneListResponse,
|
||||||
// STARTER: serializers - do not remove comment
|
// STARTER: serializers - do not remove comment
|
||||||
QuoteEntity,
|
|
||||||
|
|
||||||
])
|
])
|
||||||
final Serializers serializers =
|
final Serializers serializers =
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ Serializers _$serializers = (new Serializers().toBuilder()
|
||||||
..add(ProjectEntity.serializer)
|
..add(ProjectEntity.serializer)
|
||||||
..add(ProjectItemResponse.serializer)
|
..add(ProjectItemResponse.serializer)
|
||||||
..add(ProjectListResponse.serializer)
|
..add(ProjectListResponse.serializer)
|
||||||
..add(QuoteEntity.serializer)
|
..add(InvoiceEntity.serializer)
|
||||||
..add(QuoteState.serializer)
|
..add(QuoteState.serializer)
|
||||||
..add(QuoteUIState.serializer)
|
..add(QuoteUIState.serializer)
|
||||||
..add(SizeEntity.serializer)
|
..add(SizeEntity.serializer)
|
||||||
|
|
@ -344,6 +344,6 @@ Serializers _$serializers = (new Serializers().toBuilder()
|
||||||
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
|
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
|
||||||
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ProductEntity)]), () => new MapBuilder<int, ProductEntity>())
|
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ProductEntity)]), () => new MapBuilder<int, ProductEntity>())
|
||||||
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
|
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
|
||||||
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(QuoteEntity)]), () => new MapBuilder<int, QuoteEntity>())
|
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceEntity)]), () => new MapBuilder<int, InvoiceEntity>())
|
||||||
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>()))
|
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>()))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,18 @@ class QuoteRepository {
|
||||||
this.webClient = const WebClient(),
|
this.webClient = const WebClient(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<QuoteEntity> loadItem(
|
Future<InvoiceEntity> loadItem(
|
||||||
CompanyEntity company, AuthState auth, int entityId) async {
|
CompanyEntity company, AuthState auth, int entityId) async {
|
||||||
final dynamic response = await webClient.get(
|
final dynamic response = await webClient.get(
|
||||||
'${auth.url}/quotes/$entityId', company.token);
|
'${auth.url}/invoices/$entityId', company.token);
|
||||||
|
|
||||||
final QuoteItemResponse quoteResponse =
|
final InvoiceItemResponse quoteResponse =
|
||||||
serializers.deserializeWith(QuoteItemResponse.serializer, response);
|
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
|
||||||
|
|
||||||
return quoteResponse.data;
|
return quoteResponse.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<BuiltList<QuoteEntity>> loadList(
|
Future<BuiltList<InvoiceEntity>> loadList(
|
||||||
CompanyEntity company, AuthState auth, int updatedAt) async {
|
CompanyEntity company, AuthState auth, int updatedAt) async {
|
||||||
String url = auth.url + '/quotes';
|
String url = auth.url + '/quotes';
|
||||||
|
|
||||||
|
|
@ -35,16 +35,16 @@ class QuoteRepository {
|
||||||
|
|
||||||
final dynamic response = await webClient.get(url, company.token);
|
final dynamic response = await webClient.get(url, company.token);
|
||||||
|
|
||||||
final QuoteListResponse quoteResponse =
|
final InvoiceListResponse quoteResponse =
|
||||||
serializers.deserializeWith(QuoteListResponse.serializer, response);
|
serializers.deserializeWith(InvoiceListResponse.serializer, response);
|
||||||
|
|
||||||
return quoteResponse.data;
|
return quoteResponse.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<QuoteEntity> saveData(
|
Future<InvoiceEntity> saveData(
|
||||||
CompanyEntity company, AuthState auth, QuoteEntity quote,
|
CompanyEntity company, AuthState auth, InvoiceEntity quote,
|
||||||
[EntityAction action]) async {
|
[EntityAction action]) async {
|
||||||
final data = serializers.serializeWith(QuoteEntity.serializer, quote);
|
final data = serializers.serializeWith(InvoiceEntity.serializer, quote);
|
||||||
dynamic response;
|
dynamic response;
|
||||||
|
|
||||||
if (quote.isNew) {
|
if (quote.isNew) {
|
||||||
|
|
@ -60,8 +60,8 @@ class QuoteRepository {
|
||||||
response = await webClient.put(url, company.token, json.encode(data));
|
response = await webClient.put(url, company.token, json.encode(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
final QuoteItemResponse quoteResponse =
|
final InvoiceItemResponse quoteResponse =
|
||||||
serializers.deserializeWith(QuoteItemResponse.serializer, response);
|
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
|
||||||
|
|
||||||
return quoteResponse.data;
|
return quoteResponse.data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import 'package:invoiceninja_flutter/redux/product/product_middleware.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_middleware.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_middleware.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/invoice/invoice_screen.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/invoice_screen.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
// STARTER: import - do not remove comment
|
// STARTER: import - do not remove comment
|
||||||
import 'package:invoiceninja_flutter/ui/quote/quote_screen.dart';
|
import 'package:invoiceninja_flutter/ui/quote/quote_screen.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_vm.dart';
|
||||||
|
|
@ -41,7 +42,6 @@ import 'package:invoiceninja_flutter/ui/quote/view/quote_view_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_middleware.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_middleware.dart';
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final enableDarkMode = prefs.getBool(kSharedPrefEnableDarkMode);
|
final enableDarkMode = prefs.getBool(kSharedPrefEnableDarkMode);
|
||||||
|
|
@ -57,7 +57,6 @@ void main() async {
|
||||||
..addAll(createStorePersistenceMiddleware())
|
..addAll(createStorePersistenceMiddleware())
|
||||||
// STARTER: middleware - do not remove comment
|
// STARTER: middleware - do not remove comment
|
||||||
..addAll(createStoreQuotesMiddleware())
|
..addAll(createStoreQuotesMiddleware())
|
||||||
|
|
||||||
..addAll([
|
..addAll([
|
||||||
LoggingMiddleware<dynamic>.printer(),
|
LoggingMiddleware<dynamic>.printer(),
|
||||||
]));
|
]));
|
||||||
|
|
@ -147,7 +146,6 @@ return QuoteScreen();
|
||||||
},
|
},
|
||||||
QuoteViewScreen.route: (context) => QuoteViewScreen(),
|
QuoteViewScreen.route: (context) => QuoteViewScreen(),
|
||||||
QuoteEditScreen.route: (context) => QuoteEditScreen(),
|
QuoteEditScreen.route: (context) => QuoteEditScreen(),
|
||||||
|
|
||||||
SettingsScreen.route: (context) => SettingsScreen(),
|
SettingsScreen.route: (context) => SettingsScreen(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -212,9 +212,9 @@ Middleware<AppState> _saveInvoice(InvoiceRepository repository) {
|
||||||
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
repository
|
repository
|
||||||
.saveData(
|
.saveData(
|
||||||
store.state.selectedCompany, store.state.authState, action.invoice)
|
store.state.selectedCompany, store.state.authState, action.quote)
|
||||||
.then((dynamic invoice) {
|
.then((dynamic invoice) {
|
||||||
if (action.invoice.isNew) {
|
if (action.quote.isNew) {
|
||||||
store.dispatch(AddInvoiceSuccess(invoice));
|
store.dispatch(AddInvoiceSuccess(invoice));
|
||||||
} else {
|
} else {
|
||||||
store.dispatch(SaveInvoiceSuccess(invoice));
|
store.dispatch(SaveInvoiceSuccess(invoice));
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ final editingItemReducer = combineReducers<InvoiceItemEntity>([
|
||||||
|
|
||||||
InvoiceItemEntity editInvoiceItem(
|
InvoiceItemEntity editInvoiceItem(
|
||||||
InvoiceItemEntity invoiceItem, dynamic action) {
|
InvoiceItemEntity invoiceItem, dynamic action) {
|
||||||
return action.invoiceItem ?? InvoiceItemEntity();
|
return action.quoteItem ?? InvoiceItemEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
Reducer<String> dropdownFilterReducer = combineReducers([
|
Reducer<String> dropdownFilterReducer = combineReducers([
|
||||||
|
|
@ -37,9 +37,9 @@ Reducer<int> selectedIdReducer = combineReducers([
|
||||||
TypedReducer<int, ViewInvoice>(
|
TypedReducer<int, ViewInvoice>(
|
||||||
(int selectedId, dynamic action) => action.invoiceId),
|
(int selectedId, dynamic action) => action.invoiceId),
|
||||||
TypedReducer<int, AddInvoiceSuccess>(
|
TypedReducer<int, AddInvoiceSuccess>(
|
||||||
(int selectedId, dynamic action) => action.invoice.id),
|
(int selectedId, dynamic action) => action.quote.id),
|
||||||
TypedReducer<int, ShowEmailInvoice>(
|
TypedReducer<int, ShowEmailInvoice>(
|
||||||
(int selectedId, dynamic action) => action.invoice.id),
|
(int selectedId, dynamic action) => action.quote.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final editingReducer = combineReducers<InvoiceEntity>([
|
final editingReducer = combineReducers<InvoiceEntity>([
|
||||||
|
|
@ -62,7 +62,7 @@ InvoiceEntity _clearEditing(InvoiceEntity client, dynamic action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
InvoiceEntity _updateEditing(InvoiceEntity invoice, dynamic action) {
|
InvoiceEntity _updateEditing(InvoiceEntity invoice, dynamic action) {
|
||||||
return action.invoice;
|
return action.quote;
|
||||||
}
|
}
|
||||||
|
|
||||||
InvoiceEntity _addInvoiceItem(InvoiceEntity invoice, AddInvoiceItem action) {
|
InvoiceEntity _addInvoiceItem(InvoiceEntity invoice, AddInvoiceItem action) {
|
||||||
|
|
@ -244,7 +244,7 @@ InvoiceState _addInvoice(InvoiceState invoiceState, AddInvoiceSuccess action) {
|
||||||
|
|
||||||
InvoiceState _updateInvoice(InvoiceState invoiceState, dynamic action) {
|
InvoiceState _updateInvoice(InvoiceState invoiceState, dynamic action) {
|
||||||
return invoiceState
|
return invoiceState
|
||||||
.rebuild((b) => b..map[action.invoice.id] = action.invoice);
|
.rebuild((b) => b..map[action.quote.id] = action.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
InvoiceState _setNoInvoices(
|
InvoiceState _setNoInvoices(
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class ViewQuote implements PersistUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditQuote implements PersistUI {
|
class EditQuote implements PersistUI {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
final InvoiceItemEntity quoteItem;
|
final InvoiceItemEntity quoteItem;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final Completer completer;
|
final Completer completer;
|
||||||
|
|
@ -28,7 +28,7 @@ class EditQuote implements PersistUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShowEmailQuote {
|
class ShowEmailQuote {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final Completer completer;
|
final Completer completer;
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ class EditQuoteItem implements PersistUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateQuote implements PersistUI {
|
class UpdateQuote implements PersistUI {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
UpdateQuote(this.quote);
|
UpdateQuote(this.quote);
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ class LoadQuoteFailure implements StopLoading {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadQuoteSuccess implements StopLoading, PersistData {
|
class LoadQuoteSuccess implements StopLoading, PersistData {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
LoadQuoteSuccess(this.quote);
|
LoadQuoteSuccess(this.quote);
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ class LoadQuotesFailure implements StopLoading {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadQuotesSuccess implements StopLoading, PersistData {
|
class LoadQuotesSuccess implements StopLoading, PersistData {
|
||||||
final BuiltList<QuoteEntity> quotes;
|
final BuiltList<InvoiceEntity> quotes;
|
||||||
|
|
||||||
LoadQuotesSuccess(this.quotes);
|
LoadQuotesSuccess(this.quotes);
|
||||||
|
|
||||||
|
|
@ -136,19 +136,19 @@ class DeleteQuoteItem implements PersistUI {
|
||||||
|
|
||||||
class SaveQuoteRequest implements StartSaving {
|
class SaveQuoteRequest implements StartSaving {
|
||||||
final Completer completer;
|
final Completer completer;
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
SaveQuoteRequest({this.completer, this.quote});
|
SaveQuoteRequest({this.completer, this.quote});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SaveQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
class SaveQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
SaveQuoteSuccess(this.quote);
|
SaveQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
class AddQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
AddQuoteSuccess(this.quote);
|
AddQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
@ -186,13 +186,13 @@ class MarkSentQuoteRequest implements StartSaving {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarkSentQuoteSuccess implements StopSaving, PersistData {
|
class MarkSentQuoteSuccess implements StopSaving, PersistData {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
MarkSentQuoteSuccess(this.quote);
|
MarkSentQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarkSentQuoteFailure implements StopSaving {
|
class MarkSentQuoteFailure implements StopSaving {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
MarkSentQuoteFailure(this.quote);
|
MarkSentQuoteFailure(this.quote);
|
||||||
}
|
}
|
||||||
|
|
@ -205,13 +205,13 @@ class ArchiveQuoteRequest implements StartSaving {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArchiveQuoteSuccess implements StopSaving, PersistData {
|
class ArchiveQuoteSuccess implements StopSaving, PersistData {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
ArchiveQuoteSuccess(this.quote);
|
ArchiveQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArchiveQuoteFailure implements StopSaving {
|
class ArchiveQuoteFailure implements StopSaving {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
ArchiveQuoteFailure(this.quote);
|
ArchiveQuoteFailure(this.quote);
|
||||||
}
|
}
|
||||||
|
|
@ -224,13 +224,13 @@ class DeleteQuoteRequest implements StartSaving {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteQuoteSuccess implements StopSaving, PersistData {
|
class DeleteQuoteSuccess implements StopSaving, PersistData {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
DeleteQuoteSuccess(this.quote);
|
DeleteQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteQuoteFailure implements StopSaving {
|
class DeleteQuoteFailure implements StopSaving {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
DeleteQuoteFailure(this.quote);
|
DeleteQuoteFailure(this.quote);
|
||||||
}
|
}
|
||||||
|
|
@ -243,13 +243,13 @@ class RestoreQuoteRequest implements StartSaving {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestoreQuoteSuccess implements StopSaving, PersistData {
|
class RestoreQuoteSuccess implements StopSaving, PersistData {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
RestoreQuoteSuccess(this.quote);
|
RestoreQuoteSuccess(this.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestoreQuoteFailure implements StopSaving {
|
class RestoreQuoteFailure implements StopSaving {
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
|
|
||||||
RestoreQuoteFailure(this.quote);
|
RestoreQuoteFailure(this.quote);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,22 @@ Reducer<int> selectedIdReducer = combineReducers([
|
||||||
(int selectedId, dynamic action) => action.quote.id),
|
(int selectedId, dynamic action) => action.quote.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final editingReducer = combineReducers<QuoteEntity>([
|
final editingReducer = combineReducers<InvoiceEntity>([
|
||||||
TypedReducer<QuoteEntity, SaveQuoteSuccess>(_updateEditing),
|
TypedReducer<InvoiceEntity, SaveQuoteSuccess>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, AddQuoteSuccess>(_updateEditing),
|
TypedReducer<InvoiceEntity, AddQuoteSuccess>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, RestoreQuoteSuccess>(_updateEditing),
|
TypedReducer<InvoiceEntity, RestoreQuoteSuccess>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, ArchiveQuoteSuccess>(_updateEditing),
|
TypedReducer<InvoiceEntity, ArchiveQuoteSuccess>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, DeleteQuoteSuccess>(_updateEditing),
|
TypedReducer<InvoiceEntity, DeleteQuoteSuccess>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, EditQuote>(_updateEditing),
|
TypedReducer<InvoiceEntity, EditQuote>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, UpdateQuote>(_updateEditing),
|
TypedReducer<InvoiceEntity, UpdateQuote>(_updateEditing),
|
||||||
TypedReducer<QuoteEntity, SelectCompany>(_clearEditing),
|
TypedReducer<InvoiceEntity, SelectCompany>(_clearEditing),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
QuoteEntity _clearEditing(QuoteEntity quote, dynamic action) {
|
InvoiceEntity _clearEditing(InvoiceEntity quote, dynamic action) {
|
||||||
return QuoteEntity();
|
return InvoiceEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
QuoteEntity _updateEditing(QuoteEntity quote, dynamic action) {
|
InvoiceEntity _updateEditing(InvoiceEntity quote, dynamic action) {
|
||||||
return action.quote;
|
return action.quote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,52 +3,96 @@ import 'package:built_collection/built_collection.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
||||||
|
|
||||||
var memoizedDropdownQuoteList = memo2(
|
ClientEntity quoteClientSelector(
|
||||||
(BuiltMap<int, QuoteEntity> quoteMap, BuiltList<int> quoteList) =>
|
InvoiceEntity invoice, BuiltMap<int, ClientEntity> clientMap) {
|
||||||
dropdownQuotesSelector(quoteMap, quoteList));
|
return clientMap[invoice.clientId];
|
||||||
|
|
||||||
List<int> dropdownQuotesSelector(
|
|
||||||
BuiltMap<int, QuoteEntity> quoteMap, BuiltList<int> quoteList) {
|
|
||||||
final list =
|
|
||||||
quoteList.where((quoteId) => quoteMap[quoteId].isActive).toList();
|
|
||||||
|
|
||||||
list.sort((quoteAId, quoteBId) {
|
|
||||||
final quoteA = quoteMap[quoteAId];
|
|
||||||
final quoteB = quoteMap[quoteBId];
|
|
||||||
return quoteA.compareTo(quoteB, QuoteFields.quoteNumber, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var memoizedFilteredQuoteList = memo3((BuiltMap<int, QuoteEntity> quoteMap,
|
var memoizedFilteredQuoteList = memo4(
|
||||||
BuiltList<int> quoteList, ListUIState quoteListState) =>
|
(BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||||
filteredQuotesSelector(quoteMap, quoteList, quoteListState));
|
BuiltList<int> invoiceList,
|
||||||
|
BuiltMap<int, ClientEntity> clientMap,
|
||||||
|
ListUIState invoiceListState) =>
|
||||||
|
filteredQuotesSelector(
|
||||||
|
invoiceMap, invoiceList, clientMap, invoiceListState));
|
||||||
|
|
||||||
List<int> filteredQuotesSelector(BuiltMap<int, QuoteEntity> quoteMap,
|
List<int> filteredQuotesSelector(
|
||||||
BuiltList<int> quoteList, ListUIState quoteListState) {
|
BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||||
final list = quoteList.where((quoteId) {
|
BuiltList<int> invoiceList,
|
||||||
final quote = quoteMap[quoteId];
|
BuiltMap<int, ClientEntity> clientMap,
|
||||||
if (!quote.matchesStates(quoteListState.stateFilters)) {
|
ListUIState invoiceListState) {
|
||||||
|
final list = invoiceList.where((invoiceId) {
|
||||||
|
final invoice = invoiceMap[invoiceId];
|
||||||
|
final client = clientMap[invoice.clientId];
|
||||||
|
if (client == null || ! client.isActive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (quoteListState.custom1Filters.isNotEmpty &&
|
if (!invoice.matchesStates(invoiceListState.stateFilters)) {
|
||||||
!quoteListState.custom1Filters.contains(quote.customTextValue1)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (quoteListState.custom2Filters.isNotEmpty &&
|
if (!invoice.matchesStatuses(invoiceListState.statusFilters)) {
|
||||||
!quoteListState.custom2Filters.contains(quote.customTextValue2)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return quote.matchesFilter(quoteListState.filter);
|
if (!invoice.matchesFilter(invoiceListState.filter) &&
|
||||||
|
!client.matchesFilter(invoiceListState.filter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (invoiceListState.filterClientId != null &&
|
||||||
|
invoice.clientId != invoiceListState.filterClientId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (invoiceListState.custom1Filters.isNotEmpty &&
|
||||||
|
!invoiceListState.custom1Filters.contains(invoice.customTextValue1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (invoiceListState.custom2Filters.isNotEmpty &&
|
||||||
|
!invoiceListState.custom2Filters.contains(invoice.customTextValue2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
list.sort((quoteAId, quoteBId) {
|
list.sort((invoiceAId, invoiceBId) {
|
||||||
final quoteA = quoteMap[quoteAId];
|
return invoiceMap[invoiceAId].compareTo(invoiceMap[invoiceBId],
|
||||||
final quoteB = quoteMap[quoteBId];
|
invoiceListState.sortField, invoiceListState.sortAscending);
|
||||||
return quoteA.compareTo(
|
|
||||||
quoteB, quoteListState.sortField, quoteListState.sortAscending);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var memoizedQuoteStatsForClient = memo4((int clientId,
|
||||||
|
BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||||
|
String activeLabel,
|
||||||
|
String archivedLabel) =>
|
||||||
|
quoteStatsForClient(clientId, invoiceMap, activeLabel, archivedLabel));
|
||||||
|
|
||||||
|
String quoteStatsForClient(
|
||||||
|
int clientId,
|
||||||
|
BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||||
|
String activeLabel,
|
||||||
|
String archivedLabel) {
|
||||||
|
int countActive = 0;
|
||||||
|
int countArchived = 0;
|
||||||
|
invoiceMap.forEach((invoiceId, invoice) {
|
||||||
|
if (invoice.clientId == clientId) {
|
||||||
|
if (invoice.isActive) {
|
||||||
|
countActive++;
|
||||||
|
} else if (invoice.isArchived) {
|
||||||
|
countArchived++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String str = '';
|
||||||
|
if (countActive > 0) {
|
||||||
|
str = '$countActive $activeLabel';
|
||||||
|
if (countArchived > 0) {
|
||||||
|
str += ' • ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (countArchived > 0) {
|
||||||
|
str += '$countArchived $archivedLabel';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:built_value/built_value.dart';
|
||||||
import 'package:built_value/serializer.dart';
|
import 'package:built_value/serializer.dart';
|
||||||
import 'package:built_collection/built_collection.dart';
|
import 'package:built_collection/built_collection.dart';
|
||||||
import 'package:invoiceninja_flutter/constants.dart';
|
import 'package:invoiceninja_flutter/constants.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/quote_model.dart';
|
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
|
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ abstract class QuoteState implements Built<QuoteState, QuoteStateBuilder> {
|
||||||
factory QuoteState() {
|
factory QuoteState() {
|
||||||
return _$QuoteState._(
|
return _$QuoteState._(
|
||||||
lastUpdated: 0,
|
lastUpdated: 0,
|
||||||
map: BuiltMap<int, QuoteEntity>(),
|
map: BuiltMap<int, InvoiceEntity>(),
|
||||||
list: BuiltList<int>(),
|
list: BuiltList<int>(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ abstract class QuoteState implements Built<QuoteState, QuoteStateBuilder> {
|
||||||
@nullable
|
@nullable
|
||||||
int get lastUpdated;
|
int get lastUpdated;
|
||||||
|
|
||||||
BuiltMap<int, QuoteEntity> get map;
|
BuiltMap<int, InvoiceEntity> get map;
|
||||||
BuiltList<int> get list;
|
BuiltList<int> get list;
|
||||||
|
|
||||||
bool get isStale {
|
bool get isStale {
|
||||||
|
|
@ -42,15 +42,19 @@ abstract class QuoteUIState extends Object with EntityUIState implements Built<Q
|
||||||
|
|
||||||
factory QuoteUIState() {
|
factory QuoteUIState() {
|
||||||
return _$QuoteUIState._(
|
return _$QuoteUIState._(
|
||||||
listUIState: ListUIState(QuoteFields.quoteNumber),
|
listUIState: ListUIState(InvoiceFields.invoiceNumber),
|
||||||
editing: QuoteEntity(),
|
editing: InvoiceEntity(),
|
||||||
|
|
||||||
selectedId: 0,
|
selectedId: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
QuoteUIState._();
|
QuoteUIState._();
|
||||||
|
|
||||||
@nullable
|
@nullable
|
||||||
QuoteEntity get editing;
|
InvoiceEntity get editing;
|
||||||
|
|
||||||
|
@nullable
|
||||||
|
InvoiceItemEntity get editingItem;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isCreatingNew => editing.isNew;
|
bool get isCreatingNew => editing.isNew;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class _$QuoteStateSerializer implements StructuredSerializer<QuoteState> {
|
||||||
'map',
|
'map',
|
||||||
serializers.serialize(object.map,
|
serializers.serialize(object.map,
|
||||||
specifiedType: const FullType(BuiltMap,
|
specifiedType: const FullType(BuiltMap,
|
||||||
const [const FullType(int), const FullType(QuoteEntity)])),
|
const [const FullType(int), const FullType(InvoiceEntity)])),
|
||||||
'list',
|
'list',
|
||||||
serializers.serialize(object.list,
|
serializers.serialize(object.list,
|
||||||
specifiedType:
|
specifiedType:
|
||||||
|
|
@ -68,7 +68,7 @@ class _$QuoteStateSerializer implements StructuredSerializer<QuoteState> {
|
||||||
result.map.replace(serializers.deserialize(value,
|
result.map.replace(serializers.deserialize(value,
|
||||||
specifiedType: const FullType(BuiltMap, const [
|
specifiedType: const FullType(BuiltMap, const [
|
||||||
const FullType(int),
|
const FullType(int),
|
||||||
const FullType(QuoteEntity)
|
const FullType(InvoiceEntity)
|
||||||
])) as BuiltMap);
|
])) as BuiltMap);
|
||||||
break;
|
break;
|
||||||
case 'list':
|
case 'list':
|
||||||
|
|
@ -105,7 +105,13 @@ class _$QuoteUIStateSerializer implements StructuredSerializer<QuoteUIState> {
|
||||||
result
|
result
|
||||||
..add('editing')
|
..add('editing')
|
||||||
..add(serializers.serialize(object.editing,
|
..add(serializers.serialize(object.editing,
|
||||||
specifiedType: const FullType(QuoteEntity)));
|
specifiedType: const FullType(InvoiceEntity)));
|
||||||
|
}
|
||||||
|
if (object.editingItem != null) {
|
||||||
|
result
|
||||||
|
..add('editingItem')
|
||||||
|
..add(serializers.serialize(object.editingItem,
|
||||||
|
specifiedType: const FullType(InvoiceItemEntity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -124,7 +130,12 @@ class _$QuoteUIStateSerializer implements StructuredSerializer<QuoteUIState> {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'editing':
|
case 'editing':
|
||||||
result.editing.replace(serializers.deserialize(value,
|
result.editing.replace(serializers.deserialize(value,
|
||||||
specifiedType: const FullType(QuoteEntity)) as QuoteEntity);
|
specifiedType: const FullType(InvoiceEntity)) as InvoiceEntity);
|
||||||
|
break;
|
||||||
|
case 'editingItem':
|
||||||
|
result.editingItem.replace(serializers.deserialize(value,
|
||||||
|
specifiedType: const FullType(InvoiceItemEntity))
|
||||||
|
as InvoiceItemEntity);
|
||||||
break;
|
break;
|
||||||
case 'selectedId':
|
case 'selectedId':
|
||||||
result.selectedId = serializers.deserialize(value,
|
result.selectedId = serializers.deserialize(value,
|
||||||
|
|
@ -145,7 +156,7 @@ class _$QuoteState extends QuoteState {
|
||||||
@override
|
@override
|
||||||
final int lastUpdated;
|
final int lastUpdated;
|
||||||
@override
|
@override
|
||||||
final BuiltMap<int, QuoteEntity> map;
|
final BuiltMap<int, InvoiceEntity> map;
|
||||||
@override
|
@override
|
||||||
final BuiltList<int> list;
|
final BuiltList<int> list;
|
||||||
|
|
||||||
|
|
@ -196,10 +207,10 @@ class QuoteStateBuilder implements Builder<QuoteState, QuoteStateBuilder> {
|
||||||
int get lastUpdated => _$this._lastUpdated;
|
int get lastUpdated => _$this._lastUpdated;
|
||||||
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
|
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
|
||||||
|
|
||||||
MapBuilder<int, QuoteEntity> _map;
|
MapBuilder<int, InvoiceEntity> _map;
|
||||||
MapBuilder<int, QuoteEntity> get map =>
|
MapBuilder<int, InvoiceEntity> get map =>
|
||||||
_$this._map ??= new MapBuilder<int, QuoteEntity>();
|
_$this._map ??= new MapBuilder<int, InvoiceEntity>();
|
||||||
set map(MapBuilder<int, QuoteEntity> map) => _$this._map = map;
|
set map(MapBuilder<int, InvoiceEntity> map) => _$this._map = map;
|
||||||
|
|
||||||
ListBuilder<int> _list;
|
ListBuilder<int> _list;
|
||||||
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
|
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
|
||||||
|
|
@ -255,7 +266,9 @@ class QuoteStateBuilder implements Builder<QuoteState, QuoteStateBuilder> {
|
||||||
|
|
||||||
class _$QuoteUIState extends QuoteUIState {
|
class _$QuoteUIState extends QuoteUIState {
|
||||||
@override
|
@override
|
||||||
final QuoteEntity editing;
|
final InvoiceEntity editing;
|
||||||
|
@override
|
||||||
|
final InvoiceItemEntity editingItem;
|
||||||
@override
|
@override
|
||||||
final int selectedId;
|
final int selectedId;
|
||||||
@override
|
@override
|
||||||
|
|
@ -264,7 +277,8 @@ class _$QuoteUIState extends QuoteUIState {
|
||||||
factory _$QuoteUIState([void updates(QuoteUIStateBuilder b)]) =>
|
factory _$QuoteUIState([void updates(QuoteUIStateBuilder b)]) =>
|
||||||
(new QuoteUIStateBuilder()..update(updates)).build();
|
(new QuoteUIStateBuilder()..update(updates)).build();
|
||||||
|
|
||||||
_$QuoteUIState._({this.editing, this.selectedId, this.listUIState})
|
_$QuoteUIState._(
|
||||||
|
{this.editing, this.editingItem, this.selectedId, this.listUIState})
|
||||||
: super._() {
|
: super._() {
|
||||||
if (selectedId == null)
|
if (selectedId == null)
|
||||||
throw new BuiltValueNullFieldError('QuoteUIState', 'selectedId');
|
throw new BuiltValueNullFieldError('QuoteUIState', 'selectedId');
|
||||||
|
|
@ -284,13 +298,16 @@ class _$QuoteUIState extends QuoteUIState {
|
||||||
if (identical(other, this)) return true;
|
if (identical(other, this)) return true;
|
||||||
if (other is! QuoteUIState) return false;
|
if (other is! QuoteUIState) return false;
|
||||||
return editing == other.editing &&
|
return editing == other.editing &&
|
||||||
|
editingItem == other.editingItem &&
|
||||||
selectedId == other.selectedId &&
|
selectedId == other.selectedId &&
|
||||||
listUIState == other.listUIState;
|
listUIState == other.listUIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return $jf($jc($jc($jc(0, editing.hashCode), selectedId.hashCode),
|
return $jf($jc(
|
||||||
|
$jc($jc($jc(0, editing.hashCode), editingItem.hashCode),
|
||||||
|
selectedId.hashCode),
|
||||||
listUIState.hashCode));
|
listUIState.hashCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,6 +315,7 @@ class _$QuoteUIState extends QuoteUIState {
|
||||||
String toString() {
|
String toString() {
|
||||||
return (newBuiltValueToStringHelper('QuoteUIState')
|
return (newBuiltValueToStringHelper('QuoteUIState')
|
||||||
..add('editing', editing)
|
..add('editing', editing)
|
||||||
|
..add('editingItem', editingItem)
|
||||||
..add('selectedId', selectedId)
|
..add('selectedId', selectedId)
|
||||||
..add('listUIState', listUIState))
|
..add('listUIState', listUIState))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -308,10 +326,16 @@ class QuoteUIStateBuilder
|
||||||
implements Builder<QuoteUIState, QuoteUIStateBuilder> {
|
implements Builder<QuoteUIState, QuoteUIStateBuilder> {
|
||||||
_$QuoteUIState _$v;
|
_$QuoteUIState _$v;
|
||||||
|
|
||||||
QuoteEntityBuilder _editing;
|
InvoiceEntityBuilder _editing;
|
||||||
QuoteEntityBuilder get editing =>
|
InvoiceEntityBuilder get editing =>
|
||||||
_$this._editing ??= new QuoteEntityBuilder();
|
_$this._editing ??= new InvoiceEntityBuilder();
|
||||||
set editing(QuoteEntityBuilder editing) => _$this._editing = editing;
|
set editing(InvoiceEntityBuilder editing) => _$this._editing = editing;
|
||||||
|
|
||||||
|
InvoiceItemEntityBuilder _editingItem;
|
||||||
|
InvoiceItemEntityBuilder get editingItem =>
|
||||||
|
_$this._editingItem ??= new InvoiceItemEntityBuilder();
|
||||||
|
set editingItem(InvoiceItemEntityBuilder editingItem) =>
|
||||||
|
_$this._editingItem = editingItem;
|
||||||
|
|
||||||
int _selectedId;
|
int _selectedId;
|
||||||
int get selectedId => _$this._selectedId;
|
int get selectedId => _$this._selectedId;
|
||||||
|
|
@ -328,6 +352,7 @@ class QuoteUIStateBuilder
|
||||||
QuoteUIStateBuilder get _$this {
|
QuoteUIStateBuilder get _$this {
|
||||||
if (_$v != null) {
|
if (_$v != null) {
|
||||||
_editing = _$v.editing?.toBuilder();
|
_editing = _$v.editing?.toBuilder();
|
||||||
|
_editingItem = _$v.editingItem?.toBuilder();
|
||||||
_selectedId = _$v.selectedId;
|
_selectedId = _$v.selectedId;
|
||||||
_listUIState = _$v.listUIState?.toBuilder();
|
_listUIState = _$v.listUIState?.toBuilder();
|
||||||
_$v = null;
|
_$v = null;
|
||||||
|
|
@ -353,6 +378,7 @@ class QuoteUIStateBuilder
|
||||||
_$result = _$v ??
|
_$result = _$v ??
|
||||||
new _$QuoteUIState._(
|
new _$QuoteUIState._(
|
||||||
editing: _editing?.build(),
|
editing: _editing?.build(),
|
||||||
|
editingItem: _editingItem?.build(),
|
||||||
selectedId: selectedId,
|
selectedId: selectedId,
|
||||||
listUIState: listUIState.build());
|
listUIState: listUIState.build());
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
@ -360,6 +386,8 @@ class QuoteUIStateBuilder
|
||||||
try {
|
try {
|
||||||
_$failedField = 'editing';
|
_$failedField = 'editing';
|
||||||
_editing?.build();
|
_editing?.build();
|
||||||
|
_$failedField = 'editingItem';
|
||||||
|
_editingItem?.build();
|
||||||
|
|
||||||
_$failedField = 'listUIState';
|
_$failedField = 'listUIState';
|
||||||
listUIState.build();
|
listUIState.build();
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ import 'package:invoiceninja_flutter/redux/client/client_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/product/product_state.dart';
|
import 'package:invoiceninja_flutter/redux/product/product_state.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
|
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
|
||||||
|
|
||||||
// STARTER: import - do not remove comment
|
// STARTER: import - do not remove comment
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
||||||
|
|
||||||
part 'ui_state.g.dart';
|
part 'ui_state.g.dart';
|
||||||
|
|
||||||
abstract class UIState implements Built<UIState, UIStateBuilder> {
|
abstract class UIState implements Built<UIState, UIStateBuilder> {
|
||||||
|
|
||||||
factory UIState({bool enableDarkMode}) {
|
factory UIState({bool enableDarkMode}) {
|
||||||
return _$UIState._(
|
return _$UIState._(
|
||||||
selectedCompanyIndex: 0,
|
selectedCompanyIndex: 0,
|
||||||
|
|
@ -22,16 +21,21 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
|
||||||
invoiceUIState: InvoiceUIState(),
|
invoiceUIState: InvoiceUIState(),
|
||||||
// STARTER: constructor - do not remove comment
|
// STARTER: constructor - do not remove comment
|
||||||
quoteUIState: QuoteUIState(),
|
quoteUIState: QuoteUIState(),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
UIState._();
|
UIState._();
|
||||||
|
|
||||||
int get selectedCompanyIndex;
|
int get selectedCompanyIndex;
|
||||||
|
|
||||||
String get currentRoute;
|
String get currentRoute;
|
||||||
|
|
||||||
bool get enableDarkMode;
|
bool get enableDarkMode;
|
||||||
|
|
||||||
ProductUIState get productUIState;
|
ProductUIState get productUIState;
|
||||||
|
|
||||||
ClientUIState get clientUIState;
|
ClientUIState get clientUIState;
|
||||||
|
|
||||||
InvoiceUIState get invoiceUIState;
|
InvoiceUIState get invoiceUIState;
|
||||||
|
|
||||||
@nullable
|
@nullable
|
||||||
|
|
@ -40,7 +44,5 @@ quoteUIState: QuoteUIState(),
|
||||||
// STARTER: properties - do not remove comment
|
// STARTER: properties - do not remove comment
|
||||||
QuoteUIState get quoteUIState;
|
QuoteUIState get quoteUIState;
|
||||||
|
|
||||||
|
|
||||||
static Serializer<UIState> get serializer => _$uIStateSerializer;
|
static Serializer<UIState> get serializer => _$uIStateSerializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ class InvoiceListItem extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
final filterMatch = filter != null && filter.isNotEmpty
|
final filterMatch = filter != null && filter.isNotEmpty
|
||||||
? (invoice.matchesFilterValue(filter) ?? client.matchesFilterValue(filter))
|
? (invoice.matchesFilterValue(filter) ??
|
||||||
|
client.matchesFilterValue(filter))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return DismissibleEntity(
|
return DismissibleEntity(
|
||||||
|
|
@ -71,10 +72,15 @@ class InvoiceListItem extends StatelessWidget {
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(invoice.isPastDue ? localization.pastDue : localization.lookup('invoice_status_${invoice.invoiceStatusId}'),
|
Text(
|
||||||
|
invoice.isPastDue
|
||||||
|
? localization.pastDue
|
||||||
|
: localization.lookup(
|
||||||
|
'invoice_status_${invoice.invoiceStatusId}'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: invoice.isPastDue
|
||||||
invoice.isPastDue ? Colors.red : InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
? Colors.red
|
||||||
|
: InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -85,4 +91,3 @@ class InvoiceListItem extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class InvoiceViewVM {
|
||||||
bool operator ==(dynamic other) =>
|
bool operator ==(dynamic other) =>
|
||||||
client == other.client &&
|
client == other.client &&
|
||||||
company == other.company &&
|
company == other.company &&
|
||||||
invoice == other.invoice &&
|
invoice == other.quote &&
|
||||||
isSaving == other.isSaving &&
|
isSaving == other.isSaving &&
|
||||||
isDirty == other.isDirty;
|
isDirty == other.isDirty;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
|
||||||
import 'package:flutter_redux_starter/ui/quote/edit/quote_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_vm.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/save_icon_button.dart';
|
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_item_selector.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/buttons/refresh_icon_button.dart';
|
||||||
|
|
||||||
class QuoteEdit extends StatefulWidget {
|
class QuoteEdit extends StatefulWidget {
|
||||||
final QuoteEditVM viewModel;
|
final QuoteEditVM viewModel;
|
||||||
|
|
||||||
QuoteEdit({
|
const QuoteEdit({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.viewModel,
|
@required this.viewModel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
@ -16,52 +20,38 @@ class QuoteEdit extends StatefulWidget {
|
||||||
_QuoteEditState createState() => _QuoteEditState();
|
_QuoteEditState createState() => _QuoteEditState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _QuoteEditState extends State<QuoteEdit> {
|
class _QuoteEditState extends State<QuoteEdit>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
TabController _controller;
|
||||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// STARTER: controllers - do not remove comment
|
static const kDetailsScreen = 0;
|
||||||
|
static const kItemScreen = 1;
|
||||||
var _controllers = [];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
_controllers = [
|
final invoice = widget.viewModel.quote;
|
||||||
// STARTER: array - do not remove comment
|
final invoiceItem = widget.viewModel.quoteItem;
|
||||||
];
|
|
||||||
|
|
||||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
final index = invoice.invoiceItems.contains(invoiceItem)
|
||||||
|
? kItemScreen
|
||||||
var quote = widget.viewModel.quote;
|
: kDetailsScreen;
|
||||||
// STARTER: read value - do not remove comment
|
_controller = TabController(vsync: this, length: 2, initialIndex: index);
|
||||||
|
|
||||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
|
||||||
|
|
||||||
super.didChangeDependencies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controllers.forEach((controller) {
|
_controller.dispose();
|
||||||
controller.removeListener(_onChanged);
|
|
||||||
controller.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChanged() {
|
|
||||||
var quote = widget.viewModel.quote.rebuild((b) => b
|
|
||||||
// STARTER: set value - do not remove comment
|
|
||||||
);
|
|
||||||
if (quote != widget.viewModel.quote) {
|
|
||||||
widget.viewModel.onChanged(quote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var viewModel = widget.viewModel;
|
final localization = AppLocalization.of(context);
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final invoice = viewModel.quote;
|
||||||
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
|
|
@ -70,35 +60,80 @@ class _QuoteEditState extends State<QuoteEdit> {
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(viewModel.quote.isNew
|
title: Text(invoice.isNew
|
||||||
? 'New Quote'
|
? localization.newQuote
|
||||||
: viewModel.quote.displayName),
|
: '${localization.quote} ${viewModel.origQuote.invoiceNumber}'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
Builder(builder: (BuildContext context) {
|
RefreshIconButton(
|
||||||
return SaveIconButton(
|
icon: Icons.cloud_upload,
|
||||||
isLoading: viewModel.isLoading,
|
tooltip: localization.save,
|
||||||
|
isVisible: !invoice.isDeleted,
|
||||||
|
isSaving: widget.viewModel.isSaving,
|
||||||
|
isDirty: invoice.isNew || invoice != viewModel.origQuote,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (!_formKey.currentState.validate()) {
|
if (!_formKey.currentState.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.onSavePressed(context);
|
widget.viewModel.onSavePressed(context);
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _controller,
|
||||||
|
//isScrollable: true,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
text: localization.details,
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
text: localization.items,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: Form(
|
body: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: TabBarView(
|
||||||
|
controller: _controller,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FormCard(
|
InvoiceEditDetailsScreen(),
|
||||||
children: <Widget>[
|
InvoiceEditItemsScreen(),
|
||||||
// STARTER: widgets - do not remove comment
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
bottomNavigationBar: BottomAppBar(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
shape: CircularNotchedRectangle(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(14.0),
|
||||||
|
child: Text(
|
||||||
|
'${localization.total}: ${formatNumber(invoice.calculateTotal(viewModel.company.enableInclusiveTaxes), context, clientId: viewModel.quote.clientId)}',
|
||||||
|
style: TextStyle(
|
||||||
|
//color: Theme.of(context).selectedRowColor,
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
backgroundColor: Theme.of(context).primaryColorDark,
|
||||||
|
onPressed: () {
|
||||||
|
showDialog<InvoiceItemSelector>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return InvoiceItemSelector(
|
||||||
|
onItemsSelected: (items) {
|
||||||
|
viewModel.onItemsAdded(items);
|
||||||
|
_controller.animateTo(kItemScreen);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add, color: Colors.white),
|
||||||
|
tooltip: localization.addItem,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,21 @@ import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
||||||
import 'package:flutter_redux_starter/ui/quote/quote_screen.dart';
|
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/invoice/invoice_screen.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'package:flutter_redux_starter/redux/quote/quote_actions.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
||||||
import 'package:flutter_redux_starter/data/models/quote_model.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
import 'package:flutter_redux_starter/ui/quote/edit/quote_edit.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit.dart';
|
||||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
|
||||||
|
|
||||||
class QuoteEditScreen extends StatelessWidget {
|
class QuoteEditScreen extends StatelessWidget {
|
||||||
static final String route = '/quote/edit';
|
static const String route = '/quote/edit';
|
||||||
QuoteEditScreen({Key key}) : super(key: key);
|
|
||||||
|
const QuoteEditScreen({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -31,44 +34,61 @@ class QuoteEditScreen extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuoteEditVM {
|
class QuoteEditVM {
|
||||||
final QuoteEntity quote;
|
final CompanyEntity company;
|
||||||
final Function(QuoteEntity) onChanged;
|
final InvoiceEntity quote;
|
||||||
|
final InvoiceItemEntity quoteItem;
|
||||||
|
final InvoiceEntity origQuote;
|
||||||
final Function(BuildContext) onSavePressed;
|
final Function(BuildContext) onSavePressed;
|
||||||
|
final Function(List<InvoiceItemEntity>) onItemsAdded;
|
||||||
final Function onBackPressed;
|
final Function onBackPressed;
|
||||||
final bool isLoading;
|
final bool isSaving;
|
||||||
|
|
||||||
QuoteEditVM({
|
QuoteEditVM({
|
||||||
|
@required this.company,
|
||||||
@required this.quote,
|
@required this.quote,
|
||||||
@required this.onChanged,
|
@required this.quoteItem,
|
||||||
|
@required this.origQuote,
|
||||||
@required this.onSavePressed,
|
@required this.onSavePressed,
|
||||||
|
@required this.onItemsAdded,
|
||||||
@required this.onBackPressed,
|
@required this.onBackPressed,
|
||||||
@required this.isLoading,
|
@required this.isSaving,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory QuoteEditVM.fromStore(Store<AppState> store) {
|
factory QuoteEditVM.fromStore(Store<AppState> store) {
|
||||||
final quote = store.state.quoteUIState.selected;
|
final AppState state = store.state;
|
||||||
|
final invoice = state.invoiceUIState.editing;
|
||||||
|
|
||||||
return QuoteEditVM(
|
return QuoteEditVM(
|
||||||
isLoading: store.state.isLoading,
|
company: state.selectedCompany,
|
||||||
quote: quote,
|
isSaving: state.isSaving,
|
||||||
onChanged: (QuoteEntity quote) {
|
quote: invoice,
|
||||||
store.dispatch(UpdateQuote(quote));
|
quoteItem: state.invoiceUIState.editingItem,
|
||||||
},
|
origQuote: store.state.invoiceState.map[invoice.id],
|
||||||
onBackPressed: () {
|
onBackPressed: () =>
|
||||||
store.dispatch(UpdateCurrentRoute(QuoteScreen.route));
|
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route)),
|
||||||
},
|
|
||||||
onSavePressed: (BuildContext context) {
|
onSavePressed: (BuildContext context) {
|
||||||
final Completer<Null> completer = new Completer<Null>();
|
final Completer<InvoiceEntity> completer = Completer<InvoiceEntity>();
|
||||||
store.dispatch(SaveQuoteRequest(completer: completer, quote: quote));
|
store.dispatch(
|
||||||
return completer.future.then((_) {
|
SaveInvoiceRequest(completer: completer, invoice: invoice));
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
return completer.future.then((savedInvoice) {
|
||||||
content: IconMessage(
|
if (invoice.isNew) {
|
||||||
message: quote.isNew
|
Navigator.of(context).pushReplacementNamed(InvoiceViewScreen.route);
|
||||||
? 'Successfully Created Quote'
|
} else {
|
||||||
: 'Successfully Updated Quote',
|
Navigator.of(context).pop(savedInvoice);
|
||||||
),
|
}
|
||||||
duration: Duration(seconds: 3)));
|
}).catchError((Object error) {
|
||||||
|
showDialog<ErrorDialog>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ErrorDialog(error);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onItemsAdded: (items) {
|
||||||
|
if (items.length == 1) {
|
||||||
|
store.dispatch(EditInvoiceItem(items[0]));
|
||||||
|
}
|
||||||
|
store.dispatch(AddInvoiceItems(items));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/quote/quote_list_item.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/quote/quote_list_vm.dart';
|
import 'package:invoiceninja_flutter/ui/quote/quote_list_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
|
|
@ -15,63 +17,65 @@ class QuoteList extends StatelessWidget {
|
||||||
@required this.viewModel,
|
@required this.viewModel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
void _showMenu(
|
||||||
Widget build(BuildContext context) {
|
BuildContext context, InvoiceEntity invoice, ClientEntity client) async {
|
||||||
if (!viewModel.isLoaded) {
|
|
||||||
return LoadingIndicator();
|
|
||||||
} else if (viewModel.quoteList.isEmpty) {
|
|
||||||
return Opacity(
|
|
||||||
opacity: 0.5,
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
AppLocalization.of(context).noRecordsFound,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _buildListView(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showMenu(BuildContext context, QuoteEntity quote) async {
|
|
||||||
final user = viewModel.user;
|
final user = viewModel.user;
|
||||||
final message = await showDialog<String>(
|
final message = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
||||||
user.canCreate(EntityType.quote)
|
user.canCreate(EntityType.invoice)
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.control_point_duplicate),
|
leading: Icon(Icons.control_point_duplicate),
|
||||||
title: Text(AppLocalization.of(context).clone),
|
title: Text(AppLocalization.of(context).clone),
|
||||||
onTap: () => viewModel.onEntityAction(
|
onTap: () => viewModel.onEntityAction(
|
||||||
context, quote, EntityAction.clone),
|
context, invoice, EntityAction.clone),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
|
user.canEditEntity(invoice) && !invoice.isPublic
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.publish),
|
||||||
|
title: Text(AppLocalization.of(context).markSent),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, invoice, EntityAction.markSent),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
user.canEditEntity(invoice) && client.hasEmailAddress
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.send),
|
||||||
|
title: Text(AppLocalization.of(context).email),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, invoice, EntityAction.emailInvoice),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.picture_as_pdf),
|
||||||
|
title: Text(AppLocalization.of(context).pdf),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, invoice, EntityAction.pdf),
|
||||||
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
user.canEditEntity(quote) && !quote.isActive
|
user.canEditEntity(invoice) && !invoice.isActive
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.restore),
|
leading: Icon(Icons.restore),
|
||||||
title: Text(AppLocalization.of(context).restore),
|
title: Text(AppLocalization.of(context).restore),
|
||||||
onTap: () => viewModel.onEntityAction(
|
onTap: () => viewModel.onEntityAction(
|
||||||
context, quote, EntityAction.restore),
|
context, invoice, EntityAction.restore),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
user.canEditEntity(quote) && quote.isActive
|
user.canEditEntity(invoice) && invoice.isActive
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.archive),
|
leading: Icon(Icons.archive),
|
||||||
title: Text(AppLocalization.of(context).archive),
|
title: Text(AppLocalization.of(context).archive),
|
||||||
onTap: () => viewModel.onEntityAction(
|
onTap: () => viewModel.onEntityAction(
|
||||||
context, quote, EntityAction.archive),
|
context, invoice, EntityAction.archive),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
user.canEditEntity(quote) && !quote.isDeleted
|
user.canEditEntity(invoice) && !invoice.isDeleted
|
||||||
? ListTile(
|
? ListTile(
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
title: Text(AppLocalization.of(context).delete),
|
title: Text(AppLocalization.of(context).delete),
|
||||||
onTap: () => viewModel.onEntityAction(
|
onTap: () => viewModel.onEntityAction(
|
||||||
context, quote, EntityAction.delete),
|
context, invoice, EntityAction.delete),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
]));
|
]));
|
||||||
|
|
@ -83,30 +87,97 @@ class QuoteList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView(BuildContext context) {
|
@override
|
||||||
return RefreshIndicator(
|
Widget build(BuildContext context) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final listState = viewModel.listState;
|
||||||
|
final filteredClientId = listState.filterClientId;
|
||||||
|
final filteredClient =
|
||||||
|
filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
filteredClient != null
|
||||||
|
? Material(
|
||||||
|
color: Colors.orangeAccent,
|
||||||
|
elevation: 6.0,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => viewModel.onViewClientFilterPressed(context),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(width: 18.0),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
localization.clientsInvoices.replaceFirst(
|
||||||
|
':client', filteredClient.displayName),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: () => viewModel.onClearClientFilterPressed(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
Expanded(
|
||||||
|
child: !viewModel.isLoaded
|
||||||
|
? LoadingIndicator()
|
||||||
|
: RefreshIndicator(
|
||||||
onRefresh: () => viewModel.onRefreshed(context),
|
onRefresh: () => viewModel.onRefreshed(context),
|
||||||
child: ListView.builder(
|
child: viewModel.invoiceList.isEmpty
|
||||||
itemCount: viewModel.quoteList.length,
|
? Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalization.of(context).noRecordsFound,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: viewModel.invoiceList.length,
|
||||||
itemBuilder: (BuildContext context, index) {
|
itemBuilder: (BuildContext context, index) {
|
||||||
final quoteId = viewModel.quoteList[index];
|
final invoiceId = viewModel.invoiceList[index];
|
||||||
final quote = viewModel.quoteMap[quoteId];
|
final invoice = viewModel.invoiceMap[invoiceId];
|
||||||
return Column(children: <Widget>[
|
final client =
|
||||||
QuoteListItem(
|
viewModel.clientMap[invoice.clientId];
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
InvoiceListItem(
|
||||||
user: viewModel.user,
|
user: viewModel.user,
|
||||||
filter: viewModel.filter,
|
filter: viewModel.filter,
|
||||||
quote: quote,
|
invoice: invoice,
|
||||||
client: viewModel.clientMap[quote.clientId],
|
client: viewModel.clientMap[invoice.clientId],
|
||||||
onDismissed: (DismissDirection direction) =>
|
onDismissed: (DismissDirection direction) =>
|
||||||
viewModel.onDismissed(context, quote, direction),
|
viewModel.onDismissed(
|
||||||
onTap: () => viewModel.onQuoteTap(context, quote),
|
context, invoice, direction),
|
||||||
onLongPress: () => _showMenu(context, quote),
|
onTap: () =>
|
||||||
|
viewModel.onInvoiceTap(context, invoice),
|
||||||
|
onLongPress: () =>
|
||||||
|
_showMenu(context, invoice, client),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
]);
|
],
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class QuoteListItem extends StatelessWidget {
|
||||||
final DismissDirectionCallback onDismissed;
|
final DismissDirectionCallback onDismissed;
|
||||||
final GestureTapCallback onTap;
|
final GestureTapCallback onTap;
|
||||||
final GestureTapCallback onLongPress;
|
final GestureTapCallback onLongPress;
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity invoice;
|
||||||
final ClientEntity client;
|
final ClientEntity client;
|
||||||
final String filter;
|
final String filter;
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ class QuoteListItem extends StatelessWidget {
|
||||||
@required this.onDismissed,
|
@required this.onDismissed,
|
||||||
@required this.onTap,
|
@required this.onTap,
|
||||||
@required this.onLongPress,
|
@required this.onLongPress,
|
||||||
@required this.quote,
|
@required this.invoice,
|
||||||
@required this.client,
|
@required this.client,
|
||||||
@required this.filter,
|
@required this.filter,
|
||||||
});
|
});
|
||||||
|
|
@ -30,12 +30,13 @@ class QuoteListItem extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
final filterMatch = filter != null && filter.isNotEmpty
|
final filterMatch = filter != null && filter.isNotEmpty
|
||||||
? (quote.matchesFilterValue(filter) ?? client.matchesFilterValue(filter))
|
? (invoice.matchesFilterValue(filter) ??
|
||||||
|
client.matchesFilterValue(filter))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return DismissibleEntity(
|
return DismissibleEntity(
|
||||||
user: user,
|
user: user,
|
||||||
entity: quote,
|
entity: invoice,
|
||||||
onDismissed: onDismissed,
|
onDismissed: onDismissed,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|
@ -51,8 +52,8 @@ class QuoteListItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatNumber(quote.amount, context,
|
formatNumber(invoice.amount, context,
|
||||||
clientId: quote.clientId),
|
clientId: invoice.clientId),
|
||||||
style: Theme.of(context).textTheme.title),
|
style: Theme.of(context).textTheme.title),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -64,25 +65,29 @@ class QuoteListItem extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: filterMatch == null
|
child: filterMatch == null
|
||||||
? Text(quote.quoteNumber)
|
? Text(invoice.invoiceNumber)
|
||||||
: Text(
|
: Text(
|
||||||
filterMatch,
|
filterMatch,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(quote.isPastDue ? localization.pastDue : localization.lookup('invoice_status_${quote.quoteStatusId}'),
|
Text(
|
||||||
|
invoice.isPastDue
|
||||||
|
? localization.pastDue
|
||||||
|
: localization.lookup(
|
||||||
|
'invoice_status_${invoice.invoiceStatusId}'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: invoice.isPastDue
|
||||||
quote.isPastDue ? Colors.red : InvoiceStatusColors.colors[quote.quoteStatusId],
|
? Colors.red
|
||||||
|
: InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
EntityStateLabel(quote),
|
EntityStateLabel(invoice),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,35 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:built_collection/built_collection.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:built_collection/built_collection.dart';
|
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/quote/quote_list.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/completers.dart';
|
import 'package:invoiceninja_flutter/utils/completers.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
|
import 'package:invoiceninja_flutter/utils/pdf.dart';
|
||||||
|
import 'package:redux/redux.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/quote/quote_list.dart';
|
import 'package:invoiceninja_flutter/ui/invoice/invoice_list.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
||||||
|
|
||||||
class QuoteListBuilder extends StatelessWidget {
|
class QuoteListBuilder extends StatelessWidget {
|
||||||
|
static const String route = '/invoices/edit';
|
||||||
|
|
||||||
const QuoteListBuilder({Key key}) : super(key: key);
|
const QuoteListBuilder({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StoreConnector<AppState, QuoteListVM>(
|
return StoreConnector<AppState, QuoteListVM>(
|
||||||
|
//rebuildOnChange: true,
|
||||||
converter: QuoteListVM.fromStore,
|
converter: QuoteListVM.fromStore,
|
||||||
builder: (context, viewModel) {
|
builder: (context, vm) {
|
||||||
return QuoteList(
|
return QuoteList(
|
||||||
viewModel: viewModel,
|
viewModel: vm,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -31,28 +38,34 @@ class QuoteListBuilder extends StatelessWidget {
|
||||||
|
|
||||||
class QuoteListVM {
|
class QuoteListVM {
|
||||||
final UserEntity user;
|
final UserEntity user;
|
||||||
final List<int> quoteList;
|
final ListUIState listState;
|
||||||
final BuiltMap<int, QuoteEntity> quoteMap;
|
final List<int> invoiceList;
|
||||||
|
final BuiltMap<int, InvoiceEntity> invoiceMap;
|
||||||
final BuiltMap<int, ClientEntity> clientMap;
|
final BuiltMap<int, ClientEntity> clientMap;
|
||||||
final String filter;
|
final String filter;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final bool isLoaded;
|
final bool isLoaded;
|
||||||
final Function(BuildContext, QuoteEntity) onQuoteTap;
|
final Function(BuildContext, InvoiceEntity) onInvoiceTap;
|
||||||
final Function(BuildContext, QuoteEntity, DismissDirection) onDismissed;
|
final Function(BuildContext, InvoiceEntity, DismissDirection) onDismissed;
|
||||||
final Function(BuildContext) onRefreshed;
|
final Function(BuildContext) onRefreshed;
|
||||||
final Function(BuildContext, QuoteEntity, EntityAction) onEntityAction;
|
final Function onClearClientFilterPressed;
|
||||||
|
final Function(BuildContext) onViewClientFilterPressed;
|
||||||
|
final Function(BuildContext, InvoiceEntity, EntityAction) onEntityAction;
|
||||||
|
|
||||||
QuoteListVM({
|
QuoteListVM({
|
||||||
@required this.user,
|
@required this.user,
|
||||||
@required this.quoteList,
|
@required this.listState,
|
||||||
@required this.quoteMap,
|
@required this.invoiceList,
|
||||||
|
@required this.invoiceMap,
|
||||||
@required this.clientMap,
|
@required this.clientMap,
|
||||||
@required this.filter,
|
|
||||||
@required this.isLoading,
|
@required this.isLoading,
|
||||||
@required this.isLoaded,
|
@required this.isLoaded,
|
||||||
@required this.onQuoteTap,
|
@required this.filter,
|
||||||
|
@required this.onInvoiceTap,
|
||||||
@required this.onDismissed,
|
@required this.onDismissed,
|
||||||
@required this.onRefreshed,
|
@required this.onRefreshed,
|
||||||
|
@required this.onClearClientFilterPressed,
|
||||||
|
@required this.onViewClientFilterPressed,
|
||||||
@required this.onEntityAction,
|
@required this.onEntityAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -63,7 +76,7 @@ class QuoteListVM {
|
||||||
}
|
}
|
||||||
final completer = snackBarCompleter(
|
final completer = snackBarCompleter(
|
||||||
context, AppLocalization.of(context).refreshComplete);
|
context, AppLocalization.of(context).refreshComplete);
|
||||||
store.dispatch(LoadQuotes(completer: completer, force: true));
|
store.dispatch(LoadInvoices(completer: completer, force: true));
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,66 +84,98 @@ class QuoteListVM {
|
||||||
|
|
||||||
return QuoteListVM(
|
return QuoteListVM(
|
||||||
user: state.user,
|
user: state.user,
|
||||||
quoteList: memoizedFilteredQuoteList(state.quoteState.map,
|
listState: state.invoiceListState,
|
||||||
state.quoteState.list, state.quoteListState),
|
invoiceList: memoizedFilteredQuoteList(
|
||||||
quoteMap: state.quoteState.map,
|
state.invoiceState.map,
|
||||||
|
state.invoiceState.list,
|
||||||
|
state.clientState.map,
|
||||||
|
state.invoiceListState),
|
||||||
|
invoiceMap: state.invoiceState.map,
|
||||||
clientMap: state.clientState.map,
|
clientMap: state.clientState.map,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
isLoaded: state.quoteState.isLoaded,
|
isLoaded: state.invoiceState.isLoaded && state.clientState.isLoaded,
|
||||||
filter: state.quoteUIState.listUIState.filter,
|
filter: state.invoiceListState.filter,
|
||||||
onQuoteTap: (context, quote) {
|
onInvoiceTap: (context, invoice) {
|
||||||
store.dispatch(EditQuote(quote: quote, context: context));
|
store.dispatch(ViewInvoice(invoiceId: invoice.id, context: context));
|
||||||
},
|
},
|
||||||
onEntityAction: (context, quote, action) {
|
onRefreshed: (context) => _handleRefresh(context),
|
||||||
|
onClearClientFilterPressed: () =>
|
||||||
|
store.dispatch(FilterInvoicesByClient()),
|
||||||
|
onViewClientFilterPressed: (BuildContext context) => store.dispatch(
|
||||||
|
ViewClient(
|
||||||
|
clientId: state.invoiceListState.filterClientId,
|
||||||
|
context: context)),
|
||||||
|
onEntityAction: (context, invoice, action) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case EntityAction.pdf:
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
viewPdf(invoice, context);
|
||||||
|
break;
|
||||||
|
case EntityAction.markSent:
|
||||||
|
store.dispatch(MarkSentInvoiceRequest(
|
||||||
|
popCompleter(
|
||||||
|
context, localization.markedInvoiceAsSent),
|
||||||
|
invoice.id));
|
||||||
|
break;
|
||||||
|
case EntityAction.emailInvoice:
|
||||||
|
store.dispatch(ShowEmailInvoice(
|
||||||
|
completer: popCompleter(
|
||||||
|
context, localization.emailedInvoice),
|
||||||
|
invoice: invoice,
|
||||||
|
context: context));
|
||||||
|
break;
|
||||||
case EntityAction.clone:
|
case EntityAction.clone:
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
EditQuote(context: context, quote: quote.clone));
|
EditInvoice(context: context, invoice: invoice.clone));
|
||||||
break;
|
break;
|
||||||
case EntityAction.restore:
|
case EntityAction.restore:
|
||||||
store.dispatch(RestoreQuoteRequest(
|
store.dispatch(RestoreInvoiceRequest(
|
||||||
popCompleter(
|
popCompleter(
|
||||||
context, AppLocalization.of(context).restoredQuote),
|
context, localization.restoredInvoice),
|
||||||
quote.id));
|
invoice.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.archive:
|
case EntityAction.archive:
|
||||||
store.dispatch(ArchiveQuoteRequest(
|
store.dispatch(ArchiveInvoiceRequest(
|
||||||
popCompleter(
|
popCompleter(
|
||||||
context, AppLocalization.of(context).archivedQuote),
|
context, localization.archivedInvoice),
|
||||||
quote.id));
|
invoice.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.delete:
|
case EntityAction.delete:
|
||||||
store.dispatch(DeleteQuoteRequest(
|
store.dispatch(DeleteInvoiceRequest(
|
||||||
popCompleter(
|
popCompleter(
|
||||||
context, AppLocalization.of(context).deletedQuote),
|
context, localization.deletedInvoice),
|
||||||
quote.id));
|
invoice.id));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRefreshed: (context) => _handleRefresh(context),
|
onDismissed: (BuildContext context, InvoiceEntity invoice,
|
||||||
onDismissed: (BuildContext context, QuoteEntity quote,
|
|
||||||
DismissDirection direction) {
|
DismissDirection direction) {
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
if (direction == DismissDirection.endToStart) {
|
if (direction == DismissDirection.endToStart) {
|
||||||
if (quote.isDeleted || quote.isArchived) {
|
if (invoice.isDeleted || invoice.isArchived) {
|
||||||
store.dispatch(RestoreQuoteRequest(
|
store.dispatch(RestoreInvoiceRequest(
|
||||||
snackBarCompleter(context, localization.restoredQuote),
|
snackBarCompleter(
|
||||||
quote.id));
|
context, localization.restoredInvoice),
|
||||||
|
invoice.id));
|
||||||
} else {
|
} else {
|
||||||
store.dispatch(ArchiveQuoteRequest(
|
store.dispatch(ArchiveInvoiceRequest(
|
||||||
snackBarCompleter(context, localization.archivedQuote),
|
snackBarCompleter(
|
||||||
quote.id));
|
context, localization.archivedInvoice),
|
||||||
|
invoice.id));
|
||||||
}
|
}
|
||||||
} else if (direction == DismissDirection.startToEnd) {
|
} else if (direction == DismissDirection.startToEnd) {
|
||||||
if (quote.isDeleted) {
|
if (invoice.isDeleted) {
|
||||||
store.dispatch(RestoreQuoteRequest(
|
store.dispatch(RestoreInvoiceRequest(
|
||||||
snackBarCompleter(context, localization.restoredQuote),
|
snackBarCompleter(
|
||||||
quote.id));
|
context, localization.restoredInvoice),
|
||||||
|
invoice.id));
|
||||||
} else {
|
} else {
|
||||||
store.dispatch(DeleteQuoteRequest(
|
store.dispatch(DeleteInvoiceRequest(
|
||||||
snackBarCompleter(context, localization.deletedQuote),
|
snackBarCompleter(
|
||||||
quote.id));
|
context, localization.deletedInvoice),
|
||||||
|
invoice.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ class QuoteScreen extends StatelessWidget {
|
||||||
onSelectedCustom2: (value) =>
|
onSelectedCustom2: (value) =>
|
||||||
store.dispatch(FilterQuotesByCustom2(value)),
|
store.dispatch(FilterQuotesByCustom2(value)),
|
||||||
sortFields: [
|
sortFields: [
|
||||||
QuoteFields.quoteNumber,
|
InvoiceFields.invoiceNumber,
|
||||||
QuoteFields.quoteDate,
|
InvoiceFields.invoiceDate,
|
||||||
QuoteFields.updatedAt,
|
InvoiceFields.updatedAt,
|
||||||
],
|
],
|
||||||
onSelectedState: (EntityState state, value) {
|
onSelectedState: (EntityState state, value) {
|
||||||
store.dispatch(FilterQuotesByState(state));
|
store.dispatch(FilterQuotesByState(state));
|
||||||
|
|
@ -67,7 +67,7 @@ class QuoteScreen extends StatelessWidget {
|
||||||
backgroundColor: Theme.of(context).primaryColorDark,
|
backgroundColor: Theme.of(context).primaryColorDark,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
EditQuote(quote: QuoteEntity(), context: context));
|
EditQuote(quote: InvoiceEntity(), context: context));
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
||||||
|
|
||||||
class QuoteViewScreen extends StatelessWidget {
|
class QuoteViewScreen extends StatelessWidget {
|
||||||
static const String route = '/invoice/view';
|
static const String route = '/quote/view';
|
||||||
|
|
||||||
const QuoteViewScreen({Key key}) : super(key: key);
|
const QuoteViewScreen({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ class QuoteViewScreen extends StatelessWidget {
|
||||||
|
|
||||||
class QuoteViewVM {
|
class QuoteViewVM {
|
||||||
final CompanyEntity company;
|
final CompanyEntity company;
|
||||||
final QuoteEntity quote;
|
final InvoiceEntity quote;
|
||||||
final ClientEntity client;
|
final ClientEntity client;
|
||||||
final bool isSaving;
|
final bool isSaving;
|
||||||
final bool isDirty;
|
final bool isDirty;
|
||||||
|
|
@ -80,13 +80,13 @@ class QuoteViewVM {
|
||||||
quote: quote,
|
quote: quote,
|
||||||
client: client,
|
client: client,
|
||||||
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
|
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
|
||||||
final Completer<QuoteEntity> completer =
|
final Completer<InvoiceEntity> completer =
|
||||||
new Completer<QuoteEntity>();
|
new Completer<InvoiceEntity>();
|
||||||
store.dispatch(EditQuote(
|
store.dispatch(EditQuote(
|
||||||
quote: quote,
|
quote: quote,
|
||||||
context: context,
|
context: context,
|
||||||
completer: completer,
|
completer: completer,
|
||||||
invoiceItem: invoiceItem));
|
quoteItem: invoiceItem));
|
||||||
completer.future.then((invoice) {
|
completer.future.then((invoice) {
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
content: SnackBarRow(
|
content: SnackBarRow(
|
||||||
|
|
@ -104,39 +104,39 @@ class QuoteViewVM {
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case EntityAction.pdf:
|
case EntityAction.pdf:
|
||||||
viewPdf(invoice, context);
|
viewPdf(quote, context);
|
||||||
break;
|
break;
|
||||||
case EntityAction.markSent:
|
case EntityAction.markSent:
|
||||||
store.dispatch(MarkSentQuoteRequest(
|
store.dispatch(MarkSentQuoteRequest(
|
||||||
snackBarCompleter(context, localization.markedQuoteAsSent),
|
snackBarCompleter(context, localization.markedQuoteAsSent),
|
||||||
invoice.id));
|
quote.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.emailQuote:
|
case EntityAction.emailInvoice:
|
||||||
store.dispatch(ShowEmailQuote(
|
store.dispatch(ShowEmailQuote(
|
||||||
completer:
|
completer:
|
||||||
snackBarCompleter(context, localization.emailedQuote),
|
snackBarCompleter(context, localization.emailedQuote),
|
||||||
invoice: invoice,
|
quote: quote,
|
||||||
context: context));
|
context: context));
|
||||||
break;
|
break;
|
||||||
case EntityAction.archive:
|
case EntityAction.archive:
|
||||||
store.dispatch(ArchiveQuoteRequest(
|
store.dispatch(ArchiveQuoteRequest(
|
||||||
popCompleter(context, localization.archivedQuote),
|
popCompleter(context, localization.archivedQuote),
|
||||||
invoice.id));
|
quote.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.delete:
|
case EntityAction.delete:
|
||||||
store.dispatch(DeleteQuoteRequest(
|
store.dispatch(DeleteQuoteRequest(
|
||||||
popCompleter(context, localization.deletedQuote),
|
popCompleter(context, localization.deletedQuote),
|
||||||
invoice.id));
|
quote.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.restore:
|
case EntityAction.restore:
|
||||||
store.dispatch(RestoreQuoteRequest(
|
store.dispatch(RestoreQuoteRequest(
|
||||||
snackBarCompleter(context, localization.restoredQuote),
|
snackBarCompleter(context, localization.restoredQuote),
|
||||||
invoice.id));
|
quote.id));
|
||||||
break;
|
break;
|
||||||
case EntityAction.clone:
|
case EntityAction.clone:
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
EditQuote(context: context, invoice: invoice.clone));
|
EditQuote(context: context, quote: quote.clone));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -146,7 +146,7 @@ class QuoteViewVM {
|
||||||
bool operator ==(dynamic other) =>
|
bool operator ==(dynamic other) =>
|
||||||
client == other.client &&
|
client == other.client &&
|
||||||
company == other.company &&
|
company == other.company &&
|
||||||
invoice == other.invoice &&
|
quote == other.quote &&
|
||||||
isSaving == other.isSaving &&
|
isSaving == other.isSaving &&
|
||||||
isDirty == other.isDirty;
|
isDirty == other.isDirty;
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ class QuoteViewVM {
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
client.hashCode ^
|
client.hashCode ^
|
||||||
company.hashCode ^
|
company.hashCode ^
|
||||||
invoice.hashCode ^
|
quote.hashCode ^
|
||||||
isSaving.hashCode ^
|
isSaving.hashCode ^
|
||||||
isDirty.hashCode;
|
isDirty.hashCode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/stub/edit/stub_edit_vm.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/save_icon_button.dart';
|
import 'package:invoiceninja_flutter/ui/app/buttons/refresh_icon_button.dart';
|
||||||
|
|
||||||
class StubEdit extends StatefulWidget {
|
class StubEdit extends StatefulWidget {
|
||||||
final StubEditVM viewModel;
|
final StubEditVM viewModel;
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
||||||
import 'package:flutter_redux_starter/ui/stub/stub_screen.dart';
|
import 'package:invoiceninja_flutter/ui/stub/stub_screen.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
import 'package:invoiceninja_flutter/redux/stub/stub_actions.dart';
|
||||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
import 'package:invoiceninja_flutter/data/models/stub_model.dart';
|
||||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit.dart';
|
import 'package:invoiceninja_flutter/ui/stub/edit/stub_edit.dart';
|
||||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
|
||||||
|
|
||||||
class StubEditScreen extends StatelessWidget {
|
class StubEditScreen extends StatelessWidget {
|
||||||
static final String route = '/stub/edit';
|
static final String route = '/stub/edit';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue