This commit is contained in:
Hillel Coren 2019-11-04 21:17:37 +02:00
parent 10fa911cef
commit a73adb462b
5 changed files with 211 additions and 109 deletions

View File

@ -124,6 +124,12 @@ const String kEntityStateActive = 'active';
const String kEntityStateArchived = 'archived'; const String kEntityStateArchived = 'archived';
const String kEntityStateDeleted = 'deleted'; const String kEntityStateDeleted = 'deleted';
const String kFieldTypeSingleLineText = 'single_line_text';
const String kFieldTypeMultiLineText = 'multi_line_text';
const String kFieldTypeDropdown = 'dropdown';
const String kFieldTypeDate = 'date';
const String kFieldTypeSwitch = 'switch';
const String kTaskStatusLogged = '-1'; const String kTaskStatusLogged = '-1';
const String kTaskStatusRunning = '-2'; const String kTaskStatusRunning = '-2';
const String kTaskStatusInvoiced = '-3'; const String kTaskStatusInvoiced = '-3';

View File

@ -347,7 +347,7 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
@override @override
String toString() { String toString() {
//return 'Custom fields [UI]: ${uiState.settingsUIState.userCompany.company.customFields}, [DB] ${selectedCompany.customFields}'; //return 'Custom fields [UI]: ${uiState.settingsUIState.userCompany.company.customFields}, [DB] ${selectedCompany.customFields}';
return 'template: ${uiState.settingsUIState.settings.emailBodyInvoice}'; return 'custom fields: ${uiState.settingsUIState.userCompany.company.customFields}';
//return 'defaultInvoiceDesignId: ${selectedCompany.settings.defaultInvoiceDesignId}'; //return 'defaultInvoiceDesignId: ${selectedCompany.settings.defaultInvoiceDesignId}';
//return 'Routes: Current: ${uiState.currentRoute} Prev: ${uiState.previousRoute}'; //return 'Routes: Current: ${uiState.currentRoute} Prev: ${uiState.previousRoute}';
//return 'Route: ${uiState.currentRoute}, Setting Type: ${uiState.settingsUIState.entityType}, Name: ${uiState.settingsUIState.settings.name}, Updated: ${uiState.settingsUIState.updatedAt}'; //return 'Route: ${uiState.currentRoute}, Setting Type: ${uiState.settingsUIState.entityType}, Name: ${uiState.settingsUIState.settings.name}, Updated: ${uiState.settingsUIState.updatedAt}';

View File

@ -11,10 +11,12 @@ class DecoratedFormField extends StatelessWidget {
this.textInputAction, this.textInputAction,
this.onFieldSubmitted, this.onFieldSubmitted,
this.enabled, this.enabled,
this.hint,
}); });
final TextEditingController controller; final TextEditingController controller;
final String label; final String label;
final String hint;
final Function(String) validator; final Function(String) validator;
final TextInputType keyboardType; final TextInputType keyboardType;
final int maxLines; final int maxLines;
@ -31,6 +33,7 @@ class DecoratedFormField extends StatelessWidget {
controller: controller, controller: controller,
decoration: InputDecoration( decoration: InputDecoration(
labelText: label, labelText: label,
hintText: hint,
), ),
validator: validator, validator: validator,
keyboardType: null, keyboardType: null,

View File

@ -1,7 +1,9 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_form.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/app/forms/decorated_form_field.dart';
import 'package:invoiceninja_flutter/ui/settings/custom_fields_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/custom_fields_vm.dart';
@ -116,7 +118,7 @@ class _CustomFieldsState extends State<CustomFields>
} }
} }
class CustomFieldsSettings extends StatefulWidget { class CustomFieldsSettings extends StatelessWidget {
const CustomFieldsSettings({ const CustomFieldsSettings({
@required this.fieldType, @required this.fieldType,
@required this.viewModel, @required this.viewModel,
@ -128,14 +130,82 @@ class CustomFieldsSettings extends StatefulWidget {
final String fieldType; final String fieldType;
@override @override
_CustomFieldsSettingsState createState() => _CustomFieldsSettingsState(); Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final company = viewModel.company;
return FormCard(
children: <Widget>[
CustomFormField(
label: localization.lookup('${fieldType}_field'),
value: company.customFields['${fieldType}1'],
onChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..customFields['${fieldType}1'] = value)),
showTaxes: showChargeTaxes,
taxesEnabled: company.enableCustomSurchargeTaxes1,
onTaxesChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..enableCustomSurchargeTaxes1 = value)),
),
CustomFormField(
label: localization.lookup('${fieldType}_field'),
value: company.customFields['${fieldType}2'],
onChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..customFields['${fieldType}1'] = value)),
showTaxes: showChargeTaxes,
taxesEnabled: company.enableCustomSurchargeTaxes2,
onTaxesChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..enableCustomSurchargeTaxes2 = value)),
),
CustomFormField(
label: localization.lookup('${fieldType}_field'),
value: company.customFields['${fieldType}3'],
onChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..customFields['${fieldType}1'] = value)),
showTaxes: showChargeTaxes,
taxesEnabled: company.enableCustomSurchargeTaxes3,
onTaxesChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..enableCustomSurchargeTaxes3 = value)),
),
CustomFormField(
label: localization.lookup('${fieldType}_field'),
value: company.customFields['${fieldType}4'],
onChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..customFields['${fieldType}1'] = value)),
showTaxes: showChargeTaxes,
taxesEnabled: company.enableCustomSurchargeTaxes4,
onTaxesChanged: (value) => viewModel.onCompanyChanged(
company.rebuild((b) => b..enableCustomSurchargeTaxes4 = value)),
),
],
);
}
} }
class _CustomFieldsSettingsState extends State<CustomFieldsSettings> { class CustomFormField extends StatefulWidget {
final _customField1Controller = TextEditingController(); const CustomFormField({
final _customField2Controller = TextEditingController(); @required this.label,
final _customField3Controller = TextEditingController(); @required this.onChanged,
final _customField4Controller = TextEditingController(); @required this.value,
this.showTaxes = false,
this.taxesEnabled,
this.onTaxesChanged,
});
final String label;
final String value;
final bool showTaxes;
final bool taxesEnabled;
final Function(String) onChanged;
final Function(bool) onTaxesChanged;
@override
_CustomFormFieldState createState() => _CustomFormFieldState();
}
class _CustomFormFieldState extends State<CustomFormField> {
final _customFieldController = TextEditingController();
final _optionsController = TextEditingController();
String _fieldType = kFieldTypeMultiLineText;
List<TextEditingController> _controllers = []; List<TextEditingController> _controllers = [];
final _debouncer = Debouncer(); final _debouncer = Debouncer();
@ -152,21 +222,34 @@ class _CustomFieldsSettingsState extends State<CustomFieldsSettings> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
_controllers = [ _controllers = [
_customField1Controller, _customFieldController,
_customField2Controller, _optionsController,
_customField3Controller,
_customField4Controller,
]; ];
_controllers _controllers
.forEach((dynamic controller) => controller.removeListener(_onChanged)); .forEach((dynamic controller) => controller.removeListener(_onChanged));
final fieldType = widget.fieldType; if ('${widget.value ?? ''}'.isNotEmpty && widget.value.contains('|')) {
final customFields = widget.viewModel.company.customFields; final parts = widget.value.split('|');
_customField1Controller.text = customFields['${fieldType}1']; _customFieldController.text = parts[0];
_customField2Controller.text = customFields['${fieldType}2']; switch (parts[1]) {
_customField3Controller.text = customFields['${fieldType}3']; case kFieldTypeSingleLineText:
_customField4Controller.text = customFields['${fieldType}4']; _fieldType = kFieldTypeSingleLineText;
break;
case kFieldTypeDate:
_fieldType = kFieldTypeDate;
break;
case kFieldTypeSwitch:
_fieldType = kFieldTypeSwitch;
break;
default:
_fieldType = kFieldTypeDropdown;
_optionsController.text = parts[1];
break;
}
} else {
_customFieldController.text = widget.value;
}
_controllers _controllers
.forEach((dynamic controller) => controller.addListener(_onChanged)); .forEach((dynamic controller) => controller.addListener(_onChanged));
@ -176,107 +259,95 @@ class _CustomFieldsSettingsState extends State<CustomFieldsSettings> {
void _onChanged() { void _onChanged() {
_debouncer.run(() { _debouncer.run(() {
final viewModel = widget.viewModel; var value = _customFieldController.text.trim();
final fieldType = widget.fieldType; if ([
final company = widget.viewModel.company; kFieldTypeSingleLineText,
final origFields = company.customFields; kFieldTypeDate,
kFieldTypeSwitch,
final updatedFields = origFields.rebuild((b) => b ].contains(_fieldType)) {
..addAll({'${fieldType}1': _customField1Controller.text.trim()}) value = '$value|$_fieldType';
..addAll({'${fieldType}2': _customField2Controller.text.trim()}) } else if (_fieldType == kFieldTypeDropdown) {
..addAll({'${fieldType}3': _customField3Controller.text.trim()}) value = '$value|${_optionsController.text.trim()}';
..addAll({'${fieldType}4': _customField4Controller.text.trim()}));
if (viewModel.company.customFields != updatedFields) {
viewModel.onCompanyChanged(
company.rebuild((b) => b..customFields.replace(updatedFields)));
} }
widget.onChanged(value);
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final viewModel = widget.viewModel;
final company = viewModel.company;
return FormCard( return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
CustomFormField( Row(
label: localization.lookup('${widget.fieldType}_field'), children: <Widget>[
controller: _customField1Controller, Flexible(
showTaxes: widget.showChargeTaxes, child: DecoratedFormField(
taxesEnabled: company.enableCustomSurchargeTaxes1, label: widget.label,
onTaxesChanged: (value) => viewModel.onCompanyChanged( controller: _customFieldController,
company.rebuild((b) => b..enableCustomSurchargeTaxes1 = value)), ),
), ),
CustomFormField( if (widget.showTaxes) ...[
label: localization.lookup('${widget.fieldType}_field'), Checkbox(
controller: _customField2Controller, activeColor: Theme.of(context).accentColor,
showTaxes: widget.showChargeTaxes, value: widget.taxesEnabled ?? false,
taxesEnabled: company.enableCustomSurchargeTaxes2, onChanged: widget.onTaxesChanged,
onTaxesChanged: (value) => viewModel.onCompanyChanged( ),
company.rebuild((b) => b..enableCustomSurchargeTaxes2 = value)), GestureDetector(
), child: Text(localization.chargeTaxes),
CustomFormField( onTap: () => widget.onTaxesChanged(!widget.taxesEnabled),
label: localization.lookup('${widget.fieldType}_field'), ),
controller: _customField3Controller, ] else ...[
showTaxes: widget.showChargeTaxes, SizedBox(width: 20),
taxesEnabled: company.enableCustomSurchargeTaxes3, Flexible(
onTaxesChanged: (value) => viewModel.onCompanyChanged( child: AppDropdownButton(
company.rebuild((b) => b..enableCustomSurchargeTaxes3 = value)), labelText: localization.fieldType,
), value: _fieldType,
CustomFormField( onChanged: (value) {
label: localization.lookup('${widget.fieldType}_field'), setState(() {
controller: _customField4Controller, _fieldType = value;
showTaxes: widget.showChargeTaxes, _onChanged();
taxesEnabled: company.enableCustomSurchargeTaxes4, });
onTaxesChanged: (value) => viewModel.onCompanyChanged( },
company.rebuild((b) => b..enableCustomSurchargeTaxes4 = value)), items: [
DropdownMenuItem(
child: Text(localization.singleLineText),
value: kFieldTypeSingleLineText,
),
DropdownMenuItem(
child: Text(localization.multiLineText),
value: kFieldTypeMultiLineText,
),
DropdownMenuItem(
child: Text(localization.switchLabel),
value: kFieldTypeSwitch,
),
DropdownMenuItem(
child: Text(localization.dropdown),
value: kFieldTypeDropdown,
),
DropdownMenuItem(
child: Text(localization.date),
value: kFieldTypeDate,
),
],
),
),
],
],
), ),
], if (_fieldType == kFieldTypeDropdown)
); Flexible(
} child: Padding(
} padding: const EdgeInsets.only(bottom: 20),
child: DecoratedFormField(
class CustomFormField extends StatelessWidget { label: localization.options,
const CustomFormField({ controller: _optionsController,
@required this.label, hint: localization.useCommaSeparatedList,
@required this.controller, ),
this.showTaxes = false, ),
this.taxesEnabled, ),
this.onTaxesChanged,
});
final String label;
final TextEditingController controller;
final bool showTaxes;
final bool taxesEnabled;
final Function(bool) onTaxesChanged;
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
return Row(
children: <Widget>[
Expanded(
child: DecoratedFormField(
label: label,
controller: controller,
),
),
if (showTaxes) ...[
Checkbox(
activeColor: Theme.of(context).accentColor,
value: taxesEnabled ?? false,
onChanged: onTaxesChanged,
),
GestureDetector(
child: Text(localization.chargeTaxes),
onTap: () => onTaxesChanged(!taxesEnabled),
),
]
], ],
); );
} }

View File

@ -14,6 +14,13 @@ abstract class LocaleCodeAware {
mixin LocalizationsProvider on LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = { static final Map<String, Map<String, String>> _localizedValues = {
'en': { 'en': {
'switch': 'Switch',
'use_comma_sparated_list': 'Use a comma separated list',
'options': 'Options',
'single_line_text': 'Single-line text',
'multi_line_text': 'Multi-line text',
'dropdown': 'Dropdown',
'field_type': 'Field Type',
'recover_password_email_sent': 'A password recovery email has been sent', 'recover_password_email_sent': 'A password recovery email has been sent',
'submit': 'Submit', 'submit': 'Submit',
'recover_password': 'Recover Password', 'recover_password': 'Recover Password',
@ -15461,6 +15468,21 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get recoverPasswordEmailSent => String get recoverPasswordEmailSent =>
_localizedValues[localeCode]['recover_password_email_sent']; _localizedValues[localeCode]['recover_password_email_sent'];
String get fieldType => _localizedValues[localeCode]['field_type'];
String get singleLineText => _localizedValues[localeCode]['single_line_text'];
String get multiLineText => _localizedValues[localeCode]['multi_line_text'];
String get dropdown => _localizedValues[localeCode]['dropdown'];
String get options => _localizedValues[localeCode]['options'];
String get useCommaSeparatedList =>
_localizedValues[localeCode]['use_comma_sparated_list'];
String get switchLabel => _localizedValues[localeCode]['switch'];
String lookup(String key) { String lookup(String key) {
final lookupKey = toSnakeCase(key); final lookupKey = toSnakeCase(key);
return _localizedValues[localeCode][lookupKey] ?? return _localizedValues[localeCode][lookupKey] ??