diff --git a/lib/main.dart b/lib/main.dart index aea284d00..1d2b8b4ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -45,7 +45,7 @@ import 'package:invoiceninja_flutter/ui/settings/device_settings_list_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/email_settings_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/import_export_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/invoice_design_vm.dart'; -import 'package:invoiceninja_flutter/ui/settings/invoice_settings_vm.dart'; +import 'package:invoiceninja_flutter/ui/settings/system_settings_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/localization_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart'; @@ -354,7 +354,7 @@ class InvoiceNinjaAppState extends State { GroupSettingsScreen.route: (context) => GroupSettingsScreen(), GroupEditScreen.route: (context) => GroupEditScreen(), GroupViewScreen.route: (context) => GroupViewScreen(), - InvoiceSettingsScreen.route: (context) => InvoiceSettingsScreen(), + SystemSettingsScreen.route: (context) => SystemSettingsScreen(), InvoiceDesignScreen.route: (context) => InvoiceDesignScreen(), ClientPortalScreen.route: (context) => ClientPortalScreen(), BuyNowButtonsScreen.route: (context) => BuyNowButtonsScreen(), diff --git a/lib/ui/app/main_screen.dart b/lib/ui/app/main_screen.dart index d1f1c28ea..84380f9be 100644 --- a/lib/ui/app/main_screen.dart +++ b/lib/ui/app/main_screen.dart @@ -25,7 +25,7 @@ import 'package:invoiceninja_flutter/ui/settings/device_settings_list_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/email_settings_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/import_export_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/invoice_design_vm.dart'; -import 'package:invoiceninja_flutter/ui/settings/invoice_settings_vm.dart'; +import 'package:invoiceninja_flutter/ui/settings/system_settings_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/localization_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/products_vm.dart'; @@ -205,7 +205,7 @@ class SettingsScreens extends StatelessWidget { screen = GroupEditScreen(); break; case kSettingsInvoiceSettings: - screen = InvoiceSettingsScreen(); + screen = SystemSettingsScreen(); break; case kSettingsInvoiceDesign: screen = InvoiceDesignScreen(); diff --git a/lib/ui/settings/client_portal.dart b/lib/ui/settings/client_portal.dart index e9608492d..1566d46b8 100644 --- a/lib/ui/settings/client_portal.dart +++ b/lib/ui/settings/client_portal.dart @@ -101,7 +101,7 @@ class _ClientPortalState extends State return SettingsScaffold( title: localization.clientPortal, - onSavePressed: null, + onSavePressed: viewModel.onSavePressed, appBarBottom: TabBar( key: ValueKey(state.settingsUIState.updatedAt), controller: _controller, diff --git a/lib/ui/settings/invoice_settings.dart b/lib/ui/settings/invoice_settings.dart deleted file mode 100644 index 57a713907..000000000 --- a/lib/ui/settings/invoice_settings.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:invoiceninja_flutter/ui/settings/invoice_settings_vm.dart'; -import 'package:invoiceninja_flutter/ui/settings/settings_scaffold.dart'; -import 'package:invoiceninja_flutter/utils/localization.dart'; - -class InvoiceSettings extends StatefulWidget { - const InvoiceSettings({ - Key key, - @required this.viewModel, - }) : super(key: key); - - final InvoiceSettingsVM viewModel; - - @override - _InvoiceSettingsState createState() => _InvoiceSettingsState(); -} - -class _InvoiceSettingsState extends State - with SingleTickerProviderStateMixin { - //static final GlobalKey _formKey = GlobalKey(); - - TabController _controller; - - bool autoValidate = false; - - final _nameController = TextEditingController(); - - List _controllers = []; - - @override - void initState() { - super.initState(); - _controller = TabController(vsync: this, length: 3); - } - - @override - void dispose() { - _controller.dispose(); - _controllers.forEach((dynamic controller) { - controller.removeListener(_onChanged); - controller.dispose(); - }); - super.dispose(); - } - - @override - void didChangeDependencies() { - _controllers = [_nameController]; - - _controllers - .forEach((dynamic controller) => controller.removeListener(_onChanged)); - - /* - final product = widget.viewModel.product; - _productKeyController.text = product.productKey; - */ - - _controllers - .forEach((dynamic controller) => controller.addListener(_onChanged)); - - super.didChangeDependencies(); - } - - void _onChanged() { - /* - final product = widget.viewModel.product.rebuild((b) => b - ..customValue2 = _custom2Controller.text.trim()); - if (product != widget.viewModel.product) { - widget.viewModel.onChanged(product); - } - */ - } - - @override - Widget build(BuildContext context) { - final localization = AppLocalization.of(context); - //final viewModel = widget.viewModel; - - return SettingsScaffold( - title: localization.invoiceSettings, - onSavePressed: null, - body: SizedBox(), - ); - } -} diff --git a/lib/ui/settings/invoice_settings_vm.dart b/lib/ui/settings/invoice_settings_vm.dart deleted file mode 100644 index 31e51346e..000000000 --- a/lib/ui/settings/invoice_settings_vm.dart +++ /dev/null @@ -1,47 +0,0 @@ -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/ui/settings/invoice_settings.dart'; -import 'package:redux/redux.dart'; -import 'package:invoiceninja_flutter/redux/app/app_state.dart'; - -class InvoiceSettingsScreen extends StatelessWidget { - const InvoiceSettingsScreen({Key key}) : super(key: key); - static const String route = '/$kSettings/$kSettingsInvoiceSettings'; - - @override - Widget build(BuildContext context) { - return StoreConnector( - converter: InvoiceSettingsVM.fromStore, - builder: (context, viewModel) { - return InvoiceSettings( - viewModel: viewModel, - key: ValueKey(viewModel.state.settingsUIState.updatedAt), - ); - }, - ); - } -} - -class InvoiceSettingsVM { - InvoiceSettingsVM({ - @required this.state, - @required this.onSavePressed, - @required this.onCancelPressed, - }); - - static InvoiceSettingsVM fromStore(Store store) { - final state = store.state; - - return InvoiceSettingsVM( - state: state, - onSavePressed: null, - onCancelPressed: null, - ); - } - - final AppState state; - final Function(BuildContext) onSavePressed; - final Function(BuildContext) onCancelPressed; -} diff --git a/lib/ui/settings/system_settings.dart b/lib/ui/settings/system_settings.dart new file mode 100644 index 000000000..e0904e1de --- /dev/null +++ b/lib/ui/settings/system_settings.dart @@ -0,0 +1,329 @@ +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/app/forms/decorated_form_field.dart'; +import 'package:invoiceninja_flutter/ui/settings/system_settings_vm.dart'; +import 'package:invoiceninja_flutter/ui/settings/settings_scaffold.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; + +class SystemSettings extends StatefulWidget { + const SystemSettings({ + Key key, + @required this.viewModel, + }) : super(key: key); + + final SystemSettingsVM viewModel; + + @override + _SystemSettingsState createState() => _SystemSettingsState(); +} + +class _SystemSettingsState extends State + with SingleTickerProviderStateMixin { + static final GlobalKey _formKey = GlobalKey(); + + FocusScopeNode _focusNode; + TabController _controller; + + bool autoValidate = false; + + final _recurringPrefixController = TextEditingController(); + + List _controllers = []; + + @override + void initState() { + super.initState(); + _focusNode = FocusScopeNode(); + _controller = TabController(vsync: this, length: 3); + } + + @override + void dispose() { + _focusNode.dispose(); + _controller.dispose(); + _controllers.forEach((dynamic controller) { + controller.removeListener(_onChanged); + controller.dispose(); + }); + super.dispose(); + } + + @override + void didChangeDependencies() { + _controllers = [ + _recurringPrefixController, + ]; + + _controllers + .forEach((dynamic controller) => controller.removeListener(_onChanged)); + + + //_recurringPrefixController.text = widget.viewModel.settings. + /* + final product = widget.viewModel.product; + _productKeyController.text = product.productKey; + */ + + _controllers + .forEach((dynamic controller) => controller.addListener(_onChanged)); + + super.didChangeDependencies(); + } + + void _onChanged() { + /* + final product = widget.viewModel.product.rebuild((b) => b + ..customValue2 = _custom2Controller.text.trim()); + if (product != widget.viewModel.product) { + widget.viewModel.onChanged(product); + } + */ + } + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + final viewModel = widget.viewModel; + final settings = viewModel.settings; + final state = viewModel.state; + + return SettingsScaffold( + title: localization.systemSettings, + onSavePressed: null, + appBarBottom: TabBar( + key: ValueKey(state.settingsUIState.updatedAt), + controller: _controller, + tabs: [ + Tab( + text: localization.general, + ), + Tab( + text: localization.clients, + ), + Tab( + text: localization.products, + ), + ], + ), + body: AppTabForm( + tabController: _controller, + formKey: _formKey, + focusNode: _focusNode, + children: [ + ListView( + children: [ + FormCard( + children: [ + InputDecorator( + decoration: InputDecoration( + labelText: localization.padding, + ), + //isEmpty: false, + child: DropdownButtonHideUnderline( + child: DropdownButton( + //value: + isExpanded: true, + isDense: true, + /* + onChanged: (value) => viewModel.onSettingsChanged( + settings + .rebuild((b) => b..portalMode = value)), + */ + items: List.generate(10, (i) => i + 1) + .map((value) => DropdownMenuItem( + child: Text('${'0' * value}1'), + value: value, + )) + .toList(), + ), + ), + ), + DecoratedFormField( + label: localization.recurringPrefix, + controller: _recurringPrefixController, + ), + ], + ), + ], + ), + ListView(children: [ + FormCard( + children: [ + EntityNumberSettings(), + CustomFieldsSettings(), + ], + ), + ]), + ListView( + children: [ + FormCard( + children: [ + EntityNumberSettings(), + CustomFieldsSettings(), + ], + ), + ], + ), + ], + ), + ); + } +} + +class EntityNumberSettings extends StatefulWidget { + @override + _EntityNumberSettingsState createState() => _EntityNumberSettingsState(); +} + +class _EntityNumberSettingsState extends State { + final _counterController = TextEditingController(); + final _patternController = TextEditingController(); + + List _controllers = []; + + @override + void dispose() { + _controllers.forEach((dynamic controller) { + controller.removeListener(_onChanged); + controller.dispose(); + }); + super.dispose(); + } + + @override + void didChangeDependencies() { + _controllers = [ + _counterController, + _patternController, + ]; + + _controllers + .forEach((dynamic controller) => controller.removeListener(_onChanged)); + + /* + final product = widget.viewModel.product; + _productKeyController.text = product.productKey; + */ + + _controllers + .forEach((dynamic controller) => controller.addListener(_onChanged)); + + super.didChangeDependencies(); + } + + void _onChanged() { + /* + final product = widget.viewModel.product.rebuild((b) => b + ..customValue2 = _custom2Controller.text.trim()); + if (product != widget.viewModel.product) { + widget.viewModel.onChanged(product); + } + */ + } + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + + return Column( + children: [ + DecoratedFormField( + label: localization.pattern, + controller: _patternController, + ), + DecoratedFormField( + label: localization.counter, + controller: _counterController, + ), + ], + ); + } +} + +class CustomFieldsSettings extends StatefulWidget { + const CustomFieldsSettings({this.fieldLabel}); + + final String fieldLabel; + + @override + _CustomFieldsSettingsState createState() => _CustomFieldsSettingsState(); +} + +class _CustomFieldsSettingsState extends State { + final _customField1Controller = TextEditingController(); + final _customField2Controller = TextEditingController(); + final _customField3Controller = TextEditingController(); + final _customField4Controller = TextEditingController(); + + List _controllers = []; + + @override + void dispose() { + _controllers.forEach((dynamic controller) { + controller.removeListener(_onChanged); + controller.dispose(); + }); + super.dispose(); + } + + @override + void didChangeDependencies() { + _controllers = [ + _customField1Controller, + _customField2Controller, + _customField3Controller, + _customField4Controller, + ]; + + _controllers + .forEach((dynamic controller) => controller.removeListener(_onChanged)); + + /* + final product = widget.viewModel.product; + _productKeyController.text = product.productKey; + */ + + _controllers + .forEach((dynamic controller) => controller.addListener(_onChanged)); + + super.didChangeDependencies(); + } + + void _onChanged() { + /* + final product = widget.viewModel.product.rebuild((b) => b + ..customValue2 = _custom2Controller.text.trim()); + if (product != widget.viewModel.product) { + widget.viewModel.onChanged(product); + } + */ + } + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + + return FormCard( + children: [ + DecoratedFormField( + label: widget.fieldLabel, + controller: _customField1Controller, + ), + DecoratedFormField( + label: widget.fieldLabel, + controller: _customField2Controller, + ), + DecoratedFormField( + label: widget.fieldLabel, + controller: _customField2Controller, + ), + DecoratedFormField( + label: widget.fieldLabel, + controller: _customField2Controller, + ), + ], + ); + } +} diff --git a/lib/ui/settings/system_settings_vm.dart b/lib/ui/settings/system_settings_vm.dart new file mode 100644 index 000000000..979b541da --- /dev/null +++ b/lib/ui/settings/system_settings_vm.dart @@ -0,0 +1,80 @@ +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/system_settings.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'; + +class SystemSettingsScreen extends StatelessWidget { + const SystemSettingsScreen({Key key}) : super(key: key); + static const String route = '/$kSettings/$kSettingsInvoiceSettings'; + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: SystemSettingsVM.fromStore, + builder: (context, viewModel) { + return SystemSettings( + viewModel: viewModel, + key: ValueKey(viewModel.state.settingsUIState.updatedAt), + ); + }, + ); + } +} + +class SystemSettingsVM { + SystemSettingsVM({ + @required this.state, + @required this.onSavePressed, + @required this.onCancelPressed, + @required this.settings, + @required this.onSettingsChanged, + }); + + static SystemSettingsVM fromStore(Store store) { + final state = store.state; + + return SystemSettingsVM( + 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 Function(BuildContext) onSavePressed; + final Function(BuildContext) onCancelPressed; + final SettingsEntity settings; + final Function(SettingsEntity) onSettingsChanged; +} diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 8c06d0781..76a746ce9 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -14,6 +14,23 @@ abstract class LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { + 'recurring_prefix': 'Recurring Prefix', + 'padding': 'Padding', + 'general': 'General', + 'invoice_field': 'Invoice Field', + 'client_field': 'Client Field', + 'product_field': 'Product Field', + 'quote_field': 'Quote Field', + 'payment_field': 'Payment Field', + 'contact_field': 'Contact Field', + 'vendor_field': 'Vendor Field', + 'expense_field': 'Expense Field', + 'project_field': 'Project Field', + 'task_field': 'Task Field', + 'group_field': 'Group Field', + 'counter': 'Counter', + 'prefix': 'Prefix', + 'pattern': 'Pattern', 'messages': 'Messages', 'custom_css': 'Custom CSS', 'custom_javascript': 'Custom JavaScript', @@ -170,7 +187,7 @@ mixin LocalizationsProvider on LocaleCodeAware { 'tax_rates': 'Tax Rates', 'notifications': 'Notifications', 'import_export': 'Import | Export', - 'invoice_settings': 'Invoice Settings', + 'system_settings': 'System Settings', 'invoice_design': 'Invoice Design', 'buy_now_buttons': 'Buy Now Buttons', 'email_settings': 'Email Settings', @@ -14794,8 +14811,7 @@ mixin LocalizationsProvider on LocaleCodeAware { String get importExport => _localizedValues[localeCode]['import_export']; - String get invoiceSettings => - _localizedValues[localeCode]['invoice_settings']; + String get systemSettings => _localizedValues[localeCode]['system_settings']; String get invoiceDesign => _localizedValues[localeCode]['invoice_design']; @@ -15118,21 +15134,51 @@ mixin LocalizationsProvider on LocaleCodeAware { String get requireQuoteSignature => _localizedValues[localeCode]['require_quote_signature']; - String get signatureOnPdf => - _localizedValues[localeCode]['signature_on_pdf']; + String get signatureOnPdf => _localizedValues[localeCode]['signature_on_pdf']; String get signatureOnPdfHelp => _localizedValues[localeCode]['signature_on_pdf_help']; - String get customCss => - _localizedValues[localeCode]['custom_css']; + String get customCss => _localizedValues[localeCode]['custom_css']; String get customJavascript => _localizedValues[localeCode]['custom_javascript']; - String get messages => - _localizedValues[localeCode]['messages']; + String get messages => _localizedValues[localeCode]['messages']; + String get prefix => _localizedValues[localeCode]['prefix']; + + String get pattern => _localizedValues[localeCode]['pattern']; + + String get counter => _localizedValues[localeCode]['counter']; + + String get invoiceField => _localizedValues[localeCode]['invoice_field']; + + String get clientField => _localizedValues[localeCode]['client_field']; + + String get productField => _localizedValues[localeCode]['product_field']; + + String get quotField => _localizedValues[localeCode]['quote_field']; + + String get paymentField => _localizedValues[localeCode]['payment_field']; + + String get contactField => _localizedValues[localeCode]['contact_field']; + + String get vendorField => _localizedValues[localeCode]['vendor_field']; + + String get expenseField => _localizedValues[localeCode]['expense_field']; + + String get projectField => _localizedValues[localeCode]['project_field']; + + String get taskField => _localizedValues[localeCode]['task_field']; + + String get groupField => _localizedValues[localeCode]['group_field']; + + String get general => _localizedValues[localeCode]['general']; + + String get padding => _localizedValues[localeCode]['padding']; + + String get recurringPrefix => _localizedValues[localeCode]['recurring_prefix']; String lookup(String key) { final lookupKey = toSnakeCase(key);