From da76bdee7e54bd38e17836723d6d1f7036f5b1dc Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Fri, 19 Jul 2019 10:52:49 +0300 Subject: [PATCH] Documents --- lib/ui/invoice/view/invoice_view.dart | 343 +++++------------- .../invoice/view/invoice_view_documents.dart | 30 ++ .../invoice/view/invoice_view_overview.dart | 245 +++++++++++++ lib/ui/invoice/view/invoice_view_vm.dart | 196 ++++++---- lib/ui/quote/view/quote_view_vm.dart | 25 ++ 5 files changed, 507 insertions(+), 332 deletions(-) create mode 100644 lib/ui/invoice/view/invoice_view_documents.dart create mode 100644 lib/ui/invoice/view/invoice_view_overview.dart diff --git a/lib/ui/invoice/view/invoice_view.dart b/lib/ui/invoice/view/invoice_view.dart index c5721a39b..d59abcd0e 100644 --- a/lib/ui/invoice/view/invoice_view.dart +++ b/lib/ui/invoice/view/invoice_view.dart @@ -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 { +class _InvoiceViewState extends State + 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(context).state; - final payments = memoizedPaymentsByInvoice( - invoice.id, state.paymentState.map, state.paymentState.list); - - List _buildView(BuildContext context) { - final user = widget.viewModel.company.user; - final color = invoice.isPastDue - ? Colors.red - : InvoiceStatusColors.colors[invoice.invoiceStatusId]; - final widgets = [ - 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 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: [ - 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 { 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 { } } -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 { + @override + Widget build(BuildContext context) { + final viewModel = widget.viewModel; + + return TabBarView( + controller: widget.controller, + children: [ + 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, + ) + ], ); } } diff --git a/lib/ui/invoice/view/invoice_view_documents.dart b/lib/ui/invoice/view/invoice_view_documents.dart new file mode 100644 index 000000000..ce83cd5b8 --- /dev/null +++ b/lib/ui/invoice/view/invoice_view_documents.dart @@ -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(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), + ); + } +} diff --git a/lib/ui/invoice/view/invoice_view_overview.dart b/lib/ui/invoice/view/invoice_view_overview.dart new file mode 100644 index 000000000..234227dda --- /dev/null +++ b/lib/ui/invoice/view/invoice_view_overview.dart @@ -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(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 = [ + 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 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: [ + 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, + ); + } +} diff --git a/lib/ui/invoice/view/invoice_view_vm.dart b/lib/ui/invoice/view/invoice_view_vm.dart index 545623a39..d87c93e92 100644 --- a/lib/ui/invoice/view/invoice_view_vm.dart +++ b/lib/ui/invoice/view/invoice_view_vm.dart @@ -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 store) { final state = store.state; @@ -120,73 +129,104 @@ class InvoiceViewVM extends EntityViewVM { Future _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 completer = - new Completer(); - 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 completer = + new Completer(); + 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 completer = + Completer(); + 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)); + }, ); } } diff --git a/lib/ui/quote/view/quote_view_vm.dart b/lib/ui/quote/view/quote_view_vm.dart index b3908f807..c033f15c5 100644 --- a/lib/ui/quote/view/quote_view_vm.dart +++ b/lib/ui/quote/view/quote_view_vm.dart @@ -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 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 completer = Completer(); + 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)); + }, ); } }