Skip to content

Commit

Permalink
Support LNURL Withdraw with new UI
Browse files Browse the repository at this point in the history
TODO: Create a dedicated page for LNURL Withdraw with extensions on request data & pre-process the payment to see if it's within payment limits before opening the page.
  • Loading branch information
erdemyerebasmaz committed Sep 11, 2024
1 parent afd66f8 commit 3fc92cf
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 25 deletions.
23 changes: 17 additions & 6 deletions lib/routes/lnurl/withdraw/lnurl_withdraw_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:l_breez/routes/home/home_page.dart';
import 'package:l_breez/routes/lnurl/widgets/lnurl_page_result.dart';
import 'package:l_breez/routes/receive_payment/lightning/receive_lightning_page.dart';
import 'package:l_breez/routes/receive_payment/lightning/widgets/widgets.dart';
import 'package:l_breez/widgets/back_button.dart' as back_button;
import 'package:l_breez/widgets/error_dialog.dart';
import 'package:l_breez/widgets/transparent_page_route.dart';
import 'package:logging/logging.dart';
Expand All @@ -21,16 +22,26 @@ Future<LNURLPageResult?> handleWithdrawRequest(
LnUrlWithdrawRequestData requestData,
) async {
Completer<LNURLPageResult?> completer = Completer();
final texts = context.texts();
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider(
create: (BuildContext context) => PaymentLimitsCubit(ServiceInjector().liquidSDK),
child: ReceiveLightningPaymentPage(
requestData: requestData,
onFinish: (LNURLPageResult? response) {
completer.complete(response);
Navigator.of(context).popUntil((route) => route.settings.name == Home.routeName);
},
child: Scaffold(
appBar: AppBar(
leading: const back_button.BackButton(),
title: Text("Receive via ${texts.lnurl_payment_page_title}"),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 0.0),
child: ReceiveLightningPaymentPage(
requestData: requestData,
onFinish: (LNURLPageResult? response) {
completer.complete(response);
Navigator.of(context).popUntil((route) => route.settings.name == Home.routeName);
},
),
),
),
),
),
Expand Down
108 changes: 89 additions & 19 deletions lib/routes/receive_payment/lightning/receive_lightning_page.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:breez_liquid/breez_liquid.dart';
import 'package:breez_translations/breez_translations_locales.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart' as liquid_sdk;
import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:l_breez/cubit/cubit.dart';
import 'package:l_breez/routes/lnurl/widgets/lnurl_page_result.dart';
import 'package:l_breez/routes/lnurl/withdraw/lnurl_withdraw_dialog.dart';
import 'package:l_breez/routes/receive_payment/widgets/address_widget/address_widget.dart';
import 'package:l_breez/routes/receive_payment/widgets/payment_info_message_box/payment_fees_message_box.dart';
import 'package:l_breez/theme/src/theme.dart';
import 'package:l_breez/theme/src/theme_extensions.dart';
import 'package:l_breez/theme/theme.dart';
import 'package:l_breez/utils/min_font_size.dart';
import 'package:l_breez/utils/payment_validator.dart';
Expand All @@ -24,6 +26,7 @@ final _log = Logger("ReceiveLightningPaymentPage");

