Skip to content

Commit

Permalink
LNURL-Payment & outgoing LN Invoices UI Rework (#214)
Browse files Browse the repository at this point in the history
* Convert processing payment dialog into a fullscreen

Match system navigation bar color to background color

* Handle all LNURL Payment requests on LnUrlPaymentPage

* Remove LNURLPaymentDialog, LNURLPaymentInfo
* Return PrepareLnUrlPayResponse from LnUrlPaymentPage

* Check if metadata text is available before rendering LNURLMetadataText

Display LNURLMetadataImage at top

* Move comment section to bottom

* Fetch fees on clicking min & max limits

* Change payment limits error message

Remove trailing zeros from formatted amounts

* Add Fee information

* Show prepareLnurlPay errors on text form field

* Add payee information for fixed amount invoices

* Correct form paddings

* Calculate fees each time amount form field gets submitted

* Add Amount information for fixed amount invoices

* Add missing imports

* Remove unnecessary value reset

* Display Close button if there's an error validating LNURL-Payment

Show error

* Show errors in a warning box

* Do not display payee information widget if fees are not fetched for fixed amount invoices

* Display fee as "?" if no valid amount is selected in accepted range

* Create LnUrlPaymentPage widgets

* Make LnUrlPaymentPage form scrollable

* Show errors as validation error messages

* Increase validation error max lines to 3 of AmountFormField

* Merge payment validation functions

Merge validatorErrorMessage & _errorMessage to errorMessage
Prepare LnUrlPayment only if there are no error messages

* Display error message if fetching lightning payment limits fails

* Display error messages below payment amount for fixed amount invoices

* Correct the displayed payment amount

* Don't validate manually on pasting & submitting

* Fix light theme issues on LNURL Payment pages

* Handle LNURL success actions

* Add custom handler for currency converter dialog

Only call handlers if amount str is not empty

* Convert SuccessAction dialog into fullscreen dialog

Display scrollbar by default on the message
Use the same message widget on ShareablePaymentRow

* Hide fees if there's an error

* Create a Fullscreen UI for outgoing Lightning payments

closes #210

* Remove 'PaymentRequestDialog' and it's components
* Remove 'Invoice' helper class
* Make LnUrlMetadaText widget scrollable

* Fix the zero-amount invoice dismissal

* TODO: Handle SendPaymentResponse results

* Show fiat value on long pressing invoice amount

* unfocus amount field if min or max limits are pressed

* Show payment amount at all times on LNInvoicePaymentPage

* fix: Validate the correct amount for non-fixed amount lnurl payments

* Add a confirmation page for non-fixed amount LNURL payments
  • Loading branch information
erdemyerebasmaz authored Nov 6, 2024
1 parent 09764bd commit 8a9f499
Show file tree
Hide file tree
Showing 29 changed files with 1,144 additions and 1,378 deletions.
22 changes: 22 additions & 0 deletions lib/app/routes/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:l_breez/routes/home/home_page.dart';
import 'package:l_breez/routes/initial_walkthrough/initial_walkthrough.dart';
import 'package:l_breez/routes/initial_walkthrough/mnemonics/enter_mnemonics_page.dart';
import 'package:l_breez/routes/initial_walkthrough/mnemonics/mnemonics_confirmation_page.dart';
import 'package:l_breez/routes/ln_invoice/ln_invoice_payment_page.dart';
import 'package:l_breez/routes/lnurl/payment/lnurl_payment_page.dart';
import 'package:l_breez/routes/qr_scan/qr_scan.dart';
import 'package:l_breez/routes/receive_payment/lightning/receive_lightning_page.dart';
import 'package:l_breez/routes/receive_payment/ln_address/receive_lightning_address_page.dart';
Expand Down Expand Up @@ -124,6 +126,26 @@ Route? onGenerateRoute({
),
settings: settings,
);
case LNInvoicePaymentPage.routeName:
return FadeInRoute<PrepareSendResponse?>(
builder: (_) => BlocProvider(
create: (BuildContext context) => PaymentLimitsCubit(ServiceInjector().liquidSDK),
child: LNInvoicePaymentPage(
lnInvoice: settings.arguments as LNInvoice,
),
),
settings: settings,
);
case LnUrlPaymentPage.routeName:
return FadeInRoute<PrepareLnUrlPayResponse?>(
builder: (_) => BlocProvider(
create: (BuildContext context) => PaymentLimitsCubit(ServiceInjector().liquidSDK),
child: LnUrlPaymentPage(
requestData: settings.arguments as LnUrlPayRequestData,
),
),
settings: settings,
);
case FiatCurrencySettings.routeName:
return FadeInRoute(
builder: (_) => const FiatCurrencySettings(),
Expand Down
12 changes: 1 addition & 11 deletions lib/cubit/input/input_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:device_client/device_client.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:l_breez/cubit/cubit.dart';
import 'package:l_breez/models/invoice.dart';
import 'package:lightning_links/lightning_links.dart';
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
Expand Down Expand Up @@ -86,16 +85,7 @@ class InputCubit extends Cubit<InputState> {

Future<InputState> handlePaymentRequest(InputType_Bolt11 inputData, InputSource source) async {
_log.info("handlePaymentRequest: $inputData source: $source");
final LNInvoice lnInvoice = inputData.invoice;

final invoice = Invoice(
bolt11: lnInvoice.bolt11,
paymentHash: lnInvoice.paymentHash,
description: lnInvoice.description ?? "",
amountMsat: lnInvoice.amountMsat ?? BigInt.zero,
expiry: lnInvoice.expiry,
);
return InputState.invoice(invoice, source);
return InputState.invoice(inputData.invoice, source);
}

Future<InputState> _handleParsedInput(InputType parsedInput, InputSource source) async {
Expand Down
21 changes: 10 additions & 11 deletions lib/cubit/input/input_state.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:l_breez/cubit/model/src/input/input_printer.dart';
import 'package:l_breez/cubit/model/src/input/input_source.dart';
import 'package:l_breez/models/invoice.dart';

class InputState {
const InputState._();
Expand All @@ -11,9 +10,9 @@ class InputState {
const factory InputState.loading() = LoadingInputState;

const factory InputState.invoice(
Invoice invoice,
LNInvoice invoice,
InputSource source,
) = InvoiceInputState;
) = LnInvoiceInputState;

const factory InputState.lnUrlPay(
LnUrlPayRequestData data,
Expand Down Expand Up @@ -83,30 +82,30 @@ class LoadingInputState extends InputState {
int get hashCode => 0;
}

class InvoiceInputState extends InputState {
const InvoiceInputState(
this.invoice,
class LnInvoiceInputState extends InputState {
const LnInvoiceInputState(
this.lnInvoice,
this.source,
) : super._();

final Invoice invoice;
final LNInvoice lnInvoice;
final InputSource source;

@override
String toString() {
return 'InvoiceInputState{invoice: $invoice, source: $source}';
return 'InvoiceInputState{lnInvoice: $lnInvoice, source: $source}';
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is InvoiceInputState &&
other is LnInvoiceInputState &&
runtimeType == other.runtimeType &&
invoice == other.invoice &&
lnInvoice == other.lnInvoice &&
source == other.source;

@override
int get hashCode => invoice.hashCode ^ source.hashCode;
int get hashCode => lnInvoice.hashCode ^ source.hashCode;
}

class LnUrlPayInputState extends InputState {
Expand Down
67 changes: 53 additions & 14 deletions lib/handlers/input_handler/src/input_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import 'dart:async';
import 'package:breez_translations/breez_translations_locales.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:l_breez/cubit/cubit.dart';
import 'package:l_breez/handlers/handler/handler.dart';
import 'package:l_breez/models/invoice.dart';
import 'package:l_breez/routes/chainswap/send/send_chainswap_page.dart';
import 'package:l_breez/routes/ln_invoice/ln_invoice_payment_page.dart';
import 'package:l_breez/routes/lnurl/auth/lnurl_auth_handler.dart';
import 'package:l_breez/routes/lnurl/lnurl_invoice_delegate.dart';
import 'package:l_breez/routes/lnurl/payment/lnurl_payment_handler.dart';
import 'package:l_breez/routes/lnurl/widgets/lnurl_page_result.dart';
import 'package:l_breez/routes/lnurl/withdraw/lnurl_withdraw_handler.dart';
import 'package:l_breez/utils/exceptions.dart';
import 'package:l_breez/widgets/flushbar.dart';
import 'package:l_breez/widgets/loader.dart';
import 'package:l_breez/widgets/payment_dialogs/payment_request_dialog.dart';
import 'package:l_breez/widgets/payment_dialogs/processing_payment_dialog.dart';
import 'package:logging/logging.dart';

final _log = Logger("InputHandler");
Expand Down Expand Up @@ -61,7 +64,10 @@ class InputHandler extends Handler {
_handlingRequest = isLoading;
_setLoading(isLoading);

handleInputData(inputState).whenComplete(() => _handlingRequest = false).onError((error, _) {
handleInputData(inputState)
.then((result) => handleResult(result))
.whenComplete(() => _handlingRequest = false)
.onError((error, _) {
_log.severe("Input state error", error);
_handlingRequest = false;
_setLoading(false);
Expand All @@ -84,8 +90,8 @@ class InputHandler extends Handler {
return;
}

if (inputState is InvoiceInputState) {
return handleInvoice(context, inputState.invoice);
if (inputState is LnInvoiceInputState) {
return handleLnInvoice(context, inputState.lnInvoice);
} else if (inputState is LnUrlPayInputState) {
return handlePayRequest(context, firstPaymentItemKey, inputState.data);
} else if (inputState is LnUrlWithdrawInputState) {
Expand All @@ -101,27 +107,60 @@ class InputHandler extends Handler {
}
}

Future handleInvoice(BuildContext context, Invoice invoice) async {
_log.info("handle invoice $invoice");
Future handleLnInvoice(BuildContext context, LNInvoice lnInvoice) async {
_log.info("handle LnInvoice $lnInvoice");
final navigator = Navigator.of(context);
PrepareSendResponse? prepareResponse = await navigator.pushNamed<PrepareSendResponse?>(
LNInvoicePaymentPage.routeName,
arguments: lnInvoice,
);
if (prepareResponse == null || !context.mounted) {
return Future.value();
}

// Show Processing Payment Dialog
return await showDialog(
useRootNavigator: false,
context: context,
barrierDismissible: false,
builder: (_) => PaymentRequestDialog(
invoice,
firstPaymentItemKey,
builder: (_) => ProcessingPaymentDialog(
isLnUrlPayment: true,
firstPaymentItemKey: firstPaymentItemKey,
paymentFunc: () async {
final paymentsCubit = context.read<PaymentsCubit>();
return await paymentsCubit.sendPayment(prepareResponse);
},
),
).then((message) {
if (message != null && context.mounted) {
showFlushbar(context, message: message);
).then((result) {
if (result is String && context.mounted) {
showFlushbar(context, message: result);
}
// TODO: Handle SendPaymentResponse results, return a SendPaymentResult to be handled by handleResult()
if (result is SendPaymentResponse) {
_log.info("SendPaymentResponse result - payment status: ${result.payment.status}");
}
});
}

Future handleBitcoinAddress(BuildContext context, BitcoinAddressInputState inputState) async {
_log.fine("handle bitcoin address $inputState");
if (inputState.source == InputSource.qrcodeReader) {
return await Navigator.of(context).pushNamed(SendChainSwapPage.routeName, arguments: inputState.data);
return await Navigator.of(context).pushNamed(
SendChainSwapPage.routeName,
arguments: inputState.data,
);
}
}

void handleResult(result) {
_log.info("Input state handled: $result");
if (result is LNURLPageResult && result.protocol != null) {
final context = contextProvider?.getBuildContext();
if (context != null) {
handleLNURLPageResult(context, result);
} else {
_log.info("Skipping handling of result: $result because context is null");
}
}
}

Expand Down
58 changes: 0 additions & 58 deletions lib/models/invoice.dart

This file was deleted.

Loading

0 comments on commit 8a9f499

Please sign in to comment.