Documents

This commit is contained in:
Hillel Coren 2019-07-19 10:52:49 +03:00
parent a37a569d37
commit da76bdee7e
5 changed files with 507 additions and 332 deletions

View File

@ -1,21 +1,10 @@
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/edit_icon_button.dart';
import 'package:invoiceninja_flutter/ui/app/one_value_header.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/actions_menu_button.dart';
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
import 'package:invoiceninja_flutter/ui/app/invoice/invoice_item_view.dart';
import 'package:invoiceninja_flutter/ui/app/two_value_header.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_documents.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_overview.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_state_title.dart';
@ -31,226 +20,25 @@ class InvoiceView extends StatefulWidget {
_InvoiceViewState createState() => new _InvoiceViewState();
}
class _InvoiceViewState extends State<InvoiceView> {
class _InvoiceViewState extends State<InvoiceView>
with SingleTickerProviderStateMixin {
TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: 2);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final viewModel = widget.viewModel;
final invoice = widget.viewModel.invoice;
final client = viewModel.client;
final company = viewModel.company;
final state = StoreProvider.of<AppState>(context).state;
final payments = memoizedPaymentsByInvoice(
invoice.id, state.paymentState.map, state.paymentState.list);
List<Widget> _buildView(BuildContext context) {
final user = widget.viewModel.company.user;
final color = invoice.isPastDue
? Colors.red
: InvoiceStatusColors.colors[invoice.invoiceStatusId];
final widgets = <Widget>[
invoice.isQuote
? OneValueHeader(
backgroundColor: color,
label: localization.totalAmount,
value: formatNumber(invoice.amount, context,
clientId: invoice.clientId),
)
: TwoValueHeader(
backgroundColor: color,
label1: localization.totalAmount,
value1: formatNumber(invoice.amount, context,
clientId: invoice.clientId),
label2: localization.balanceDue,
value2: formatNumber(invoice.balance, context,
clientId: invoice.clientId),
),
];
final Map<String, String> fields = {
InvoiceFields.invoiceStatusId: invoice.isPastDue
? localization.pastDue
: (invoice.invoiceStatusId > 0
? localization
.lookup('invoice_status_${invoice.invoiceStatusId}')
: null),
InvoiceFields.invoiceDate: formatDate(invoice.invoiceDate, context),
InvoiceFields.dueDate: formatDate(invoice.dueDate, context),
InvoiceFields.partial: formatNumber(invoice.partial, context,
clientId: invoice.clientId, zeroIsNull: true),
InvoiceFields.partialDueDate:
formatDate(invoice.partialDueDate, context),
InvoiceFields.poNumber: invoice.poNumber,
InvoiceFields.discount: formatNumber(invoice.discount, context,
clientId: invoice.clientId,
zeroIsNull: true,
formatNumberType: invoice.isAmountDiscount
? FormatNumberType.money
: FormatNumberType.percent),
};
if (invoice.customTextValue1.isNotEmpty) {
final label1 = company.getCustomFieldLabel(CustomFieldType.invoice1);
fields[label1] = invoice.customTextValue1;
}
if (invoice.customTextValue2.isNotEmpty) {
final label2 = company.getCustomFieldLabel(CustomFieldType.invoice2);
fields[label2] = invoice.customTextValue2;
}
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: EntityStateTitle(entity: client),
leading: Icon(getEntityIcon(EntityType.client), size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onClientPressed(context),
onLongPress: () => viewModel.onClientPressed(context, true),
),
),
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
if (payments.isNotEmpty) {
if (payments.length == 1) {
final payment = payments.first;
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: EntityStateTitle(entity: payment),
subtitle: Text(
formatNumber(payment.amount, context, clientId: client.id) +
'' +
formatDate(payment.paymentDate, context)),
leading: Icon(FontAwesomeIcons.creditCard, size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onPaymentPressed(context, payment),
onLongPress: () =>
viewModel.onPaymentPressed(context, payment, true),
),
),
]);
} else {
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: Text(localization.payments),
leading: Icon(FontAwesomeIcons.creditCard, size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onPaymentsPressed(context),
),
),
]);
}
widgets.addAll([
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
}
widgets.addAll([
FieldGrid(fields,
fieldConverter: invoice.isQuote ? QuoteFields.convertField : null),
]);
if (invoice.privateNotes != null && invoice.privateNotes.isNotEmpty) {
widgets.addAll([
IconMessage(invoice.privateNotes),
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
}
invoice.invoiceItems.forEach((invoiceItem) {
widgets.addAll([
Builder(
builder: (BuildContext context) {
return InvoiceItemListTile(
invoice: invoice,
invoiceItem: invoiceItem,
onTap: () => user.canEditEntity(invoice)
? viewModel.onEditPressed(context, invoiceItem)
: null,
);
},
),
]);
});
widgets.addAll([
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
Widget surchargeRow(String label, double amount) {
return Container(
color: Theme.of(context).canvasColor,
child: Padding(
padding: const EdgeInsets.only(
left: 16.0, top: 12.0, right: 16.0, bottom: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(label),
SizedBox(
width: 80.0,
child: Align(
alignment: Alignment.centerRight,
child: Text(formatNumber(amount, context,
clientId: invoice.clientId))),
),
],
),
),
);
}
if (invoice.customValue1 != 0 && company.enableCustomInvoiceTaxes1) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge1),
invoice.customValue1));
}
if (invoice.customValue2 != 0 && company.enableCustomInvoiceTaxes2) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge2),
invoice.customValue2));
}
invoice
.calculateTaxes(company.enableInclusiveTaxes)
.forEach((taxName, taxAmount) {
widgets.add(surchargeRow(taxName, taxAmount));
});
if (invoice.customValue1 != 0 && !company.enableCustomInvoiceTaxes1) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge1),
invoice.customValue1));
}
if (invoice.customValue2 != 0 && !company.enableCustomInvoiceTaxes2) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge2),
invoice.customValue2));
}
return widgets;
}
return WillPopScope(
onWillPop: () async {
@ -260,16 +48,15 @@ class _InvoiceViewState extends State<InvoiceView> {
child: Scaffold(
appBar: _CustomAppBar(
viewModel: viewModel,
controller: _controller,
),
body: Builder(
builder: (BuildContext context) {
return RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: Container(
color: Theme.of(context).backgroundColor,
child: ListView(
children: _buildView(context),
),
child: CustomTabBarView(
viewModel: viewModel,
controller: _controller,
),
);
},
@ -279,46 +66,94 @@ class _InvoiceViewState extends State<InvoiceView> {
}
}
class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
const _CustomAppBar({
class CustomTabBarView extends StatefulWidget {
const CustomTabBarView({
@required this.viewModel,
@required this.controller,
});
final EntityViewVM viewModel;
final TabController controller;
@override
final Size preferredSize = const Size(double.infinity, kToolbarHeight);
_CustomTabBarViewState createState() => _CustomTabBarViewState();
}
class _CustomTabBarViewState extends State<CustomTabBarView> {
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
return TabBarView(
controller: widget.controller,
children: <Widget>[
RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: InvoiceOverview(viewModel: viewModel),
),
RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: InvoiceViewDocuments(
viewModel: viewModel, invoice: viewModel.invoice),
),
],
);
}
}
class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
const _CustomAppBar({
@required this.viewModel,
@required this.controller,
});
final EntityViewVM viewModel;
final TabController controller;
@override
final Size preferredSize = const Size(double.infinity, kToolbarHeight * 2);
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final invoice = viewModel.invoice;
final client = viewModel.client;
final user = viewModel.company.user;
final client = viewModel.client;
return AppBar(
title: EntityStateTitle(
entity: invoice,
title:
'${invoice.isQuote ? localization.quote : localization.invoice} ${invoice.invoiceNumber}',
'${invoice.isQuote ? localization.quote : localization.invoice} ${invoice.invoiceNumber}',
),
bottom: TabBar(
controller: controller,
tabs: [
Tab(
text: localization.overview,
),
Tab(
text: localization.documents,
),
],
),
actions: invoice.isNew
? []
: [
user.canEditEntity(invoice)
? EditIconButton(
isVisible: !invoice.isDeleted,
onPressed: () => viewModel.onEditPressed(context),
)
: Container(),
ActionMenuButton(
user: user,
entityActions: invoice.getActions(client: client, user: user),
isSaving: viewModel.isSaving,
entity: invoice,
onSelected: viewModel.onActionSelected,
)
],
user.canEditEntity(invoice)
? EditIconButton(
isVisible: !invoice.isDeleted,
onPressed: () => viewModel.onEditPressed(context),
)
: Container(),
ActionMenuButton(
user: user,
entityActions: invoice.getActions(client: client, user: user),
isSaving: viewModel.isSaving,
entity: invoice,
onSelected: viewModel.onActionSelected,
)
],
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/document/document_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
class InvoiceViewDocuments extends StatelessWidget {
const InvoiceViewDocuments(
{@required this.invoice, @required this.viewModel});
final EntityViewVM viewModel;
final InvoiceEntity invoice;
@override
Widget build(BuildContext context) {
final state = StoreProvider.of<AppState>(context).state;
final documentState = state.documentState;
final documents = memoizedDocumentsSelector(
documentState.map, documentState.list, invoice);
return DocumentGrid(
documents: documents,
onFileUpload: (path) => viewModel.onUploadDocument(context, path),
onDeleteDocument: (document) =>
viewModel.onDeleteDocument(context, document),
);
}
}

View File

@ -0,0 +1,245 @@
import 'package:flutter_redux/flutter_redux.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_state_title.dart';
import 'package:invoiceninja_flutter/ui/app/invoice/invoice_item_view.dart';
import 'package:invoiceninja_flutter/ui/app/one_value_header.dart';
import 'package:invoiceninja_flutter/ui/app/two_value_header.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class InvoiceOverview extends StatelessWidget {
const InvoiceOverview({
Key key,
@required this.viewModel,
}) : super(key: key);
final InvoiceViewVM viewModel;
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final invoice = viewModel.invoice;
final client = viewModel.client;
final company = viewModel.company;
final state = StoreProvider.of<AppState>(context).state;
final payments = memoizedPaymentsByInvoice(
invoice.id, state.paymentState.map, state.paymentState.list);
final user = viewModel.company.user;
final color = invoice.isPastDue
? Colors.red
: InvoiceStatusColors.colors[invoice.invoiceStatusId];
final widgets = <Widget>[
invoice.isQuote
? OneValueHeader(
backgroundColor: color,
label: localization.totalAmount,
value: formatNumber(invoice.amount, context,
clientId: invoice.clientId),
)
: TwoValueHeader(
backgroundColor: color,
label1: localization.totalAmount,
value1: formatNumber(invoice.amount, context,
clientId: invoice.clientId),
label2: localization.balanceDue,
value2: formatNumber(invoice.balance, context,
clientId: invoice.clientId),
),
];
final Map<String, String> fields = {
InvoiceFields.invoiceStatusId: invoice.isPastDue
? localization.pastDue
: (invoice.invoiceStatusId > 0
? localization.lookup('invoice_status_${invoice.invoiceStatusId}')
: null),
InvoiceFields.invoiceDate: formatDate(invoice.invoiceDate, context),
InvoiceFields.dueDate: formatDate(invoice.dueDate, context),
InvoiceFields.partial: formatNumber(invoice.partial, context,
clientId: invoice.clientId, zeroIsNull: true),
InvoiceFields.partialDueDate: formatDate(invoice.partialDueDate, context),
InvoiceFields.poNumber: invoice.poNumber,
InvoiceFields.discount: formatNumber(invoice.discount, context,
clientId: invoice.clientId,
zeroIsNull: true,
formatNumberType: invoice.isAmountDiscount
? FormatNumberType.money
: FormatNumberType.percent),
};
if (invoice.customTextValue1.isNotEmpty) {
final label1 = company.getCustomFieldLabel(CustomFieldType.invoice1);
fields[label1] = invoice.customTextValue1;
}
if (invoice.customTextValue2.isNotEmpty) {
final label2 = company.getCustomFieldLabel(CustomFieldType.invoice2);
fields[label2] = invoice.customTextValue2;
}
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: EntityStateTitle(entity: client),
leading: Icon(getEntityIcon(EntityType.client), size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onClientPressed(context),
onLongPress: () => viewModel.onClientPressed(context, true),
),
),
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
if (payments.isNotEmpty) {
if (payments.length == 1) {
final payment = payments.first;
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: EntityStateTitle(entity: payment),
subtitle: Text(
formatNumber(payment.amount, context, clientId: client.id) +
'' +
formatDate(payment.paymentDate, context)),
leading: Icon(FontAwesomeIcons.creditCard, size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onPaymentPressed(context, payment),
onLongPress: () =>
viewModel.onPaymentPressed(context, payment, true),
),
),
]);
} else {
widgets.addAll([
Material(
color: Theme.of(context).canvasColor,
child: ListTile(
title: Text(localization.payments),
leading: Icon(FontAwesomeIcons.creditCard, size: 18.0),
trailing: Icon(Icons.navigate_next),
onTap: () => viewModel.onPaymentsPressed(context),
),
),
]);
}
widgets.addAll([
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
}
widgets.addAll([
FieldGrid(fields,
fieldConverter: invoice.isQuote ? QuoteFields.convertField : null),
]);
if (invoice.privateNotes != null && invoice.privateNotes.isNotEmpty) {
widgets.addAll([
IconMessage(invoice.privateNotes),
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
}
invoice.invoiceItems.forEach((invoiceItem) {
widgets.addAll([
Builder(
builder: (BuildContext context) {
return InvoiceItemListTile(
invoice: invoice,
invoiceItem: invoiceItem,
onTap: () => user.canEditEntity(invoice)
? viewModel.onEditPressed(context, invoiceItem)
: null,
);
},
),
]);
});
widgets.addAll([
Container(
color: Theme.of(context).backgroundColor,
height: 12.0,
),
]);
Widget surchargeRow(String label, double amount) {
return Container(
color: Theme.of(context).canvasColor,
child: Padding(
padding: const EdgeInsets.only(
left: 16.0, top: 12.0, right: 16.0, bottom: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(label),
SizedBox(
width: 80.0,
child: Align(
alignment: Alignment.centerRight,
child: Text(formatNumber(amount, context,
clientId: invoice.clientId))),
),
],
),
),
);
}
if (invoice.customValue1 != 0 && company.enableCustomInvoiceTaxes1) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge1),
invoice.customValue1));
}
if (invoice.customValue2 != 0 && company.enableCustomInvoiceTaxes2) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge2),
invoice.customValue2));
}
invoice
.calculateTaxes(company.enableInclusiveTaxes)
.forEach((taxName, taxAmount) {
widgets.add(surchargeRow(taxName, taxAmount));
});
if (invoice.customValue1 != 0 && !company.enableCustomInvoiceTaxes1) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge1),
invoice.customValue1));
}
if (invoice.customValue2 != 0 && !company.enableCustomInvoiceTaxes2) {
widgets.add(surchargeRow(
company.getCustomFieldLabel(CustomFieldType.surcharge2),
invoice.customValue2));
}
return ListView(
children: widgets,
);
}
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/document/document_actions.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:redux/redux.dart';
@ -45,6 +46,8 @@ class EntityViewVM {
@required this.isSaving,
@required this.isDirty,
@required this.onActionSelected,
@required this.onUploadDocument,
@required this.onDeleteDocument,
@required this.onEditPressed,
@required this.onBackPressed,
@required this.onClientPressed,
@ -65,14 +68,16 @@ class EntityViewVM {
final Function(BuildContext, PaymentEntity, [bool]) onPaymentPressed;
final Function(BuildContext) onRefreshed;
final Function onBackPressed;
final Function(BuildContext, String) onUploadDocument;
final Function(BuildContext, DocumentEntity) onDeleteDocument;
@override
bool operator ==(dynamic other) =>
client == other.client &&
company == other.company &&
invoice == other.invoice &&
isSaving == other.isSaving &&
isDirty == other.isDirty;
company == other.company &&
invoice == other.invoice &&
isSaving == other.isSaving &&
isDirty == other.isDirty;
@override
int get hashCode =>
@ -97,20 +102,24 @@ class InvoiceViewVM extends EntityViewVM {
Function(BuildContext) onPaymentsPressed,
Function(BuildContext) onRefreshed,
Function onBackPressed,
Function(BuildContext, String) onUploadDocument,
Function(BuildContext, DocumentEntity) onDeleteDocument,
}) : super(
company: company,
invoice: invoice,
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentPressed: onPaymentPressed,
onPaymentsPressed: onPaymentsPressed,
onRefreshed: onRefreshed,
onBackPressed: onBackPressed,
);
company: company,
invoice: invoice,
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentPressed: onPaymentPressed,
onPaymentsPressed: onPaymentsPressed,
onRefreshed: onRefreshed,
onBackPressed: onBackPressed,
onUploadDocument: onUploadDocument,
onDeleteDocument: onDeleteDocument,
);
factory InvoiceViewVM.fromStore(Store<AppState> store) {
final state = store.state;
@ -120,73 +129,104 @@ class InvoiceViewVM extends EntityViewVM {
Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete);
context, AppLocalization
.of(context)
.refreshComplete);
store.dispatch(LoadInvoice(completer: completer, invoiceId: invoice.id));
return completer.future;
}
return InvoiceViewVM(
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: invoice.isNew,
invoice: invoice,
client: client,
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(EditInvoice(
invoice: invoice,
context: context,
completer: completer,
invoiceItem: invoiceItem));
completer.future.then((invoice) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).updatedInvoice,
)));
});
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(InvoiceScreen.route)) {
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route));
}
},
onClientPressed: (BuildContext context, [bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: state.selectedCompany.user,
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: invoice.isNew,
invoice: invoice,
client: client,
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(EditInvoice(
invoice: invoice,
context: context,
entity: client,
onEntityAction: (BuildContext context, BaseEntity client,
EntityAction action) =>
handleClientAction(context, client, action));
} else {
store.dispatch(ViewClient(clientId: client.id, context: context));
}
},
onPaymentPressed: (BuildContext context, payment,
[bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: user,
context: context,
client: client,
entity: payment,
onEntityAction: (BuildContext context, BaseEntity payment,
EntityAction action) =>
handlePaymentAction(context, payment, action));
} else {
store.dispatch(ViewPayment(paymentId: payment.id, context: context));
}
},
onPaymentsPressed: (BuildContext context) {
store.dispatch(FilterPaymentsByEntity(
entityId: invoice.id, entityType: EntityType.invoice));
store.dispatch(ViewPaymentList(context));
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleInvoiceAction(context, invoice, action),
completer: completer,
invoiceItem: invoiceItem));
completer.future.then((invoice) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization
.of(context)
.updatedInvoice,
)));
});
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(InvoiceScreen.route)) {
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route));
}
},
onClientPressed: (BuildContext context, [bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: state.selectedCompany.user,
context: context,
entity: client,
onEntityAction: (BuildContext context, BaseEntity client,
EntityAction action) =>
handleClientAction(context, client, action));
} else {
store.dispatch(ViewClient(clientId: client.id, context: context));
}
},
onPaymentPressed: (BuildContext context, payment,
[bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: user,
context: context,
client: client,
entity: payment,
onEntityAction: (BuildContext context, BaseEntity payment,
EntityAction action) =>
handlePaymentAction(context, payment, action));
} else {
store.dispatch(
ViewPayment(paymentId: payment.id, context: context));
}
},
onPaymentsPressed: (BuildContext context) {
store.dispatch(FilterPaymentsByEntity(
entityId: invoice.id, entityType: EntityType.invoice));
store.dispatch(ViewPaymentList(context));
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleInvoiceAction(context, invoice, action),
onUploadDocument: (BuildContext context, String path) {
final Completer<DocumentEntity> completer =
Completer<DocumentEntity>();
final document = DocumentEntity().rebuild((b) =>
b
..invoiceId = invoice.id
..path = path);
store.dispatch(
SaveDocumentRequest(document: document, completer: completer));
completer.future.then((client) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization
.of(context)
.uploadedDocument,
)));
});
},
onDeleteDocument: (BuildContext context, DocumentEntity document) {
store.dispatch(DeleteDocumentRequest(
snackBarCompleter(
context, AppLocalization
.of(context)
.deletedDocument),
document.id));
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/document/document_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view.dart';
@ -51,6 +52,8 @@ class QuoteViewVM extends EntityViewVM {
Function(BuildContext, PaymentEntity) onPaymentPressed,
Function(BuildContext) onRefreshed,
Function onBackPressed,
Function(BuildContext, String) onUploadDocument,
Function(BuildContext, DocumentEntity) onDeleteDocument,
}) : super(
company: company,
invoice: invoice,
@ -64,6 +67,8 @@ class QuoteViewVM extends EntityViewVM {
onPaymentPressed: onPaymentPressed,
onRefreshed: onRefreshed,
onBackPressed: onBackPressed,
onUploadDocument: onUploadDocument,
onDeleteDocument: onDeleteDocument,
);
factory QuoteViewVM.fromStore(Store<AppState> store) {
@ -122,6 +127,26 @@ class QuoteViewVM extends EntityViewVM {
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleQuoteAction(context, quote, action),
onUploadDocument: (BuildContext context, String path) {
final Completer<DocumentEntity> completer = Completer<DocumentEntity>();
final document = DocumentEntity().rebuild((b) => b
..invoiceId = quote.id
..path = path);
store.dispatch(
SaveDocumentRequest(document: document, completer: completer));
completer.future.then((client) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).uploadedDocument,
)));
});
},
onDeleteDocument: (BuildContext context, DocumentEntity document) {
store.dispatch(DeleteDocumentRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedDocument),
document.id));
},
);
}
}