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 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 kTaskStatusRunning = '-2';
const String kTaskStatusInvoiced = '-3';

View File

@ -347,7 +347,7 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
@override
String toString() {
//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 '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}';

View File

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

View File

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

View File

@ -14,6 +14,13 @@ abstract class LocaleCodeAware {
mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'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',
'submit': 'Submit',
'recover_password': 'Recover Password',
@ -15461,6 +15468,21 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get recoverPasswordEmailSent =>
_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) {
final lookupKey = toSnakeCase(key);
return _localizedValues[localeCode][lookupKey] ??