Skip to content

Commit

Permalink
Merge pull request #620 from breez/618-visual-feedback-dynamic-fee-ui…
Browse files Browse the repository at this point in the history
…-when-lsp-does-not-support-opening-channels

618 visual feedback dynamic fee UI when lsp does not support opening channels
  • Loading branch information
ubbabeck authored Sep 12, 2023
2 parents c546590 + 21e1e94 commit 0335064
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 71 deletions.
15 changes: 9 additions & 6 deletions lib/bloc/account/account_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ class AccountBloc extends Cubit<AccountState> with HydratedMixin {
// constraints.
void validatePayment(
int amount,
bool outgoing, {
bool outgoing,
bool channelCreationPossible, {
int? channelMinimumFee,
}) {
_log.v("validatePayment: $amount, $outgoing, $channelMinimumFee");
Expand All @@ -230,14 +231,16 @@ class AccountBloc extends Cubit<AccountState> with HydratedMixin {
}

if (!outgoing) {
if (channelMinimumFee != null &&
if (!channelCreationPossible && accState.maxInboundLiquidity == 0) {
throw NoChannelCreationZeroLiqudityError();
} else if (!channelCreationPossible && accState.maxInboundLiquidity < amount) {
throw PaymentExcededLiqudityChannelCreationNotPossibleError(accState.maxInboundLiquidity);
} else if (channelMinimumFee != null &&
(amount > accState.maxInboundLiquidity && amount <= channelMinimumFee)) {
throw PaymentBelowSetupFeesError(channelMinimumFee);
}
if (channelMinimumFee == null && amount > accState.maxInboundLiquidity) {
} else if (channelMinimumFee == null && amount > accState.maxInboundLiquidity) {
throw PaymentExceedLiquidityError(accState.maxInboundLiquidity);
}
if (amount > accState.maxAllowedToReceive) {
} else if (amount > accState.maxAllowedToReceive) {
throw PaymentExceededLimitError(accState.maxAllowedToReceive);
}
}
Expand Down
10 changes: 10 additions & 0 deletions lib/bloc/account/payment_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,13 @@ class PaymentExceedLiquidityError implements Exception {
this.limitSat,
);
}

class PaymentExcededLiqudityChannelCreationNotPossibleError implements Exception {
final int limitSat;

const PaymentExcededLiqudityChannelCreationNotPossibleError(
this.limitSat,
);
}

class NoChannelCreationZeroLiqudityError implements Exception {}
5 changes: 5 additions & 0 deletions lib/bloc/lsp/lsp_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ class LspState {
final String? selectedLspId;

LspState({this.lspInfo, this.selectedLspId});

// this returns true if the current LSP supports opeing new channels.
bool get isChannelOpeningAvailiable {
return (lspInfo != null) ? lspInfo!.openingFeeParamsList.values.isNotEmpty : false;
}
}
33 changes: 25 additions & 8 deletions lib/routes/create_invoice/create_invoice_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:c_breez/bloc/currency/currency_bloc.dart';
import 'package:c_breez/bloc/currency/currency_state.dart';
import 'package:c_breez/bloc/ext/block_builder_extensions.dart';
import 'package:c_breez/bloc/lsp/lsp_bloc.dart';
import 'package:c_breez/bloc/lsp/lsp_state.dart';
import 'package:c_breez/routes/create_invoice/qr_code_dialog.dart';
import 'package:c_breez/routes/create_invoice/widgets/successful_payment.dart';
import 'package:c_breez/routes/lnurl/widgets/lnurl_page_result.dart';
Expand Down Expand Up @@ -119,15 +120,28 @@ class CreateInvoicePageState extends State<CreateInvoicePage> {
);
},
),
BlocBuilder2<AccountBloc, AccountState, CurrencyBloc, CurrencyState>(
builder: (context, accountState, currencyState) {
BlocBuilder3<AccountBloc, AccountState, CurrencyBloc, CurrencyState, LSPBloc, LspState?>(
builder: (context, accountState, currencyState, lspState) {
return ReceivableBTCBox(
onTap: () {
_amountController.text = currencyState.bitcoinCurrency.format(
accountState.maxAllowedToReceive,
includeDisplayName: false,
userInput: true,
);
if (lspState != null &&
!lspState.isChannelOpeningAvailiable &&
accountState.maxInboundLiquidity > 0) {
_amountController.text = currencyState.bitcoinCurrency.format(
accountState.maxInboundLiquidity,
includeDisplayName: false,
userInput: true,
);
} else if (lspState != null &&
!lspState.isChannelOpeningAvailiable &&
accountState.maxInboundLiquidity == 0) {
// do nothing
} else {
_amountController.text = currencyState.bitcoinCurrency.format(
accountState.maxAllowedToReceive,
includeDisplayName: false,
userInput: true);
}
},
);
},
Expand Down Expand Up @@ -242,14 +256,16 @@ class CreateInvoicePageState extends State<CreateInvoicePage> {
return PaymentValidator(
validatePayment: _validatePayment,
currency: context.read<CurrencyBloc>().state.bitcoinCurrency,
channelCreationPossible: context.read<LSPBloc>().state?.isChannelOpeningAvailiable ?? false,
channelMinimumFee: channelMinimumFee,
texts: context.texts(),
).validateIncoming(amount);
}

void _validatePayment(
int amount,
bool outgoing, {
bool outgoing,
bool channelCreationPossible, {
int? channelMinimumFee,
}) {
final data = widget.requestData;
Expand All @@ -264,6 +280,7 @@ class CreateInvoicePageState extends State<CreateInvoicePage> {
return context.read<AccountBloc>().validatePayment(
amount,
outgoing,
channelCreationPossible,
channelMinimumFee: channelMinimumFee,
);
}
Expand Down
27 changes: 19 additions & 8 deletions lib/routes/home/widgets/status_text.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:breez_translations/breez_translations_locales.dart';
import 'package:c_breez/bloc/account/account_bloc.dart';
import 'package:c_breez/bloc/account/account_state.dart';
import 'package:c_breez/bloc/ext/block_builder_extensions.dart';
import 'package:c_breez/bloc/lsp/lsp_bloc.dart';
import 'package:c_breez/bloc/lsp/lsp_state.dart';
import 'package:c_breez/theme/theme_provider.dart';
import 'package:c_breez/utils/min_font_size.dart';
import 'package:c_breez/widgets/loading_animated_text.dart';
Expand All @@ -23,14 +28,20 @@ class StatusText extends StatelessWidget {
final texts = context.texts();
final themeData = Theme.of(context);

return AutoSizeText(
texts.status_text_ready,
style: themeData.textTheme.bodyMedium?.copyWith(
color: themeData.isLightTheme ? BreezColors.grey[600] : themeData.colorScheme.onSecondary,
),
textAlign: TextAlign.center,
minFontSize: MinFontSize(context).minFontSize,
stepGranularity: 0.1,
return BlocBuilder2<AccountBloc, AccountState, LSPBloc, LspState?>(
builder: (context, accountState, lspState) {
return AutoSizeText(
(lspState != null && lspState.isChannelOpeningAvailiable && accountState.maxInboundLiquidity <= 0)
? texts.status_text_ready
: texts.lsp_error_cannot_open_channel,
style: themeData.textTheme.bodyMedium?.copyWith(
color: themeData.isLightTheme ? BreezColors.grey[600] : themeData.colorScheme.onSecondary,
),
textAlign: TextAlign.center,
minFontSize: MinFontSize(context).minFontSize,
stepGranularity: 0.1,
);
},
);
}
}
Expand Down
7 changes: 4 additions & 3 deletions lib/routes/lnurl/payment/lnurl_payment_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class LNURLPaymentPageState extends State<LNURLPaymentPage> {
String? validatePayment(int amount) {
final texts = context.texts();
final accBloc = context.read<AccountBloc>();
final lspInfo = context.read<LSPBloc>().state?.lspInfo;
final lspState = context.read<LSPBloc>().state;
final currencyState = context.read<CurrencyBloc>().state;

final maxSendable = widget.requestData.maxSendable ~/ 1000;
Expand All @@ -222,13 +222,14 @@ class LNURLPaymentPageState extends State<LNURLPaymentPage> {
}

int? channelMinimumFee;
if (lspInfo != null) {
channelMinimumFee = lspInfo.openingFeeParamsList.values.first.minMsat ~/ 1000;
if (lspState != null && lspState.lspInfo != null) {
channelMinimumFee = lspState.lspInfo!.openingFeeParamsList.values.first.minMsat ~/ 1000;
}

return PaymentValidator(
validatePayment: accBloc.validatePayment,
currency: currencyState.bitcoinCurrency,
channelCreationPossible: lspState?.isChannelOpeningAvailiable ?? false,
channelMinimumFee: channelMinimumFee,
texts: context.texts(),
).validateIncoming(amount);
Expand Down
3 changes: 3 additions & 0 deletions lib/routes/spontaneous_payment/spontaneous_payment_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:c_breez/bloc/account/account_bloc.dart';
import 'package:c_breez/bloc/account/account_state.dart';
import 'package:c_breez/bloc/currency/currency_bloc.dart';
import 'package:c_breez/bloc/currency/currency_state.dart';
import 'package:c_breez/bloc/lsp/lsp_bloc.dart';
import 'package:c_breez/theme/theme_provider.dart' as theme;
import 'package:c_breez/utils/fiat_conversion.dart';
import 'package:c_breez/utils/min_font_size.dart';
Expand Down Expand Up @@ -116,6 +117,8 @@ class SpontaneousPaymentPageState extends State<SpontaneousPaymentPage> {
validatorFn: PaymentValidator(
validatePayment: accBloc.validatePayment,
currency: currencyState.bitcoinCurrency,
channelCreationPossible:
context.read<LSPBloc>().state?.isChannelOpeningAvailiable ?? false,
texts: context.texts(),
).validateOutgoing,
style: theme.FieldTextStyle.textStyle),
Expand Down
14 changes: 7 additions & 7 deletions lib/routes/subswap/swap/widgets/swap_error_message.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:breez_translations/breez_translations_locales.dart';
import 'package:c_breez/utils/exceptions.dart';
import 'package:c_breez/utils/min_font_size.dart';
import 'package:c_breez/widgets/warning_box.dart';
import 'package:flutter/material.dart';

class SwapErrorMessage extends StatelessWidget {
Expand All @@ -13,18 +14,17 @@ class SwapErrorMessage extends StatelessWidget {

@override
Widget build(BuildContext context) {
final texts = context.texts();
return Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
height: 360,
child: Center(
child: WarningBox(
boxPadding: EdgeInsets.zero,
contentPadding: const EdgeInsets.all(8),
child: Padding(
padding: const EdgeInsets.all(8),
child: AutoSizeText(
errorMessage,
maxLines: 2,
textAlign: TextAlign.center,
extractExceptionMessage(errorMessage, texts),
textAlign: TextAlign.justify,
minFontSize: MinFontSize(context).minFontSize,
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'package:breez_translations/breez_translations_locales.dart';
import 'package:c_breez/bloc/account/payment_error.dart';
import 'package:c_breez/bloc/lsp/lsp_bloc.dart';
import 'package:c_breez/models/currency.dart';
import 'package:c_breez/routes/withdraw_funds/withdraw_funds_address_page.dart';
import 'package:c_breez/utils/payment_validator.dart';
import 'package:c_breez/widgets/amount_form_field/amount_form_field.dart';
import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

final _log = FimberLog("WithdrawAmountTextFormField");

Expand All @@ -28,7 +30,8 @@ class WithdrawAmountTextFormField extends AmountFormField {
return PaymentValidator(
currency: bitcoinCurrency,
texts: context.texts(),
validatePayment: (amount, outgoing, {channelMinimumFee}) {
channelCreationPossible: context.read<LSPBloc>().state?.isChannelOpeningAvailiable ?? false,
validatePayment: (amount, outgoing, channelCreationPossible, {channelMinimumFee}) {
_log.v("Validating $amount $policy");
if (amount < policy.minValue) {
throw PaymentBelowLimitError(policy.minValue);
Expand Down
46 changes: 23 additions & 23 deletions lib/utils/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,28 @@ String _localizedExceptionMessage(
String originalMessage,
) {
_log.v("localizedExceptionMessage: $originalMessage");
switch (originalMessage.toLowerCase()) {
case "transport error":
return texts.generic_network_error;
case "insufficient_balance":
return texts.payment_error_insufficient_balance;
case "incorrect_payment_details":
return texts.payment_error_incorrect_payment_details;
case "error":
return texts.payment_error_unexpected_error;
case "no_route":
return texts.payment_error_no_route;
case "timeout":
return texts.payment_error_payment_timeout_exceeded;
case "none":
return texts.payment_error_none;
default:
if (originalMessage.contains("dns error") || originalMessage.contains("os error 104")) {
return texts.generic_network_error;
}
if (originalMessage.contains("Recovery failed:")) {
return texts.enter_backup_phrase_error;
}
return originalMessage;
final messageToLower = originalMessage.toLowerCase();
if (messageToLower == "transport error") {
return texts.generic_network_error;
} else if (messageToLower == "insufficient_balance") {
return texts.payment_error_insufficient_balance;
} else if (messageToLower == "incorrect_payment_details") {
return texts.payment_error_incorrect_payment_details;
} else if (messageToLower == "error") {
return texts.payment_error_unexpected_error;
} else if (messageToLower == "no_route") {
return texts.payment_error_no_route;
} else if (messageToLower == "timeout") {
return texts.payment_error_payment_timeout_exceeded;
} else if (messageToLower == "none") {
return texts.payment_error_none;
} else if (messageToLower.startsWith("lsp doesn't support opening a new channel")) {
return texts.lsp_error_cannot_open_channel;
} else if (messageToLower.contains("dns error") || messageToLower.contains("os error 104")) {
return texts.generic_network_error;
} else if (messageToLower.contains("Recovery failed:")) {
return texts.enter_backup_phrase_error;
} else {
return originalMessage;
}
}
12 changes: 9 additions & 3 deletions lib/utils/payment_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ class PaymentValidator {
final BitcoinCurrency currency;
final void Function(
int amount,
bool outgoing, {
bool outgoing,
bool channelCreationPossible, {
int? channelMinimumFee,
}) validatePayment;
final bool channelCreationPossible;
final int? channelMinimumFee;
final BreezTranslations texts;

const PaymentValidator({
required this.validatePayment,
required this.currency,
required this.channelCreationPossible,
this.channelMinimumFee,
required this.texts,
});
Expand All @@ -34,7 +37,7 @@ class PaymentValidator {
String? _validate(int amount, bool outgoing) {
_log.v("Validating for $amount and $outgoing");
try {
validatePayment(amount, outgoing, channelMinimumFee: channelMinimumFee);
validatePayment(amount, outgoing, channelCreationPossible, channelMinimumFee: channelMinimumFee);
} on PaymentExceededLimitError catch (e) {
_log.v("Got PaymentExceededLimitError", ex: e);
return texts.invoice_payment_validator_error_payment_exceeded_limit(
Expand All @@ -51,7 +54,6 @@ class PaymentValidator {
currency.format(e.reserveAmount),
);
} on PaymentExceedLiquidityError catch (e) {
// TODO: Add translation
return "Insufficient inbound liquidity (${currency.format(e.limitSat)})";
} on InsufficientLocalBalanceError {
return texts.invoice_payment_validator_error_insufficient_local_balance;
Expand All @@ -60,6 +62,10 @@ class PaymentValidator {
return texts.invoice_payment_validator_error_payment_below_setup_fees_error(
currency.format(e.setupFees),
);
} on PaymentExcededLiqudityChannelCreationNotPossibleError catch (e) {
return texts.lnurl_fetch_invoice_error_max(currency.format(e.limitSat));
} on NoChannelCreationZeroLiqudityError {
return texts.lsp_error_cannot_open_channel;
} catch (e) {
_log.v("Got Generic error", ex: e);
return texts.invoice_payment_validator_error_unknown(
Expand Down
3 changes: 3 additions & 0 deletions lib/widgets/payment_dialogs/payment_request_info_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:c_breez/bloc/account/account_bloc.dart';
import 'package:c_breez/bloc/account/account_state.dart';
import 'package:c_breez/bloc/currency/currency_bloc.dart';
import 'package:c_breez/bloc/currency/currency_state.dart';
import 'package:c_breez/bloc/lsp/lsp_bloc.dart';
import 'package:c_breez/models/currency.dart';
import 'package:c_breez/models/invoice.dart';
import 'package:c_breez/theme/theme_provider.dart' as theme;
Expand Down Expand Up @@ -179,6 +180,7 @@ class PaymentRequestInfoDialogState extends State<PaymentRequestInfoDialog> {
controller: _invoiceAmountController,
validatorFn: PaymentValidator(
validatePayment: context.read<AccountBloc>().validatePayment,
channelCreationPossible: context.read<LSPBloc>().state?.isChannelOpeningAvailiable ?? false,
currency: currencyState.bitcoinCurrency,
texts: context.texts(),
).validateOutgoing,
Expand Down Expand Up @@ -254,6 +256,7 @@ class PaymentRequestInfoDialogState extends State<PaymentRequestInfoDialog> {
final validationError = PaymentValidator(
validatePayment: context.read<AccountBloc>().validatePayment,
currency: currencyState.bitcoinCurrency,
channelCreationPossible: context.read<LSPBloc>().state?.isChannelOpeningAvailiable ?? false,
texts: context.texts(),
).validateOutgoing(
amountToPay(currencyState),
Expand Down
Loading

0 comments on commit 0335064

Please sign in to comment.