Add ‘Configure gateways’ button to Settings > Online Payments
This commit is contained in:
parent
8b03373297
commit
0810ab02d7
|
|
@ -291,19 +291,6 @@ const kAgeGroups = {
|
||||||
kAgeGroup120: 120,
|
kAgeGroup120: 120,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
const String kEmailTemplateInvoice = 'invoice_email';
|
|
||||||
const String kEmailTemplateQuote = 'quote_email';
|
|
||||||
const String kEmailTemplatePayment = 'payment_email';
|
|
||||||
const String kEmailTemplateReminder1 = 'first_reminder';
|
|
||||||
const String kEmailTemplateReminder2 = 'second_reminder';
|
|
||||||
const String kEmailTemplateReminder3 = 'third_reminder';
|
|
||||||
const String kEmailTemplateReminder4 = 'endless_reminder';
|
|
||||||
const String kEmailTemplateCustom1 = 'first_custom';
|
|
||||||
const String kEmailTemplateCustom2 = 'second_custom';
|
|
||||||
const String kEmailTemplateCustom3 = 'third_custom';
|
|
||||||
*/
|
|
||||||
|
|
||||||
const String kReminderScheduleAfterInvoiceDate = 'after_invoice_date';
|
const String kReminderScheduleAfterInvoiceDate = 'after_invoice_date';
|
||||||
const String kReminderScheduleBeforeDueDate = 'before_due_date';
|
const String kReminderScheduleBeforeDueDate = 'before_due_date';
|
||||||
const String kReminderScheduleAfterDueDate = 'after_due_date';
|
const String kReminderScheduleAfterDueDate = 'after_due_date';
|
||||||
|
|
@ -315,8 +302,9 @@ const String kSettingsPaymentTermEdit = 'payment_term_edit';
|
||||||
const String kSettingsUserDetails = 'user_details';
|
const String kSettingsUserDetails = 'user_details';
|
||||||
const String kSettingsLocalization = 'localization';
|
const String kSettingsLocalization = 'localization';
|
||||||
const String kSettingsOnlinePayments = 'online_payments';
|
const String kSettingsOnlinePayments = 'online_payments';
|
||||||
const String kSettingsOnlinePaymentsView = 'online_payments_view';
|
const String kSettingsCompanyGateways = 'company_gateways';
|
||||||
const String kSettingsOnlinePaymentsEdit = 'online_payments_edit';
|
const String kSettingsCompanyGatewaysView = 'company_gateways_view';
|
||||||
|
const String kSettingsCompanyGatewaysEdit = 'company_gateways_edit';
|
||||||
const String kSettingsTaxSettings = 'tax_settings';
|
const String kSettingsTaxSettings = 'tax_settings';
|
||||||
const String kSettingsTaxRates = 'tax_settings_rates';
|
const String kSettingsTaxRates = 'tax_settings_rates';
|
||||||
const String kSettingsTaxRatesView = 'tax_settings_rates_view';
|
const String kSettingsTaxRatesView = 'tax_settings_rates_view';
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import 'package:invoiceninja_flutter/ui/reports/reports_screen.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/reports/reports_screen_vm.dart';
|
import 'package:invoiceninja_flutter/ui/reports/reports_screen_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/account_management_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/account_management_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/expense_settings_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/expense_settings_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart';
|
||||||
|
|
@ -292,7 +293,6 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
||||||
RecurringInvoiceViewScreen(),
|
RecurringInvoiceViewScreen(),
|
||||||
RecurringInvoiceEditScreen.route: (context) =>
|
RecurringInvoiceEditScreen.route: (context) =>
|
||||||
RecurringInvoiceEditScreen(),
|
RecurringInvoiceEditScreen(),
|
||||||
|
|
||||||
WebhookScreen.route: (context) => WebhookScreenBuilder(),
|
WebhookScreen.route: (context) => WebhookScreenBuilder(),
|
||||||
WebhookViewScreen.route: (context) => WebhookViewScreen(),
|
WebhookViewScreen.route: (context) => WebhookViewScreen(),
|
||||||
WebhookEditScreen.route: (context) => WebhookEditScreen(),
|
WebhookEditScreen.route: (context) => WebhookEditScreen(),
|
||||||
|
|
@ -323,6 +323,8 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
||||||
CompanyDetailsScreen(),
|
CompanyDetailsScreen(),
|
||||||
UserDetailsScreen.route: (context) => UserDetailsScreen(),
|
UserDetailsScreen.route: (context) => UserDetailsScreen(),
|
||||||
LocalizationScreen.route: (context) => LocalizationScreen(),
|
LocalizationScreen.route: (context) => LocalizationScreen(),
|
||||||
|
OnlinePaymentsScreen.route: (context) =>
|
||||||
|
OnlinePaymentsScreen(),
|
||||||
CompanyGatewayScreen.route: (context) =>
|
CompanyGatewayScreen.route: (context) =>
|
||||||
CompanyGatewayScreenBuilder(),
|
CompanyGatewayScreenBuilder(),
|
||||||
CompanyGatewayViewScreen.route: (context) =>
|
CompanyGatewayViewScreen.route: (context) =>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import 'package:invoiceninja_flutter/ui/reports/reports_screen.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/reports/reports_screen_vm.dart';
|
import 'package:invoiceninja_flutter/ui/reports/reports_screen_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/account_management_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/account_management_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/expense_settings_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/expense_settings_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
|
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/token/edit/token_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/token/edit/token_edit_vm.dart';
|
||||||
|
|
@ -380,12 +381,15 @@ class SettingsScreens extends StatelessWidget {
|
||||||
screen = LocalizationScreen();
|
screen = LocalizationScreen();
|
||||||
break;
|
break;
|
||||||
case kSettingsOnlinePayments:
|
case kSettingsOnlinePayments:
|
||||||
|
screen = OnlinePaymentsScreen();
|
||||||
|
break;
|
||||||
|
case kSettingsCompanyGateways:
|
||||||
screen = CompanyGatewayScreenBuilder();
|
screen = CompanyGatewayScreenBuilder();
|
||||||
break;
|
break;
|
||||||
case kSettingsOnlinePaymentsView:
|
case kSettingsCompanyGatewaysView:
|
||||||
screen = CompanyGatewayViewScreen();
|
screen = CompanyGatewayViewScreen();
|
||||||
break;
|
break;
|
||||||
case kSettingsOnlinePaymentsEdit:
|
case kSettingsCompanyGatewaysEdit:
|
||||||
screen = CompanyGatewayEditScreen();
|
screen = CompanyGatewayEditScreen();
|
||||||
break;
|
break;
|
||||||
case kSettingsTaxSettings:
|
case kSettingsTaxSettings:
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/company_model.dart';
|
|
||||||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_state.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/bool_dropdown_button.dart';
|
|
||||||
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
|
||||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_item.dart';
|
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_item.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_vm.dart';
|
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
|
||||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
|
||||||
|
|
||||||
class CompanyGatewayList extends StatelessWidget {
|
class CompanyGatewayList extends StatelessWidget {
|
||||||
const CompanyGatewayList({
|
const CompanyGatewayList({
|
||||||
|
|
@ -29,190 +21,38 @@ class CompanyGatewayList extends StatelessWidget {
|
||||||
final listUIState = state.uiState.companyGatewayUIState.listUIState;
|
final listUIState = state.uiState.companyGatewayUIState.listUIState;
|
||||||
final isInMultiselect = listUIState.isInMultiselect();
|
final isInMultiselect = listUIState.isInMultiselect();
|
||||||
|
|
||||||
return ListView(
|
return !viewModel.state.isLoaded && viewModel.companyGatewayList.isEmpty
|
||||||
shrinkWrap: true,
|
? LoadingIndicator()
|
||||||
children: <Widget>[
|
: RefreshIndicator(
|
||||||
_OnlinePaymentForm(
|
onRefresh: () => viewModel.onRefreshed(context),
|
||||||
viewModel: viewModel,
|
child: ReorderableListView(
|
||||||
key: ValueKey('__settings_${state.settingsUIState.updatedAt}__'),
|
onReorder: (oldIndex, newIndex) {
|
||||||
),
|
// https://stackoverflow.com/a/54164333/497368
|
||||||
!viewModel.state.isLoaded && viewModel.companyGatewayList.isEmpty
|
// These two lines are workarounds for ReorderableListView problems
|
||||||
? LoadingIndicator()
|
if (newIndex > viewModel.companyGatewayList.length) {
|
||||||
: RefreshIndicator(
|
newIndex = viewModel.companyGatewayList.length;
|
||||||
onRefresh: () => viewModel.onRefreshed(context),
|
}
|
||||||
child: SizedBox(
|
if (oldIndex < newIndex) {
|
||||||
height: (viewModel.companyGatewayList.length * 80.0) + 100,
|
newIndex--;
|
||||||
child: ReorderableListView(
|
}
|
||||||
onReorder: (oldIndex, newIndex) {
|
|
||||||
// https://stackoverflow.com/a/54164333/497368
|
|
||||||
// These two lines are workarounds for ReorderableListView problems
|
|
||||||
if (newIndex > viewModel.companyGatewayList.length) {
|
|
||||||
newIndex = viewModel.companyGatewayList.length;
|
|
||||||
}
|
|
||||||
if (oldIndex < newIndex) {
|
|
||||||
newIndex--;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.onSortChanged(oldIndex, newIndex);
|
viewModel.onSortChanged(oldIndex, newIndex);
|
||||||
},
|
},
|
||||||
children:
|
children: viewModel.companyGatewayList.map((companyGatewayId) {
|
||||||
viewModel.companyGatewayList.map((companyGatewayId) {
|
final companyGateway =
|
||||||
final companyGateway =
|
viewModel.companyGatewayMap[companyGatewayId];
|
||||||
viewModel.companyGatewayMap[companyGatewayId];
|
return CompanyGatewayListItem(
|
||||||
return CompanyGatewayListItem(
|
key: ValueKey('__company_gateway_$companyGatewayId'),
|
||||||
key: ValueKey('__company_gateway_$companyGatewayId'),
|
user: state.userCompany.user,
|
||||||
user: state.userCompany.user,
|
filter: viewModel.filter,
|
||||||
filter: viewModel.filter,
|
companyGateway: companyGateway,
|
||||||
companyGateway: companyGateway,
|
onRemovePressed: viewModel.state.settingsUIState.isFiltered
|
||||||
onRemovePressed: viewModel
|
? () => viewModel.onRemovePressed(companyGatewayId)
|
||||||
.state.settingsUIState.isFiltered
|
: null,
|
||||||
? () =>
|
isChecked: isInMultiselect &&
|
||||||
viewModel.onRemovePressed(companyGatewayId)
|
listUIState.isSelected(companyGateway.id));
|
||||||
: null,
|
}).toList(),
|
||||||
isChecked: isInMultiselect &&
|
),
|
||||||
listUIState.isSelected(companyGateway.id));
|
);
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _OnlinePaymentForm extends StatefulWidget {
|
|
||||||
const _OnlinePaymentForm({Key key, @required this.viewModel})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
final CompanyGatewayListVM viewModel;
|
|
||||||
|
|
||||||
@override
|
|
||||||
__OnlinePaymentFormState createState() => __OnlinePaymentFormState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class __OnlinePaymentFormState extends State<_OnlinePaymentForm> {
|
|
||||||
static final GlobalKey<FormState> _formKey =
|
|
||||||
GlobalKey<FormState>(debugLabel: '_companyGatewayList');
|
|
||||||
final FocusScopeNode _focusNode = FocusScopeNode();
|
|
||||||
|
|
||||||
//bool _autoValidate = false;
|
|
||||||
|
|
||||||
final _minimumAmountController = TextEditingController();
|
|
||||||
|
|
||||||
List<TextEditingController> _controllers = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
_controllers = [
|
|
||||||
_minimumAmountController,
|
|
||||||
];
|
|
||||||
|
|
||||||
_controllers
|
|
||||||
.forEach((dynamic controller) => controller.removeListener(_onChanged));
|
|
||||||
|
|
||||||
_minimumAmountController.text = formatNumber(
|
|
||||||
widget.viewModel.settings.clientPortalUnderPaymentMinimum, context,
|
|
||||||
formatNumberType: FormatNumberType.inputMoney);
|
|
||||||
|
|
||||||
_controllers
|
|
||||||
.forEach((dynamic controller) => controller.addListener(_onChanged));
|
|
||||||
|
|
||||||
super.didChangeDependencies();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controllers.forEach((dynamic controller) {
|
|
||||||
controller.removeListener(_onChanged);
|
|
||||||
controller.dispose();
|
|
||||||
});
|
|
||||||
_focusNode.dispose();
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onChanged() {
|
|
||||||
final viewModel = widget.viewModel;
|
|
||||||
final settings = viewModel.settings.rebuild((b) => b
|
|
||||||
..clientPortalUnderPaymentMinimum =
|
|
||||||
parseDouble(_minimumAmountController.text));
|
|
||||||
if (settings != viewModel.settings) {
|
|
||||||
viewModel.onSettingsChanged(settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final localization = AppLocalization.of(context);
|
|
||||||
final viewModel = widget.viewModel;
|
|
||||||
final company = viewModel.state.settingsUIState.company;
|
|
||||||
final settings = viewModel.settings;
|
|
||||||
|
|
||||||
return AppForm(
|
|
||||||
formKey: _formKey,
|
|
||||||
focusNode: _focusNode,
|
|
||||||
child: FormCard(children: [
|
|
||||||
AppDropdownButton<String>(
|
|
||||||
labelText: localization.autoBillOn,
|
|
||||||
value: settings.autoBillDate,
|
|
||||||
onChanged: (dynamic value) => viewModel.onSettingsChanged(
|
|
||||||
settings.rebuild((b) => b..autoBillDate = value)),
|
|
||||||
items: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(localization.sendDate),
|
|
||||||
value: SettingsEntity.AUTO_BILL_ON_SEND_DATE,
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(localization.dueDate),
|
|
||||||
value: SettingsEntity.AUTO_BILL_ON_DUE_DATE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AppDropdownButton<String>(
|
|
||||||
labelText: localization.useAvailableCredits,
|
|
||||||
value: company.useCreditsPayment,
|
|
||||||
onChanged: (dynamic value) {
|
|
||||||
viewModel.onCompanyChanged(
|
|
||||||
company.rebuild((b) => b..useCreditsPayment = value));
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(localization.always),
|
|
||||||
value: CompanyEntity.USE_CREDITS_ALWAYS,
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(localization.showOption),
|
|
||||||
value: CompanyEntity.USE_CREDITS_OPTION,
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(localization.off),
|
|
||||||
value: CompanyEntity.USE_CREDITS_OFF,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
BoolDropdownButton(
|
|
||||||
label: localization.allowOverPayment,
|
|
||||||
value: settings.clientPortalAllowOverPayment,
|
|
||||||
helpLabel: localization.allowOverPaymentHelp,
|
|
||||||
onChanged: (value) => viewModel.onSettingsChanged(
|
|
||||||
settings.rebuild((b) => b..clientPortalAllowOverPayment = value)),
|
|
||||||
),
|
|
||||||
BoolDropdownButton(
|
|
||||||
label: localization.allowUnderPayment,
|
|
||||||
value: settings.clientPortalAllowUnderPayment,
|
|
||||||
helpLabel: localization.allowUnderPaymentHelp,
|
|
||||||
onChanged: (value) => viewModel.onSettingsChanged(settings
|
|
||||||
.rebuild((b) => b..clientPortalAllowUnderPayment = value)),
|
|
||||||
),
|
|
||||||
if (settings.clientPortalAllowUnderPayment == true)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16),
|
|
||||||
child: DecoratedFormField(
|
|
||||||
label: localization.minimumUnderPaymentAmount,
|
|
||||||
controller: _minimumAmountController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:invoiceninja_flutter/data/models/company_gateway_model.dart';
|
import 'package:invoiceninja_flutter/data/models/company_gateway_model.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
|
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
|
|
||||||
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
|
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -45,9 +44,6 @@ class CompanyGatewayListVM {
|
||||||
@required this.onRefreshed,
|
@required this.onRefreshed,
|
||||||
@required this.onSortChanged,
|
@required this.onSortChanged,
|
||||||
@required this.onRemovePressed,
|
@required this.onRemovePressed,
|
||||||
@required this.onSettingsChanged,
|
|
||||||
@required this.settings,
|
|
||||||
@required this.onCompanyChanged,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static CompanyGatewayListVM fromStore(Store<AppState> store) {
|
static CompanyGatewayListVM fromStore(Store<AppState> store) {
|
||||||
|
|
@ -81,9 +77,6 @@ class CompanyGatewayListVM {
|
||||||
state: state,
|
state: state,
|
||||||
listState: state.companyGatewayListState,
|
listState: state.companyGatewayListState,
|
||||||
companyGatewayList: gatewayIds,
|
companyGatewayList: gatewayIds,
|
||||||
settings: state.uiState.settingsUIState.settings,
|
|
||||||
onCompanyChanged: (company) =>
|
|
||||||
store.dispatch(UpdateCompany(company: company)),
|
|
||||||
companyGatewayMap: state.companyGatewayState.map,
|
companyGatewayMap: state.companyGatewayState.map,
|
||||||
filter: state.companyGatewayUIState.listUIState.filter,
|
filter: state.companyGatewayUIState.listUIState.filter,
|
||||||
onCompanyGatewayTap: (context, companyGateway) {
|
onCompanyGatewayTap: (context, companyGateway) {
|
||||||
|
|
@ -111,8 +104,6 @@ class CompanyGatewayListVM {
|
||||||
.rebuild((b) => b..companyGatewayIds = gatewayIds.join(','));
|
.rebuild((b) => b..companyGatewayIds = gatewayIds.join(','));
|
||||||
store.dispatch(UpdateSettings(settings: settings));
|
store.dispatch(UpdateSettings(settings: settings));
|
||||||
},
|
},
|
||||||
onSettingsChanged: (settings) =>
|
|
||||||
store.dispatch(UpdateSettings(settings: settings)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +116,4 @@ class CompanyGatewayListVM {
|
||||||
final Function(BuildContext) onRefreshed;
|
final Function(BuildContext) onRefreshed;
|
||||||
final Function(int, int) onSortChanged;
|
final Function(int, int) onSortChanged;
|
||||||
final Function(String) onRemovePressed;
|
final Function(String) onRemovePressed;
|
||||||
final SettingsEntity settings;
|
|
||||||
final Function(SettingsEntity) onSettingsChanged;
|
|
||||||
final Function(CompanyEntity) onCompanyChanged;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class CompanyGatewayScreen extends StatelessWidget {
|
||||||
@required this.viewModel,
|
@required this.viewModel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
static const String route = '/$kSettings/$kSettingsOnlinePayments';
|
static const String route = '/$kSettings/$kSettingsCompanyGateways';
|
||||||
|
|
||||||
final CompanyGatewayScreenVM viewModel;
|
final CompanyGatewayScreenVM viewModel;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
|
||||||
class CompanyGatewayEditScreen extends StatelessWidget {
|
class CompanyGatewayEditScreen extends StatelessWidget {
|
||||||
const CompanyGatewayEditScreen({Key key}) : super(key: key);
|
const CompanyGatewayEditScreen({Key key}) : super(key: key);
|
||||||
static const String route = '/$kSettings/$kSettingsOnlinePaymentsEdit';
|
static const String route = '/$kSettings/$kSettingsCompanyGatewaysEdit';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class CompanyGatewayViewScreen extends StatelessWidget {
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
final bool isFilter;
|
final bool isFilter;
|
||||||
|
|
||||||
static const String route = '/$kSettings/$kSettingsOnlinePaymentsView';
|
static const String route = '/$kSettings/$kSettingsCompanyGatewaysView';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/company_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.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/bool_dropdown_button.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/settings/online_payments_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
|
class OnlinePayments extends StatefulWidget {
|
||||||
|
const OnlinePayments({
|
||||||
|
Key key,
|
||||||
|
@required this.viewModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final OnlinePaymentsVM viewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_OnlinePaymentsState createState() => _OnlinePaymentsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OnlinePaymentsState extends State<OnlinePayments> {
|
||||||
|
static final GlobalKey<FormState> _formKey =
|
||||||
|
GlobalKey<FormState>(debugLabel: '_onlinePayments');
|
||||||
|
FocusScopeNode _focusNode;
|
||||||
|
final _minimumAmountController = TextEditingController();
|
||||||
|
List<TextEditingController> _controllers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusNode = FocusScopeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
_controllers = [
|
||||||
|
_minimumAmountController,
|
||||||
|
];
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.removeListener(_onChanged));
|
||||||
|
|
||||||
|
_minimumAmountController.text = formatNumber(
|
||||||
|
widget.viewModel.settings.clientPortalUnderPaymentMinimum, context,
|
||||||
|
formatNumberType: FormatNumberType.inputMoney);
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.addListener(_onChanged));
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChanged() {
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final settings = viewModel.settings.rebuild((b) => b
|
||||||
|
..clientPortalUnderPaymentMinimum =
|
||||||
|
parseDouble(_minimumAmountController.text));
|
||||||
|
if (settings != viewModel.settings) {
|
||||||
|
viewModel.onSettingsChanged(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final company = viewModel.company;
|
||||||
|
final settings = viewModel.settings;
|
||||||
|
|
||||||
|
return EditScaffold(
|
||||||
|
title: localization.onlinePayments,
|
||||||
|
onSavePressed: viewModel.onSavePressed,
|
||||||
|
body: AppForm(
|
||||||
|
formKey: _formKey,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
children: <Widget>[
|
||||||
|
FormCard(children: <Widget>[
|
||||||
|
AppDropdownButton<String>(
|
||||||
|
labelText: localization.autoBillOn,
|
||||||
|
value: settings.autoBillDate,
|
||||||
|
onChanged: (dynamic value) => viewModel.onSettingsChanged(
|
||||||
|
settings.rebuild((b) => b..autoBillDate = value)),
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(localization.sendDate),
|
||||||
|
value: SettingsEntity.AUTO_BILL_ON_SEND_DATE,
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(localization.dueDate),
|
||||||
|
value: SettingsEntity.AUTO_BILL_ON_DUE_DATE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AppDropdownButton<String>(
|
||||||
|
labelText: localization.useAvailableCredits,
|
||||||
|
value: company.useCreditsPayment,
|
||||||
|
onChanged: (dynamic value) {
|
||||||
|
viewModel.onCompanyChanged(
|
||||||
|
company.rebuild((b) => b..useCreditsPayment = value));
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(localization.always),
|
||||||
|
value: CompanyEntity.USE_CREDITS_ALWAYS,
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(localization.showOption),
|
||||||
|
value: CompanyEntity.USE_CREDITS_OPTION,
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text(localization.off),
|
||||||
|
value: CompanyEntity.USE_CREDITS_OFF,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
BoolDropdownButton(
|
||||||
|
label: localization.allowOverPayment,
|
||||||
|
value: settings.clientPortalAllowOverPayment,
|
||||||
|
helpLabel: localization.allowOverPaymentHelp,
|
||||||
|
onChanged: (value) => viewModel.onSettingsChanged(settings
|
||||||
|
.rebuild((b) => b..clientPortalAllowOverPayment = value)),
|
||||||
|
),
|
||||||
|
BoolDropdownButton(
|
||||||
|
label: localization.allowUnderPayment,
|
||||||
|
value: settings.clientPortalAllowUnderPayment,
|
||||||
|
helpLabel: localization.allowUnderPaymentHelp,
|
||||||
|
onChanged: (value) => viewModel.onSettingsChanged(settings
|
||||||
|
.rebuild((b) => b..clientPortalAllowUnderPayment = value)),
|
||||||
|
),
|
||||||
|
if (settings.clientPortalAllowUnderPayment == true)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16),
|
||||||
|
child: DecoratedFormField(
|
||||||
|
label: localization.minimumUnderPaymentAmount,
|
||||||
|
controller: _minimumAmountController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: AppButton(
|
||||||
|
iconData: Icons.settings,
|
||||||
|
label: localization.configureGateways.toUpperCase(),
|
||||||
|
onPressed: () => viewModel.onConfigureGatewaysPressed(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
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/client_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/company_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/entities.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/group_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/company/company_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/online_payments.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 OnlinePaymentsScreen extends StatelessWidget {
|
||||||
|
const OnlinePaymentsScreen({Key key}) : super(key: key);
|
||||||
|
static const String route = '/$kSettings/$kSettingsOnlinePayments';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreConnector<AppState, OnlinePaymentsVM>(
|
||||||
|
converter: OnlinePaymentsVM.fromStore,
|
||||||
|
builder: (context, viewModel) {
|
||||||
|
return OnlinePayments(
|
||||||
|
viewModel: viewModel,
|
||||||
|
key: ValueKey(viewModel.state.settingsUIState.updatedAt),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OnlinePaymentsVM {
|
||||||
|
OnlinePaymentsVM({
|
||||||
|
@required this.state,
|
||||||
|
@required this.company,
|
||||||
|
@required this.onCompanyChanged,
|
||||||
|
@required this.onSavePressed,
|
||||||
|
@required this.onSettingsChanged,
|
||||||
|
@required this.settings,
|
||||||
|
@required this.onConfigureGatewaysPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
static OnlinePaymentsVM fromStore(Store<AppState> store) {
|
||||||
|
final state = store.state;
|
||||||
|
|
||||||
|
return OnlinePaymentsVM(
|
||||||
|
state: state,
|
||||||
|
company: state.uiState.settingsUIState.company,
|
||||||
|
settings: state.uiState.settingsUIState.settings,
|
||||||
|
onCompanyChanged: (company) =>
|
||||||
|
store.dispatch(UpdateCompany(company: company)),
|
||||||
|
onSettingsChanged: (settings) =>
|
||||||
|
store.dispatch(UpdateSettings(settings: settings)),
|
||||||
|
onSavePressed: (context) {
|
||||||
|
final settingsUIState = state.uiState.settingsUIState;
|
||||||
|
switch (settingsUIState.entityType) {
|
||||||
|
case EntityType.company:
|
||||||
|
final completer = snackBarCompleter<Null>(
|
||||||
|
context, AppLocalization.of(context).savedSettings);
|
||||||
|
store.dispatch(SaveCompanyRequest(
|
||||||
|
completer: completer, company: settingsUIState.company));
|
||||||
|
break;
|
||||||
|
case EntityType.group:
|
||||||
|
final completer = snackBarCompleter<GroupEntity>(
|
||||||
|
context, AppLocalization.of(context).savedSettings);
|
||||||
|
store.dispatch(SaveGroupRequest(
|
||||||
|
completer: completer, group: settingsUIState.group));
|
||||||
|
break;
|
||||||
|
case EntityType.client:
|
||||||
|
final completer = snackBarCompleter<ClientEntity>(
|
||||||
|
context, AppLocalization.of(context).savedSettings);
|
||||||
|
store.dispatch(SaveClientRequest(
|
||||||
|
completer: completer, client: settingsUIState.client));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfigureGatewaysPressed: (context) {
|
||||||
|
store.dispatch(ViewSettings(
|
||||||
|
navigator: Navigator.of(context),
|
||||||
|
section: kSettingsCompanyGateways));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppState state;
|
||||||
|
final CompanyEntity company;
|
||||||
|
final SettingsEntity settings;
|
||||||
|
final Function(BuildContext) onSavePressed;
|
||||||
|
final Function(CompanyEntity) onCompanyChanged;
|
||||||
|
final Function(SettingsEntity) onSettingsChanged;
|
||||||
|
final Function(BuildContext) onConfigureGatewaysPressed;
|
||||||
|
}
|
||||||
|
|
@ -284,7 +284,7 @@ class SettingsSearch extends StatelessWidget {
|
||||||
'first_month_of_the_year',
|
'first_month_of_the_year',
|
||||||
'custom_labels',
|
'custom_labels',
|
||||||
],
|
],
|
||||||
kSettingsOnlinePayments: [
|
kSettingsCompanyGateways: [
|
||||||
'accepted_card_logos',
|
'accepted_card_logos',
|
||||||
'limits_and_fees',
|
'limits_and_fees',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -570,7 +570,8 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
'invoice_tax_rates': 'Invoice Tax Rates',
|
'invoice_tax_rates': 'Invoice Tax Rates',
|
||||||
'item_tax_rates': 'Item Tax Rates',
|
'item_tax_rates': 'Item Tax Rates',
|
||||||
'no_client_selected': 'No client selected',
|
'no_client_selected': 'No client selected',
|
||||||
'configure_rates': 'Configure rates',
|
'configure_rates': 'Configure Rates',
|
||||||
|
'configure_gateways': 'Configure Gateways',
|
||||||
'tax_settings': 'Tax Settings',
|
'tax_settings': 'Tax Settings',
|
||||||
'tax_settings_rates': 'Tax Rates',
|
'tax_settings_rates': 'Tax Rates',
|
||||||
'accent_color': 'Accent Color',
|
'accent_color': 'Accent Color',
|
||||||
|
|
@ -3128,6 +3129,9 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
String get configureRates =>
|
String get configureRates =>
|
||||||
_localizedValues[localeCode]['configure_rates'] ?? '';
|
_localizedValues[localeCode]['configure_rates'] ?? '';
|
||||||
|
|
||||||
|
String get configureGateways =>
|
||||||
|
_localizedValues[localeCode]['configure_gateways'] ?? '';
|
||||||
|
|
||||||
String get taxSettingsRates =>
|
String get taxSettingsRates =>
|
||||||
_localizedValues[localeCode]['tax_settings_rates'] ?? '';
|
_localizedValues[localeCode]['tax_settings_rates'] ?? '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ IconData getSettingIcon(String section) {
|
||||||
case kSettingsLocalization:
|
case kSettingsLocalization:
|
||||||
return Icons.language;
|
return Icons.language;
|
||||||
case kSettingsOnlinePayments:
|
case kSettingsOnlinePayments:
|
||||||
|
case kSettingsCompanyGateways:
|
||||||
return MdiIcons.creditCard;
|
return MdiIcons.creditCard;
|
||||||
case kSettingsTaxSettings:
|
case kSettingsTaxSettings:
|
||||||
case kSettingsTaxRates:
|
case kSettingsTaxRates:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue