Settings
This commit is contained in:
parent
10fa911cef
commit
a73adb462b
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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}';
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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] ??
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue