From 15f4f11d3a85f3aca47b5ef489a22b6e70dd511a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Fri, 3 Jun 2022 15:49:50 +0300 Subject: [PATCH] Track inventory --- lib/ui/settings/company_details.dart | 23 +++---- lib/ui/settings/product_settings.dart | 91 ++++++++++++++++++++------- lib/utils/i18n.dart | 6 ++ 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/lib/ui/settings/company_details.dart b/lib/ui/settings/company_details.dart index 54189d601..e5a0cddbc 100644 --- a/lib/ui/settings/company_details.dart +++ b/lib/ui/settings/company_details.dart @@ -98,17 +98,6 @@ class _CompanyDetailsState extends State store.dispatch(UpdateSettingsTab(tabIndex: _controller.index)); } - @override - void dispose() { - _controller.removeListener(_onTabChanged); - _controller.dispose(); - _controllers.forEach((dynamic controller) { - controller.removeListener(_onSettingsChanged); - controller.dispose(); - }); - super.dispose(); - } - @override void didChangeDependencies() { _controllers = [ @@ -169,6 +158,18 @@ class _CompanyDetailsState extends State super.didChangeDependencies(); } + @override + void dispose() { + _focusNode.dispose(); + _controller.removeListener(_onTabChanged); + _controller.dispose(); + _controllers.forEach((dynamic controller) { + controller.removeListener(_onSettingsChanged); + controller.dispose(); + }); + super.dispose(); + } + void _onSettingsChanged() { final settings = widget.viewModel.settings.rebuild((b) => b ..name = _nameController.text.trim() diff --git a/lib/ui/settings/product_settings.dart b/lib/ui/settings/product_settings.dart index 68fc99a64..4b4b3c2cc 100644 --- a/lib/ui/settings/product_settings.dart +++ b/lib/ui/settings/product_settings.dart @@ -6,7 +6,10 @@ import 'package:invoiceninja_flutter/ui/app/edit_scaffold.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/app/forms/bool_dropdown_button.dart'; +import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/settings/product_settings_vm.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; class ProductSettings extends StatefulWidget { @@ -25,6 +28,9 @@ class _ProductSettingsState extends State { static final GlobalKey _formKey = GlobalKey(debugLabel: '_productSettings'); FocusScopeNode _focusNode; + final _debouncer = Debouncer(sendFirstAction: true); + final _stockThresholdController = TextEditingController(); + List _controllers = []; @override void initState() { @@ -32,12 +38,49 @@ class _ProductSettingsState extends State { _focusNode = FocusScopeNode(); } + @override + void didChangeDependencies() { + _controllers = [_stockThresholdController]; + + _controllers + .forEach((dynamic controller) => controller.removeListener(_onChanged)); + + final viewModel = widget.viewModel; + final company = viewModel.state.company; + + _stockThresholdController.text = formatNumber( + company.stockNotificationThreshold.toDouble(), + context, + formatNumberType: FormatNumberType.int, + ); + + _controllers + .forEach((dynamic controller) => controller.addListener(_onChanged)); + + super.didChangeDependencies(); + } + @override void dispose() { _focusNode.dispose(); + _controllers.forEach((dynamic controller) { + controller.removeListener(_onChanged); + controller.dispose(); + }); super.dispose(); } + void _onChanged() { + final company = widget.viewModel.company.rebuild((b) => b + ..stockNotificationThreshold = + parseInt(_stockThresholdController.text.trim())); + if (company != widget.viewModel.company) { + _debouncer.run(() { + widget.viewModel.onCompanyChanged(company); + }); + } + } + @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); @@ -51,6 +94,32 @@ class _ProductSettingsState extends State { formKey: _formKey, focusNode: _focusNode, children: [ + FormCard( + children: [ + SwitchListTile( + activeColor: Theme.of(context).colorScheme.secondary, + title: Text(localization.trackInventory), + value: company.trackInventory, + subtitle: Text(localization.trackInventoryHelp), + onChanged: (value) => viewModel.onCompanyChanged( + company.rebuild((b) => b..trackInventory = value)), + ), + SwitchListTile( + activeColor: Theme.of(context).colorScheme.secondary, + title: Text(localization.stockNotifications), + value: company.stockNotification, + subtitle: Text(localization.stockNotificationsHelp), + onChanged: (value) => viewModel.onCompanyChanged( + company.rebuild((b) => b..stockNotification = value)), + ), + if (company.trackInventory && company.stockNotification) + DecoratedFormField( + keyboardType: TextInputType.number, + controller: _stockThresholdController, + label: localization.notificationThreshold, + ), + ], + ), FormCard( children: [ SwitchListTile( @@ -88,6 +157,7 @@ class _ProductSettingsState extends State { ], ), FormCard( + isLast: true, children: [ SwitchListTile( activeColor: Theme.of(context).colorScheme.secondary, @@ -135,27 +205,6 @@ class _ProductSettingsState extends State { ) ], ), - FormCard( - isLast: true, - children: [ - SwitchListTile( - activeColor: Theme.of(context).colorScheme.secondary, - title: Text(localization.trackInventory), - value: company.trackInventory, - subtitle: Text(localization.trackInventoryHelp), - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..trackInventory = value)), - ), - SwitchListTile( - activeColor: Theme.of(context).colorScheme.secondary, - title: Text(localization.stockNotifications), - value: company.stockNotification, - subtitle: Text(localization.stockNotificationsHelp), - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..stockNotification = value)), - ), - ], - ), ], ), ); diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 01c4555b2..93a85309b 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -16,6 +16,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'notification_threshold': 'Notification Threshold', 'track_inventory': 'Track Inventory', 'track_inventory_help': 'Display a product stock field and update when invoices are sent', @@ -70568,6 +70569,11 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]['stock_notifications_help'] ?? _localizedValues['en']['stock_notifications_help']; + String get notificationThreshold => + _localizedValues[localeCode]['notification_threshold'] ?? + _localizedValues['en']['notification_threshold']; + + // STARTER: lang field - do not remove comment String lookup(String key) {