This commit is contained in:
Hillel Coren 2019-10-24 18:44:25 +03:00
parent 16fdb6f5ca
commit 1cb94eafd8
8 changed files with 469 additions and 147 deletions

View File

@ -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/email_settings_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/import_export_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_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/localization_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart';
@ -354,7 +354,7 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
GroupSettingsScreen.route: (context) => GroupSettingsScreen(), GroupSettingsScreen.route: (context) => GroupSettingsScreen(),
GroupEditScreen.route: (context) => GroupEditScreen(), GroupEditScreen.route: (context) => GroupEditScreen(),
GroupViewScreen.route: (context) => GroupViewScreen(), GroupViewScreen.route: (context) => GroupViewScreen(),
InvoiceSettingsScreen.route: (context) => InvoiceSettingsScreen(), SystemSettingsScreen.route: (context) => SystemSettingsScreen(),
InvoiceDesignScreen.route: (context) => InvoiceDesignScreen(), InvoiceDesignScreen.route: (context) => InvoiceDesignScreen(),
ClientPortalScreen.route: (context) => ClientPortalScreen(), ClientPortalScreen.route: (context) => ClientPortalScreen(),
BuyNowButtonsScreen.route: (context) => BuyNowButtonsScreen(), BuyNowButtonsScreen.route: (context) => BuyNowButtonsScreen(),

View File

@ -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/email_settings_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/import_export_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_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/localization_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/notifications_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/products_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/products_vm.dart';
@ -205,7 +205,7 @@ class SettingsScreens extends StatelessWidget {
screen = GroupEditScreen(); screen = GroupEditScreen();
break; break;
case kSettingsInvoiceSettings: case kSettingsInvoiceSettings:
screen = InvoiceSettingsScreen(); screen = SystemSettingsScreen();
break; break;
case kSettingsInvoiceDesign: case kSettingsInvoiceDesign:
screen = InvoiceDesignScreen(); screen = InvoiceDesignScreen();

View File

@ -101,7 +101,7 @@ class _ClientPortalState extends State<ClientPortal>
return SettingsScaffold( return SettingsScaffold(
title: localization.clientPortal, title: localization.clientPortal,
onSavePressed: null, onSavePressed: viewModel.onSavePressed,
appBarBottom: TabBar( appBarBottom: TabBar(
key: ValueKey(state.settingsUIState.updatedAt), key: ValueKey(state.settingsUIState.updatedAt),
controller: _controller, controller: _controller,

View File

@ -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<InvoiceSettings>
with SingleTickerProviderStateMixin {
//static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
TabController _controller;
bool autoValidate = false;
final _nameController = TextEditingController();
List<TextEditingController> _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(),
);
}
}

View File

@ -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<AppState, InvoiceSettingsVM>(
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<AppState> store) {
final state = store.state;
return InvoiceSettingsVM(
state: state,
onSavePressed: null,
onCancelPressed: null,
);
}
final AppState state;
final Function(BuildContext) onSavePressed;
final Function(BuildContext) onCancelPressed;
}

View File

@ -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<SystemSettings>
with SingleTickerProviderStateMixin {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
FocusScopeNode _focusNode;
TabController _controller;
bool autoValidate = false;
final _recurringPrefixController = TextEditingController();
List<TextEditingController> _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: <Widget>[
ListView(
children: <Widget>[
FormCard(
children: <Widget>[
InputDecorator(
decoration: InputDecoration(
labelText: localization.padding,
),
//isEmpty: false,
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
//value:
isExpanded: true,
isDense: true,
/*
onChanged: (value) => viewModel.onSettingsChanged(
settings
.rebuild((b) => b..portalMode = value)),
*/
items: List<int>.generate(10, (i) => i + 1)
.map((value) => DropdownMenuItem(
child: Text('${'0' * value}1'),
value: value,
))
.toList(),
),
),
),
DecoratedFormField(
label: localization.recurringPrefix,
controller: _recurringPrefixController,
),
],
),
],
),
ListView(children: <Widget>[
FormCard(
children: <Widget>[
EntityNumberSettings(),
CustomFieldsSettings(),
],
),
]),
ListView(
children: <Widget>[
FormCard(
children: <Widget>[
EntityNumberSettings(),
CustomFieldsSettings(),
],
),
],
),
],
),
);
}
}
class EntityNumberSettings extends StatefulWidget {
@override
_EntityNumberSettingsState createState() => _EntityNumberSettingsState();
}
class _EntityNumberSettingsState extends State<EntityNumberSettings> {
final _counterController = TextEditingController();
final _patternController = TextEditingController();
List<TextEditingController> _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: <Widget>[
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<CustomFieldsSettings> {
final _customField1Controller = TextEditingController();
final _customField2Controller = TextEditingController();
final _customField3Controller = TextEditingController();
final _customField4Controller = TextEditingController();
List<TextEditingController> _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: <Widget>[
DecoratedFormField(
label: widget.fieldLabel,
controller: _customField1Controller,
),
DecoratedFormField(
label: widget.fieldLabel,
controller: _customField2Controller,
),
DecoratedFormField(
label: widget.fieldLabel,
controller: _customField2Controller,
),
DecoratedFormField(
label: widget.fieldLabel,
controller: _customField2Controller,
),
],
);
}
}

