Documents
This commit is contained in:
parent
a37a569d37
commit
da76bdee7e
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue