From 1e111c1eef31455c161f57df3c39fde551331e01 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 29 Oct 2019 12:06:00 +0200 Subject: [PATCH] Settings --- lib/data/models/invoice_model.dart | 6 +- lib/data/models/invoice_model.g.dart | 33 +++++---- lib/data/models/mixins/invoice_mixin.dart | 6 +- lib/data/web_client.dart | 5 +- lib/redux/expense/expense_selectors.dart | 2 +- lib/redux/product/product_selectors.dart | 2 +- lib/redux/task/task_selectors.dart | 2 +- lib/ui/app/invoice/invoice_item_view.dart | 2 +- lib/ui/invoice/edit/invoice_edit_items.dart | 4 +- .../invoice/edit/invoice_item_selector.dart | 2 +- lib/ui/settings/invoice_design.dart | 73 +++++++++++++++++-- lib/ui/settings/invoice_design_vm.dart | 42 ++++++++++- lib/ui/settings/tax_rates.dart | 4 +- lib/utils/i18n.dart | 6 ++ 14 files changed, 146 insertions(+), 43 deletions(-) diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index 9cbf0893d..3444f7d15 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -497,7 +497,7 @@ abstract class InvoiceItemEntity extends Object productKey: '', notes: '', cost: 0.0, - qty: 0.0, + quantity: 0.0, taxName1: '', taxRate1: 0.0, taxName2: '', @@ -526,7 +526,7 @@ abstract class InvoiceItemEntity extends Object double get cost; - double get qty; + double get quantity; @BuiltValueField(wireName: 'tax_name1') String get taxName1; @@ -559,7 +559,7 @@ abstract class InvoiceItemEntity extends Object @BuiltValueField(wireName: 'expense_public_id') String get expenseId; - double get total => round(qty * cost, 2); + double get total => round(quantity * cost, 2); bool get isTask => taskId != null && taskId.isNotEmpty; diff --git a/lib/data/models/invoice_model.g.dart b/lib/data/models/invoice_model.g.dart index 797868b25..1a4bbd83c 100644 --- a/lib/data/models/invoice_model.g.dart +++ b/lib/data/models/invoice_model.g.dart @@ -512,8 +512,9 @@ class _$InvoiceItemEntitySerializer specifiedType: const FullType(String)), 'cost', serializers.serialize(object.cost, specifiedType: const FullType(double)), - 'qty', - serializers.serialize(object.qty, specifiedType: const FullType(double)), + 'quantity', + serializers.serialize(object.quantity, + specifiedType: const FullType(double)), 'tax_name1', serializers.serialize(object.taxName1, specifiedType: const FullType(String)), @@ -620,8 +621,8 @@ class _$InvoiceItemEntitySerializer result.cost = serializers.deserialize(value, specifiedType: const FullType(double)) as double; break; - case 'qty': - result.qty = serializers.deserialize(value, + case 'quantity': + result.quantity = serializers.deserialize(value, specifiedType: const FullType(double)) as double; break; case 'tax_name1': @@ -1761,7 +1762,7 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { @override final double cost; @override - final double qty; + final double quantity; @override final String taxName1; @override @@ -1805,7 +1806,7 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { {this.productKey, this.notes, this.cost, - this.qty, + this.quantity, this.taxName1, this.taxRate1, this.taxName2, @@ -1833,8 +1834,8 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { if (cost == null) { throw new BuiltValueNullFieldError('InvoiceItemEntity', 'cost'); } - if (qty == null) { - throw new BuiltValueNullFieldError('InvoiceItemEntity', 'qty'); + if (quantity == null) { + throw new BuiltValueNullFieldError('InvoiceItemEntity', 'quantity'); } if (taxName1 == null) { throw new BuiltValueNullFieldError('InvoiceItemEntity', 'taxName1'); @@ -1878,7 +1879,7 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { productKey == other.productKey && notes == other.notes && cost == other.cost && - qty == other.qty && + quantity == other.quantity && taxName1 == other.taxName1 && taxRate1 == other.taxRate1 && taxName2 == other.taxName2 && @@ -1920,7 +1921,7 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { $jc( $jc($jc($jc(0, productKey.hashCode), notes.hashCode), cost.hashCode), - qty.hashCode), + quantity.hashCode), taxName1.hashCode), taxRate1.hashCode), taxName2.hashCode), @@ -1946,7 +1947,7 @@ class _$InvoiceItemEntity extends InvoiceItemEntity { ..add('productKey', productKey) ..add('notes', notes) ..add('cost', cost) - ..add('qty', qty) + ..add('quantity', quantity) ..add('taxName1', taxName1) ..add('taxRate1', taxRate1) ..add('taxName2', taxName2) @@ -1984,9 +1985,9 @@ class InvoiceItemEntityBuilder double get cost => _$this._cost; set cost(double cost) => _$this._cost = cost; - double _qty; - double get qty => _$this._qty; - set qty(double qty) => _$this._qty = qty; + double _quantity; + double get quantity => _$this._quantity; + set quantity(double quantity) => _$this._quantity = quantity; String _taxName1; String get taxName1 => _$this._taxName1; @@ -2064,7 +2065,7 @@ class InvoiceItemEntityBuilder _productKey = _$v.productKey; _notes = _$v.notes; _cost = _$v.cost; - _qty = _$v.qty; + _quantity = _$v.quantity; _taxName1 = _$v.taxName1; _taxRate1 = _$v.taxRate1; _taxName2 = _$v.taxName2; @@ -2107,7 +2108,7 @@ class InvoiceItemEntityBuilder productKey: productKey, notes: notes, cost: cost, - qty: qty, + quantity: quantity, taxName1: taxName1, taxRate1: taxRate1, taxName2: taxName2, diff --git a/lib/data/models/mixins/invoice_mixin.dart b/lib/data/models/mixins/invoice_mixin.dart index de5f9d34d..88a5e81d8 100644 --- a/lib/data/models/mixins/invoice_mixin.dart +++ b/lib/data/models/mixins/invoice_mixin.dart @@ -36,7 +36,7 @@ abstract class CalculateInvoiceTotal { final map = {}; invoiceItems.forEach((item) { - final double qty = round(item.qty, 4); + final double qty = round(item.quantity, 4); final double cost = round(item.cost, 4); final double itemDiscount = round(item.discount, 2); final double taxRate1 = round(item.taxRate1, 3); @@ -108,7 +108,7 @@ abstract class CalculateInvoiceTotal { double itemTax = 0.0; invoiceItems.forEach((item) { - final double qty = round(item.qty, 4); + final double qty = round(item.quantity, 4); final double cost = round(item.cost, 4); final double itemDiscount = round(item.discount, 2); final double taxRate1 = round(item.taxRate1, 3); @@ -177,7 +177,7 @@ abstract class CalculateInvoiceTotal { var total = 0.0; invoiceItems.forEach((item) { - final double qty = round(item.qty, 4); + final double qty = round(item.quantity, 4); final double cost = round(item.cost, 4); final double discount = round(item.discount, 2); diff --git a/lib/data/web_client.dart b/lib/data/web_client.dart index fdcb5bdd5..ccdfd4c69 100644 --- a/lib/data/web_client.dart +++ b/lib/data/web_client.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:core'; import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:invoiceninja_flutter/.env.dart'; import 'package:http/http.dart' as http; import 'package:invoiceninja_flutter/constants.dart'; @@ -39,7 +40,7 @@ class WebClient { {dynamic data, String filePath, String fileIndex}) async { url = _checkUrl(url); print('POST: $url'); - print('Data: $data'); + debugPrint('Data: $data'); http.Response response; if (filePath != null) { @@ -60,7 +61,7 @@ class WebClient { {dynamic data, String filePath, String fileIndex = 'file'}) async { url = _checkUrl(url); print('PUT: $url'); - print('Data: $data'); + debugPrint('Data: $data'); http.Response response; diff --git a/lib/redux/expense/expense_selectors.dart b/lib/redux/expense/expense_selectors.dart index 4ca380f7a..bb0d4ac6c 100644 --- a/lib/redux/expense/expense_selectors.dart +++ b/lib/redux/expense/expense_selectors.dart @@ -11,7 +11,7 @@ InvoiceItemEntity convertExpenseToInvoiceItem( ..expenseId = expense.id ..productKey = categoryMap[expense.categoryId]?.name ?? '' ..notes = expense.publicNotes - ..qty = 1 + ..quantity = 1 ..cost = expense.convertedAmount ..taxName1 = expense.taxName1 ..taxRate1 = expense.taxRate1 diff --git a/lib/redux/product/product_selectors.dart b/lib/redux/product/product_selectors.dart index 8d0d757b4..8add24665 100644 --- a/lib/redux/product/product_selectors.dart +++ b/lib/redux/product/product_selectors.dart @@ -10,7 +10,7 @@ InvoiceItemEntity convertProductToInvoiceItem( ..productKey = product.productKey ..notes = product.notes ..cost = product.price - ..qty = product.quantity + ..quantity = product.quantity ..customValue1 = product.customValue1 ..customValue2 = product.customValue2 ..taxName1 = product.taxName1 diff --git a/lib/redux/task/task_selectors.dart b/lib/redux/task/task_selectors.dart index c4b6a95f4..13bd6ed80 100644 --- a/lib/redux/task/task_selectors.dart +++ b/lib/redux/task/task_selectors.dart @@ -29,7 +29,7 @@ InvoiceItemEntity convertTaskToInvoiceItem( ..notes = notes ..cost = taskRateSelector( company: state.selectedCompany, project: project, client: client) - ..qty = round(task.duration / 3600, 3)); + ..quantity = round(task.duration / 3600, 3)); } var memoizedTaskList = memo2( diff --git a/lib/ui/app/invoice/invoice_item_view.dart b/lib/ui/app/invoice/invoice_item_view.dart index 91de16b5c..ce4c92891 100644 --- a/lib/ui/app/invoice/invoice_item_view.dart +++ b/lib/ui/app/invoice/invoice_item_view.dart @@ -18,7 +18,7 @@ class InvoiceItemListTile extends StatelessWidget { Widget build(BuildContext context) { final String cost = formatNumber(invoiceItem.cost, context, clientId: invoice.clientId); - final String qty = formatNumber(invoiceItem.qty, context, + final String qty = formatNumber(invoiceItem.quantity, context, clientId: invoice.clientId, formatNumberType: FormatNumberType.double); final localization = AppLocalization.of(context); diff --git a/lib/ui/invoice/edit/invoice_edit_items.dart b/lib/ui/invoice/edit/invoice_edit_items.dart index a89c9683c..56361344e 100644 --- a/lib/ui/invoice/edit/invoice_edit_items.dart +++ b/lib/ui/invoice/edit/invoice_edit_items.dart @@ -126,7 +126,7 @@ class ItemEditDetailsState extends State { _notesController.text = invoiceItem.notes; _costController.text = formatNumber(invoiceItem.cost, context, formatNumberType: FormatNumberType.input); - _qtyController.text = formatNumber(invoiceItem.qty, context, + _qtyController.text = formatNumber(invoiceItem.quantity, context, formatNumberType: FormatNumberType.input); _discountController.text = formatNumber(invoiceItem.discount, context, formatNumberType: FormatNumberType.input); @@ -164,7 +164,7 @@ class ItemEditDetailsState extends State { ..productKey = _productKeyController.text.trim() ..notes = _notesController.text ..cost = parseDouble(_costController.text) - ..qty = parseDouble(_qtyController.text) + ..quantity = parseDouble(_qtyController.text) ..discount = parseDouble(_discountController.text) ..customValue1 = _custom1Controller.text.trim() ..customValue2 = _custom2Controller.text.trim()); diff --git a/lib/ui/invoice/edit/invoice_item_selector.dart b/lib/ui/invoice/edit/invoice_item_selector.dart index bd5ff92b1..94fe49f3f 100644 --- a/lib/ui/invoice/edit/invoice_item_selector.dart +++ b/lib/ui/invoice/edit/invoice_item_selector.dart @@ -71,7 +71,7 @@ class _InvoiceItemSelectorState extends State } else { items.add(InvoiceItemEntity().rebuild((b) => b ..productKey = product.productKey - ..qty = 1)); + ..quantity = 1)); } } else if (entity.entityType == EntityType.task) { final task = entity as TaskEntity; diff --git a/lib/ui/settings/invoice_design.dart b/lib/ui/settings/invoice_design.dart index 5f8ede246..650444a78 100644 --- a/lib/ui/settings/invoice_design.dart +++ b/lib/ui/settings/invoice_design.dart @@ -1,5 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/ui/app/form_card.dart'; +import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart'; import 'package:invoiceninja_flutter/ui/settings/invoice_design_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/settings_scaffold.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; @@ -18,11 +20,10 @@ class InvoiceDesign extends StatefulWidget { class _InvoiceDesignState extends State with SingleTickerProviderStateMixin { - //static final GlobalKey _formKey = GlobalKey(); + static final GlobalKey _formKey = GlobalKey(); TabController _controller; - - bool autoValidate = false; + FocusScopeNode _focusNode; final _nameController = TextEditingController(); @@ -31,12 +32,14 @@ class _InvoiceDesignState extends State @override void initState() { super.initState(); + _focusNode = FocusScopeNode(); _controller = TabController(vsync: this, length: 3); } @override void dispose() { _controller.dispose(); + _focusNode.dispose(); _controllers.forEach((dynamic controller) { controller.removeListener(_onChanged); controller.dispose(); @@ -75,12 +78,70 @@ class _InvoiceDesignState extends State @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); - //final viewModel = widget.viewModel; + final viewModel = widget.viewModel; + final state = viewModel.state; + //final settings = viewModel.; return SettingsScaffold( title: localization.invoiceDesign, - onSavePressed: null, - body: SizedBox(), + onSavePressed: viewModel.onSavePressed, + appBarBottom: TabBar( + key: ValueKey(state.settingsUIState.updatedAt), + controller: _controller, + tabs: [ + Tab( + text: localization.settings, + ), + Tab( + text: localization.invoiceFields, + ), + Tab( + text: localization.productFields, + ), + ], + ), + body: AppTabForm( + tabController: _controller, + formKey: _formKey, + focusNode: _focusNode, + children: [ + ListView(children: [ + FormCard( + children: [ + /* + InputDecorator( + decoration: InputDecoration( + labelText: localization.firstMonthOfTheYear, + ), + //isEmpty: company.financialYearStart == null, + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: company.financialYearStart, + isExpanded: true, + isDense: true, + onChanged: (value) => viewModel.onCompanyChanged(company + .rebuild((b) => b..financialYearStart = value)), + items: kMonthsOfTheYear + .map((id, month) => + MapEntry>( + id, + DropdownMenuItem( + child: Text(localization.lookup(month)), + value: id, + ))) + .values + .toList()), + ), + ) + + */ + ], + ), + ]), + ListView(), + ListView(), + ], + ), ); } } diff --git a/lib/ui/settings/invoice_design_vm.dart b/lib/ui/settings/invoice_design_vm.dart index 7869f94c4..db8390d34 100644 --- a/lib/ui/settings/invoice_design_vm.dart +++ b/lib/ui/settings/invoice_design_vm.dart @@ -2,7 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/models/company_model.dart'; +import 'package:invoiceninja_flutter/data/models/entities.dart'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/group/group_actions.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/ui/settings/invoice_design.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:redux/redux.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; @@ -29,19 +36,46 @@ class InvoiceDesignVM { @required this.state, @required this.onSavePressed, @required this.onCancelPressed, + @required this.settings, + @required this.onSettingsChanged, + }); static InvoiceDesignVM fromStore(Store store) { final state = store.state; return InvoiceDesignVM( - state: state, - onSavePressed: null, - onCancelPressed: null, - ); + state: state, + settings: state.uiState.settingsUIState.settings, + onSettingsChanged: (settings) { + store.dispatch(UpdateSettings(settings: settings)); + }, + onCancelPressed: (context) => store.dispatch(ResetSettings()), + onSavePressed: (context) { + final settingsUIState = state.uiState.settingsUIState; + final completer = snackBarCompleter( + context, AppLocalization.of(context).savedSettings); + switch (settingsUIState.entityType) { + case EntityType.company: + store.dispatch(SaveCompanyRequest( + completer: completer, + company: settingsUIState.userCompany.company)); + break; + case EntityType.group: + store.dispatch(SaveGroupRequest( + completer: completer, group: settingsUIState.group)); + break; + case EntityType.client: + store.dispatch(SaveClientRequest( + completer: completer, client: settingsUIState.client)); + break; + } + }); } final AppState state; + final SettingsEntity settings; + final Function(SettingsEntity) onSettingsChanged; final Function(BuildContext) onSavePressed; final Function(BuildContext) onCancelPressed; } diff --git a/lib/ui/settings/tax_rates.dart b/lib/ui/settings/tax_rates.dart index 7168aae4c..8869ed664 100644 --- a/lib/ui/settings/tax_rates.dart +++ b/lib/ui/settings/tax_rates.dart @@ -75,11 +75,11 @@ class _TaxRatesState extends State @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); - //final viewModel = widget.viewModel; + final viewModel = widget.viewModel; return SettingsScaffold( title: localization.taxRates, - onSavePressed: null, + onSavePressed: viewModel.onSavePressed, body: SizedBox(), ); } diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 0f2edba24..23755f182 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -14,6 +14,8 @@ abstract class LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { + 'invoice_fields': 'Invoice Fields', + 'product_fields': 'Product Fields', 'invoice_terms': 'Invoice Terms', 'invoice_footer': 'Invoice Footer', 'quote_terms': 'Quote Terms', @@ -15302,6 +15304,10 @@ mixin LocalizationsProvider on LocaleCodeAware { String get quoteFooter => _localizedValues[localeCode]['quote_footer']; + String get invoiceFields => _localizedValues[localeCode]['invoice_fields']; + + String get productFields => _localizedValues[localeCode]['product_fields']; + String lookup(String key) { final lookupKey = toSnakeCase(key); return _localizedValues[localeCode][lookupKey] ??