View File

@ -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<AppState, SystemSettingsVM>(
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<AppState> 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;
}

View File

@ -14,6 +14,23 @@ 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': {
'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', 'messages': 'Messages',
'custom_css': 'Custom CSS', 'custom_css': 'Custom CSS',
'custom_javascript': 'Custom JavaScript', 'custom_javascript': 'Custom JavaScript',
@ -170,7 +187,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
'tax_rates': 'Tax Rates', 'tax_rates': 'Tax Rates',
'notifications': 'Notifications', 'notifications': 'Notifications',
'import_export': 'Import | Export', 'import_export': 'Import | Export',
'invoice_settings': 'Invoice Settings', 'system_settings': 'System Settings',
'invoice_design': 'Invoice Design', 'invoice_design': 'Invoice Design',
'buy_now_buttons': 'Buy Now Buttons', 'buy_now_buttons': 'Buy Now Buttons',
'email_settings': 'Email Settings', 'email_settings': 'Email Settings',
@ -14794,8 +14811,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get importExport => _localizedValues[localeCode]['import_export']; String get importExport => _localizedValues[localeCode]['import_export'];
String get invoiceSettings => String get systemSettings => _localizedValues[localeCode]['system_settings'];
_localizedValues[localeCode]['invoice_settings'];
String get invoiceDesign => _localizedValues[localeCode]['invoice_design']; String get invoiceDesign => _localizedValues[localeCode]['invoice_design'];
@ -15118,21 +15134,51 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get requireQuoteSignature => String get requireQuoteSignature =>
_localizedValues[localeCode]['require_quote_signature']; _localizedValues[localeCode]['require_quote_signature'];
String get signatureOnPdf => String get signatureOnPdf => _localizedValues[localeCode]['signature_on_pdf'];
_localizedValues[localeCode]['signature_on_pdf'];
String get signatureOnPdfHelp => String get signatureOnPdfHelp =>
_localizedValues[localeCode]['signature_on_pdf_help']; _localizedValues[localeCode]['signature_on_pdf_help'];
String get customCss => String get customCss => _localizedValues[localeCode]['custom_css'];
_localizedValues[localeCode]['custom_css'];
String get customJavascript => String get customJavascript =>
_localizedValues[localeCode]['custom_javascript']; _localizedValues[localeCode]['custom_javascript'];
String get messages => String get messages => _localizedValues[localeCode]['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) { String lookup(String key) {
final lookupKey = toSnakeCase(key); final lookupKey = toSnakeCase(key);