diff --git a/lib/ui/client/edit/client_edit.dart b/lib/ui/client/edit/client_edit.dart index 1f3f81376..e77bbfff4 100644 --- a/lib/ui/client/edit/client_edit.dart +++ b/lib/ui/client/edit/client_edit.dart @@ -4,11 +4,10 @@ import 'package:invoiceninja/data/models/models.dart'; import 'package:invoiceninja/ui/client/edit/client_edit_details.dart'; import 'package:invoiceninja/ui/client/edit/client_edit_vm.dart'; import 'package:invoiceninja/utils/localization.dart'; - -import '../../app/save_icon_button.dart'; -import 'client_edit_billing_address.dart'; -import 'client_edit_contacts.dart'; -import 'client_edit_shipping_address.dart'; +import 'package:invoiceninja/ui/app/save_icon_button.dart'; +import 'package:invoiceninja/ui/client/edit/client_edit_billing_address.dart'; +import 'package:invoiceninja/ui/client/edit/client_edit_contacts.dart'; +import 'package:invoiceninja/ui/client/edit/client_edit_shipping_address.dart'; class ClientEdit extends StatefulWidget { final ClientEditVM viewModel; diff --git a/lib/ui/client/edit/client_edit_details.dart b/lib/ui/client/edit/client_edit_details.dart index c6340ab43..c71ee3d65 100644 --- a/lib/ui/client/edit/client_edit_details.dart +++ b/lib/ui/client/edit/client_edit_details.dart @@ -4,8 +4,7 @@ import 'package:invoiceninja/data/models/models.dart'; import 'package:invoiceninja/ui/client/edit/client_edit.dart'; import 'package:invoiceninja/ui/client/edit/client_edit_vm.dart'; import 'package:invoiceninja/utils/localization.dart'; - -import '../../app/form_card.dart'; +import 'package:invoiceninja/ui/app/form_card.dart'; class ClientEditDetails extends StatefulWidget { ClientEditDetails({ diff --git a/lib/ui/invoice/edit/invoice_edit.dart b/lib/ui/invoice/edit/invoice_edit.dart index 8502296cf..d0a811046 100644 --- a/lib/ui/invoice/edit/invoice_edit.dart +++ b/lib/ui/invoice/edit/invoice_edit.dart @@ -3,10 +3,11 @@ import 'package:flutter/material.dart'; import 'package:invoiceninja/ui/app/actions_menu_button.dart'; import 'package:invoiceninja/ui/app/form_card.dart'; import 'package:invoiceninja/ui/app/progress_button.dart'; +import 'package:invoiceninja/ui/invoice/edit/invoice_edit_details.dart'; +import 'package:invoiceninja/ui/invoice/edit/invoice_edit_items.dart'; import 'package:invoiceninja/ui/invoice/edit/invoice_edit_vm.dart'; import 'package:invoiceninja/utils/localization.dart'; - -import '../../app/save_icon_button.dart'; +import 'package:invoiceninja/ui/app/save_icon_button.dart'; class InvoiceEdit extends StatefulWidget { final InvoiceEditVM viewModel; @@ -20,100 +21,89 @@ class InvoiceEdit extends StatefulWidget { _InvoiceEditState createState() => _InvoiceEditState(); } -class _InvoiceEditState extends State { +class _InvoiceEditState extends State + with SingleTickerProviderStateMixin { + TabController _controller; static final GlobalKey _formKey = GlobalKey(); + static final GlobalKey _detailsKey = + GlobalKey(); + static final GlobalKey _itemsKey = + GlobalKey(); - String _invoiceKey; - String _notes; - double _cost; + @override + void initState() { + super.initState(); + _controller = new TabController(vsync: this, length: 2); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - var viewModel = widget.viewModel; + var localization = AppLocalization.of(context); + var invoice = widget.viewModel.invoice; + + List editors = [ + InvoiceEditDetails( + invoice: invoice, + key: _detailsKey, + ), + InvoiceEditItems( + invoice: invoice, + key: _itemsKey, + ), + ]; return Scaffold( appBar: AppBar( - title: Text(viewModel.invoice.isNew() - ? AppLocalization.of(context).newInvoice - : viewModel.invoice.invoiceNumber), + title: Text(invoice.isNew() + ? localization.newInvoice + : invoice.invoiceNumber), actions: [ - Builder(builder: (BuildContext context) { - return SaveIconButton( - isLoading: viewModel.isLoading, - onPressed: () { - if (!_formKey.currentState.validate()) { - return; - } + SaveIconButton( + isLoading: widget.viewModel.isLoading, + onPressed: () { + if (! _formKey.currentState.validate()) { + return; + } - _formKey.currentState.save(); - /* - viewModel.onSaveClicked( - context, - viewModel.invoice.rebuild((b) => b - ..invoiceNumber= _invoiceKey - ..notes = _notes - ..cost = _cost)); - */ - }, - ); - }), - viewModel.invoice.isNew() - ? Container() - : ActionMenuButton( - entity: viewModel.invoice, - onSelected: viewModel.onActionSelected, - ) + _formKey.currentState.save(); + + var detailsState = _detailsKey.currentState; + var itemsState = _itemsKey.currentState; + + /* + InvoiceEntity invoice = widget.viewModel.invoice.rebuild((b) => b + ..items.replace( + itemState?.getItems() ?? widget.viewModel.invoice.items)); + */ + + widget.viewModel.onSaveClicked(context, invoice); + }, + ) ], + bottom: TabBar( + controller: _controller, + //isScrollable: true, + tabs: [ + Tab( + text: localization.details, + ), + Tab( + text: localization.items, + ), + ], + ), ), body: Form( key: _formKey, - child: ListView( - children: [ - FormCard( - children: [ - /* - TextFormField( - autocorrect: false, - onSaved: (value) { - _invoiceKey = value; - }, - initialValue: viewModel.invoice.invoiceKey, - decoration: InputDecoration( - //border: InputBorder.none, - labelText: AppLocalization.of(context).invoice, - ), - validator: (val) => val.isEmpty || val.trim().length == 0 - ? AppLocalization.of(context).pleaseEnterAInvoiceKey - : null, - ), - TextFormField( - initialValue: viewModel.invoice.notes, - onSaved: (value) { - _notes = value; - }, - maxLines: 4, - decoration: InputDecoration( - labelText: AppLocalization.of(context).notes, - ), - ), - TextFormField( - initialValue: viewModel.invoice.cost == null || - viewModel.invoice.cost == 0.0 - ? null - : viewModel.invoice.cost.toStringAsFixed(2), - onSaved: (value) { - _cost = double.tryParse(value) ?? 0.0; - }, - keyboardType: TextInputType.number, - decoration: InputDecoration( - //border: InputBorder.none, - labelText: AppLocalization.of(context).cost, - ), - ), - */ - ], - ), - ], + child: TabBarView( + controller: _controller, + children: editors, ), ), ); diff --git a/lib/ui/invoice/edit/invoice_edit_details.dart b/lib/ui/invoice/edit/invoice_edit_details.dart new file mode 100644 index 000000000..00f187921 --- /dev/null +++ b/lib/ui/invoice/edit/invoice_edit_details.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:invoiceninja/data/models/models.dart'; +import 'package:invoiceninja/ui/app/form_card.dart'; +import 'package:invoiceninja/utils/localization.dart'; + +class InvoiceEditDetails extends StatefulWidget { + InvoiceEditDetails({ + Key key, + @required this.invoice, + }) : super(key: key); + + final InvoiceEntity invoice; + + @override + InvoiceEditDetailsState createState() => new InvoiceEditDetailsState(); +} + +class InvoiceEditDetailsState extends State + with AutomaticKeepAliveClientMixin { + int clientId; + String invoiceDate; + String dueDate; + double partial; + String partialDate; + String invoiceNumber; + String poNumber; + double discount; + bool isAmountDiscount; + + @override + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + var localization = AppLocalization.of(context); + var invoice = widget.invoice; + + return ListView( + shrinkWrap: true, + children: [ + invoice.isNew() + ? Container() + : TextFormField( + autocorrect: false, + onSaved: (value) => invoiceNumber = value.trim(), + initialValue: invoice.invoiceNumber, + decoration: InputDecoration( + labelText: localization.invoiceNumber, + ), + ), + TextFormField( + autocorrect: false, + onSaved: (value) => poNumber = value.trim(), + initialValue: invoice.poNumber, + decoration: InputDecoration( + labelText: localization.poNumber, + ), + ), + TextFormField( + autocorrect: false, + onSaved: (value) => discount = double.tryParse(value) ?? 0.0, + initialValue: invoice.discount.toStringAsFixed(2), + decoration: InputDecoration( + labelText: localization.discount, + ), + ), + TextFormField( + autocorrect: false, + onSaved: (value) => partial = double.tryParse(value) ?? 0.0, + initialValue: invoice.partial.toStringAsFixed(2), + decoration: InputDecoration( + labelText: localization.partial, + ), + ), + ], + ); + } +} diff --git a/lib/ui/invoice/edit/invoice_edit_items.dart b/lib/ui/invoice/edit/invoice_edit_items.dart new file mode 100644 index 000000000..8b489b5f1 --- /dev/null +++ b/lib/ui/invoice/edit/invoice_edit_items.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:invoiceninja/data/models/models.dart'; +import 'package:invoiceninja/utils/localization.dart'; +import 'package:invoiceninja/ui/app/form_card.dart'; + +class InvoiceEditItems extends StatefulWidget { + InvoiceEditItems({ + Key key, + @required this.invoice, + }) : super(key: key); + + final InvoiceEntity invoice; + + @override + InvoiceEditItemsState createState() => new InvoiceEditItemsState(); +} + +class InvoiceEditItemsState extends State + with AutomaticKeepAliveClientMixin { + + List invoiceItems; + List> invoiceItemKeys; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + var invoice = widget.invoice; + invoiceItems = invoice.invoiceItems.toList(); + invoiceItemKeys = invoice.invoiceItems + .map((invoiceItem) => GlobalKey()) + .toList(); + } + + List getItems() { + List invoiceItems = []; + invoiceItemKeys.forEach((invoiceItemKey) { + if (invoiceItemKey.currentState != null) { + invoiceItems.add(invoiceItemKey.currentState.getItem()); + } + }); + return invoiceItems; + } + + _onAddPressed() { + setState(() { + invoiceItems.add(InvoiceItemEntity()); + invoiceItemKeys.add(GlobalKey()); + }); + } + + _onRemovePressed(GlobalKey key) { + setState(() { + var index = invoiceItemKeys.indexOf(key); + invoiceItemKeys.removeAt(index); + invoiceItems.removeAt(index); + }); + } + + @override + Widget build(BuildContext context) { + var localization = AppLocalization.of(context); + List invoiceItems = []; + + for (var i = 0; i < invoiceItems.length; i++) { + var invoiceItem = invoiceItems[i]; + var invoiceItemKey = invoiceItemKeys[i]; + invoiceItems.add(ItemEditDetails( + //invoiceItem: invoiceItem, + key: invoiceItemKey, + onRemovePressed: (key) => _onRemovePressed(key), + isRemoveVisible: invoiceItems.length > 1, + )); + } + + invoiceItems.add(Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 4.0), + child: RaisedButton( + elevation: 4.0, + color: Theme.of(context).primaryColor, + textColor: Theme.of(context).secondaryHeaderColor, + //child: Text(localization.addItem.toUpperCase()), + onPressed: _onAddPressed, + ), + )); + + return ListView( + children: invoiceItems, + ); + } +} + +class ItemEditDetails extends StatefulWidget { + ItemEditDetails({ + Key key, + @required this.invoiceItem, + @required this.onRemovePressed, + @required this.isRemoveVisible, + }) : super(key: key); + + final InvoiceItemEntity invoiceItem; + final Function(GlobalKey) onRemovePressed; + final bool isRemoveVisible; + + @override + ItemEditDetailsState createState() => ItemEditDetailsState(); +} + +class ItemEditDetailsState extends State { + String _firstName; + String _lastName; + String _email; + String _phone; + + InvoiceItemEntity getItem() { + return widget.invoiceItem.rebuild((b) => b + //..phone = _phone + ); + } + + @override + Widget build(BuildContext context) { + var localization = AppLocalization.of(context); + + _confirmDelete() { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + semanticLabel: localization.areYouSure, + title: Text(localization.areYouSure), + actions: [ + new FlatButton( + child: Text(localization.cancel.toUpperCase()), + onPressed: () { + Navigator.pop(context); + }), + new FlatButton( + child: Text(localization.ok.toUpperCase()), + onPressed: () { + widget.onRemovePressed(widget.key); + Navigator.pop(context); + }) + ], + ), + ); + } + + return FormCard( + children: [ + /* + TextFormField( + autocorrect: false, + initialValue: widget.invoiceItem.firstName, + onSaved: (value) => _firstName = value.trim(), + decoration: InputDecoration( + labelText: localization.firstName, + ), + ), + */ + widget.isRemoveVisible + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(top: 14.0), + child: FlatButton( + child: Text( + localization.remove, + style: TextStyle( + color: Colors.grey[600], + ), + ), + onPressed: _confirmDelete, + ), + ) + ], + ) + : Container(), + ], + ); + } +} diff --git a/lib/ui/product/edit/product_edit.dart b/lib/ui/product/edit/product_edit.dart index ed9a62b6b..18e238a36 100644 --- a/lib/ui/product/edit/product_edit.dart +++ b/lib/ui/product/edit/product_edit.dart @@ -5,8 +5,7 @@ import 'package:invoiceninja/ui/app/form_card.dart'; import 'package:invoiceninja/ui/app/progress_button.dart'; import 'package:invoiceninja/ui/product/edit/product_edit_vm.dart'; import 'package:invoiceninja/utils/localization.dart'; - -import '../../app/save_icon_button.dart'; +import 'package:invoiceninja/ui/app/save_icon_button.dart'; class ProductEdit extends StatefulWidget { final ProductEditVM viewModel; diff --git a/lib/utils/localization.dart b/lib/utils/localization.dart index 33497e157..04264e9f9 100644 --- a/lib/utils/localization.dart +++ b/lib/utils/localization.dart @@ -119,6 +119,8 @@ class AppLocalization { 'quote_number': 'Quote Number', 'quote_date': 'Quote Date', 'valid_until': 'Valid Until', + 'items': 'Items', + 'partial': 'Partial/Deposit', 'payment': 'Payment', 'payments': 'Payments', @@ -230,6 +232,9 @@ class AppLocalization { String get quoteNumber => _localizedValues[locale.languageCode]['quote_number']; String get quoteDate => _localizedValues[locale.languageCode]['quote_date']; String get validUntil => _localizedValues[locale.languageCode]['valid_until']; + String get items => _localizedValues[locale.languageCode]['items']; + String get partial => _localizedValues[locale.languageCode]['partial']; + String get payment => _localizedValues[locale.languageCode]['payment']; String get payments => _localizedValues[locale.languageCode]['payments'];