class ReceiveLightningPaymentPage extends StatefulWidget {
final Function(LNURLPageResult? result)? onFinish;
// TODO: Create a dedicated page for LNURL-Withdraw page.
final LnUrlWithdrawRequestData? requestData;

static const routeName = "/receive_lightning";
Expand Down Expand Up @@ -53,10 +56,13 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
PrepareReceiveResponse? prepareResponse;
Future<ReceivePaymentResponse>? receivePaymentResponse;

bool isBelowPaymentLimit = false;

@override
void initState() {
super.initState();
if (_amountFocusNode.canRequestFocus) {
final data = widget.requestData;
if (data != null && data.minWithdrawable != data.maxWithdrawable) {
_amountFocusNode.requestFocus();
}
_doneAction = KeyboardDoneAction(focusNodes: [_amountFocusNode]);
Expand All @@ -68,7 +74,7 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
final paymentLimitsState = context.read<PaymentLimitsCubit>().state;
final minSat = paymentLimitsState.lightningPaymentLimits?.receive.minSat.toInt();
if (minSat != null && data.maxWithdrawable.toInt() ~/ 1000 < minSat) {
throw Exception("Payment is below network limit of $minSat sats.");
isBelowPaymentLimit = true;
}

final currencyState = context.read<CurrencyCubit>().state;
Expand Down Expand Up @@ -116,6 +122,22 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage

_lightningLimits = snapshot.lightningPaymentLimits!;

var minSat = _lightningLimits.receive.minSat.toInt();
var data = widget.requestData;
isBelowPaymentLimit = data != null && (minSat > data.maxWithdrawable.toInt() ~/ 1000);

if (isBelowPaymentLimit) {
return BlocBuilder<CurrencyCubit, CurrencyState>(builder: (context, currencyState) {
final formattedMinSat = currencyState.bitcoinCurrency.format(minSat);
return Center(
child: Text(
texts.invoice_payment_validator_error_payment_below_invoice_limit(formattedMinSat),
textAlign: TextAlign.center,
),
);
});
}

return Padding(
padding: const EdgeInsets.only(bottom: 40.0),
child: SingleChildScrollView(
Expand All @@ -125,7 +147,7 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
);
},
),
bottomNavigationBar: (receivePaymentResponse == null)
bottomNavigationBar: (receivePaymentResponse == null && !isBelowPaymentLimit)
? SingleButtonBottomBar(
stickToBottom: true,
text: widget.requestData != null ? texts.invoice_action_redeem : texts.invoice_action_create,
Expand All @@ -152,6 +174,7 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage

Widget _buildForm() {
final texts = context.texts();
final data = widget.requestData;

return BlocBuilder<CurrencyCubit, CurrencyState>(
builder: (context, currencyState) {
Expand Down Expand Up @@ -180,24 +203,61 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
texts: texts,
bitcoinCurrency: currencyState.bitcoinCurrency,
focusNode: _amountFocusNode,
autofocus: true,
autofocus: (data != null) ? !(data.minWithdrawable == data.maxWithdrawable) : true,
readOnly: (data != null) ? (data.minWithdrawable == data.maxWithdrawable) : false,
controller: _amountController,
validatorFn: (v) => validatePayment(v),
style: FieldTextStyle.textStyle,
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: AutoSizeText(
texts.invoice_min_payment_limit(
currencyState.bitcoinCurrency.format(
_lightningLimits.receive.minSat.toInt(),
),
),
style: textStyle,
maxLines: 1,
minFontSize: MinFontSize(context).minFontSize,
),
),
(data != null)
? (data.minWithdrawable == data.maxWithdrawable)
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.only(top: 8),
child: RichText(
text: TextSpan(
style: FieldTextStyle.labelStyle,
children: <TextSpan>[
TextSpan(
text: texts.lnurl_fetch_invoice_min(
currencyState.bitcoinCurrency.format(
data.minWithdrawable.toInt() ~/ 1000,
),
),
recognizer: TapGestureRecognizer()
..onTap = () => _pasteAmount(
currencyState,
data.minWithdrawable.toInt() ~/ 1000,
),
),
TextSpan(
text:
texts.lnurl_fetch_invoice_and(currencyState.bitcoinCurrency.format(
data.maxWithdrawable.toInt() ~/ 1000,
)),
recognizer: TapGestureRecognizer()
..onTap = () => _pasteAmount(
currencyState,
data.maxWithdrawable.toInt() ~/ 1000,
),
),
],
),
),
)
: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: AutoSizeText(
texts.invoice_min_payment_limit(
currencyState.bitcoinCurrency.format(
_lightningLimits.receive.minSat.toInt(),
),
),
style: textStyle,
maxLines: 1,
minFontSize: MinFontSize(context).minFontSize,
),
)
],
),
),
Expand All @@ -210,7 +270,7 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
Widget _buildQRCode() {
return FutureBuilder(
future: receivePaymentResponse,
builder: (BuildContext context, AsyncSnapshot<liquid_sdk.ReceivePaymentResponse> snapshot) {
builder: (BuildContext context, AsyncSnapshot<ReceivePaymentResponse> snapshot) {
return AddressWidget(
snapshot: snapshot,
title: context.texts().receive_payment_method_lightning_invoice,
Expand Down Expand Up @@ -285,4 +345,14 @@ class ReceiveLightningPaymentPageState extends State<ReceiveLightningPaymentPage
final lnUrlCubit = context.read<LnUrlCubit>();
return lnUrlCubit.validateLnUrlPayment(BigInt.from(amount), outgoing, _lightningLimits, balance);
}

void _pasteAmount(CurrencyState currencyState, int amount) {
setState(() {
_amountController.text = currencyState.bitcoinCurrency.format(
amount,
includeDisplayName: false,
userInput: true,
);
});
}
}

0 comments on commit 3fc92cf

Please sign in to comment.