// Flutter imports: import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; // Package imports: import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/company_gateway_model.dart'; import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/redux/static/static_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart'; import 'package:invoiceninja_flutter/ui/app/entity_dropdown.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/color_picker.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/app/forms/learn_more.dart'; import 'package:invoiceninja_flutter/ui/app/help_text.dart'; import 'package:invoiceninja_flutter/ui/app/icon_text.dart'; import 'package:invoiceninja_flutter/ui/app/invoice/tax_rate_dropdown.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/company_gateway/edit/company_gateway_edit_vm.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/strings.dart'; class CompanyGatewayEdit extends StatefulWidget { const CompanyGatewayEdit({ Key key, @required this.viewModel, }) : super(key: key); final CompanyGatewayEditVM viewModel; @override _CompanyGatewayEditState createState() => _CompanyGatewayEditState(); } class _CompanyGatewayEditState extends State with SingleTickerProviderStateMixin { static final GlobalKey _formKey = GlobalKey(debugLabel: '_companyGatewayEdit'); final FocusScopeNode _focusNode = FocusScopeNode(); TabController _controller; // ignore: unused_field String _gatewayTypeId = kGatewayTypeCreditCard; @override void initState() { super.initState(); _controller = TabController(vsync: this, length: 3); } @override void didChangeDependencies() { final companyGateway = widget.viewModel.companyGateway; final gateway = widget.viewModel.state.staticState.gatewayMap[companyGateway.gatewayId]; final enabledGatewayIds = (gateway?.options?.keys ?? []).where( (gatewayTypeId) => companyGateway .getSettingsForGatewayTypeId(gatewayTypeId) .isEnabled); if (enabledGatewayIds.isNotEmpty) { _gatewayTypeId = enabledGatewayIds.first; } else { _gatewayTypeId = gateway?.defaultGatewayTypeId ?? kGatewayTypeCreditCard; } super.didChangeDependencies(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final viewModel = widget.viewModel; final state = viewModel.state; final localization = AppLocalization.of(context); final companyGateway = viewModel.companyGateway; final origCompanyGateway = state.companyGatewayState.get(companyGateway.id); final gateway = state.staticState.gatewayMap[companyGateway.gatewayId]; final accountId = (companyGateway.parsedConfig['account_id'] ?? '').toString(); final connectGateways = [ kGatewayStripeConnect, kGatewayWePay, ]; final disableSave = (connectGateways.contains(companyGateway.gatewayId) && companyGateway.isNew) || state.isDemo; final enabledGatewayIds = (gateway?.options?.keys ?? []).where( (gatewayTypeId) => companyGateway .getSettingsForGatewayTypeId(gatewayTypeId) .isEnabled); return EditScaffold( entity: companyGateway, title: viewModel.companyGateway.isNew ? localization.newCompanyGateway : origCompanyGateway.listDisplayName, onSavePressed: disableSave ? null : viewModel.onSavePressed, onCancelPressed: viewModel.onCancelPressed, appBarBottom: TabBar( key: ValueKey(state.settingsUIState.updatedAt), controller: _controller, isScrollable: isMobile(context), tabs: [ Tab( text: localization.credentials, ), Tab( text: localization.settings, ), Tab( text: localization.limitsAndFees, ), ], ), body: AppTabForm( formKey: _formKey, focusNode: _focusNode, tabController: _controller, children: [ ScrollableListView( children: [ FormCard( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (companyGateway.isNew) EntityDropdown( key: ValueKey('__gateway_${companyGateway.gatewayId}__'), entityType: EntityType.gateway, entityList: memoizedGatewayList(state.staticState.gatewayMap), labelText: localization.provider, entityId: companyGateway.gatewayId, onSelected: (SelectableEntity gateway) { gateway ??= GatewayEntity(); viewModel.onChanged( companyGateway.rebuild((b) => b ..feesAndLimitsMap[(gateway as GatewayEntity) .defaultGatewayTypeId ?? kGatewayTypeCreditCard] = FeesAndLimitsSettings(isEnabled: true) ..gatewayId = gateway.id ..config = '{}' ..label = gateway.listDisplayName), ); }, ), if (connectGateways.contains(companyGateway.gatewayId)) if (companyGateway.isNew || accountId.isEmpty) ...[ AppButton( label: localization.gatewaySetup.toUpperCase(), onPressed: viewModel.state.isSaving ? null : () { viewModel.onCancelPressed(context); viewModel.onGatewaySignUpPressed( companyGateway.gatewayId); }, ), if (companyGateway.gatewayId == kGatewayStripeConnect) Padding( padding: const EdgeInsets.only(top: 20), child: OutlinedButton( onPressed: () => launch(kDocsStripeConnectUrl), child: Padding( padding: const EdgeInsets.all(8.0), child: IconText( icon: MdiIcons.openInNew, text: localization.learnMore.toUpperCase(), ), ), ), ), ] else DecoratedFormField( enabled: false, label: localization.accountId, initialValue: accountId, ) else GatewayConfigSettings( key: ValueKey('__${companyGateway.gatewayId}__'), companyGateway: companyGateway, viewModel: viewModel, ), ], ), ], ), ScrollableListView( children: [ FormCard(children: [ if (companyGateway.gatewayId != kGatewayCustom) DecoratedFormField( label: localization.label, initialValue: companyGateway.label, onChanged: (String value) => viewModel.onChanged( companyGateway.rebuild((b) => b..label = value.trim())), ), if (state.staticState.gatewayMap[companyGateway.gatewayId] ?.supportsTokenBilling == true) AppDropdownButton( labelText: localization.captureCard, value: companyGateway.tokenBilling, onChanged: (dynamic value) => viewModel.onChanged( companyGateway.rebuild((b) => b..tokenBilling = value)), items: [ CompanyGatewayEntity.TOKEN_BILLING_ALWAYS, CompanyGatewayEntity.TOKEN_BILLING_OPT_OUT, CompanyGatewayEntity.TOKEN_BILLING_OPT_IN, CompanyGatewayEntity.TOKEN_BILLING_DISABLED ] .map((value) => DropdownMenuItem( child: Text(localization.lookup(value)), value: value, )) .toList(), ), SizedBox(height: 16), for (var gatewayTypeId in gateway?.options?.keys ?? []) SwitchListTile( title: Text(localization .lookup(kGatewayTypes[gatewayTypeId] ?? '')), activeColor: Theme.of(context).colorScheme.secondary, value: companyGateway .getSettingsForGatewayTypeId(gatewayTypeId) .isEnabled, onChanged: (value) { final settings = companyGateway .getSettingsForGatewayTypeId(gatewayTypeId); viewModel.onChanged(companyGateway.rebuild((b) => b ..feesAndLimitsMap[gatewayTypeId] = settings.rebuild((b) => b..isEnabled = value))); }), ]), FormCard( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16), child: Text( localization.requiredFields, style: Theme.of(context).textTheme.headline6, ), ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.clientName), value: companyGateway.requireClientName, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireClientName = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.clientPhone), value: companyGateway.requireClientPhone, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireClientPhone = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.contactName), value: companyGateway.requireContactName, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireContactName = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.contactEmail), value: companyGateway.requireContactEmail, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireContactEmail = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.postalCode), value: companyGateway.requirePostalCode, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requirePostalCode = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.cvv), value: companyGateway.requireCvv, onChanged: (value) => viewModel.onChanged( companyGateway.rebuild((b) => b..requireCvv = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.billingAddress), value: companyGateway.requireBillingAddress, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireBillingAddress = value)), controlAffinity: ListTileControlAffinity.leading, ), CheckboxListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.shippingAddress), value: companyGateway.requireShippingAddress, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..requireShippingAddress = value)), controlAffinity: ListTileControlAffinity.leading, ), SizedBox(height: 16), SwitchListTile( activeColor: Theme.of(context).colorScheme.secondary, title: Text(localization.updateAddress), subtitle: Text(localization.updateAddressHelp), value: companyGateway.updateDetails, onChanged: (value) => viewModel.onChanged(companyGateway .rebuild((b) => b..updateDetails = value)), ), ], ), if (gateway?.isOffsite != true) FormCard( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16), child: Text( localization.acceptedCardLogos, style: Theme.of(context).textTheme.headline6, ), ), CardListTile( viewModel: viewModel, cardType: kCardTypeVisa, paymentType: kPaymentTypeVisa, ), CardListTile( viewModel: viewModel, cardType: kCardTypeMasterCard, paymentType: kPaymentTypeMasterCard, ), CardListTile( viewModel: viewModel, cardType: kCardTypeAmEx, paymentType: kPaymentTypeAmEx, ), CardListTile( viewModel: viewModel, cardType: kCardTypeDiscover, paymentType: kPaymentTypeDiscover, ), CardListTile( viewModel: viewModel, cardType: kCardTypeDiners, paymentType: kPaymentTypeDiners, ), ], ) ], ), if (enabledGatewayIds.isEmpty) Center( child: HelpText(localization.noPaymentTypesEnabled), ) else ScrollableListView( children: [ FormCard( children: [ AppDropdownButton( labelText: localization.paymentType, value: _gatewayTypeId, items: enabledGatewayIds .map((gatewayTypeId) => DropdownMenuItem( child: Text(localization.lookup( kGatewayTypes[gatewayTypeId] ?? '')), value: gatewayTypeId, )) .toList(), onChanged: (dynamic value) { setState(() { _gatewayTypeId = value; }); }, ), ], ), if (enabledGatewayIds.contains(_gatewayTypeId)) ...[ LimitEditor( key: ValueKey('__limits_${_gatewayTypeId}__'), gatewayTypeId: _gatewayTypeId, viewModel: viewModel, companyGateway: companyGateway, ), FeesEditor( key: ValueKey('__fees_${_gatewayTypeId}__'), gatewayTypeId: _gatewayTypeId, viewModel: viewModel, companyGateway: companyGateway, ), ], ], ), ], ), ); } } class CardListTile extends StatelessWidget { const CardListTile({this.viewModel, this.paymentType, this.cardType}); final CompanyGatewayEditVM viewModel; final String paymentType; final int cardType; @override Widget build(BuildContext context) { final staticState = viewModel.state.staticState; final companyGateway = viewModel.companyGateway; return CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, activeColor: Theme.of(context).colorScheme.secondary, title: Text(staticState.paymentTypeMap[paymentType]?.name ?? ''), value: companyGateway.supportsCard(cardType), onChanged: (value) => viewModel.onChanged(value ? companyGateway.addCard(cardType) : companyGateway.removeCard(cardType)), ); } } class GatewayConfigSettings extends StatelessWidget { const GatewayConfigSettings({Key key, this.companyGateway, this.viewModel}) : super(key: key); final CompanyGatewayEntity companyGateway; final CompanyGatewayEditVM viewModel; @override Widget build(BuildContext context) { final state = viewModel.state; final localization = AppLocalization.of(context); final gateway = state.staticState.gatewayMap[companyGateway.gatewayId]; if (gateway == null) { return SizedBox(); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (gateway.siteUrl.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 16), child: OutlinedButton( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: IconText( icon: MdiIcons.openInNew, text: localization.learnMore.toUpperCase(), ), ), onPressed: () => launch(gateway.siteUrl), ), ), ...gateway.parsedFields.keys .map((field) => GatewayConfigField( field: field, value: companyGateway.parsedConfig[field], gateway: gateway, defaultValue: gateway.parsedFields[field], onChanged: (dynamic value) { viewModel .onChanged(companyGateway.updateConfig(field, value)); }, )) .toList() ], ); } } class GatewayConfigField extends StatefulWidget { const GatewayConfigField({ Key key, @required this.gateway, @required this.field, @required this.value, @required this.defaultValue, @required this.onChanged, }) : super(key: key); final GatewayEntity gateway; final String field; final dynamic value; final dynamic defaultValue; final Function(dynamic) onChanged; @override _GatewayConfigFieldState createState() => _GatewayConfigFieldState(); } class _GatewayConfigFieldState extends State { bool autoValidate = false; TextEditingController _textController; final _debouncer = Debouncer(); @override void initState() { super.initState(); _textController = TextEditingController(); } @override void dispose() { _textController.dispose(); super.dispose(); } @override void didChangeDependencies() { _textController.removeListener(_onChanged); _textController.text = (widget.value ?? widget.defaultValue).toString(); _textController.addListener(_onChanged); super.didChangeDependencies(); } void _onChanged() { _debouncer.run(() { widget.onChanged(_textController.text.trim()); }); } bool _obscureText(String field) { bool obscure = false; ['password', 'secret', 'key'].forEach((word) { if (field.toLowerCase().contains(word)) { obscure = true; } }); return obscure; } @override Widget build(BuildContext context) { String label; if (widget.gateway.id == kGatewayStripe && widget.field == 'apiKey') { label = 'Secret Key'; } else { label = toTitleCase(widget.field); } if ('${widget.defaultValue}'.startsWith('[') && '${widget.defaultValue}'.endsWith(']')) { final options = '${widget.defaultValue}' .replaceFirst('[', '') .replaceFirst(']', '') .split(','); final dynamic value = (widget.value == null || widget.value == widget.defaultValue) ? '' : widget.value; return AppDropdownButton( labelText: toTitleCase(widget.field), value: value, onChanged: (dynamic value) => widget.onChanged(value), items: options .map((value) => DropdownMenuItem( child: Text(value.trim()), value: value.trim(), )) .toList(), ); } else if (widget.field.toLowerCase().contains('color')) { return FormColorPicker( initialValue: widget.value, labelText: toTitleCase(widget.field), onSelected: (value) => widget.onChanged(value), ); } else if (widget.defaultValue.runtimeType == bool) { return CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, activeColor: Theme.of(context).colorScheme.secondary, title: Text(toTitleCase(widget.field)), value: widget.value ?? false, onChanged: (value) => widget.onChanged(value), ); } else { return DecoratedFormField( controller: _textController, label: label, maxLines: widget.field == 'text' ? 6 : 1, onChanged: (value) => _onChanged(), obscureText: _obscureText(widget.field), keyboardType: widget.field == 'text' ? TextInputType.multiline : TextInputType.visiblePassword, ); } } } class LimitEditor extends StatefulWidget { const LimitEditor( {Key key, this.companyGateway, this.viewModel, this.gatewayTypeId}) : super(key: key); final CompanyGatewayEntity companyGateway; final CompanyGatewayEditVM viewModel; final String gatewayTypeId; @override _LimitEditorState createState() => _LimitEditorState(); } class _LimitEditorState extends State { final _debouncer = Debouncer(); bool _enableMin = false; bool _enableMax = false; TextEditingController _minController; TextEditingController _maxController; @override void initState() { super.initState(); _minController = TextEditingController(); _maxController = TextEditingController(); } @override void dispose() { _minController.dispose(); _maxController.dispose(); super.dispose(); } @override void didChangeDependencies() { _minController.removeListener(_onTextChange); _maxController.removeListener(_onTextChange); final companyGateway = widget.companyGateway; final settings = companyGateway.getSettingsForGatewayTypeId(widget.gatewayTypeId); if (settings.minLimit != -1) { _enableMin = true; } if (settings.maxLimit != -1) { _enableMax = true; } _minController.text = settings.minLimit == -1 ? '' : formatNumber((settings.minLimit ?? 0).toDouble(), context, formatNumberType: FormatNumberType.inputMoney); _maxController.text = settings.maxLimit == -1 ? '' : formatNumber((settings.maxLimit ?? 0).toDouble(), context, formatNumberType: FormatNumberType.inputMoney); _minController.addListener(_onTextChange); _maxController.addListener(_onTextChange); super.didChangeDependencies(); } void _onChanged() { final viewModel = widget.viewModel; final companyGateway = viewModel.companyGateway; final settings = companyGateway.getSettingsForGatewayTypeId(widget.gatewayTypeId); final updatedSettings = settings.rebuild((b) => b ..minLimit = _enableMin ? parseDouble(_minController.text.trim()) : -1 ..maxLimit = _enableMax ? parseDouble(_maxController.text.trim()) : -1); if (settings != updatedSettings) { viewModel.onChanged(companyGateway.rebuild( (b) => b..feesAndLimitsMap[widget.gatewayTypeId] = updatedSettings)); } } void _onTextChange() { _debouncer.run(() { _onChanged(); }); } @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); return FormCard( children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DecoratedFormField( label: localization.minLimit, enabled: _enableMin, controller: _minController, keyboardType: TextInputType.numberWithOptions( decimal: true, signed: true), autocorrect: false, ), SizedBox(height: 10), CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, contentPadding: const EdgeInsets.all(0), activeColor: Theme.of(context).colorScheme.secondary, title: Text(isDesktop(context) ? localization.enableMin : localization.enable), value: _enableMin, onChanged: (value) { setState(() { _enableMin = value; _onChanged(); if (!value) { _minController.text = ''; } }); }, ) ], ), ), SizedBox(width: 40), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DecoratedFormField( label: localization.maxLimit, enabled: _enableMax, controller: _maxController, keyboardType: TextInputType.numberWithOptions( decimal: true, signed: true), autocorrect: false, ), SizedBox(height: 10), CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, contentPadding: const EdgeInsets.all(0), activeColor: Theme.of(context).colorScheme.secondary, title: Text(isDesktop(context) ? localization.enableMax : localization.enable), value: _enableMax, onChanged: (value) { setState(() { _enableMax = value; _onChanged(); if (!value) { _maxController.text = ''; } }); }, ) ], ), ), ], ), ], ); } } class FeesEditor extends StatefulWidget { const FeesEditor( {Key key, this.companyGateway, this.viewModel, this.gatewayTypeId}) : super(key: key); final CompanyGatewayEntity companyGateway; final CompanyGatewayEditVM viewModel; final String gatewayTypeId; @override _FeesEditorState createState() => _FeesEditorState(); } class _FeesEditorState extends State { final _amountController = TextEditingController(); final _percentController = TextEditingController(); final _capController = TextEditingController(); List _controllers; final _debouncer = Debouncer(); @override void dispose() { _controllers.forEach((dynamic controller) { controller.removeListener(_onChanged); controller.dispose(); }); super.dispose(); } @override void didChangeDependencies() { _controllers = [ _amountController, _percentController, _capController, ]; final companyGateway = widget.companyGateway; final settings = companyGateway.getSettingsForGatewayTypeId(widget.gatewayTypeId); _controllers .forEach((dynamic controller) => controller.removeListener(_onChanged)); _amountController.text = formatNumber(settings.feeAmount, context, formatNumberType: FormatNumberType.inputMoney); _percentController.text = formatNumber(settings.feePercent, context, formatNumberType: FormatNumberType.inputMoney); _capController.text = formatNumber(settings.feeCap, context, formatNumberType: FormatNumberType.inputMoney); _controllers .forEach((dynamic controller) => controller.addListener(_onChanged)); super.didChangeDependencies(); } void _onChanged() { final viewModel = widget.viewModel; final companyGateway = viewModel.companyGateway; final settings = companyGateway.getSettingsForGatewayTypeId(widget.gatewayTypeId); final amount = parseDouble(_amountController.text.trim()); final percent = parseDouble(_percentController.text.trim()); final cap = parseDouble(_capController.text.trim()); final updatedSettings = settings.rebuild((b) => b ..feeAmount = amount ..feePercent = percent ..feeCap = cap); if (settings != updatedSettings) { _debouncer.run(() { viewModel.onChanged(companyGateway.rebuild((b) => b..feesAndLimitsMap[widget.gatewayTypeId] = updatedSettings)); }); } } @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); final viewModel = widget.viewModel; final companyGateway = viewModel.companyGateway; final company = viewModel.state.company; final settings = companyGateway.getSettingsForGatewayTypeId(widget.gatewayTypeId); return FormCard( children: [ DecoratedFormField( label: localization.feePercent, controller: _percentController, isPercent: true, ), DecoratedFormField( label: localization.feeAmount, controller: _amountController, isMoney: true, ), DecoratedFormField( label: localization.feeCap, controller: _capController, isMoney: true, ), if (company.enableFirstItemTaxRate) TaxRateDropdown( onSelected: (taxRate) => viewModel.onChanged(companyGateway.rebuild( (b) => b ..feesAndLimitsMap[widget.gatewayTypeId] = settings.rebuild((b) => b ..taxRate1 = taxRate.rate ..taxName1 = taxRate.name))), labelText: localization.tax, initialTaxName: settings.taxName1, initialTaxRate: settings.taxRate1, ), if (company.enableSecondItemTaxRate) TaxRateDropdown( onSelected: (taxRate) => viewModel.onChanged(companyGateway.rebuild( (b) => b ..feesAndLimitsMap[widget.gatewayTypeId] = settings.rebuild((b) => b ..taxRate2 = taxRate.rate ..taxName2 = taxRate.name))), labelText: localization.tax, initialTaxName: settings.taxName2, initialTaxRate: settings.taxRate2, ), if (company.enableThirdItemTaxRate) TaxRateDropdown( onSelected: (taxRate) => viewModel.onChanged(companyGateway.rebuild( (b) => b ..feesAndLimitsMap[widget.gatewayTypeId] = settings.rebuild((b) => b ..taxRate3 = taxRate.rate ..taxName3 = taxRate.name))), labelText: localization.tax, initialTaxName: settings.taxName3, initialTaxRate: settings.taxRate3, ), SizedBox(height: 16), LearnMoreUrl( url: kGatewayFeeHelpURL, child: SwitchListTile( value: settings.adjustFeePercent, onChanged: (value) => viewModel.onChanged(companyGateway.rebuild( (b) => b ..feesAndLimitsMap[widget.gatewayTypeId] = settings.rebuild((b) => b..adjustFeePercent = value))), title: Text(localization.adjustFeePercent), activeColor: Theme.of(context).colorScheme.secondary, subtitle: Text(localization.adjustFeePercentHelp), ), ), ], ); } }