Quotes
This commit is contained in:
parent
29ff8f415e
commit
5a3a30dc4e
|
|
@ -367,7 +367,7 @@ abstract class ActivityEntity implements Built<ActivityEntity, ActivityEntityBui
|
|||
//ContactEntity contact,
|
||||
PaymentEntity payment,
|
||||
CreditEntity credit,
|
||||
//QuoteEntity quote,
|
||||
//InvoiceEntity quote,
|
||||
TaskEntity task,
|
||||
ExpenseEntity expense,
|
||||
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/expense_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/currency_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/invoice/invoice_state.dart';
|
||||
// STARTER: import - do not remove comment
|
||||
import 'package:invoiceninja_flutter/data/models/quote_model.dart';
|
||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||
|
||||
|
||||
|
|
@ -73,7 +72,6 @@ part 'serializers.g.dart';
|
|||
TimezoneItemResponse,
|
||||
TimezoneListResponse,
|
||||
// STARTER: serializers - do not remove comment
|
||||
QuoteEntity,
|
||||
|
||||
])
|
||||
final Serializers serializers =
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ Serializers _$serializers = (new Serializers().toBuilder()
|
|||
..add(ProjectEntity.serializer)
|
||||
..add(ProjectItemResponse.serializer)
|
||||
..add(ProjectListResponse.serializer)
|
||||
..add(QuoteEntity.serializer)
|
||||
..add(InvoiceEntity.serializer)
|
||||
..add(QuoteState.serializer)
|
||||
..add(QuoteUIState.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(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(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>()))
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ class QuoteRepository {
|
|||
this.webClient = const WebClient(),
|
||||
});
|
||||
|
||||
Future<QuoteEntity> loadItem(
|
||||
Future<InvoiceEntity> loadItem(
|
||||
CompanyEntity company, AuthState auth, int entityId) async {
|
||||
final dynamic response = await webClient.get(
|
||||
'${auth.url}/quotes/$entityId', company.token);
|
||||
'${auth.url}/invoices/$entityId', company.token);
|
||||
|
||||
final QuoteItemResponse quoteResponse =
|
||||
serializers.deserializeWith(QuoteItemResponse.serializer, response);
|
||||
final InvoiceItemResponse quoteResponse =
|
||||
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
|
||||
|
||||
return quoteResponse.data;
|
||||
}
|
||||
|
||||
Future<BuiltList<QuoteEntity>> loadList(
|
||||
Future<BuiltList<InvoiceEntity>> loadList(
|
||||
CompanyEntity company, AuthState auth, int updatedAt) async {
|
||||
String url = auth.url + '/quotes';
|
||||
|
||||
|
|
@ -35,16 +35,16 @@ class QuoteRepository {
|
|||
|
||||
final dynamic response = await webClient.get(url, company.token);
|
||||
|
||||
final QuoteListResponse quoteResponse =
|
||||
serializers.deserializeWith(QuoteListResponse.serializer, response);
|
||||
final InvoiceListResponse quoteResponse =
|
||||
serializers.deserializeWith(InvoiceListResponse.serializer, response);
|
||||
|
||||
return quoteResponse.data;
|
||||
}
|
||||
|
||||
Future<QuoteEntity> saveData(
|
||||
CompanyEntity company, AuthState auth, QuoteEntity quote,
|
||||
Future<InvoiceEntity> saveData(
|
||||
CompanyEntity company, AuthState auth, InvoiceEntity quote,
|
||||
[EntityAction action]) async {
|
||||
final data = serializers.serializeWith(QuoteEntity.serializer, quote);
|
||||
final data = serializers.serializeWith(InvoiceEntity.serializer, quote);
|
||||
dynamic response;
|
||||
|
||||
if (quote.isNew) {
|
||||
|
|
@ -60,8 +60,8 @@ class QuoteRepository {
|
|||
response = await webClient.put(url, company.token, json.encode(data));
|
||||
}
|
||||
|
||||
final QuoteItemResponse quoteResponse =
|
||||
serializers.deserializeWith(QuoteItemResponse.serializer, response);
|
||||
final InvoiceItemResponse quoteResponse =
|
||||
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
|
||||
|
||||
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/ui/invoice/invoice_screen.dart';
|
||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||
|
||||
// STARTER: import - do not remove comment
|
||||
import 'package:invoiceninja_flutter/ui/quote/quote_screen.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_middleware.dart';
|
||||
|
||||
|
||||
void main() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final enableDarkMode = prefs.getBool(kSharedPrefEnableDarkMode);
|
||||
|
|
@ -56,8 +56,7 @@ void main() async {
|
|||
..addAll(createStoreInvoicesMiddleware())
|
||||
..addAll(createStorePersistenceMiddleware())
|
||||
// STARTER: middleware - do not remove comment
|
||||
..addAll(createStoreQuotesMiddleware())
|
||||
|
||||
..addAll(createStoreQuotesMiddleware())
|
||||
..addAll([
|
||||
LoggingMiddleware<dynamic>.printer(),
|
||||
]));
|
||||
|
|
@ -141,13 +140,12 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
|||
InvoiceEditScreen.route: (context) => InvoiceEditScreen(),
|
||||
InvoiceEmailScreen.route: (context) => InvoiceEmailScreen(),
|
||||
// STARTER: routes - do not remove comment
|
||||
QuoteScreen.route: (context) {
|
||||
widget.store.dispatch(LoadQuotes());
|
||||
return QuoteScreen();
|
||||
},
|
||||
QuoteViewScreen.route: (context) => QuoteViewScreen(),
|
||||
QuoteEditScreen.route: (context) => QuoteEditScreen(),
|
||||
|
||||
QuoteScreen.route: (context) {
|
||||
widget.store.dispatch(LoadQuotes());
|
||||
return QuoteScreen();
|
||||
},
|
||||
QuoteViewScreen.route: (context) => QuoteViewScreen(),
|
||||
QuoteEditScreen.route: (context) => QuoteEditScreen(),
|
||||
SettingsScreen.route: (context) => SettingsScreen(),
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -212,9 +212,9 @@ Middleware<AppState> _saveInvoice(InvoiceRepository repository) {
|
|||
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||
repository
|
||||
.saveData(
|
||||
store.state.selectedCompany, store.state.authState, action.invoice)
|
||||
store.state.selectedCompany, store.state.authState, action.quote)
|
||||
.then((dynamic invoice) {
|
||||
if (action.invoice.isNew) {
|
||||
if (action.quote.isNew) {
|
||||
store.dispatch(AddInvoiceSuccess(invoice));
|
||||
} else {
|
||||
store.dispatch(SaveInvoiceSuccess(invoice));
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ final editingItemReducer = combineReducers<InvoiceItemEntity>([
|
|||
|
||||
InvoiceItemEntity editInvoiceItem(
|
||||
InvoiceItemEntity invoiceItem, dynamic action) {
|
||||
return action.invoiceItem ?? InvoiceItemEntity();
|
||||
return action.quoteItem ?? InvoiceItemEntity();
|
||||
}
|
||||
|
||||
Reducer<String> dropdownFilterReducer = combineReducers([
|
||||
|
|
@ -37,9 +37,9 @@ Reducer<int> selectedIdReducer = combineReducers([
|
|||
TypedReducer<int, ViewInvoice>(
|
||||
(int selectedId, dynamic action) => action.invoiceId),
|
||||
TypedReducer<int, AddInvoiceSuccess>(
|
||||
(int selectedId, dynamic action) => action.invoice.id),
|
||||
(int selectedId, dynamic action) => action.quote.id),
|
||||
TypedReducer<int, ShowEmailInvoice>(
|
||||
(int selectedId, dynamic action) => action.invoice.id),
|
||||
(int selectedId, dynamic action) => action.quote.id),
|
||||
]);
|
||||
|
||||
final editingReducer = combineReducers<InvoiceEntity>([
|
||||
|
|
@ -62,7 +62,7 @@ InvoiceEntity _clearEditing(InvoiceEntity client, dynamic action) {
|
|||
}
|
||||
|
||||
InvoiceEntity _updateEditing(InvoiceEntity invoice, dynamic action) {
|
||||
return action.invoice;
|
||||
return action.quote;
|
||||
}
|
||||
|
||||
InvoiceEntity _addInvoiceItem(InvoiceEntity invoice, AddInvoiceItem action) {
|
||||
|
|
@ -244,7 +244,7 @@ InvoiceState _addInvoice(InvoiceState invoiceState, AddInvoiceSuccess action) {
|
|||
|
||||
InvoiceState _updateInvoice(InvoiceState invoiceState, dynamic action) {
|
||||
return invoiceState
|
||||
.rebuild((b) => b..map[action.invoice.id] = action.invoice);
|
||||
.rebuild((b) => b..map[action.quote.id] = action.quote);
|
||||
}
|
||||
|
||||
InvoiceState _setNoInvoices(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ViewQuote implements PersistUI {
|
|||
}
|
||||
|
||||
class EditQuote implements PersistUI {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
final InvoiceItemEntity quoteItem;
|
||||
final BuildContext context;
|
||||
final Completer completer;
|
||||
|
|
@ -28,7 +28,7 @@ class EditQuote implements PersistUI {
|
|||
}
|
||||
|
||||
class ShowEmailQuote {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
final BuildContext context;
|
||||
final Completer completer;
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class EditQuoteItem implements PersistUI {
|
|||
}
|
||||
|
||||
class UpdateQuote implements PersistUI {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
UpdateQuote(this.quote);
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ class LoadQuoteFailure implements StopLoading {
|
|||
}
|
||||
|
||||
class LoadQuoteSuccess implements StopLoading, PersistData {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
LoadQuoteSuccess(this.quote);
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ class LoadQuotesFailure implements StopLoading {
|
|||
}
|
||||
|
||||
class LoadQuotesSuccess implements StopLoading, PersistData {
|
||||
final BuiltList<QuoteEntity> quotes;
|
||||
final BuiltList<InvoiceEntity> quotes;
|
||||
|
||||
LoadQuotesSuccess(this.quotes);
|
||||
|
||||
|
|
@ -136,19 +136,19 @@ class DeleteQuoteItem implements PersistUI {
|
|||
|
||||
class SaveQuoteRequest implements StartSaving {
|
||||
final Completer completer;
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
SaveQuoteRequest({this.completer, this.quote});
|
||||
}
|
||||
|
||||
class SaveQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
SaveQuoteSuccess(this.quote);
|
||||
}
|
||||
|
||||
class AddQuoteSuccess implements StopSaving, PersistData, PersistUI {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
AddQuoteSuccess(this.quote);
|
||||
}
|
||||
|
|
@ -186,13 +186,13 @@ class MarkSentQuoteRequest implements StartSaving {
|
|||
}
|
||||
|
||||
class MarkSentQuoteSuccess implements StopSaving, PersistData {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
MarkSentQuoteSuccess(this.quote);
|
||||
}
|
||||
|
||||
class MarkSentQuoteFailure implements StopSaving {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
MarkSentQuoteFailure(this.quote);
|
||||
}
|
||||
|
|
@ -205,13 +205,13 @@ class ArchiveQuoteRequest implements StartSaving {
|
|||
}
|
||||
|
||||
class ArchiveQuoteSuccess implements StopSaving, PersistData {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
ArchiveQuoteSuccess(this.quote);
|
||||
}
|
||||
|
||||
class ArchiveQuoteFailure implements StopSaving {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
ArchiveQuoteFailure(this.quote);
|
||||
}
|
||||
|
|
@ -224,13 +224,13 @@ class DeleteQuoteRequest implements StartSaving {
|
|||
}
|
||||
|
||||
class DeleteQuoteSuccess implements StopSaving, PersistData {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
DeleteQuoteSuccess(this.quote);
|
||||
}
|
||||
|
||||
class DeleteQuoteFailure implements StopSaving {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
DeleteQuoteFailure(this.quote);
|
||||
}
|
||||
|
|
@ -243,13 +243,13 @@ class RestoreQuoteRequest implements StartSaving {
|
|||
}
|
||||
|
||||
class RestoreQuoteSuccess implements StopSaving, PersistData {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
RestoreQuoteSuccess(this.quote);
|
||||
}
|
||||
|
||||
class RestoreQuoteFailure implements StopSaving {
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
|
||||
RestoreQuoteFailure(this.quote);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,22 +20,22 @@ Reducer<int> selectedIdReducer = combineReducers([
|
|||
(int selectedId, dynamic action) => action.quote.id),
|
||||
]);
|
||||
|
||||
final editingReducer = combineReducers<QuoteEntity>([
|
||||
TypedReducer<QuoteEntity, SaveQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, AddQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, RestoreQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, ArchiveQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, DeleteQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, EditQuote>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, UpdateQuote>(_updateEditing),
|
||||
TypedReducer<QuoteEntity, SelectCompany>(_clearEditing),
|
||||
final editingReducer = combineReducers<InvoiceEntity>([
|
||||
TypedReducer<InvoiceEntity, SaveQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, AddQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, RestoreQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, ArchiveQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, DeleteQuoteSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, EditQuote>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, UpdateQuote>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, SelectCompany>(_clearEditing),
|
||||
]);
|
||||
|
||||
QuoteEntity _clearEditing(QuoteEntity quote, dynamic action) {
|
||||
return QuoteEntity();
|
||||
InvoiceEntity _clearEditing(InvoiceEntity quote, dynamic action) {
|
||||
return InvoiceEntity();
|
||||
}
|
||||
|
||||
QuoteEntity _updateEditing(QuoteEntity quote, dynamic action) {
|
||||
InvoiceEntity _updateEditing(InvoiceEntity quote, dynamic action) {
|
||||
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/redux/ui/list_ui_state.dart';
|
||||
|
||||
var memoizedDropdownQuoteList = memo2(
|
||||
(BuiltMap<int, QuoteEntity> quoteMap, BuiltList<int> quoteList) =>
|
||||
dropdownQuotesSelector(quoteMap, quoteList));
|
||||
|
||||
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;
|
||||
ClientEntity quoteClientSelector(
|
||||
InvoiceEntity invoice, BuiltMap<int, ClientEntity> clientMap) {
|
||||
return clientMap[invoice.clientId];
|
||||
}
|
||||
|
||||
var memoizedFilteredQuoteList = memo3((BuiltMap<int, QuoteEntity> quoteMap,
|
||||
BuiltList<int> quoteList, ListUIState quoteListState) =>
|
||||
filteredQuotesSelector(quoteMap, quoteList, quoteListState));
|
||||
var memoizedFilteredQuoteList = memo4(
|
||||
(BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||
BuiltList<int> invoiceList,
|
||||
BuiltMap<int, ClientEntity> clientMap,
|
||||
ListUIState invoiceListState) =>
|
||||
filteredQuotesSelector(
|
||||
invoiceMap, invoiceList, clientMap, invoiceListState));
|
||||
|
||||
List<int> filteredQuotesSelector(BuiltMap<int, QuoteEntity> quoteMap,
|
||||
BuiltList<int> quoteList, ListUIState quoteListState) {
|
||||
final list = quoteList.where((quoteId) {
|
||||
final quote = quoteMap[quoteId];
|
||||
if (!quote.matchesStates(quoteListState.stateFilters)) {
|
||||
List<int> filteredQuotesSelector(
|
||||
BuiltMap<int, InvoiceEntity> invoiceMap,
|
||||
BuiltList<int> invoiceList,
|
||||
BuiltMap<int, ClientEntity> clientMap,
|
||||
ListUIState invoiceListState) {
|
||||
final list = invoiceList.where((invoiceId) {
|
||||
final invoice = invoiceMap[invoiceId];
|
||||
final client = clientMap[invoice.clientId];
|
||||
if (client == null || ! client.isActive) {
|
||||
return false;
|
||||
}
|
||||
if (quoteListState.custom1Filters.isNotEmpty &&
|
||||
!quoteListState.custom1Filters.contains(quote.customTextValue1)) {
|
||||
if (!invoice.matchesStates(invoiceListState.stateFilters)) {
|
||||
return false;
|
||||
}
|
||||
if (quoteListState.custom2Filters.isNotEmpty &&
|
||||
!quoteListState.custom2Filters.contains(quote.customTextValue2)) {
|
||||
if (!invoice.matchesStatuses(invoiceListState.statusFilters)) {
|
||||
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();
|
||||
|
||||
list.sort((quoteAId, quoteBId) {
|
||||
final quoteA = quoteMap[quoteAId];
|
||||
final quoteB = quoteMap[quoteBId];
|
||||
return quoteA.compareTo(
|
||||
quoteB, quoteListState.sortField, quoteListState.sortAscending);
|
||||
list.sort((invoiceAId, invoiceBId) {
|
||||
return invoiceMap[invoiceAId].compareTo(invoiceMap[invoiceBId],
|
||||
invoiceListState.sortField, invoiceListState.sortAscending);
|
||||
});
|
||||
|
||||
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_collection/built_collection.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/list_ui_state.dart';
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ abstract class QuoteState implements Built<QuoteState, QuoteStateBuilder> {
|
|||
factory QuoteState() {
|
||||
return _$QuoteState._(
|
||||
lastUpdated: 0,
|
||||
map: BuiltMap<int, QuoteEntity>(),
|
||||
map: BuiltMap<int, InvoiceEntity>(),
|
||||
list: BuiltList<int>(),
|
||||
);
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ abstract class QuoteState implements Built<QuoteState, QuoteStateBuilder> {
|
|||
@nullable
|
||||
int get lastUpdated;
|
||||
|
||||
BuiltMap<int, QuoteEntity> get map;
|
||||
BuiltMap<int, InvoiceEntity> get map;
|
||||
BuiltList<int> get list;
|
||||
|
||||
bool get isStale {
|
||||
|
|
@ -42,15 +42,19 @@ abstract class QuoteUIState extends Object with EntityUIState implements Built<Q
|
|||
|
||||
factory QuoteUIState() {
|
||||
return _$QuoteUIState._(
|
||||
listUIState: ListUIState(QuoteFields.quoteNumber),
|
||||
editing: QuoteEntity(),
|
||||
listUIState: ListUIState(InvoiceFields.invoiceNumber),
|
||||
editing: InvoiceEntity(),
|
||||
|
||||
selectedId: 0,
|
||||
);
|
||||
}
|
||||
QuoteUIState._();
|
||||
|
||||
@nullable
|
||||
QuoteEntity get editing;
|
||||
InvoiceEntity get editing;
|
||||
|
||||
@nullable
|
||||
InvoiceItemEntity get editingItem;
|
||||
|
||||
@override
|
||||
bool get isCreatingNew => editing.isNew;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class _$QuoteStateSerializer implements StructuredSerializer<QuoteState> {
|
|||
'map',
|
||||
serializers.serialize(object.map,
|
||||
specifiedType: const FullType(BuiltMap,
|
||||
const [const FullType(int), const FullType(QuoteEntity)])),
|
||||
const [const FullType(int), const FullType(InvoiceEntity)])),
|
||||
'list',
|
||||
serializers.serialize(object.list,
|
||||
specifiedType:
|
||||
|
|
@ -68,7 +68,7 @@ class _$QuoteStateSerializer implements StructuredSerializer<QuoteState> {
|
|||
result.map.replace(serializers.deserialize(value,
|
||||
specifiedType: const FullType(BuiltMap, const [
|
||||
const FullType(int),
|
||||
const FullType(QuoteEntity)
|
||||
const FullType(InvoiceEntity)
|
||||
])) as BuiltMap);
|
||||
break;
|
||||
case 'list':
|
||||
|
|
@ -105,7 +105,13 @@ class _$QuoteUIStateSerializer implements StructuredSerializer<QuoteUIState> {
|
|||
result
|
||||
..add('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;
|
||||
|
|
@ -124,7 +130,12 @@ class _$QuoteUIStateSerializer implements StructuredSerializer<QuoteUIState> {
|
|||
switch (key) {
|
||||
case 'editing':
|
||||
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;
|
||||
case 'selectedId':
|
||||
result.selectedId = serializers.deserialize(value,
|
||||
|
|
@ -145,7 +156,7 @@ class _$QuoteState extends QuoteState {
|
|||
@override
|
||||
final int lastUpdated;
|
||||
@override
|
||||
final BuiltMap<int, QuoteEntity> map;
|
||||
final BuiltMap<int, InvoiceEntity> map;
|
||||
@override
|
||||
final BuiltList<int> list;
|
||||
|
||||
|
|
@ -196,10 +207,10 @@ class QuoteStateBuilder implements Builder<QuoteState, QuoteStateBuilder> {
|
|||
int get lastUpdated => _$this._lastUpdated;
|
||||
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
|
||||
|
||||
MapBuilder<int, QuoteEntity> _map;
|
||||
MapBuilder<int, QuoteEntity> get map =>
|
||||
_$this._map ??= new MapBuilder<int, QuoteEntity>();
|
||||
set map(MapBuilder<int, QuoteEntity> map) => _$this._map = map;
|
||||
MapBuilder<int, InvoiceEntity> _map;
|
||||
MapBuilder<int, InvoiceEntity> get map =>
|
||||
_$this._map ??= new MapBuilder<int, InvoiceEntity>();
|
||||
set map(MapBuilder<int, InvoiceEntity> map) => _$this._map = map;
|
||||
|
||||
ListBuilder<int> _list;
|
||||
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
|
||||
|
|
@ -255,7 +266,9 @@ class QuoteStateBuilder implements Builder<QuoteState, QuoteStateBuilder> {
|
|||
|
||||
class _$QuoteUIState extends QuoteUIState {
|
||||
@override
|
||||
final QuoteEntity editing;
|
||||
final InvoiceEntity editing;
|
||||
@override
|
||||
final InvoiceItemEntity editingItem;
|
||||
@override
|
||||
final int selectedId;
|
||||
@override
|
||||
|
|
@ -264,7 +277,8 @@ class _$QuoteUIState extends QuoteUIState {
|
|||
factory _$QuoteUIState([void updates(QuoteUIStateBuilder b)]) =>
|
||||
(new QuoteUIStateBuilder()..update(updates)).build();
|
||||
|
||||
_$QuoteUIState._({this.editing, this.selectedId, this.listUIState})
|
||||
_$QuoteUIState._(
|
||||
{this.editing, this.editingItem, this.selectedId, this.listUIState})
|
||||
: super._() {
|
||||
if (selectedId == null)
|
||||
throw new BuiltValueNullFieldError('QuoteUIState', 'selectedId');
|
||||
|
|
@ -284,13 +298,16 @@ class _$QuoteUIState extends QuoteUIState {
|
|||
if (identical(other, this)) return true;
|
||||
if (other is! QuoteUIState) return false;
|
||||
return editing == other.editing &&
|
||||
editingItem == other.editingItem &&
|
||||
selectedId == other.selectedId &&
|
||||
listUIState == other.listUIState;
|
||||
}
|
||||
|
||||
@override
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -298,6 +315,7 @@ class _$QuoteUIState extends QuoteUIState {
|
|||
String toString() {
|
||||
return (newBuiltValueToStringHelper('QuoteUIState')
|
||||
..add('editing', editing)
|
||||
..add('editingItem', editingItem)
|
||||
..add('selectedId', selectedId)
|
||||
..add('listUIState', listUIState))
|
||||
.toString();
|
||||
|
|
@ -308,10 +326,16 @@ class QuoteUIStateBuilder
|
|||
implements Builder<QuoteUIState, QuoteUIStateBuilder> {
|
||||
_$QuoteUIState _$v;
|
||||
|
||||
QuoteEntityBuilder _editing;
|
||||
QuoteEntityBuilder get editing =>
|
||||
_$this._editing ??= new QuoteEntityBuilder();
|
||||
set editing(QuoteEntityBuilder editing) => _$this._editing = editing;
|
||||
InvoiceEntityBuilder _editing;
|
||||
InvoiceEntityBuilder get editing =>
|
||||
_$this._editing ??= new InvoiceEntityBuilder();
|
||||
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 get selectedId => _$this._selectedId;
|
||||
|
|
@ -328,6 +352,7 @@ class QuoteUIStateBuilder
|
|||
QuoteUIStateBuilder get _$this {
|
||||
if (_$v != null) {
|
||||
_editing = _$v.editing?.toBuilder();
|
||||
_editingItem = _$v.editingItem?.toBuilder();
|
||||
_selectedId = _$v.selectedId;
|
||||
_listUIState = _$v.listUIState?.toBuilder();
|
||||
_$v = null;
|
||||
|
|
@ -353,6 +378,7 @@ class QuoteUIStateBuilder
|
|||
_$result = _$v ??
|
||||
new _$QuoteUIState._(
|
||||
editing: _editing?.build(),
|
||||
editingItem: _editingItem?.build(),
|
||||
selectedId: selectedId,
|
||||
listUIState: listUIState.build());
|
||||
} catch (_) {
|
||||
|
|
@ -360,6 +386,8 @@ class QuoteUIStateBuilder
|
|||
try {
|
||||
_$failedField = 'editing';
|
||||
_editing?.build();
|
||||
_$failedField = 'editingItem';
|
||||
_editingItem?.build();
|
||||
|
||||
_$failedField = 'listUIState';
|
||||
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/product/product_state.dart';
|
||||
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
|
||||
|
||||
// STARTER: import - do not remove comment
|
||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||
|
||||
|
||||
part 'ui_state.g.dart';
|
||||
|
||||
abstract class UIState implements Built<UIState, UIStateBuilder> {
|
||||
|
||||
factory UIState({bool enableDarkMode}) {
|
||||
return _$UIState._(
|
||||
selectedCompanyIndex: 0,
|
||||
|
|
@ -21,26 +20,29 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
|
|||
clientUIState: ClientUIState(),
|
||||
invoiceUIState: InvoiceUIState(),
|
||||
// STARTER: constructor - do not remove comment
|
||||
quoteUIState: QuoteUIState(),
|
||||
|
||||
quoteUIState: QuoteUIState(),
|
||||
);
|
||||
}
|
||||
|
||||
UIState._();
|
||||
|
||||
int get selectedCompanyIndex;
|
||||
|
||||
String get currentRoute;
|
||||
|
||||
bool get enableDarkMode;
|
||||
|
||||
ProductUIState get productUIState;
|
||||
|
||||
ClientUIState get clientUIState;
|
||||
|
||||
InvoiceUIState get invoiceUIState;
|
||||
|
||||
@nullable
|
||||
String get filter;
|
||||
|
||||
// STARTER: properties - do not remove comment
|
||||
QuoteUIState get quoteUIState;
|
||||
|
||||
QuoteUIState get quoteUIState;
|
||||
|
||||
static Serializer<UIState> get serializer => _$uIStateSerializer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,141 +1,141 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_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';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_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 InvoiceEdit extends StatefulWidget {
|
||||
final InvoiceEditVM viewModel;
|
||||
class InvoiceEdit extends StatefulWidget {
|
||||
final InvoiceEditVM viewModel;
|
||||
|
||||
const InvoiceEdit({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
const InvoiceEdit({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_InvoiceEditState createState() => _InvoiceEditState();
|
||||
}
|
||||
|
||||
class _InvoiceEditState extends State<InvoiceEdit>
|
||||
with SingleTickerProviderStateMixin {
|
||||
TabController _controller;
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
static const kDetailsScreen = 0;
|
||||
static const kItemScreen = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final invoice = widget.viewModel.invoice;
|
||||
final invoiceItem = widget.viewModel.invoiceItem;
|
||||
|
||||
final index =
|
||||
invoice.invoiceItems.contains(invoiceItem) ? kItemScreen : kDetailsScreen;
|
||||
_controller =
|
||||
TabController(vsync: this, length: 2, initialIndex: index);
|
||||
@override
|
||||
_InvoiceEditState createState() => _InvoiceEditState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
class _InvoiceEditState extends State<InvoiceEdit>
|
||||
with SingleTickerProviderStateMixin {
|
||||
TabController _controller;
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localization = AppLocalization.of(context);
|
||||
final viewModel = widget.viewModel;
|
||||
final invoice = viewModel.invoice;
|
||||
static const kDetailsScreen = 0;
|
||||
static const kItemScreen = 1;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
viewModel.onBackPressed();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(invoice.isNew
|
||||
? localization.newInvoice
|
||||
: '${localization.invoice} ${viewModel.origInvoice.invoiceNumber}'),
|
||||
actions: <Widget>[
|
||||
RefreshIconButton(
|
||||
icon: Icons.cloud_upload,
|
||||
tooltip: localization.save,
|
||||
isVisible: !invoice.isDeleted,
|
||||
isSaving: widget.viewModel.isSaving,
|
||||
isDirty: invoice.isNew || invoice != viewModel.origInvoice,
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.viewModel.onSavePressed(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _controller,
|
||||
//isScrollable: true,
|
||||
tabs: [
|
||||
Tab(
|
||||
text: localization.details,
|
||||
),
|
||||
Tab(
|
||||
text: localization.items,
|
||||
),
|
||||
final invoice = widget.viewModel.invoice;
|
||||
final invoiceItem = widget.viewModel.invoiceItem;
|
||||
|
||||
final index =
|
||||
invoice.invoiceItems.contains(invoiceItem) ? kItemScreen : kDetailsScreen;
|
||||
_controller =
|
||||
TabController(vsync: this, length: 2, initialIndex: index);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localization = AppLocalization.of(context);
|
||||
final viewModel = widget.viewModel;
|
||||
final invoice = viewModel.invoice;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
viewModel.onBackPressed();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(invoice.isNew
|
||||
? localization.newInvoice
|
||||
: '${localization.invoice} ${viewModel.origInvoice.invoiceNumber}'),
|
||||
actions: <Widget>[
|
||||
RefreshIconButton(
|
||||
icon: Icons.cloud_upload,
|
||||
tooltip: localization.save,
|
||||
isVisible: !invoice.isDeleted,
|
||||
isSaving: widget.viewModel.isSaving,
|
||||
isDirty: invoice.isNew || invoice != viewModel.origInvoice,
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.viewModel.onSavePressed(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _controller,
|
||||
//isScrollable: true,
|
||||
tabs: [
|
||||
Tab(
|
||||
text: localization.details,
|
||||
),
|
||||
Tab(
|
||||
text: localization.items,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
InvoiceEditDetailsScreen(),
|
||||
InvoiceEditItemsScreen(),
|
||||
],
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
InvoiceEditDetailsScreen(),
|
||||
InvoiceEditItemsScreen(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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.invoice.clientId)}',
|
||||
style: TextStyle(
|
||||
//color: Theme.of(context).selectedRowColor,
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
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.invoice.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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ class InvoiceListItem extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final localization = AppLocalization.of(context);
|
||||
final filterMatch = filter != null && filter.isNotEmpty
|
||||
? (invoice.matchesFilterValue(filter) ?? client.matchesFilterValue(filter))
|
||||
? (invoice.matchesFilterValue(filter) ??
|
||||
client.matchesFilterValue(filter))
|
||||
: null;
|
||||
|
||||
return DismissibleEntity(
|
||||
|
|
@ -71,10 +72,15 @@ class InvoiceListItem extends StatelessWidget {
|
|||
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(
|
||||
color:
|
||||
invoice.isPastDue ? Colors.red : InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
||||
color: invoice.isPastDue
|
||||
? Colors.red
|
||||
: InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
||||
)),
|
||||
],
|
||||
),
|
||||
|
|
@ -85,4 +91,3 @@ class InvoiceListItem extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class InvoiceViewVM {
|
|||
bool operator ==(dynamic other) =>
|
||||
client == other.client &&
|
||||
company == other.company &&
|
||||
invoice == other.invoice &&
|
||||
invoice == other.quote &&
|
||||
isSaving == other.isSaving &&
|
||||
isDirty == other.isDirty;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
||||
import 'package:flutter_redux_starter/ui/quote/edit/quote_edit_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/save_icon_button.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_vm.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 {
|
||||
final QuoteEditVM viewModel;
|
||||
|
||||
QuoteEdit({
|
||||
const QuoteEdit({
|
||||
Key key,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
|
@ -16,52 +20,38 @@ class QuoteEdit extends StatefulWidget {
|
|||
_QuoteEditState createState() => _QuoteEditState();
|
||||
}
|
||||
|
||||
class _QuoteEditState extends State<QuoteEdit> {
|
||||
class _QuoteEditState extends State<QuoteEdit>
|
||||
with SingleTickerProviderStateMixin {
|
||||
TabController _controller;
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
// STARTER: controllers - do not remove comment
|
||||
|
||||
var _controllers = [];
|
||||
static const kDetailsScreen = 0;
|
||||
static const kItemScreen = 1;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controllers = [
|
||||
// STARTER: array - do not remove comment
|
||||
];
|
||||
final invoice = widget.viewModel.quote;
|
||||
final invoiceItem = widget.viewModel.quoteItem;
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var quote = widget.viewModel.quote;
|
||||
// STARTER: read value - do not remove comment
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
final index = invoice.invoiceItems.contains(invoiceItem)
|
||||
? kItemScreen
|
||||
: kDetailsScreen;
|
||||
_controller = TabController(vsync: this, length: 2, initialIndex: index);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
_controller.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
|
||||
Widget build(BuildContext context) {
|
||||
var viewModel = widget.viewModel;
|
||||
final localization = AppLocalization.of(context);
|
||||
final viewModel = widget.viewModel;
|
||||
final invoice = viewModel.quote;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
|
|
@ -70,36 +60,81 @@ class _QuoteEditState extends State<QuoteEdit> {
|
|||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(viewModel.quote.isNew
|
||||
? 'New Quote'
|
||||
: viewModel.quote.displayName),
|
||||
title: Text(invoice.isNew
|
||||
? localization.newQuote
|
||||
: '${localization.quote} ${viewModel.origQuote.invoiceNumber}'),
|
||||
actions: <Widget>[
|
||||
Builder(builder: (BuildContext context) {
|
||||
return SaveIconButton(
|
||||
isLoading: viewModel.isLoading,
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
RefreshIconButton(
|
||||
icon: Icons.cloud_upload,
|
||||
tooltip: localization.save,
|
||||
isVisible: !invoice.isDeleted,
|
||||
isSaving: widget.viewModel.isSaving,
|
||||
isDirty: invoice.isNew || invoice != viewModel.origQuote,
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewModel.onSavePressed(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
widget.viewModel.onSavePressed(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
FormCard(
|
||||
children: <Widget>[
|
||||
// STARTER: widgets - do not remove comment
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _controller,
|
||||
//isScrollable: true,
|
||||
tabs: [
|
||||
Tab(
|
||||
text: localization.details,
|
||||
),
|
||||
Tab(
|
||||
text: localization.items,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: <Widget>[
|
||||
InvoiceEditDetailsScreen(),
|
||||
InvoiceEditItemsScreen(),
|
||||
],
|
||||
),
|
||||
),
|
||||
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/material.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
||||
import 'package:flutter_redux_starter/ui/quote/quote_screen.dart';
|
||||
import 'package:invoiceninja_flutter/redux/ui/ui_actions.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:flutter_redux_starter/redux/quote/quote_actions.dart';
|
||||
import 'package:flutter_redux_starter/data/models/quote_model.dart';
|
||||
import 'package:flutter_redux_starter/ui/quote/edit/quote_edit.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit.dart';
|
||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||
|
||||
class QuoteEditScreen extends StatelessWidget {
|
||||
static final String route = '/quote/edit';
|
||||
QuoteEditScreen({Key key}) : super(key: key);
|
||||
static const String route = '/quote/edit';
|
||||
|
||||
const QuoteEditScreen({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -31,45 +34,62 @@ class QuoteEditScreen extends StatelessWidget {
|
|||
}
|
||||
|
||||
class QuoteEditVM {
|
||||
final QuoteEntity quote;
|
||||
final Function(QuoteEntity) onChanged;
|
||||
final CompanyEntity company;
|
||||
final InvoiceEntity quote;
|
||||
final InvoiceItemEntity quoteItem;
|
||||
final InvoiceEntity origQuote;
|
||||
final Function(BuildContext) onSavePressed;
|
||||
final Function(List<InvoiceItemEntity>) onItemsAdded;
|
||||
final Function onBackPressed;
|
||||
final bool isLoading;
|
||||
final bool isSaving;
|
||||
|
||||
QuoteEditVM({
|
||||
@required this.company,
|
||||
@required this.quote,
|
||||
@required this.onChanged,
|
||||
@required this.quoteItem,
|
||||
@required this.origQuote,
|
||||
@required this.onSavePressed,
|
||||
@required this.onItemsAdded,
|
||||
@required this.onBackPressed,
|
||||
@required this.isLoading,
|
||||
@required this.isSaving,
|
||||
});
|
||||
|
||||
factory QuoteEditVM.fromStore(Store<AppState> store) {
|
||||
final quote = store.state.quoteUIState.selected;
|
||||
final AppState state = store.state;
|
||||
final invoice = state.invoiceUIState.editing;
|
||||
|
||||
return QuoteEditVM(
|
||||
isLoading: store.state.isLoading,
|
||||
quote: quote,
|
||||
onChanged: (QuoteEntity quote) {
|
||||
store.dispatch(UpdateQuote(quote));
|
||||
},
|
||||
onBackPressed: () {
|
||||
store.dispatch(UpdateCurrentRoute(QuoteScreen.route));
|
||||
},
|
||||
company: state.selectedCompany,
|
||||
isSaving: state.isSaving,
|
||||
quote: invoice,
|
||||
quoteItem: state.invoiceUIState.editingItem,
|
||||
origQuote: store.state.invoiceState.map[invoice.id],
|
||||
onBackPressed: () =>
|
||||
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route)),
|
||||
onSavePressed: (BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveQuoteRequest(completer: completer, quote: quote));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: IconMessage(
|
||||
message: quote.isNew
|
||||
? 'Successfully Created Quote'
|
||||
: 'Successfully Updated Quote',
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
final Completer<InvoiceEntity> completer = Completer<InvoiceEntity>();
|
||||
store.dispatch(
|
||||
SaveInvoiceRequest(completer: completer, invoice: invoice));
|
||||
return completer.future.then((savedInvoice) {
|
||||
if (invoice.isNew) {
|
||||
Navigator.of(context).pushReplacementNamed(InvoiceViewScreen.route);
|
||||
} else {
|
||||
Navigator.of(context).pop(savedInvoice);
|
||||
}
|
||||
}).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/material.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.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/utils/localization.dart';
|
||||
|
||||
|
|
@ -15,98 +17,167 @@ class QuoteList extends StatelessWidget {
|
|||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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 {
|
||||
void _showMenu(
|
||||
BuildContext context, InvoiceEntity invoice, ClientEntity client) async {
|
||||
final user = viewModel.user;
|
||||
final message = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
||||
user.canCreate(EntityType.quote)
|
||||
? ListTile(
|
||||
leading: Icon(Icons.control_point_duplicate),
|
||||
title: Text(AppLocalization.of(context).clone),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, quote, EntityAction.clone),
|
||||
)
|
||||
: Container(),
|
||||
Divider(),
|
||||
user.canEditEntity(quote) && !quote.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.restore),
|
||||
title: Text(AppLocalization.of(context).restore),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, quote, EntityAction.restore),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(quote) && quote.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.archive),
|
||||
title: Text(AppLocalization.of(context).archive),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, quote, EntityAction.archive),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(quote) && !quote.isDeleted
|
||||
? ListTile(
|
||||
leading: Icon(Icons.delete),
|
||||
title: Text(AppLocalization.of(context).delete),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, quote, EntityAction.delete),
|
||||
)
|
||||
: Container(),
|
||||
]));
|
||||
user.canCreate(EntityType.invoice)
|
||||
? ListTile(
|
||||
leading: Icon(Icons.control_point_duplicate),
|
||||
title: Text(AppLocalization.of(context).clone),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, invoice, EntityAction.clone),
|
||||
)
|
||||
: 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(),
|
||||
user.canEditEntity(invoice) && !invoice.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.restore),
|
||||
title: Text(AppLocalization.of(context).restore),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, invoice, EntityAction.restore),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(invoice) && invoice.isActive
|
||||
? ListTile(
|
||||
leading: Icon(Icons.archive),
|
||||
title: Text(AppLocalization.of(context).archive),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, invoice, EntityAction.archive),
|
||||
)
|
||||
: Container(),
|
||||
user.canEditEntity(invoice) && !invoice.isDeleted
|
||||
? ListTile(
|
||||
leading: Icon(Icons.delete),
|
||||
title: Text(AppLocalization.of(context).delete),
|
||||
onTap: () => viewModel.onEntityAction(
|
||||
context, invoice, EntityAction.delete),
|
||||
)
|
||||
: Container(),
|
||||
]));
|
||||
if (message != null) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: message,
|
||||
)));
|
||||
message: message,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildListView(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => viewModel.onRefreshed(context),
|
||||
child: ListView.builder(
|
||||
itemCount: viewModel.quoteList.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
final quoteId = viewModel.quoteList[index];
|
||||
final quote = viewModel.quoteMap[quoteId];
|
||||
return Column(children: <Widget>[
|
||||
QuoteListItem(
|
||||
user: viewModel.user,
|
||||
filter: viewModel.filter,
|
||||
quote: quote,
|
||||
client: viewModel.clientMap[quote.clientId],
|
||||
onDismissed: (DismissDirection direction) =>
|
||||
viewModel.onDismissed(context, quote, direction),
|
||||
onTap: () => viewModel.onQuoteTap(context, quote),
|
||||
onLongPress: () => _showMenu(context, quote),
|
||||
@override
|
||||
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),
|
||||
child: viewModel.invoiceList.isEmpty
|
||||
? Opacity(
|
||||
opacity: 0.5,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalization.of(context).noRecordsFound,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
),
|
||||
]);
|
||||
}),
|
||||
)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.invoiceList.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
final invoiceId = viewModel.invoiceList[index];
|
||||
final invoice = viewModel.invoiceMap[invoiceId];
|
||||
final client =
|
||||
viewModel.clientMap[invoice.clientId];
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
InvoiceListItem(
|
||||
user: viewModel.user,
|
||||
filter: viewModel.filter,
|
||||
invoice: invoice,
|
||||
client: viewModel.clientMap[invoice.clientId],
|
||||
onDismissed: (DismissDirection direction) =>
|
||||
viewModel.onDismissed(
|
||||
context, invoice, direction),
|
||||
onTap: () =>
|
||||
viewModel.onInvoiceTap(context, invoice),
|
||||
onLongPress: () =>
|
||||
_showMenu(context, invoice, client),
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class QuoteListItem extends StatelessWidget {
|
|||
final DismissDirectionCallback onDismissed;
|
||||
final GestureTapCallback onTap;
|
||||
final GestureTapCallback onLongPress;
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity invoice;
|
||||
final ClientEntity client;
|
||||
final String filter;
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ class QuoteListItem extends StatelessWidget {
|
|||
@required this.onDismissed,
|
||||
@required this.onTap,
|
||||
@required this.onLongPress,
|
||||
@required this.quote,
|
||||
@required this.invoice,
|
||||
@required this.client,
|
||||
@required this.filter,
|
||||
});
|
||||
|
|
@ -30,12 +30,13 @@ class QuoteListItem extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final localization = AppLocalization.of(context);
|
||||
final filterMatch = filter != null && filter.isNotEmpty
|
||||
? (quote.matchesFilterValue(filter) ?? client.matchesFilterValue(filter))
|
||||
? (invoice.matchesFilterValue(filter) ??
|
||||
client.matchesFilterValue(filter))
|
||||
: null;
|
||||
|
||||
return DismissibleEntity(
|
||||
user: user,
|
||||
entity: quote,
|
||||
entity: invoice,
|
||||
onDismissed: onDismissed,
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
|
|
@ -51,8 +52,8 @@ class QuoteListItem extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
formatNumber(quote.amount, context,
|
||||
clientId: quote.clientId),
|
||||
formatNumber(invoice.amount, context,
|
||||
clientId: invoice.clientId),
|
||||
style: Theme.of(context).textTheme.title),
|
||||
],
|
||||
),
|
||||
|
|
@ -64,25 +65,29 @@ class QuoteListItem extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Expanded(
|
||||
child: filterMatch == null
|
||||
? Text(quote.quoteNumber)
|
||||
? Text(invoice.invoiceNumber)
|
||||
: Text(
|
||||
filterMatch,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
filterMatch,
|
||||
maxLines: 3,
|
||||
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(
|
||||
color:
|
||||
quote.isPastDue ? Colors.red : InvoiceStatusColors.colors[quote.quoteStatusId],
|
||||
color: invoice.isPastDue
|
||||
? Colors.red
|
||||
: InvoiceStatusColors.colors[invoice.invoiceStatusId],
|
||||
)),
|
||||
],
|
||||
),
|
||||
EntityStateLabel(quote),
|
||||
EntityStateLabel(invoice),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,35 @@
|
|||
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/foundation.dart';
|
||||
import 'package:flutter/widgets.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/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/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/quote/quote_actions.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
|
||||
|
||||
class QuoteListBuilder extends StatelessWidget {
|
||||
static const String route = '/invoices/edit';
|
||||
|
||||
const QuoteListBuilder({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StoreConnector<AppState, QuoteListVM>(
|
||||
//rebuildOnChange: true,
|
||||
converter: QuoteListVM.fromStore,
|
||||
builder: (context, viewModel) {
|
||||
builder: (context, vm) {
|
||||
return QuoteList(
|
||||
viewModel: viewModel,
|
||||
viewModel: vm,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -31,28 +38,34 @@ class QuoteListBuilder extends StatelessWidget {
|
|||
|
||||
class QuoteListVM {
|
||||
final UserEntity user;
|
||||
final List<int> quoteList;
|
||||
final BuiltMap<int, QuoteEntity> quoteMap;
|
||||
final ListUIState listState;
|
||||
final List<int> invoiceList;
|
||||
final BuiltMap<int, InvoiceEntity> invoiceMap;
|
||||
final BuiltMap<int, ClientEntity> clientMap;
|
||||
final String filter;
|
||||
final bool isLoading;
|
||||
final bool isLoaded;
|
||||
final Function(BuildContext, QuoteEntity) onQuoteTap;
|
||||
final Function(BuildContext, QuoteEntity, DismissDirection) onDismissed;
|
||||
final Function(BuildContext, InvoiceEntity) onInvoiceTap;
|
||||
final Function(BuildContext, InvoiceEntity, DismissDirection) onDismissed;
|
||||
final Function(BuildContext) onRefreshed;
|
||||
final Function(BuildContext, QuoteEntity, EntityAction) onEntityAction;
|
||||
final Function onClearClientFilterPressed;
|
||||
final Function(BuildContext) onViewClientFilterPressed;
|
||||
final Function(BuildContext, InvoiceEntity, EntityAction) onEntityAction;
|
||||
|
||||
QuoteListVM({
|
||||
@required this.user,
|
||||
@required this.quoteList,
|
||||
@required this.quoteMap,
|
||||
@required this.listState,
|
||||
@required this.invoiceList,
|
||||
@required this.invoiceMap,
|
||||
@required this.clientMap,
|
||||
@required this.filter,
|
||||
@required this.isLoading,
|
||||
@required this.isLoaded,
|
||||
@required this.onQuoteTap,
|
||||
@required this.filter,
|
||||
@required this.onInvoiceTap,
|
||||
@required this.onDismissed,
|
||||
@required this.onRefreshed,
|
||||
@required this.onClearClientFilterPressed,
|
||||
@required this.onViewClientFilterPressed,
|
||||
@required this.onEntityAction,
|
||||
});
|
||||
|
||||
|
|
@ -63,7 +76,7 @@ class QuoteListVM {
|
|||
}
|
||||
final completer = snackBarCompleter(
|
||||
context, AppLocalization.of(context).refreshComplete);
|
||||
store.dispatch(LoadQuotes(completer: completer, force: true));
|
||||
store.dispatch(LoadInvoices(completer: completer, force: true));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
|
|
@ -71,66 +84,98 @@ class QuoteListVM {
|
|||
|
||||
return QuoteListVM(
|
||||
user: state.user,
|
||||
quoteList: memoizedFilteredQuoteList(state.quoteState.map,
|
||||
state.quoteState.list, state.quoteListState),
|
||||
quoteMap: state.quoteState.map,
|
||||
listState: state.invoiceListState,
|
||||
invoiceList: memoizedFilteredQuoteList(
|
||||
state.invoiceState.map,
|
||||
state.invoiceState.list,
|
||||
state.clientState.map,
|
||||
state.invoiceListState),
|
||||
invoiceMap: state.invoiceState.map,
|
||||
clientMap: state.clientState.map,
|
||||
isLoading: state.isLoading,
|
||||
isLoaded: state.quoteState.isLoaded,
|
||||
filter: state.quoteUIState.listUIState.filter,
|
||||
onQuoteTap: (context, quote) {
|
||||
store.dispatch(EditQuote(quote: quote, context: context));
|
||||
isLoaded: state.invoiceState.isLoaded && state.clientState.isLoaded,
|
||||
filter: state.invoiceListState.filter,
|
||||
onInvoiceTap: (context, invoice) {
|
||||
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) {
|
||||
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:
|
||||
Navigator.of(context).pop();
|
||||
store.dispatch(
|
||||
EditQuote(context: context, quote: quote.clone));
|
||||
EditInvoice(context: context, invoice: invoice.clone));
|
||||
break;
|
||||
case EntityAction.restore:
|
||||
store.dispatch(RestoreQuoteRequest(
|
||||
store.dispatch(RestoreInvoiceRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).restoredQuote),
|
||||
quote.id));
|
||||
context, localization.restoredInvoice),
|
||||
invoice.id));
|
||||
break;
|
||||
case EntityAction.archive:
|
||||
store.dispatch(ArchiveQuoteRequest(
|
||||
store.dispatch(ArchiveInvoiceRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).archivedQuote),
|
||||
quote.id));
|
||||
context, localization.archivedInvoice),
|
||||
invoice.id));
|
||||
break;
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteQuoteRequest(
|
||||
store.dispatch(DeleteInvoiceRequest(
|
||||
popCompleter(
|
||||
context, AppLocalization.of(context).deletedQuote),
|
||||
quote.id));
|
||||
context, localization.deletedInvoice),
|
||||
invoice.id));
|
||||
break;
|
||||
}
|
||||
},
|
||||
onRefreshed: (context) => _handleRefresh(context),
|
||||
onDismissed: (BuildContext context, QuoteEntity quote,
|
||||
onDismissed: (BuildContext context, InvoiceEntity invoice,
|
||||
DismissDirection direction) {
|
||||
final localization = AppLocalization.of(context);
|
||||
if (direction == DismissDirection.endToStart) {
|
||||
if (quote.isDeleted || quote.isArchived) {
|
||||
store.dispatch(RestoreQuoteRequest(
|
||||
snackBarCompleter(context, localization.restoredQuote),
|
||||
quote.id));
|
||||
if (invoice.isDeleted || invoice.isArchived) {
|
||||
store.dispatch(RestoreInvoiceRequest(
|
||||
snackBarCompleter(
|
||||
context, localization.restoredInvoice),
|
||||
invoice.id));
|
||||
} else {
|
||||
store.dispatch(ArchiveQuoteRequest(
|
||||
snackBarCompleter(context, localization.archivedQuote),
|
||||
quote.id));
|
||||
store.dispatch(ArchiveInvoiceRequest(
|
||||
snackBarCompleter(
|
||||
context, localization.archivedInvoice),
|
||||
invoice.id));
|
||||
}
|
||||
} else if (direction == DismissDirection.startToEnd) {
|
||||
if (quote.isDeleted) {
|
||||
store.dispatch(RestoreQuoteRequest(
|
||||
snackBarCompleter(context, localization.restoredQuote),
|
||||
quote.id));
|
||||
if (invoice.isDeleted) {
|
||||
store.dispatch(RestoreInvoiceRequest(
|
||||
snackBarCompleter(
|
||||
context, localization.restoredInvoice),
|
||||
invoice.id));
|
||||
} else {
|
||||
store.dispatch(DeleteQuoteRequest(
|
||||
snackBarCompleter(context, localization.deletedQuote),
|
||||
quote.id));
|
||||
store.dispatch(DeleteInvoiceRequest(
|
||||
snackBarCompleter(
|
||||
context, localization.deletedInvoice),
|
||||
invoice.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ class QuoteScreen extends StatelessWidget {
|
|||
onSelectedCustom2: (value) =>
|
||||
store.dispatch(FilterQuotesByCustom2(value)),
|
||||
sortFields: [
|
||||
QuoteFields.quoteNumber,
|
||||
QuoteFields.quoteDate,
|
||||
QuoteFields.updatedAt,
|
||||
InvoiceFields.invoiceNumber,
|
||||
InvoiceFields.invoiceDate,
|
||||
InvoiceFields.updatedAt,
|
||||
],
|
||||
onSelectedState: (EntityState state, value) {
|
||||
store.dispatch(FilterQuotesByState(state));
|
||||
|
|
@ -67,7 +67,7 @@ class QuoteScreen extends StatelessWidget {
|
|||
backgroundColor: Theme.of(context).primaryColorDark,
|
||||
onPressed: () {
|
||||
store.dispatch(
|
||||
EditQuote(quote: QuoteEntity(), context: context));
|
||||
EditQuote(quote: InvoiceEntity(), context: context));
|
||||
},
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
|||
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
|
||||
|
||||
class QuoteViewScreen extends StatelessWidget {
|
||||
static const String route = '/invoice/view';
|
||||
static const String route = '/quote/view';
|
||||
|
||||
const QuoteViewScreen({Key key}) : super(key: key);
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ class QuoteViewScreen extends StatelessWidget {
|
|||
|
||||
class QuoteViewVM {
|
||||
final CompanyEntity company;
|
||||
final QuoteEntity quote;
|
||||
final InvoiceEntity quote;
|
||||
final ClientEntity client;
|
||||
final bool isSaving;
|
||||
final bool isDirty;
|
||||
|
|
@ -80,13 +80,13 @@ class QuoteViewVM {
|
|||
quote: quote,
|
||||
client: client,
|
||||
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
|
||||
final Completer<QuoteEntity> completer =
|
||||
new Completer<QuoteEntity>();
|
||||
final Completer<InvoiceEntity> completer =
|
||||
new Completer<InvoiceEntity>();
|
||||
store.dispatch(EditQuote(
|
||||
quote: quote,
|
||||
context: context,
|
||||
completer: completer,
|
||||
invoiceItem: invoiceItem));
|
||||
quoteItem: invoiceItem));
|
||||
completer.future.then((invoice) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
|
|
@ -104,39 +104,39 @@ class QuoteViewVM {
|
|||
final localization = AppLocalization.of(context);
|
||||
switch (action) {
|
||||
case EntityAction.pdf:
|
||||
viewPdf(invoice, context);
|
||||
viewPdf(quote, context);
|
||||
break;
|
||||
case EntityAction.markSent:
|
||||
store.dispatch(MarkSentQuoteRequest(
|
||||
snackBarCompleter(context, localization.markedQuoteAsSent),
|
||||
invoice.id));
|
||||
quote.id));
|
||||
break;
|
||||
case EntityAction.emailQuote:
|
||||
case EntityAction.emailInvoice:
|
||||
store.dispatch(ShowEmailQuote(
|
||||
completer:
|
||||
snackBarCompleter(context, localization.emailedQuote),
|
||||
invoice: invoice,
|
||||
quote: quote,
|
||||
context: context));
|
||||
break;
|
||||
case EntityAction.archive:
|
||||
store.dispatch(ArchiveQuoteRequest(
|
||||
popCompleter(context, localization.archivedQuote),
|
||||
invoice.id));
|
||||
quote.id));
|
||||
break;
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteQuoteRequest(
|
||||
popCompleter(context, localization.deletedQuote),
|
||||
invoice.id));
|
||||
quote.id));
|
||||
break;
|
||||
case EntityAction.restore:
|
||||
store.dispatch(RestoreQuoteRequest(
|
||||
snackBarCompleter(context, localization.restoredQuote),
|
||||
invoice.id));
|
||||
quote.id));
|
||||
break;
|
||||
case EntityAction.clone:
|
||||
Navigator.of(context).pop();
|
||||
store.dispatch(
|
||||
EditQuote(context: context, invoice: invoice.clone));
|
||||
EditQuote(context: context, quote: quote.clone));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
|
@ -146,7 +146,7 @@ class QuoteViewVM {
|
|||
bool operator ==(dynamic other) =>
|
||||
client == other.client &&
|
||||
company == other.company &&
|
||||
invoice == other.invoice &&
|
||||
quote == other.quote &&
|
||||
isSaving == other.isSaving &&
|
||||
isDirty == other.isDirty;
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ class QuoteViewVM {
|
|||
int get hashCode =>
|
||||
client.hashCode ^
|
||||
company.hashCode ^
|
||||
invoice.hashCode ^
|
||||
quote.hashCode ^
|
||||
isSaving.hashCode ^
|
||||
isDirty.hashCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/form_card.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit_vm.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/save_icon_button.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/edit/stub_edit_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/buttons/refresh_icon_button.dart';
|
||||
|
||||
class StubEdit extends StatefulWidget {
|
||||
final StubEditVM viewModel;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import 'dart:async';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/ui/ui_actions.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/stub_screen.dart';
|
||||
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/stub_screen.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:flutter_redux_starter/redux/stub/stub_actions.dart';
|
||||
import 'package:flutter_redux_starter/data/models/stub_model.dart';
|
||||
import 'package:flutter_redux_starter/ui/stub/edit/stub_edit.dart';
|
||||
import 'package:flutter_redux_starter/redux/app/app_state.dart';
|
||||
import 'package:flutter_redux_starter/ui/app/icon_message.dart';
|
||||
import 'package:invoiceninja_flutter/redux/stub/stub_actions.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/stub_model.dart';
|
||||
import 'package:invoiceninja_flutter/ui/stub/edit/stub_edit.dart';
|
||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
|
||||
|
||||
class StubEditScreen extends StatelessWidget {
|
||||
static final String route = '/stub/edit';
|
||||
|
|
|
|||
Loading…
Reference in New Issue