From 03edd658a525912e1980a617daf06d6ee00c33b2 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 17 Jul 2026 16:49:17 +0200 Subject: [PATCH] Cw 1539 lightning enhancements (#3400) * feat: add Lightning transaction URL support in transaction details view model * fix: update currency selection logic in WalletAddressListViewModel and ReceivePage * feat: integrate computed transaction amount and fee getters in TransactionDetailsViewModel, refactor CopyWrapper logic in TransactionDetailsModal * refactor: apply lint rules * refactor: apply lint rules and improve LNURL handling with new methods for withdrawal requests and error checks * auto-reformat * fix: receive_page regression * fix: Withdraw lightning to other btc wallet gives LN address * chore: update .lock files [skip ci] --------- Co-authored-by: Robert Malikowski Co-authored-by: Omar Hatem --- cw_bitcoin/pubspec.lock | 10 +- cw_core/lib/lnurl.dart | 177 ++++-- cw_core/pubspec.lock | 10 +- cw_decred/pubspec.lock | 8 +- cw_monero/pubspec.lock | 10 +- cw_nano/pubspec.lock | 8 +- cw_wownero/pubspec.lock | 8 +- cw_zano/pubspec.lock | 8 +- .../widgets/rounded_overlay_cards_widget.dart | 16 +- lib/core/lightning_invoice_service.dart | 36 +- lib/new-ui/pages/receive_page.dart | 236 ++++---- lib/new-ui/pages/send_page.dart | 4 +- .../assets_history_section.dart | 9 +- .../transaction_details_modal.dart | 360 ++++++------ .../send_page/l2_action_wallet_selector.dart | 113 ++-- .../transaction_details/rbf_details_page.dart | 304 +++++----- .../transaction_details_view_model.dart | 526 +++++++++--------- .../wallet_address_list_view_model.dart | 353 ++++++------ 18 files changed, 1193 insertions(+), 1003 deletions(-) diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 6c15a9ad1..b35b2bf09 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -634,10 +634,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1100,10 +1100,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -1322,5 +1322,5 @@ packages: source: hosted version: "2.2.2" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.29.0" diff --git a/cw_core/lib/lnurl.dart b/cw_core/lib/lnurl.dart index 8ea33b99d..e0d17685a 100644 --- a/cw_core/lib/lnurl.dart +++ b/cw_core/lib/lnurl.dart @@ -1,19 +1,20 @@ -import 'dart:convert'; +import "dart:convert"; -import 'package:bech32/bech32.dart'; -import 'package:cw_core/amount/money.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; +import "package:bech32/bech32.dart"; +import "package:cw_core/amount/money.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/utils/proxy_wrapper.dart"; const _BOLT_PREFIXES = ["lnbcrt", "lntbs", "lnbc", "lntb"]; -const _LUD17_PREFIXES = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth']; +const _LUD17_PREFIXES = ["lnurlw", "lnurlc", "lnurlp", "keyauth"]; bool isBolt11ZeroInvoice(String invoice) { try { - final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); + final request = + const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); final prefix = - _BOLT_PREFIXES.firstWhere((prefix) => request.hrp.startsWith(prefix), orElse: () => ""); + _BOLT_PREFIXES.firstWhere(request.hrp.startsWith, orElse: () => ""); return request.hrp.length == prefix.length; } catch (e) { @@ -23,48 +24,56 @@ bool isBolt11ZeroInvoice(String invoice) { /// Get the amount of a Bolt 11 Invoice in int? _getAmountBolt11Msat(String invoice) { - final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); + final request = + const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length); final prefix = _BOLT_PREFIXES.firstWhere( - (prefix) => request.hrp.startsWith(prefix), - orElse: () => throw FormatException('Invalid BOLT11 invoice: unknown HRP prefix.'), + request.hrp.startsWith, + orElse: () => throw const FormatException("Invalid BOLT11 invoice: unknown HRP prefix."), ); final amountPart = request.hrp.substring(prefix.length); - if (amountPart.isEmpty) return null; // zero-amount invoice + if (amountPart.isEmpty) { + return null; // zero-amount invoice + } - final hasMultiplier = RegExp(r'[munp]$').hasMatch(amountPart); - final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : ''; + final hasMultiplier = RegExp(r"[munp]$").hasMatch(amountPart); + final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : ""; final numberStr = hasMultiplier ? amountPart.substring(0, amountPart.length - 1) : amountPart; - if (numberStr.isEmpty || !RegExp(r'^\d+$').hasMatch(numberStr)) { - throw FormatException('Invalid BOLT11 invoice: invalid amount number in HRP.'); + if (numberStr.isEmpty || !RegExp(r"^\d+$").hasMatch(numberStr)) { + throw const FormatException("Invalid BOLT11 invoice: invalid amount number in HRP."); } final amount = int.parse(numberStr); switch (multiplier) { - case '': // bitcoin + case "": // bitcoin return amount * 100000000000; - case 'm': // milli-bitcoin + case "m": // milli-bitcoin return amount * 100000000; - case 'u': // micro-bitcoin + case "u": // micro-bitcoin return amount * 100000; - case 'n': // nano-bitcoin + case "n": // nano-bitcoin return amount * 100; - case 'p': + case "p": if (amount % 10 != 0) { - throw FormatException('Invalid BOLT11 invoice: amount not representable in whole msat.'); + throw const FormatException( + "Invalid BOLT11 invoice: amount not representable in whole msat.", + ); } return amount ~/ 10; default: - throw FormatException('Invalid BOLT11 invoice: unknown amount multiplier.'); + throw const FormatException("Invalid BOLT11 invoice: unknown amount multiplier."); } } Money? getBolt11Amount(String invoice) { final msat = _getAmountBolt11Msat(invoice); - if (msat == null || msat % 1000 != 0) return null; + if (msat == null || msat % 1000 != 0) { + return null; + } + return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln); } @@ -73,30 +82,99 @@ class LNURL { final url = decode(lnurl); final response = await ProxyWrapper().get(clearnetUri: url); - if (response.statusCode != 200) return null; + if (response.statusCode != 200) { + return null; + } try { final body = jsonDecode(response.body) as Map; final tag = body["tag"] as String?; - if (tag != "payRequest") return null; + if (tag != "payRequest") { + return null; + } final msat = body["minSendable"] as int?; final maxSendable = body["maxSendable"] as int?; // if minSendable and maxSendable are the same we assume a specific payment request - if (msat != maxSendable) return null; + if (msat != maxSendable) { + return null; + } - if (msat == null || msat % 1000 != 0) return null; + if (msat == null || msat % 1000 != 0) { + return null; + } return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln); } catch (_) { return null; } } + static Future getWithdrawRequest(String lnurl) async { + final url = decode(lnurl); + final response = await ProxyWrapper().get(clearnetUri: url); + + if (response.statusCode != 200) { + return null; + } + + try { + final body = jsonDecode(response.body) as Map; + final tag = body["tag"] as String?; + + if (tag != "withdrawRequest") { + return null; + } + + final minWithdrawable = body["minWithdrawable"] as int?; + final maxWithdrawable = body["maxWithdrawable"] as int?; + + if ((minWithdrawable == null || minWithdrawable % 1000 != 0) || + (maxWithdrawable == null || maxWithdrawable % 1000 != 0)) { + return null; + } + + return LNURLWithdrawRequest( + callback: body["callback"] as String, + k1: body["k1"] as String, + description: body["defaultDescription"] as String, + minAmount: Money.fromInt(minWithdrawable ~/ 1000, CryptoCurrency.btcln), + maxAmount: Money.fromInt(maxWithdrawable ~/ 1000, CryptoCurrency.btcln), + ); + } catch (_) { + return null; + } + } + + Future commitWithdrawRequest(LNURLWithdrawRequest request, String invoice) async { + try { + final url = Uri.parse(request.callback); + url.queryParameters["k1"] = request.k1; + url.queryParameters["pr"] = invoice; + + final response = await ProxyWrapper().get(clearnetUri: url); + + if (response.statusCode != 200) { + return false; + } + + final body = jsonDecode(response.body) as Map; + final status = body["status"] as String?; + + if (status == "OK") { + return true; + } + + return false; + } catch (_) { + return false; + } + } + static String encode(String url) { final raw = _convert(utf8.encode(url), 8, 5, true); - return const Bech32Codec().encode(Bech32('lnurl', raw), 999); + return const Bech32Codec().encode(Bech32("lnurl", raw), 999); } static Uri decode(String encodedUrl) { @@ -114,9 +192,10 @@ class LNURL { if (_LUD17_PREFIXES.contains(decodedUri.scheme)) { /// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.' decodedUri = decodedUri.replace( - scheme: decodedUri.host.endsWith('onion') || decodedUri.host.endsWith('onion.') - ? 'http' - : 'https'); + scheme: decodedUri.host.endsWith("onion") || decodedUri.host.endsWith("onion.") + ? "http" + : "https", + ); } else { /// Try to parse the input as a lnUrl. Will throw an error if it fails. final lnUrl = _findLnUrl(encodedUrl); @@ -129,12 +208,30 @@ class LNURL { } } +class LNURLWithdrawRequest { + const LNURLWithdrawRequest({ + required this.callback, + required this.k1, + required this.description, + required this.minAmount, + required this.maxAmount, + }); + + final String callback; + final String k1; + final String description; + final Money minAmount; + final Money maxAmount; +} + /// Parse and return a given lnurl string if it's valid. Will remove /// `lightning:` from the beginning of it if present. String _findLnUrl(String input) { - final res = RegExp(r',*?((lnurl)([0-9]+[a-z0-9]+))').allMatches(input.toLowerCase()); + final res = RegExp(r",*?((lnurl)([0-9]+[a-z0-9]+))").allMatches(input.toLowerCase()); - if (res.length != 1) throw ArgumentError('Not a valid lnurl string'); + if (res.length != 1) { + throw ArgumentError("Not a valid lnurl string"); + } return res.first.group(0)!; } @@ -156,10 +253,16 @@ List _convert(List data, int inBits, int outBits, bool pad) { } if (pad) { - if (bits > 0) result.add((value << (outBits - bits)) & maxV); + if (bits > 0) { + result.add((value << (outBits - bits)) & maxV); + } } else { - if (bits >= inBits) throw Exception('[BECH32] Excess padding'); - if ((value << (outBits - bits)) & maxV > 0) throw Exception('[BECH32] Non-zero padding'); + if (bits >= inBits) { + throw Exception("[BECH32] Excess padding"); + } + if ((value << (outBits - bits)) & maxV > 0) { + throw Exception("[BECH32] Non-zero padding"); + } } return result; diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 908829bcb..8eff1d338 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -394,10 +394,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -738,10 +738,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: "direct main" description: @@ -838,5 +838,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.27.0" diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 0b460e9f9..9a568daa7 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -417,10 +417,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -769,10 +769,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index b32f56c5b..79da36944 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -505,10 +505,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -906,10 +906,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: @@ -1048,5 +1048,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 2dd436985..509d74498 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -462,10 +462,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -887,10 +887,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 7fd7eac2d..4b622b350 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -417,10 +417,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -786,10 +786,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index a4a6c5381..c4602f532 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -422,10 +422,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -783,10 +783,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.10" torch_dart: dependency: transitive description: diff --git a/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart b/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart index 9df2848f8..484792bf5 100644 --- a/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart +++ b/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart @@ -15,14 +15,14 @@ class RoundedOverlayCards extends StatelessWidget { return ClipRRect( borderRadius: - BorderRadius.only(bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)), + BorderRadius.only(bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25)), child: Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(24), - bottomRight: Radius.circular(24), - ), - color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + color: Theme.of(context).colorScheme.surfaceContainer, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -30,8 +30,8 @@ class RoundedOverlayCards extends StatelessWidget { Flexible( child: ClipRRect( borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(25.0), - bottomRight: Radius.circular(25.0), + bottomLeft: Radius.circular(25), + bottomRight: Radius.circular(25), ), child: Container( decoration: BoxDecoration( diff --git a/lib/core/lightning_invoice_service.dart b/lib/core/lightning_invoice_service.dart index 995c62d6b..8b29f6c5d 100644 --- a/lib/core/lightning_invoice_service.dart +++ b/lib/core/lightning_invoice_service.dart @@ -1,6 +1,6 @@ -import 'dart:convert'; +import "dart:convert"; -import 'package:cw_core/utils/proxy_wrapper.dart'; +import "package:cw_core/utils/proxy_wrapper.dart"; Future getBolt11FromLightingAddress(String lightningAddress, {int amount = 0}) async { try { @@ -31,28 +31,34 @@ Uri getURlOfLightningAddress(String lightningAddress) { } class _LNURLPResponseDTO { + const _LNURLPResponseDTO( + this.callback, + this.maxSendable, + this.minSendable, + this.tag, + this.metadata, + ); + + factory _LNURLPResponseDTO.fromJson(Map map) => _LNURLPResponseDTO( + map["callback"] as String, + map["maxSendable"] as int, + map["minSendable"] as int, + map["tag"] as String, + map["metadata"] as String, + ); + final String callback; final int maxSendable; final int minSendable; final String tag; final String metadata; - - const _LNURLPResponseDTO( - this.callback, this.maxSendable, this.minSendable, this.tag, this.metadata); - - static _LNURLPResponseDTO fromJson(Map map) => _LNURLPResponseDTO( - map["callback"] as String, - map["maxSendable"] as int, - map["minSendable"] as int, - map["tag"] as String, - map["metadata"] as String); } class _LNURLPCallbackResponseDTO { - final String pr; - const _LNURLPCallbackResponseDTO(this.pr); - static _LNURLPCallbackResponseDTO fromJson(Map map) => + factory _LNURLPCallbackResponseDTO.fromJson(Map map) => _LNURLPCallbackResponseDTO(map["pr"] as String); + + final String pr; } diff --git a/lib/new-ui/pages/receive_page.dart b/lib/new-ui/pages/receive_page.dart index e1c6afd01..4b7846c03 100644 --- a/lib/new-ui/pages/receive_page.dart +++ b/lib/new-ui/pages/receive_page.dart @@ -1,54 +1,54 @@ -import 'package:cake_wallet/core/utilities.dart'; -import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart'; -import 'package:cake_wallet/utils/share_util.dart'; -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; -import 'package:cake_wallet/zcash/zcash.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/payment_uris.dart'; -import 'package:cw_core/receive_page_option.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/di.dart'; -import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart'; -import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import "package:cake_wallet/anonpay/anonpay_donation_link_info.dart"; +import "package:cake_wallet/bitcoin/bitcoin.dart"; +import "package:cake_wallet/core/utilities.dart"; +import "package:cake_wallet/di.dart"; +import "package:cake_wallet/entities/auto_generate_subaddress_status.dart"; +import "package:cake_wallet/entities/preferences_key.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/widgets/modern_button.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart"; +import "package:cake_wallet/routes.dart"; +import "package:cake_wallet/src/screens/receive/anonpay_receive_page.dart"; +import "package:cake_wallet/utils/share_util.dart"; +import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart"; +import "package:cake_wallet/view_model/dashboard/receive_option_view_model.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart"; +import "package:cake_wallet/zcash/zcash.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/payment_uris.dart"; +import "package:cw_core/receive_page_option.dart"; +import "package:cw_core/utils/print_verbose.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:flutter_mobx/flutter_mobx.dart"; +import "package:mobx/mobx.dart"; +import "package:modal_bottom_sheet/modal_bottom_sheet.dart"; +import "package:shared_preferences/shared_preferences.dart"; class NewReceivePage extends StatefulWidget { - NewReceivePage( - {super.key, - required this.addressListViewModel, - required this.receiveOptionViewModel, - required this.dashboardViewModel, - required this.lightningMode, - CryptoCurrency? initialCurrency}) { + NewReceivePage({ + required this.addressListViewModel, + required this.receiveOptionViewModel, + required this.dashboardViewModel, + required this.lightningMode, + super.key, + CryptoCurrency? initialCurrency, + }) { if (initialCurrency != null && initialCurrency != addressListViewModel.selectedCurrency) { addressListViewModel.setTokenCurrency(initialCurrency); } @@ -78,31 +78,33 @@ class _NewReceivePageState extends State { .firstWhereOrNull((item) => item.value.contains("Lightning")) ?? ReceivePageOption.mainnet, ); - widget.addressListViewModel.setTokenCurrency(CryptoCurrency.btcln); + widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln; } else if (widget.addressListViewModel.wallet.type == WalletType.bitcoin) { widget.receiveOptionViewModel.selectReceiveOption( widget.receiveOptionViewModel.options .firstWhereOrNull((item) => item.value.contains("Standard")) ?? ReceivePageOption.mainnet, ); + widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc; } }); - reaction((_) => widget.addressListViewModel.uri, (newAddress) { - _reloadAddressWithLabel(newAddress); - }); + reaction((_) => widget.addressListViewModel.uri, _reloadAddressWithLabel); - _addressItemWithLabel = - widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull((item) { - return (item is WalletAddressListItem && - item.address == widget.addressListViewModel.uri.address); - }) as WalletAddressListItem?; + _addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull( + (item) => + item is WalletAddressListItem && item.address == widget.addressListViewModel.uri.address, + ) as WalletAddressListItem?; - reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption, - (ReceivePageOption option) { + reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption, (option) { if (widget.dashboardViewModel.type == WalletType.bitcoin && bitcoin!.isBitcoinReceivePageOption(option)) { widget.addressListViewModel.setAddressType(bitcoin!.getOptionToType(option)); + if (option.value.contains("Lightning")) { + widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln; + } else { + widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc; + } return; } if (widget.dashboardViewModel.type == WalletType.zcash) { @@ -166,16 +168,18 @@ class _NewReceivePageState extends State { final hasAddressTypeSelector = widget.receiveOptionViewModel.options.length > 1; final hasLabel = _addressItemWithLabel?.name != null && _addressItemWithLabel!.name!.isNotEmpty; final infoboxDismissed = widget.addressListViewModel.wallet.walletInfo.receiveInfoboxDismissed; - final infobox = ReceiveInfoBox.forWalletType(widget.addressListViewModel.type, - supportedCurrencies: widget.addressListViewModel.tokenCurrencies - .whereType() - .toList(), onDismissed: () { - widget.addressListViewModel.dismissInfobox(); - setState(() {}); - }, - autoGenerateSubaddressStatus: widget.lightningMode - ? AutoGenerateSubaddressStatus.disabled - : widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus); + final infobox = ReceiveInfoBox.forWalletType( + widget.addressListViewModel.type, + supportedCurrencies: + widget.addressListViewModel.tokenCurrencies.whereType().toList(), + onDismissed: () { + widget.addressListViewModel.dismissInfobox(); + setState(() {}); + }, + autoGenerateSubaddressStatus: widget.lightningMode + ? AutoGenerateSubaddressStatus.disabled + : widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus, + ); return Container( decoration: BoxDecoration( @@ -187,9 +191,7 @@ class _NewReceivePageState extends State { begin: Alignment.topCenter, end: Alignment.bottomCenter, ), - borderRadius: BorderRadius.vertical( - top: Radius.circular(24), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( child: Column( @@ -198,13 +200,13 @@ class _NewReceivePageState extends State { children: [ ModalTopBar( title: _largeQrMode ? "" : S.of(context).receive, - leadingIcon: Icon(Icons.close), + leadingIcon: const Icon(Icons.close), onLeadingPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, trailingWidget: Observer( builder: (_) => AnimatedSwitcher( - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), child: _largeQrMode || widget.addressListViewModel.hasAddressRotation /* TODO rotating is broken on mweb, disabling for now, fix after mvp*/ @@ -217,10 +219,10 @@ class _NewReceivePageState extends State { key: ValueKey(_largeQrMode), size: 36, icon: _largeQrMode - ? Icon(Icons.share) + ? const Icon(Icons.share) : widget.addressListViewModel.isRotatingAddress - ? CupertinoActivityIndicator() - : Icon(Icons.refresh), + ? const CupertinoActivityIndicator() + : const Icon(Icons.refresh), onPressed: () { if (_largeQrMode) { ShareUtil.share( @@ -232,8 +234,9 @@ class _NewReceivePageState extends State { widget.addressListViewModel.rotateAddress(); } } - }) - : SizedBox.shrink(), + }, + ) + : const SizedBox.shrink(), ), ), ), @@ -269,11 +272,12 @@ class _NewReceivePageState extends State { addressListViewModel: widget.addressListViewModel, ), GestureDetector( - onTap: _showLabelModal, - child: ReceiveLabelWidget( - name: _addressItemWithLabel?.name ?? "", - largeQrMode: _largeQrMode, - )), + onTap: _showLabelModal, + child: ReceiveLabelWidget( + name: _addressItemWithLabel?.name ?? "", + largeQrMode: _largeQrMode, + ), + ), Observer( builder: (_) => ReceiveBottomButtons( key: const ValueKey(0), @@ -285,14 +289,16 @@ class _NewReceivePageState extends State { : ClipboardData( text: widget.addressListViewModel.displayAmount.isEmpty ? widget.addressListViewModel.uri.address - : widget.addressListViewModel.uri.toString()), + : widget.addressListViewModel.uri.toString(), + ), onCopyButtonPressed: () { if (widget.addressListViewModel.hasPayjoin) { showModalBottomSheet( - isScrollControlled: true, - context: context, - builder: (context) => - PayjoinCopyModal(uri: widget.addressListViewModel.uri)); + isScrollControlled: true, + context: context, + builder: (context) => + PayjoinCopyModal(uri: widget.addressListViewModel.uri), + ); } }, onAmountButtonPressed: () { @@ -300,12 +306,10 @@ class _NewReceivePageState extends State { context: context, backgroundColor: Colors.transparent, barrierColor: Colors.black.withAlpha(80), - builder: (context) { - return ReceiveAmountModal( - walletAddressListViewModel: widget.addressListViewModel, - onSubmitted: (amount) {}, - ); - }, + builder: (context) => ReceiveAmountModal( + walletAddressListViewModel: widget.addressListViewModel, + onSubmitted: (amount) {}, + ), ); }, onLabelButtonPressed: _showLabelModal, @@ -318,22 +322,25 @@ class _NewReceivePageState extends State { ), ), ReceiveLargeAmountPreview( - amount: widget.addressListViewModel.displayAmount, - currency: widget.addressListViewModel.cryptoCurrencySymbol, - largeQrMode: _largeQrMode), + amount: widget.addressListViewModel.displayAmount, + currency: widget.addressListViewModel.cryptoCurrencySymbol, + largeQrMode: _largeQrMode, + ), if (infobox != null && !widget.addressListViewModel.isLightning) ClipRect( - child: AnimatedAlign( - duration: const Duration(milliseconds: 200), - curve: Curves.easeOutCubic, - heightFactor: infoboxDismissed ? 0 : 1, - alignment: Alignment.center, - child: AnimatedOpacity( + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + heightFactor: infoboxDismissed ? 0 : 1, + alignment: Alignment.center, + child: AnimatedOpacity( duration: const Duration(milliseconds: 200), opacity: infoboxDismissed ? 0 : 1, curve: Curves.easeOutCubic, - child: infobox), - )) + child: infobox, + ), + ), + ), ], ), ), @@ -345,12 +352,11 @@ class _NewReceivePageState extends State { void _showLabelModal() { showMaterialModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - barrierColor: Colors.black.withAlpha(80), - builder: (context) { - return getIt.get(param1: _addressItemWithLabel); - }).then((value) { + context: context, + backgroundColor: Colors.transparent, + barrierColor: Colors.black.withAlpha(80), + builder: (_) => getIt.get(param1: _addressItemWithLabel), + ).then((value) { _reloadAddressWithLabel(widget.addressListViewModel.uri); }); } @@ -359,8 +365,8 @@ class _NewReceivePageState extends State { // FIXME: viewmodel doesn't want to load address name here, so we make it. investigate why later setState(() { _addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull( - (item) => (item is WalletAddressListItem && item.address == newAddress.address)) - as WalletAddressListItem?; + (item) => item is WalletAddressListItem && item.address == newAddress.address, + ) as WalletAddressListItem?; }); } } diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index 994e13456..6ee37d2ee 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -594,7 +594,7 @@ class _NewSendPageState extends State { builder: (context) => Material( child: L2ActionWalletSelector( showOtherWallets: false, - action: l2actions.deposit, + action: L2Actions.deposit, sendViewModel: widget.sendViewModel, contactListViewModel: widget.contactListViewModel, @@ -609,7 +609,7 @@ class _NewSendPageState extends State { builder: (context) => Material( child: L2ActionWalletSelector( showOtherWallets: false, - action: l2actions.withdraw, + action: L2Actions.withdraw, sendViewModel: widget.sendViewModel, contactListViewModel: widget.contactListViewModel, diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart index d36d67bf3..08eec7498 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart @@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State { AssetsSection( dashboardViewModel: widget.dashboardViewModel, ), - hasAssetsButton ? - AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg", - () { - Navigator.of(context).pushNamed( + hasAssetsButton ? AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg", + + () {Navigator.of(context).pushNamed( Routes.homeSettings, arguments: widget.dashboardViewModel.balanceViewModel, ); - }) : null), + }): null), AssetsHistorySectionTab( S.current.history, HistorySection( diff --git a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart index ff2248035..fb705ace6 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart @@ -1,24 +1,28 @@ -import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart'; -import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart'; -import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart'; -import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart'; -import 'package:cake_wallet/utils/address_formatter.dart'; -import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart"; +import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart"; +import "package:cake_wallet/new-ui/widgets/copy_wrapper.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart"; +import "package:cake_wallet/routes.dart"; +import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart"; +import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart"; +import "package:cake_wallet/utils/address_formatter.dart"; +import "package:cake_wallet/view_model/transaction_details_view_model.dart"; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:flutter_mobx/flutter_mobx.dart"; +import "package:modal_bottom_sheet/modal_bottom_sheet.dart"; class TransactionDetailsModal extends StatefulWidget { const TransactionDetailsModal( - {super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false}); + { + required this.transactionDetailsViewModel, + this.highlightNoteField = false, + super.key, + }); final TransactionDetailsViewModel transactionDetailsViewModel; final bool highlightNoteField; @@ -48,177 +52,195 @@ class _TransactionDetailsModalState extends State { } @override - Widget build(BuildContext context) { - final transactionInfoAmount = widget.transactionDetailsViewModel.transactionInfo.amount; - - return SafeArea( - bottom: false, - child: Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: GestureDetector( - onTap: FocusScope.of(context).unfocus, - child: Container( - decoration: BoxDecoration( + Widget build(BuildContext context) => SafeArea( + bottom: false, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: GestureDetector( + onTap: FocusScope.of(context).unfocus, + child: Container( + decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.vertical(top: Radius.circular(25))), - child: Column( - children: [ - ModalTopBar( - title: S.of(context).transaction, - leadingIcon: Icon(Icons.close), - onLeadingPressed: Navigator.of(context).pop, - ), - Expanded( - child: SingleChildScrollView( - controller: ModalScrollController.of(context), - child: Column( - children: [ - TokenImageWidget( - imageUrl: - widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "", - size: 64, - ), - SizedBox(height: 10), - Text( - widget.transactionDetailsViewModel.formattedTitle + - widget.transactionDetailsViewModel.formattedStatus, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500), - ), - CopyWrapper( - requireLongPress: true, - data: ClipboardData(text: transactionInfoAmount.toString()), - builder: (context, copied) => AnimatedSwitcher( - duration: Duration(milliseconds: 300), - child: Text( - key: ValueKey(copied), - copied - ? S.of(context).copied - : transactionInfoAmount.toStringWithSymbol(), - style: TextStyle( - fontSize: 28, - color: copied - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface), + borderRadius: const BorderRadius.vertical(top: Radius.circular(25)), + ), + child: Column( + children: [ + ModalTopBar( + title: S.of(context).transaction, + leadingIcon: const Icon(Icons.close), + onLeadingPressed: Navigator.of(context).pop, + ), + Expanded( + child: SingleChildScrollView( + controller: ModalScrollController.of(context), + child: Column( + children: [ + TokenImageWidget( + imageUrl: + widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "", + size: 64, + ), + const SizedBox(height: 10), + Text( + widget.transactionDetailsViewModel.formattedTitle + + widget.transactionDetailsViewModel.formattedStatus, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), + ), + Observer( + builder: (_) => CopyWrapper( + requireLongPress: true, + data: ClipboardData( + text: widget.transactionDetailsViewModel.transactionCopyAmount, + ), + builder: (context, copied) => AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Text( + key: ValueKey(copied), + copied + ? S.of(context).copied + : widget.transactionDetailsViewModel.transactionAmount, + style: TextStyle( + fontSize: 28, + color: copied + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + ), + ), + ), ), ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - spacing: 12, - children: [ - NewListSections(sections: { - "": widget.transactionDetailsViewModel.items - .map((item) { - if (item.value.isEmpty) return null; + Padding( + padding: const EdgeInsets.all(16), + child: Column( + spacing: 12, + children: [ + NewListSections( + sections: { + "": widget.transactionDetailsViewModel.items + .map((item) { + if (item.value.isEmpty) { + return null; + } - final shouldBuildBottomWidget = item.value.length > 25; + final shouldBuildBottomWidget = item.value.length > 25; - return ListItemRegularRow( - copyableText: item.value, - showArrow: false, - keyValue: ((item.key as ValueKey?)?.value as String?) ?? - item.title, - label: item.title, - trailingWidget: shouldBuildBottomWidget - ? null - : _buildTrailingWIdget(item), - bottomWidget: shouldBuildBottomWidget - ? _buildBottomWidget(item) - : null); - }) - .whereType() - .toList(), - }), - Container( - decoration: BoxDecoration( + return ListItemRegularRow( + copyableText: item.value, + showArrow: false, + keyValue: ((item.key as ValueKey?)?.value as String?) ?? + item.title, + label: item.title, + trailingWidget: shouldBuildBottomWidget + ? null + : _buildTrailingWidget(item), + bottomWidget: shouldBuildBottomWidget + ? _buildBottomWidget(item) + : null, + ); + }) + .whereType() + .toList(), + }, + ), + Container( + decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: Theme.of(context).colorScheme.surfaceContainer), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - spacing: 8, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(S.of(context).note), - TextField( - focusNode: noteFocusNode, - controller: noteController, - decoration: InputDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + spacing: 8, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(S.of(context).note), + TextField( + focusNode: noteFocusNode, + controller: noteController, + decoration: InputDecoration( hintText: S.of(context).add_a_note, border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, contentPadding: EdgeInsets.zero, - isDense: true), - ) - ], + isDense: true, + ), + ), + ], + ), ), ), - ), - Observer( - builder: (_) => NewListSections(sections: { - "view tx": [ - ListItemRegularRow( - keyValue: "view tx on", - label: - widget.transactionDetailsViewModel.explorerDescription, - onTap: widget.transactionDetailsViewModel.launchExplorer, - foregroundColor: Theme.of(context).colorScheme.primary, - trailingIconPath: "assets/new-ui/link_arrow.svg", - trailingIconSize: 8) - ], - if (widget.transactionDetailsViewModel.canReplaceByFee) - "rbf": [ - ListItemRegularRow( - keyValue: "replace by fee", - label: S.of(context).bump_fee, - onTap: () { - Navigator.of(context) - .pushNamed(Routes.bumpFeePage, arguments: [ - widget.transactionDetailsViewModel.transactionInfo, - widget.transactionDetailsViewModel.rawTransaction - ]); - }) - ] - }), - ) - ], + Observer( + builder: (_) => NewListSections( + sections: { + "view tx": [ + ListItemRegularRow( + keyValue: "view tx on", + label: widget + .transactionDetailsViewModel.explorerDescription, + onTap: widget.transactionDetailsViewModel.launchExplorer, + foregroundColor: Theme.of(context).colorScheme.primary, + trailingIconPath: "assets/new-ui/link_arrow.svg", + trailingIconSize: 8, + ), + ], + if (widget.transactionDetailsViewModel.canReplaceByFee) + "rbf": [ + ListItemRegularRow( + keyValue: "replace by fee", + label: S.of(context).bump_fee, + onTap: () { + Navigator.of(context).pushNamed( + Routes.bumpFeePage, + arguments: [ + widget + .transactionDetailsViewModel.transactionInfo, + widget.transactionDetailsViewModel.rawTransaction, + ], + ); + }, + ), + ], + }, + ), + ), + ], + ), ), - ), - SizedBox(height: MediaQuery.of(context).viewPadding.bottom) - ], + SizedBox(height: MediaQuery.of(context).viewPadding.bottom), + ], + ), ), ), - ) - ], + ], + ), ), ), ), - ), - ); - } + ); - Widget _buildTrailingWIdget(TransactionDetailsListItem item) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: switch (item.runtimeType) { - ConfirmationsListItem => Row( - children: [ - Text((item as ConfirmationsListItem).current.toString(), - style: TextStyle(color: Theme.of(context).colorScheme.primary)), - if (item.needed > 0) - Text("/${item.needed}", - style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)) - ], - ), - _ => Text( - item.value, - style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant), - ) - }, - ); - } + Widget _buildTrailingWidget(TransactionDetailsListItem item) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: switch (item.runtimeType) { + ConfirmationsListItem => Row( + children: [ + Text( + (item as ConfirmationsListItem).current.toString(), + style: TextStyle(color: Theme.of(context).colorScheme.primary), + ), + if (item.needed > 0) + Text( + "/${item.needed}", + style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant), + ), + ], + ), + _ => Text( + item.value, + style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant), + ) + }, + ); Widget _buildBottomWidget(TransactionDetailsListItem item) { return switch (item.runtimeType) { diff --git a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart index cea40e197..835631e9a 100644 --- a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart +++ b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart @@ -1,41 +1,41 @@ -import 'dart:async'; +import "dart:async"; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/main.dart'; -import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart'; -import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; -import 'package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart'; -import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart'; -import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart'; -import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; -import 'package:cake_wallet/view_model/send/send_view_model.dart'; -import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import "package:cake_wallet/bitcoin/bitcoin.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/main.dart"; +import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart"; +import "package:cake_wallet/new-ui/widgets/new_primary_button.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart"; +import "package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart"; +import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart"; +import "package:cake_wallet/src/widgets/cake_image_widget.dart"; +import "package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart"; +import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart"; +import "package:cake_wallet/view_model/send/send_view_model.dart"; +import "package:cake_wallet/view_model/wallet_switcher_view_model.dart"; +import "package:cw_core/currency_for_wallet_type.dart"; +import "package:cw_core/wallet_info.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:modal_bottom_sheet/modal_bottom_sheet.dart"; -enum l2actions { deposit, withdraw } +enum L2Actions { deposit, withdraw } class L2ActionWalletSelector extends StatefulWidget { const L2ActionWalletSelector({ - super.key, required this.showOtherWallets, required this.sendViewModel, required this.action, required this.onSendInitiated, required this.contactListViewModel, required this.walletSwitcherViewModel, + super.key, }); final bool showOtherWallets; final SendViewModel sendViewModel; - final l2actions action; + final L2Actions action; final VoidCallback onSendInitiated; final ContactListViewModel contactListViewModel; final WalletSwitcherViewModel walletSwitcherViewModel; @@ -56,12 +56,18 @@ class _L2ActionWalletSelectorState extends State { super.initState(); if (widget.showOtherWallets) { () async { - items.addAll((await WalletInfo.getAll()).where((item) => - item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null)); + items.addAll( + (await WalletInfo.getAll()).where( + (item) => + item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null, + ), + ); items.sort((a, b) { - if (a.name == widget.sendViewModel.wallet.name) + if (a.name == widget.sendViewModel.wallet.name) { return -1; - else if (b.name == widget.sendViewModel.wallet.name) return 1; + } else if (b.name == widget.sendViewModel.wallet.name) { + return 1; + } return 0; }); setState(() {}); @@ -80,16 +86,16 @@ class _L2ActionWalletSelectorState extends State { mainAxisSize: MainAxisSize.min, children: [ ModalTopBar( - title: widget.action == l2actions.deposit + title: widget.action == L2Actions.deposit ? "${S.of(context).send_from}..." : "${S.of(context).receive_to}...", - leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingIcon: const Icon(Icons.arrow_back_ios_new), onLeadingPressed: Navigator.of(context).pop, ), Flexible( child: SafeArea( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( spacing: 12, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -123,29 +129,17 @@ class _L2ActionWalletSelectorState extends State { itemBuilder: (context, index) { final item = items[index]; return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), + padding: const EdgeInsets.symmetric(vertical: 4), child: WalletRow( currencyIconPath: getCryptoCurrencyIconForWalletListItem(item.type), walletName: item.name, isCurrent: item.name == widget.sendViewModel.wallet.name, isSelected: _selectedWalletIndex == index && !textEntered, - onTap: () async { + onTap: () { setState(() { addressController.text = ""; _selectedWalletIndex = index; }); - // if (widget.action == l2actions.withdraw) { - // widget.sendViewModel.outputs.first.address = item.address; - // widget.onSendInitiated(); - // } else if (widget.action == l2actions.deposit) { - // setState(() { - // loadingWalletName = item.name; - // }); - // await _handleChangeWallet(item); - // widget.onSendInitiated(); - // setState(() { - // loadingWalletName = null; - // }); }, ), ); @@ -198,7 +192,7 @@ class _L2ActionWalletSelectorState extends State { // ), // ), // ), - if (widget.action == l2actions.withdraw) + if (widget.action == L2Actions.withdraw) Row( children: [ Flexible( @@ -215,12 +209,12 @@ class _L2ActionWalletSelectorState extends State { Column( spacing: 8, children: [ - if (widget.action == l2actions.deposit && widget.showOtherWallets) ...[ + if (widget.action == L2Actions.deposit && widget.showOtherWallets) ...[ Container( height: 1, color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(64), ), - SizedBox(), + const SizedBox(), Container( height: 52, decoration: BoxDecoration( @@ -232,7 +226,7 @@ class _L2ActionWalletSelectorState extends State { color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(16), - onTap: () async { + onTap: () { Navigator.of(context, rootNavigator: true).pop(); showCupertinoModalBottomSheet( context: navigatorKey.currentContext ?? context, @@ -246,7 +240,7 @@ class _L2ActionWalletSelectorState extends State { ); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( mainAxisAlignment: MainAxisAlignment.center, spacing: 10, @@ -275,19 +269,22 @@ class _L2ActionWalletSelectorState extends State { onPressed: () async { if (widget.sendViewModel.wallet.type == WalletType.bitcoin || widget.sendViewModel.wallet.type == WalletType.litecoin) { - if (widget.action == l2actions.withdraw) { + if (widget.action == L2Actions.withdraw) { widget.sendViewModel.outputs.first.address = bitcoin!.getUnusedSegwitAddress(widget.sendViewModel.wallet)!; } if (widget.showOtherWallets) { - if (widget.action == l2actions.deposit) { + if (widget.action == L2Actions.deposit) { await _handleChangeWallet(items[_selectedWalletIndex]); } else { - widget.sendViewModel.outputs.first.address = - addressController.text.isNotEmpty - ? addressController.text - : items[_selectedWalletIndex].address; + if (items[_selectedWalletIndex].name != + widget.sendViewModel.wallet.name) { + widget.sendViewModel.outputs.first.address = + addressController.text.isNotEmpty + ? addressController.text + : items[_selectedWalletIndex].address; + } } } widget.onSendInitiated(); @@ -298,7 +295,7 @@ class _L2ActionWalletSelectorState extends State { textColor: Theme.of(context).colorScheme.onPrimary, isLoading: _isLoading, ), - SizedBox() + const SizedBox() ], ), ], @@ -339,13 +336,13 @@ class _L2ActionWalletSelectorState extends State { class WalletRow extends StatelessWidget { const WalletRow({ - super.key, required this.currencyIconPath, required this.walletName, required this.onTap, this.isLoading = false, this.isCurrent = false, this.isSelected, + super.key, }); final String currencyIconPath; @@ -370,7 +367,7 @@ class WalletRow extends StatelessWidget { borderRadius: BorderRadius.circular(16), onTap: onTap, child: Padding( - padding: EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart index 0ec4d5acc..78a5321de 100644 --- a/lib/src/screens/transaction_details/rbf_details_page.dart +++ b/lib/src/screens/transaction_details/rbf_details_page.dart @@ -1,34 +1,36 @@ -import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; -import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart'; -import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart'; -import 'package:cake_wallet/src/widgets/list_row.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/standard_expandable_list.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; -import 'package:cake_wallet/src/widgets/standard_picker_list.dart'; -import 'package:cake_wallet/utils/show_bar.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; -import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; +import "package:cake_wallet/core/execution_state.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/src/screens/base_page.dart"; +import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart"; +import "package:cake_wallet/src/widgets/alert_with_one_action.dart"; +import "package:cake_wallet/src/widgets/alert_with_two_actions.dart"; +import "package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart"; +import "package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart"; +import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart"; +import "package:cake_wallet/src/widgets/list_row.dart"; +import "package:cake_wallet/src/widgets/primary_button.dart"; +import "package:cake_wallet/src/widgets/standard_expandable_list.dart"; +import "package:cake_wallet/src/widgets/standard_list.dart"; +import "package:cake_wallet/src/widgets/standard_picker_list.dart"; +import "package:cake_wallet/utils/show_bar.dart"; +import "package:cake_wallet/utils/show_pop_up.dart"; +import "package:cake_wallet/view_model/send/send_view_model_state.dart"; +import "package:cake_wallet/view_model/transaction_details_view_model.dart"; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:flutter_mobx/flutter_mobx.dart"; +import "package:mobx/mobx.dart"; class RBFDetailsPage extends BasePage { RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) { transactionDetailsViewModel.addBumpFeesListItems( - transactionDetailsViewModel.transactionInfo, rawTransaction); + transactionDetailsViewModel.transactionInfo, + rawTransaction, + ); } @override @@ -46,70 +48,70 @@ class RBFDetailsPage extends BasePage { children: [ Expanded( child: SectionStandardList( - sectionCount: 1, - itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length, - itemBuilder: (__, index) { - final item = transactionDetailsViewModel.RBFListItems[index]; + sectionCount: 1, + itemCounter: (_) => transactionDetailsViewModel.rbfListItems.length, + itemBuilder: (__, index) { + final item = transactionDetailsViewModel.rbfListItems[index]; - if (item is StandartListItem) { - return GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: item.value)); - showBar(context, S.of(context).transaction_details_copied(item.title)); - }, - child: ListRow(title: '${item.title}:', value: item.value), - ); - } + if (item is StandartListItem) { + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + showBar(context, S.of(context).transaction_details_copied(item.title)); + }, + child: ListRow(title: "${item.title}:", value: item.value), + ); + } - if (item is StandardExpandableListItem) { - return StandardExpandableList( - title: '${item.title}: ${item.expandableItems.length}', - expandableItems: item.expandableItems, - ); - } + if (item is StandardExpandableListItem) { + return StandardExpandableList( + title: "${item.title}: ${item.expandableItems.length}", + expandableItems: item.expandableItems, + ); + } - if (item is StandardPickerListItem) { - return StandardPickerList( - title: item.title, - value: item.value, - items: item.items, - displayItem: item.displayItem, - onSliderChanged: item.onSliderChanged, - onItemSelected: item.onItemSelected, - selectedIdx: item.selectedIdx, - customItemIndex: item.customItemIndex, - customValue: item.customValue, - maxValue: item.maxValue, - ); - } + if (item is StandardPickerListItem) { + return StandardPickerList( + title: item.title, + value: item.value, + items: item.items, + displayItem: item.displayItem, + onSliderChanged: item.onSliderChanged, + onItemSelected: item.onItemSelected, + selectedIdx: item.selectedIdx, + customItemIndex: item.customItemIndex, + customValue: item.customValue, + maxValue: item.maxValue, + ); + } - if (item is TextFieldListItem) { - return TextFieldListRow( - title: item.title, - value: item.value, - onSubmitted: item.onSubmitted, - ); - } + if (item is TextFieldListItem) { + return TextFieldListRow( + title: item.title, + value: item.value, + onSubmitted: item.onSubmitted, + ); + } - return Container(); - }), + return Container(); + }, + ), ), Padding( - padding: const EdgeInsets.all(24), - child: Observer( - builder: (_) => LoadingPrimaryButton( - onPressed: () async { - transactionDetailsViewModel - .replaceByFee(transactionDetailsViewModel.newFee.toString()); - }, - text: S.of(context).send, - isLoading: - transactionDetailsViewModel.sendViewModel.state is IsExecutingState, - isDisabled: transactionDetailsViewModel.sendViewModel.state - is ExecutedSuccessfullyState, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ))), + padding: const EdgeInsets.all(24), + child: Observer( + builder: (_) => LoadingPrimaryButton( + onPressed: () => transactionDetailsViewModel + .replaceByFee(transactionDetailsViewModel.newFee.toString()), + text: S.of(context).send, + isLoading: transactionDetailsViewModel.sendViewModel.state is IsExecutingState, + isDisabled: + transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), ], ); } @@ -121,7 +123,7 @@ class RBFDetailsPage extends BasePage { return; } - reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) { + reaction((_) => transactionDetailsViewModel.sendViewModel.state, (state) { if (state is! IsExecutingState && loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) { @@ -131,35 +133,35 @@ class RBFDetailsPage extends BasePage { if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( - context: context, - builder: (BuildContext popupContext) { - return AlertWithOneAction( - alertTitle: S.of(popupContext).error, - alertContent: state.error, - buttonText: S.of(popupContext).ok, - buttonAction: () => Navigator.of(popupContext).pop()); - }); + context: context, + builder: (popupContext) => AlertWithOneAction( + alertTitle: S.of(popupContext).error, + alertContent: state.error, + buttonText: S.of(popupContext).ok, + buttonAction: () => Navigator.of(popupContext).pop(), + ), + ); }); } if (state is AwaitingConfirmationState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( - context: context, - builder: (BuildContext popupContext) { - return AlertWithTwoActions( - alertTitle: state.title ?? '', - alertContent: state.message ?? '', - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - state.onConfirm?.call(); - Navigator.of(popupContext).pop(); - }, - actionLeftButton: () { - state.onCancel?.call(); - Navigator.of(popupContext).pop(); - }); - }); + context: context, + builder: (popupContext) => AlertWithTwoActions( + alertTitle: state.title ?? "", + alertContent: state.message ?? "", + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + state.onConfirm?.call(); + Navigator.of(popupContext).pop(); + }, + actionLeftButton: () { + state.onCancel?.call(); + Navigator.of(popupContext).pop(); + }, + ), + ); }); } @@ -169,7 +171,7 @@ class RBFDetailsPage extends BasePage { showModalBottomSheet( context: context, isDismissible: false, - builder: (BuildContext context) { + builder: (context) { loadingBottomSheetContext = context; return LoadingBottomSheet( titleText: S.of(context).generating_transaction, @@ -187,42 +189,42 @@ class RBFDetailsPage extends BasePage { context: context, isDismissible: false, isScrollControlled: true, - builder: (BuildContext bottomSheetContext) { - return ConfirmSendingBottomSheet( - key: ValueKey('rbf_confirm_sending_bottom_sheet'), - titleText: S.of(bottomSheetContext).confirm_transaction, - isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend, - walletType: transactionDetailsViewModel.sendViewModel.walletType, - titleIconPath: - transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath, - currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency, - amount: S.of(bottomSheetContext).send_amount, - amountValue: - transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted, - fiatAmountValue: transactionDetailsViewModel - .sendViewModel.pendingTransactionFiatAmountFormatted, - fee: S.of(bottomSheetContext).send_fee, - feeValue: - transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted, - feeFiatAmount: transactionDetailsViewModel - .sendViewModel.pendingTransactionFeeFiatAmountFormatted, - outputs: transactionDetailsViewModel.sendViewModel.outputs, - footerType: FooterType.slideActionButton, - accessibleNavigationModeSlideActionButtonText: S.of(context).send, - onSlideActionComplete: () async { - Navigator.of(bottomSheetContext).pop(); - await transactionDetailsViewModel.sendViewModel.commitTransaction(context); - try { + builder: (bottomSheetContext) => ConfirmSendingBottomSheet( + key: const ValueKey("rbf_confirm_sending_bottom_sheet"), + titleText: S.of(bottomSheetContext).confirm_transaction, + isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend, + walletType: transactionDetailsViewModel.sendViewModel.walletType, + titleIconPath: + transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath, + currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency, + amount: S.of(bottomSheetContext).send_amount, + amountValue: + transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: + transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted, + fee: S.of(bottomSheetContext).send_fee, + feeValue: + transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted, + feeFiatAmount: transactionDetailsViewModel + .sendViewModel.pendingTransactionFeeFiatAmountFormatted, + outputs: transactionDetailsViewModel.sendViewModel.outputs, + footerType: FooterType.slideActionButton, + accessibleNavigationModeSlideActionButtonText: S.of(context).send, + onSlideActionComplete: () async { + Navigator.of(bottomSheetContext).pop(); + await transactionDetailsViewModel.sendViewModel.commitTransaction(context); + try { + if (bottomSheetContext.mounted) { Navigator.of(bottomSheetContext).pop(); - } catch (_) {} - }, - change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change, - amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy, - ); - }, + } + } catch (_) {} + }, + change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change, + amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy, + ), ); if (result == null) { - transactionDetailsViewModel.sendViewModel.dismissTransaction(); + await transactionDetailsViewModel.sendViewModel.dismissTransaction(); } } }); @@ -232,14 +234,14 @@ class RBFDetailsPage extends BasePage { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { showPopUp( - context: context, - builder: (BuildContext popupContext) { - return AlertWithOneAction( - alertTitle: S.of(popupContext).sending, - alertContent: S.of(popupContext).transaction_sent, - buttonText: S.of(popupContext).ok, - buttonAction: () => Navigator.of(popupContext).pop()); - }); + context: context, + builder: (popupContext) => AlertWithOneAction( + alertTitle: S.of(popupContext).sending, + alertContent: S.of(popupContext).transaction_sent, + buttonText: S.of(popupContext).ok, + buttonAction: () => Navigator.of(popupContext).pop(), + ), + ); } }); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 16f12ac0b..e83feb90c 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -1,50 +1,50 @@ -import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/solana/solana.dart'; -import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/core/address_validator.dart'; -import 'package:cake_wallet/tron/tron.dart'; -import 'package:cake_wallet/zano/zano.dart'; -import 'package:cw_core/crypto_amount_format.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/evm/evm.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; -import 'package:cake_wallet/entities/transaction_description.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart'; -import 'package:cake_wallet/utils/date_formatter.dart'; -import 'package:cake_wallet/view_model/send/send_view_model.dart'; -import 'package:collection/collection.dart'; -import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; -import 'package:intl/intl.dart'; -import 'package:mobx/mobx.dart'; -import 'package:url_launcher/url_launcher.dart'; +import "package:cake_wallet/bitcoin/bitcoin.dart"; +import "package:cake_wallet/core/address_validator.dart"; +import "package:cake_wallet/entities/priority_for_wallet_type.dart"; +import "package:cake_wallet/entities/transaction_description.dart"; +import "package:cake_wallet/evm/evm.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/monero/monero.dart"; +import "package:cake_wallet/reactions/wallet_connect.dart"; +import "package:cake_wallet/solana/solana.dart"; +import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart"; +import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart"; +import "package:cake_wallet/store/app_store.dart"; +import "package:cake_wallet/tron/tron.dart"; +import "package:cake_wallet/view_model/send/send_view_model.dart"; +import "package:cake_wallet/zano/zano.dart"; +import "package:collection/collection.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/currency_for_wallet_type.dart"; +import "package:cw_core/transaction_direction.dart"; +import "package:cw_core/transaction_info.dart"; +import "package:cw_core/transaction_priority.dart"; +import "package:cw_core/wallet_base.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:flutter/foundation.dart"; +import "package:hive/hive.dart"; +import "package:intl/intl.dart"; +import "package:mobx/mobx.dart"; +import "package:url_launcher/url_launcher.dart"; -part 'transaction_details_view_model.g.dart'; +part "transaction_details_view_model.g.dart"; bool _trueFunc(_) => true; /// We're adding a regex here so we can remove any already saved address that has the account in it. /// In the refactor, we will make another separate variable for accounts and the UI would handle it as needed. String _moneroRecipientAddressForDisplay(String raw, WalletType walletType) { - if (walletType != WalletType.monero || raw.isEmpty) return raw; + if (walletType != WalletType.monero || raw.isEmpty) { + return raw; + } - final compact = raw.replaceAll(RegExp(r'\s'), ''); + final compact = raw.replaceAll(RegExp(r"\s"), ""); final match = RegExp( - r'4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}', + r"4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}", caseSensitive: false, ).firstMatch(compact); return match?.group(0) ?? raw.trim(); @@ -55,6 +55,14 @@ bool isLightning(TransactionInfo tx) => (tx.additionalInfo["isLightning"] as boo bool hasLightningPreimage(TransactionInfo tx) => (tx.additionalInfo["preimage"] as String?) != null; class TxDetailRowDefinition { + TxDetailRowDefinition({ + required this.keyString, + required this.title, + required this.valueGetter, + this.applicable = _trueFunc, + this.listItemBuilder = StandartListItem.new, + }); + final String keyString; final String title; final String Function(TransactionDetailsViewModelBase) valueGetter; @@ -65,20 +73,13 @@ class TxDetailRowDefinition { required Key key, }) listItemBuilder; - TxDetailRowDefinition( - {required this.keyString, - required this.title, - required this.valueGetter, - this.applicable = _trueFunc, - this.listItemBuilder = StandartListItem.new}); - static final List defs = [ TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_date_key", - title: S.current.transaction_details_date, - valueGetter: (vm) => - DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode) - .format(vm.transactionInfo.date)), + keyString: "standard_list_item_transaction_details_date_key", + title: S.current.transaction_details_date, + valueGetter: (vm) => DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode) + .format(vm.transactionInfo.date), + ), TxDetailRowDefinition( keyString: "standard_list_item_transaction_details_height_key", title: S.current.transaction_details_height, @@ -88,125 +89,137 @@ class TxDetailRowDefinition { !isLightning(vm.transactionInfo), ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_fee_key", - title: S.current.transaction_details_fee, - valueGetter: (vm) => vm.transactionInfo.fee!.toStringWithSymbol(), - applicable: (vm) => - vm.wallet.type != WalletType.nano && - (vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty), + keyString: "standard_list_item_transaction_details_fee_key", + title: S.current.transaction_details_fee, + valueGetter: (vm) => vm.feeAmount, + applicable: (vm) => + vm.wallet.type != WalletType.nano && + (vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty, + ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_confirmations_key", - title: S.current.confirmations, - valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}", - applicable: (vm) => - [...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero] - .contains(vm.wallet.type) && - !isLightning(vm.transactionInfo), - listItemBuilder: ConfirmationsListItem.new), + keyString: "standard_list_item_transaction_confirmations_key", + title: S.current.confirmations, + valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}", + applicable: (vm) => + [...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero] + .contains(vm.wallet.type) && + !isLightning(vm.transactionInfo), + listItemBuilder: ConfirmationsListItem.new, + ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_recipient_address_key", - title: S.current.transaction_details_recipient_address, - valueGetter: (vm) { - String? ret = null; + keyString: "standard_list_item_transaction_details_recipient_address_key", + title: S.current.transaction_details_recipient_address, + valueGetter: (vm) { + String? ret; - switch (vm.wallet.type) { - case WalletType.monero: - if (vm.transactionInfo.direction == TransactionDirection.incoming) { - ret = monero!.getTransactionAddress( - vm.wallet, - vm.transactionInfo.additionalInfo['accountIndex'] as int, - vm.transactionInfo.additionalInfo['addressIndex'] as int); - } - case WalletType.bitcoin: - ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? []) - .firstOrNull ?? - ""; - case WalletType.tron: - if (vm.transactionInfo.to != null) - ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet); - default: - break; - } - if (ret == null) { - ret = vm.transactionInfo.to ?? ""; - } - final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type); - vm.isRecipientAddressShown = resolvedAddress.isNotEmpty; - return resolvedAddress; - }, - applicable: (vm) => - vm.showRecipientAddress && - (vm.transactionInfo.to != null || - [WalletType.monero, WalletType.tron].contains(vm.wallet.type) || - vm.wallet.type == WalletType.bitcoin && - vm.transactionInfo.direction == TransactionDirection.incoming), - listItemBuilder: AddressListItem.new), - TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_source_address_key", - title: S.current.transaction_details_source_address, - valueGetter: (vm) { - switch (vm.wallet.type) { - case WalletType.tron: - return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet); - default: - return vm.transactionInfo.from!; - } - }, - applicable: (vm) => vm.transactionInfo.from != null, - listItemBuilder: AddressListItem.new), - TxDetailRowDefinition( - keyString: "standard_list_item_address_label_key", - title: S.current.address_label, - valueGetter: (vm) => monero!.getSubaddressLabel( - vm.wallet, - vm.transactionInfo.additionalInfo['accountIndex'] as int, - vm.transactionInfo.additionalInfo['addressIndex'] as int), - applicable: (vm) => vm.wallet.type == WalletType.monero), - TxDetailRowDefinition( - keyString: "standard_list_item_transaction_key", - title: S.current.transaction_key, - valueGetter: (vm) { - final descriptionKey = - '${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}'; + switch (vm.wallet.type) { + case WalletType.monero: + if (vm.transactionInfo.direction == TransactionDirection.incoming) { + ret = monero!.getTransactionAddress( + vm.wallet, + vm.transactionInfo.additionalInfo["accountIndex"] as int, + vm.transactionInfo.additionalInfo["addressIndex"] as int, + ); + } + case WalletType.bitcoin: + ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? []) + .firstOrNull ?? + ""; + case WalletType.tron: + if (vm.transactionInfo.to != null) { + ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet); + } + default: + break; + } + ret ??= vm.transactionInfo.to ?? ""; - final description = vm.transactionDescriptionBox.values.firstWhere( - (val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash, - orElse: () => TransactionDescription(id: descriptionKey)); - return vm.transactionInfo.additionalInfo['key'] as String? ?? - description.transactionKey ?? - ""; - }, - applicable: (vm) => vm.wallet.type == WalletType.monero), + final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type); + vm.isRecipientAddressShown = resolvedAddress.isNotEmpty; + return resolvedAddress; + }, + applicable: (vm) => + vm.showRecipientAddress && + (vm.transactionInfo.to != null || + [WalletType.monero, WalletType.tron].contains(vm.wallet.type) || + vm.wallet.type == WalletType.bitcoin && + vm.transactionInfo.direction == TransactionDirection.incoming), + listItemBuilder: AddressListItem.new, + ), + TxDetailRowDefinition( + keyString: "standard_list_item_transaction_details_source_address_key", + title: S.current.transaction_details_source_address, + valueGetter: (vm) { + switch (vm.wallet.type) { + case WalletType.tron: + return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet); + default: + return vm.transactionInfo.from!; + } + }, + applicable: (vm) => vm.transactionInfo.from != null, + listItemBuilder: AddressListItem.new, + ), + TxDetailRowDefinition( + keyString: "standard_list_item_address_label_key", + title: S.current.address_label, + valueGetter: (vm) => monero!.getSubaddressLabel( + vm.wallet, + vm.transactionInfo.additionalInfo["accountIndex"] as int, + vm.transactionInfo.additionalInfo["addressIndex"] as int, + ), + applicable: (vm) => vm.wallet.type == WalletType.monero, + ), + TxDetailRowDefinition( + keyString: "standard_list_item_transaction_key", + title: S.current.transaction_key, + valueGetter: (vm) { + final descriptionKey = + "${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}"; + + final description = vm.transactionDescriptionBox.values.firstWhere( + (val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash, + orElse: () => TransactionDescription(id: descriptionKey), + ); + return vm.transactionInfo.additionalInfo["key"] as String? ?? + description.transactionKey ?? + ""; + }, + applicable: (vm) => vm.wallet.type == WalletType.monero, + ), TxDetailRowDefinition( keyString: "standard_list_item_lightning_preimage", title: S.current.transaction_preimage, - valueGetter: (vm) => vm.transactionInfo.additionalInfo['preimage'] as String? ?? "", + valueGetter: (vm) => vm.transactionInfo.additionalInfo["preimage"] as String? ?? "", applicable: (vm) => hasLightningPreimage(vm.transactionInfo) && isLightning(vm.transactionInfo), ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_confirmed_key", - title: S.current.confirmed_tx, - valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(), - applicable: (vm) => vm.wallet.type == WalletType.nano), + keyString: "standard_list_item_transaction_confirmed_key", + title: S.current.confirmed_tx, + valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(), + applicable: (vm) => vm.wallet.type == WalletType.nano, + ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_memo_key", - title: S.current.memo, - valueGetter: (vm) => vm.transactionInfo.additionalInfo['memo'] as String, - applicable: (vm) => - vm.wallet.type == WalletType.zcash && - vm.transactionInfo.additionalInfo["memo"] != null), + keyString: "standard_list_item_transaction_details_memo_key", + title: S.current.memo, + valueGetter: (vm) => vm.transactionInfo.additionalInfo["memo"] as String, + applicable: (vm) => + vm.wallet.type == WalletType.zcash && vm.transactionInfo.additionalInfo["memo"] != null, + ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_asset_id_key", - title: "Asset ID", - valueGetter: (vm) => - vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id", - applicable: (vm) => vm.wallet.type == WalletType.zano), + keyString: "standard_list_item_transaction_details_asset_id_key", + title: "Asset ID", + valueGetter: (vm) => + vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id", + applicable: (vm) => vm.wallet.type == WalletType.zano, + ), TxDetailRowDefinition( - keyString: "standard_list_item_transaction_details_comment_key", - title: S.current.transaction_details_title, - valueGetter: (vm) => vm.transactionInfo.additionalInfo['comment'] as String? ?? "", - applicable: (vm) => vm.wallet.type == WalletType.zano), + keyString: "standard_list_item_transaction_details_comment_key", + title: S.current.transaction_details_title, + valueGetter: (vm) => vm.transactionInfo.additionalInfo["comment"] as String? ?? "", + applicable: (vm) => vm.wallet.type == WalletType.zano, + ), TxDetailRowDefinition( keyString: "standard_list_item_transaction_details_id_key", title: S.current.transaction_details_transaction_id, @@ -227,7 +240,7 @@ abstract class TransactionDetailsViewModelBase with Store { required this.sendViewModel, this.canReplaceByFee = false, }) : items = [], - RBFListItems = [], + rbfListItems = [], newFee = 0, isRecipientAddressShown = false, _appStore = appStore, @@ -236,19 +249,23 @@ abstract class TransactionDetailsViewModelBase with Store { for (final def in TxDetailRowDefinition.defs) { if (def.applicable(this)) { - items.add(def.listItemBuilder( + items.add( + def.listItemBuilder( title: def.title, value: def.valueGetter(this), - key: ValueKey(def.keyString)) as TransactionDetailsListItem); + key: ValueKey(def.keyString), + ) as TransactionDetailsListItem, + ); } } _checkForRBF(tx); - final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}"; final description = transactionDescriptionBox.values.firstWhere( - (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, - orElse: () => TransactionDescription(id: descriptionKey)); + (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, + orElse: () => TransactionDescription(id: descriptionKey), + ); if (showRecipientAddress && !isRecipientAddressShown) { final recipientAddress = description.recipientAddress; @@ -260,7 +277,7 @@ abstract class TransactionDetailsViewModelBase with Store { AddressListItem( title: S.current.transaction_details_recipient_address, value: recipientAddressForDisplay, - key: ValueKey('standard_list_item_${recipientAddressForDisplay}_key'), + key: ValueKey("standard_list_item_${recipientAddressForDisplay}_key"), ), ); } @@ -268,10 +285,11 @@ abstract class TransactionDetailsViewModelBase with Store { } void updateNote(String note) { - final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}"; final description = transactionDescriptionBox.values.firstWhere( - (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, - orElse: () => TransactionDescription(id: descriptionKey)); + (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, + orElse: () => TransactionDescription(id: descriptionKey), + ); description.transactionNote = note; @@ -283,9 +301,10 @@ abstract class TransactionDetailsViewModelBase with Store { } String get note { - final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}"; final description = transactionDescriptionBox.values - .firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash); + .firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash, + ); return description?.transactionNote ?? ""; } @@ -296,7 +315,7 @@ abstract class TransactionDetailsViewModelBase with Store { final AppStore _appStore; final List items; - final List RBFListItems; + final List rbfListItems; bool showRecipientAddress; bool isRecipientAddressShown; int newFee; @@ -304,9 +323,13 @@ abstract class TransactionDetailsViewModelBase with Store { TransactionPriority? transactionPriority; CryptoCurrency get transactionAsset { - if (isEVMCompatibleChain(wallet.type)) return evm!.assetOfTransaction(wallet, transactionInfo); + if (isEVMCompatibleChain(wallet.type)) { + return evm!.assetOfTransaction(wallet, transactionInfo); + } - if (isLightning(transactionInfo)) return CryptoCurrency.btcln; + if (isLightning(transactionInfo)) { + return CryptoCurrency.btcln; + } return switch (wallet.type) { WalletType.solana => solana!.assetOfTransaction(wallet, transactionInfo), @@ -316,26 +339,38 @@ abstract class TransactionDetailsViewModelBase with Store { }; } - // TODO integrate these getters with the TransactionInfo object + @computed + String get transactionAmount => + _appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.amount); + + @computed + String get feeAmount => + _appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.fee!); + + @computed + String get transactionCopyAmount => + _appStore.amountParsingProxy.asDisplayString(transactionInfo.amount); + + // TODO(malik1004x): integrate these getters with the TransactionInfo object String get formattedPendingStatus { switch (wallet.type) { case WalletType.monero: case WalletType.haven: case WalletType.zano: if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 10) { - return ' (${transactionInfo.confirmations}/10)'; + return " (${transactionInfo.confirmations}/10)"; } break; case WalletType.wownero: if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 3) { - return ' (${transactionInfo.confirmations}/3)'; + return " (${transactionInfo.confirmations}/3)"; } break; case WalletType.litecoin: - bool isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false; - bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false; - bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false; - String str = ''; + final isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false; + final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false; + final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false; + String str = ""; if (transactionInfo.confirmations <= 0) { str = S.current.pending; } @@ -352,10 +387,10 @@ abstract class TransactionDetailsViewModelBase with Store { } return str; default: - return ''; + return ""; } - return ''; + return ""; } String get formattedStatus { @@ -369,7 +404,7 @@ abstract class TransactionDetailsViewModelBase with Store { return formattedPendingStatus; } - return transactionInfo.isPending ? S.current.pending : ''; + return transactionInfo.isPending ? S.current.pending : ""; } int get neededConfirmations { @@ -381,9 +416,11 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.wownero: return 3; case WalletType.litecoin: - bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false; - bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false; - if (isPegOut || fromPegOut) return 6; + final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false; + final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false; + if (isPegOut || fromPegOut) { + return 6; + } default: return 0; } @@ -408,52 +445,56 @@ abstract class TransactionDetailsViewModelBase with Store { final txId = transactionInfo.txHash; if (wallet.chainId != null) { final explorerUrl = evm!.getExplorerUrlForChainId(wallet.chainId!); - if (explorerUrl != null) return '$explorerUrl/tx/${txId}'; + if (explorerUrl != null) { + return "$explorerUrl/tx/${txId}"; + } } switch (wallet.type) { case WalletType.monero: - return 'https://monero.com/tx/${txId}'; + return "https://monero.com/tx/${txId}"; case WalletType.bitcoin: - return 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}'; + return isLightning(transactionInfo) + ? "https://sparkscan.io/tx/${txId}" + : 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}'; case WalletType.litecoin: return bitcoin!.txIsMweb(transactionInfo) ? "https://www.mwebexplorer.com/blocks/block/${transactionInfo.height}" - : 'https://blockchair.com/litecoin/transaction/${txId}'; + : "https://blockchair.com/litecoin/transaction/${txId}"; case WalletType.bitcoinCash: - return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; + return "https://blockchair.com/bitcoin-cash/transaction/${txId}"; case WalletType.haven: - return 'https://explorer.havenprotocol.org/search?value=${txId}'; + return "https://explorer.havenprotocol.org/search?value=${txId}"; case WalletType.ethereum: - return 'https://etherscan.io/tx/${txId}'; + return "https://etherscan.io/tx/${txId}"; case WalletType.base: - return 'https://basescan.org/tx/${txId}'; + return "https://basescan.org/tx/${txId}"; case WalletType.arbitrum: - return 'https://arbiscan.io/tx/${txId}'; + return "https://arbiscan.io/tx/${txId}"; case WalletType.bsc: - return 'https://bscscan.com/tx/${txId}'; + return "https://bscscan.com/tx/${txId}"; case WalletType.polygon: - return 'https://polygonscan.com/tx/${txId}'; + return "https://polygonscan.com/tx/${txId}"; case WalletType.nano: - return 'https://nanexplorer.com/nano/block/${txId}'; + return "https://nanexplorer.com/nano/block/${txId}"; case WalletType.banano: - return 'https://nanexplorer.com/banano/block/${txId}'; + return "https://nanexplorer.com/banano/block/${txId}"; case WalletType.solana: - return 'https://solscan.io/tx/${txId}'; + return "https://solscan.io/tx/${txId}"; case WalletType.tron: - return 'https://tronscan.org/#/transaction/${txId}'; + return "https://tronscan.org/#/transaction/${txId}"; case WalletType.wownero: - return 'https://explore.wownero.com/tx/${txId}'; + return "https://explore.wownero.com/tx/${txId}"; case WalletType.zano: - return 'https://explorer.zano.org/transaction/${txId}'; + return "https://explorer.zano.org/transaction/${txId}"; case WalletType.decred: return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; case WalletType.dogecoin: - return 'https://blockchair.com/dogecoin/transaction/${txId}'; + return "https://blockchair.com/dogecoin/transaction/${txId}"; case WalletType.zcash: - return 'https://blockchair.com/zcash/transaction/${txId}'; + return "https://blockchair.com/zcash/transaction/${txId}"; case WalletType.none: - return ''; + return ""; } } @@ -473,13 +514,17 @@ abstract class TransactionDetailsViewModelBase with Store { : transactionInfo.outputAddresses!.length; newFee = bitcoin!.getFeeAmountForPriority( - wallet, bitcoin!.getBitcoinTransactionPriorityMedium(), inputsCount, outputsCount); + wallet, + bitcoin!.getBitcoinTransactionPriorityMedium(), + inputsCount, + outputsCount, + ); - RBFListItems.add( + rbfListItems.add( StandartListItem( title: S.current.old_fee, - value: tx.fee?.toStringWithSymbol() ?? '0.0', - key: ValueKey('standard_list_item_rbf_old_fee_key'), + value: tx.fee?.toStringWithSymbol() ?? "0.0", + key: const ValueKey("standard_list_item_rbf_old_fee_key"), ), ); @@ -488,32 +533,33 @@ abstract class TransactionDetailsViewModelBase with Store { final recommendedRate = (transactionInfo.fee! / BigInt.from(size)) + transactionInfo.fee!.copyWith(amount: BigInt.one); - RBFListItems.add( - StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte')); + rbfListItems.add( + StandartListItem(title: "New recommended fee rate", value: "$recommendedRate sat/byte"), + ); } final priorities = priorityForWalletType(wallet.type); final selectedItem = priorities.indexOf(sendViewModel.feesViewModel.transactionPriority); final customItem = priorities.firstWhereOrNull( - (element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom); + (element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom, + ); final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null; final maxCustomFeeRate = sendViewModel.feesViewModel.maxCustomFeeRate?.toDouble(); - RBFListItems.add( + rbfListItems.add( StandardPickerListItem( - key: ValueKey('standard_picker_list_item_transaction_priorities_key'), + key: const ValueKey("standard_picker_list_item_transaction_priorities_key"), title: S.current.estimated_new_fee, - value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${wallet.currency}', + value: "${bitcoin!.formatterBitcoinAmountToString(amount: newFee)} ${wallet.currency}", items: priorityForWalletType(wallet.type), customValue: _appStore.settingsStore.customBitcoinFeeRate.toDouble(), maxValue: maxCustomFeeRate, selectedIdx: selectedItem, customItemIndex: customItemIndex ?? 0, - displayItem: (dynamic priority, double sliderValue) => + displayItem: (dynamic priority, sliderValue) => sendViewModel.feesViewModel.displayFeeRate(priority, sliderValue.round()), - onSliderChanged: (double newValue) => - setNewFee(value: newValue, priority: transactionPriority!), - onItemSelected: (dynamic item, double sliderValue) { + onSliderChanged: (newValue) => setNewFee(value: newValue, priority: transactionPriority!), + onItemSelected: (dynamic item, sliderValue) { transactionPriority = item as TransactionPriority; return setNewFee(value: sliderValue, priority: transactionPriority!); }, @@ -521,9 +567,9 @@ abstract class TransactionDetailsViewModelBase with Store { ); if (transactionInfo.inputAddresses != null && transactionInfo.inputAddresses!.isNotEmpty) { - RBFListItems.add( + rbfListItems.add( StandardExpandableListItem( - key: ValueKey('standard_expandable_list_item_transaction_input_addresses_key'), + key: const ValueKey("standard_expandable_list_item_transaction_input_addresses_key"), title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!, ), @@ -532,17 +578,17 @@ abstract class TransactionDetailsViewModelBase with Store { if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) { final outputAddresses = transactionInfo.outputAddresses!.map((element) { - if (element.contains('OP_RETURN:') && element.length > 40) { - return element.substring(0, 40) + '...'; + if (element.contains("OP_RETURN:") && element.length > 40) { + return "${element.substring(0, 40)}..."; } return element; }).toList(); - RBFListItems.add( + rbfListItems.add( StandardExpandableListItem( title: S.current.outputs, expandableItems: outputAddresses, - key: ValueKey('standard_expandable_list_item_transaction_output_addresses_key'), + key: const ValueKey("standard_expandable_list_item_transaction_output_addresses_key"), ), ); } @@ -552,7 +598,7 @@ abstract class TransactionDetailsViewModelBase with Store { Future _checkForRBF(TransactionInfo tx) async { if (wallet.type == WalletType.bitcoin && transactionInfo.direction == TransactionDirection.outgoing) { - final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}"; final description = transactionDescriptionBox.values .firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash); @@ -569,45 +615,23 @@ abstract class TransactionDetailsViewModelBase with Store { } } - String setNewFee({double? value, required TransactionPriority priority}) { + String setNewFee({required TransactionPriority priority, double? value}) { newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null ? bitcoin!.feeAmountWithFeeRate( wallet, value.round(), transactionInfo.inputAddresses?.length ?? 1, - transactionInfo.outputAddresses?.length ?? 1) + transactionInfo.outputAddresses?.length ?? 1, + ) : bitcoin!.getFeeAmountForPriority( wallet, priority, transactionInfo.inputAddresses?.length ?? 1, - transactionInfo.outputAddresses?.length ?? 1); + transactionInfo.outputAddresses?.length ?? 1, + ); return bitcoin!.formatterBitcoinAmountToString(amount: newFee); } - String get formattedCryptoAmount { - if (wallet.type == WalletType.bitcoin) { - final crypto = isLightning(transactionInfo) ? CryptoCurrency.btcln : CryptoCurrency.btc; - final amount = _appStore.amountParsingProxy - .asDisplayString(transactionInfo.amount) - .withMaxDecimals(8) - .withLocalSeperator(_appStore.settingsStore.languageCode); - - return '$amount ${_appStore.amountParsingProxy.getCryptoSymbol(crypto)}'; - } - - return transactionInfo.amount.toStringWithSymbol(); - } - void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee); - - @computed - String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled - ? '' - : sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title; - - @computed - String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled - ? '' - : sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title; } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 65ee7aef8..0610381cb 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,59 +1,58 @@ -import 'dart:core'; -import 'dart:developer' as dev; +import "dart:core"; +import "dart:developer" as dev; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/core/address_resolver/yat/yat_store.dart'; -import 'package:cake_wallet/core/amount_parsing_proxy.dart'; -import 'package:cake_wallet/core/fiat_conversion_service.dart'; -import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; -import 'package:cake_wallet/decred/decred.dart'; -import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; -import 'package:cake_wallet/entities/fiat_api_mode.dart'; -import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cake_wallet/evm/evm.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/reactions/wallet_connect.dart'; -import 'package:cake_wallet/reactions/wallet_utils.dart'; -import 'package:cake_wallet/solana/solana.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; -import 'package:cake_wallet/tron/tron.dart'; -import 'package:cake_wallet/utils/list_item.dart'; -import 'package:cake_wallet/utils/qr_util.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart'; -import 'package:cake_wallet/wownero/wownero.dart'; -import 'package:cake_wallet/zcash/zcash.dart'; -import 'package:cake_wallet/zano/zano.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; -import 'package:cw_core/erc20_token.dart'; -import 'package:cw_core/payment_uris.dart'; -import 'package:cw_core/spl_token.dart'; -import 'package:cw_core/tron_token.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:mobx/mobx.dart'; +import "package:cake_wallet/bitcoin/bitcoin.dart"; +import "package:cake_wallet/core/address_resolver/yat/yat_store.dart"; +import "package:cake_wallet/core/amount_parsing_proxy.dart"; +import "package:cake_wallet/core/fiat_conversion_service.dart"; +import "package:cake_wallet/core/wallet_change_listener_view_model.dart"; +import "package:cake_wallet/decred/decred.dart"; +import "package:cake_wallet/entities/auto_generate_subaddress_status.dart"; +import "package:cake_wallet/entities/fiat_api_mode.dart"; +import "package:cake_wallet/entities/fiat_currency.dart"; +import "package:cake_wallet/evm/evm.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/monero/monero.dart"; +import "package:cake_wallet/reactions/wallet_connect.dart"; +import "package:cake_wallet/reactions/wallet_utils.dart"; +import "package:cake_wallet/solana/solana.dart"; +import "package:cake_wallet/store/app_store.dart"; +import "package:cake_wallet/store/dashboard/fiat_conversion_store.dart"; +import "package:cake_wallet/tron/tron.dart"; +import "package:cake_wallet/utils/list_item.dart"; +import "package:cake_wallet/utils/qr_util.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart"; +import "package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart"; +import "package:cake_wallet/wownero/wownero.dart"; +import "package:cake_wallet/zano/zano.dart"; +import "package:cake_wallet/zcash/zcash.dart"; +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/currency.dart"; +import "package:cw_core/currency_for_wallet_type.dart"; +import "package:cw_core/erc20_token.dart"; +import "package:cw_core/payment_uris.dart"; +import "package:cw_core/spl_token.dart"; +import "package:cw_core/tron_token.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:mobx/mobx.dart"; -part 'wallet_address_list_view_model.g.dart'; +part "wallet_address_list_view_model.g.dart"; class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel; abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ - required AppStore appStore, + required super.appStore, required this.yatStore, required this.fiatConversionStore, }) : _baseItems = [], selectedCurrency = appStore.wallet!.currency, hasAccounts = [WalletType.monero, WalletType.wownero].contains(appStore.wallet!.type), _appStore = appStore, - receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? '', - super(appStore: appStore) { + receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? "" { _init(); } @@ -81,8 +80,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo CryptoCurrency? tokenCurrency; @computed - String get cryptoCurrencySymbol => - _appStore.amountParsingProxy.getCryptoSymbol(tokenCurrency ?? wallet.currency); + String get cryptoCurrencySymbol => _appStore.amountParsingProxy.getCryptoSymbol( + tokenCurrency ?? + (selectedCurrency is CryptoCurrency + ? selectedCurrency as CryptoCurrency + : wallet.currency), + ); void setTokenCurrency(Currency curr) { if (curr == wallet.currency || curr == CryptoCurrency.btcln) { @@ -122,7 +125,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo _appStore.amountParsingProxy.useSatoshi(selectedCurrency as CryptoCurrency); @observable - String searchText = ''; + String searchText = ""; @computed int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency); @@ -131,30 +134,45 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo int get tokenCurrencyIndex => tokenCurrency == null ? 0 : tokenCurrencies.indexOf(tokenCurrency!); @observable - String _amount = ''; + String _amount = ""; @computed - String get displayAmount => _appStore.amountParsingProxy - .getDisplayCryptoAmount(_amount, tokenCurrency ?? wallet.currency); + String get displayAmount => _appStore.amountParsingProxy.getDisplayCryptoAmount( + _amount, + tokenCurrency ?? + (selectedCurrency is CryptoCurrency + ? selectedCurrency as CryptoCurrency + : wallet.currency), + ); // NOT PRECISE! just for display purposes. @computed String get fiatAmount { - if (_amount.isEmpty) return ""; + if (_amount.isEmpty) { + return ""; + } + var cryptoCurrency = tokenCurrency ?? wallet.currency; - if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc; + if (cryptoCurrency == CryptoCurrency.btcln) { + cryptoCurrency = CryptoCurrency.btc; + } + if (selectedCurrency is FiatCurrency && _fiatRate != null) { return selectedCurrencyFiatAmount; } - if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) return ""; + if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) { + return ""; + } final amount = double.tryParse(_amount) ?? 0; return (amount * fiatConversionStore.prices[cryptoCurrency]!).toStringAsFixed(2); } @computed String get selectedCurrencyFiatAmount { - if (_fiatRate == null) return ""; + if (_fiatRate == null) { + return ""; + } final amount = double.tryParse(_amount) ?? 0; return (amount * _fiatRate!).toStringAsFixed(2); } @@ -201,26 +219,29 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo PaymentURI get uri { if (tokenCurrency != null && isEVMCompatibleChain(wallet.type)) { return ERC681URI( - chainId: wallet.chainId ?? 1, - address: wallet.walletAddresses.address, - amount: _amount, - contractAddress: (tokenCurrency as Erc20Token).contractAddress); + chainId: wallet.chainId ?? 1, + address: wallet.walletAddresses.address, + amount: _amount, + contractAddress: (tokenCurrency! as Erc20Token).contractAddress, + ); } if (tokenCurrency is TronToken && wallet.type == WalletType.tron) { return TronURI( amount: _amount, address: wallet.walletAddresses.address, - contractAddress: (tokenCurrency as TronToken).contractAddress, + contractAddress: (tokenCurrency! as TronToken).contractAddress, ); } if (tokenCurrency is SPLToken && wallet.type == WalletType.solana) { return SolanaURI( amount: _amount, address: wallet.walletAddresses.address, - contractAddress: (tokenCurrency as SPLToken).mintAddress, + contractAddress: (tokenCurrency! as SPLToken).mintAddress, ); } - if (isLightning && _lnPaymentRequest != null) return _lnPaymentRequest!; + if (isLightning && _lnPaymentRequest != null) { + return _lnPaymentRequest!; + } return wallet.walletAddresses.getPaymentUri(_amount); } @@ -257,10 +278,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo final isPrimary = subaddress == primaryAddress; return WalletAddressListItem( - id: subaddress.id, - isPrimary: isPrimary, - name: subaddress.label, - address: subaddress.address); + id: subaddress.id, + isPrimary: isPrimary, + name: subaddress.label, + address: subaddress.address, + ); }); addressList.addAll(addressItems); } @@ -285,37 +307,37 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.addAll(addressItems); addressList.add(WalletAddressListHeader(title: S.current.received)); - final receivedAddressItems = - bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) { - return WalletAddressListItem( - id: address.id, - isPrimary: false, - name: address.name, - address: address.address, - txCount: address.txCount, - balance: _appStore.amountParsingProxy - .getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)), - isChange: address.isChange, - isOneTimeReceiveAddress: true, - derivationPath: address.derivationPath, - ); - }); + final receivedAddressItems = bitcoin!.getSilentPaymentReceivedAddresses(wallet).map( + (address) => WalletAddressListItem( + id: address.id, + isPrimary: false, + name: address.name, + address: address.address, + txCount: address.txCount, + balance: _appStore.amountParsingProxy + .getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)), + isChange: address.isChange, + isOneTimeReceiveAddress: true, + derivationPath: address.derivationPath, + ), + ); addressList.addAll(receivedAddressItems); } else { var addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) { final isPrimary = subaddress.id == 0; return WalletAddressListItem( - id: subaddress.id, - isPrimary: isPrimary, - name: subaddress.name, - address: subaddress.address, - txCount: subaddress.txCount, - balance: _appStore.amountParsingProxy - .getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)), - isChange: subaddress.isChange, - isLegacyDerivation: subaddress.isLegacyDerivation, - derivationPath: subaddress.derivationPath); + id: subaddress.id, + isPrimary: isPrimary, + name: subaddress.name, + address: subaddress.address, + txCount: subaddress.txCount, + balance: _appStore.amountParsingProxy + .getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)), + isChange: subaddress.isChange, + isLegacyDerivation: subaddress.isLegacyDerivation, + derivationPath: subaddress.derivationPath, + ); }); // don't show all 1000+ mweb addresses: @@ -346,11 +368,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } if (wallet.type == WalletType.nano) { - addressList.add(WalletAddressListItem( - isPrimary: true, - name: null, - address: wallet.walletAddresses.address, - )); + addressList.add( + WalletAddressListItem( + isPrimary: true, + name: null, + address: wallet.walletAddresses.address, + ), + ); } if (wallet.type == WalletType.tron) { @@ -361,29 +385,35 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo if (wallet.type == WalletType.decred) { final addrInfos = decred!.getAddressInfos(wallet); - addrInfos.forEach((info) { + for (final info in addrInfos) { addressList.add( - new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label)); - }); + WalletAddressListItem(isPrimary: false, address: info.address, name: info.label), + ); + } } if (wallet.type == WalletType.zcash) { final addrInfos = zcash!.getAddressInfos(wallet); - addrInfos.forEach((info) { + for (final info in addrInfos) { addressList.add( - new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label)); - }); + WalletAddressListItem(isPrimary: false, address: info.address, name: info.label), + ); + } } for (var i = 0; i < addressList.length; i++) { - if (!(addressList[i] is WalletAddressListItem)) continue; + if (addressList[i] is! WalletAddressListItem) { + continue; + } final item = addressList[i] as WalletAddressListItem; item.isHidden = wallet.walletAddresses.hiddenAddresses.contains(item.address) || (isElectrumWallet && item.isLegacyDerivation); } for (var i = 0; i < addressList.length; i++) { - if (!(addressList[i] is WalletAddressListItem)) continue; + if (addressList[i] is! WalletAddressListItem) { + continue; + } (addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses .contains((addressList[i] as WalletAddressListItem).address); } @@ -395,25 +425,25 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } if (searchText.isNotEmpty) { - return ObservableList.of(addressList.where((item) { - if (item is WalletAddressListItem) { - return item.address.toLowerCase().contains(searchText.toLowerCase()); - } - return false; - })); + return ObservableList.of( + addressList.where((item) { + if (item is WalletAddressListItem) { + return item.address.toLowerCase().contains(searchText.toLowerCase()); + } + return false; + }), + ); } return addressList; } @computed - ObservableList get addressList { - return _computeAddressList(); - } + ObservableList get addressList => _computeAddressList(); List get forceRecomputeItems { // necessary because the addressList contains non-observable items - List recomputed = []; + final recomputed = []; recomputed.addAll(_baseItems); recomputed.addAll(_computeAddressList()); return recomputed; @@ -451,9 +481,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo case WalletType.wownero: wownero!.getCurrentAccount(wallet).label; default: - return ''; + return ""; } - return ''; + return ""; } @computed @@ -473,7 +503,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo WalletType.litecoin, WalletType.decred, WalletType.dogecoin, - WalletType.zcash + WalletType.zcash, ].contains(wallet.type) && !isLightning && isZCashTransparent; @@ -486,7 +516,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, - WalletType.dogecoin + WalletType.dogecoin, ].contains(wallet.type); List getWalletImages(int? chainId) { @@ -494,44 +524,44 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo switch (chainId) { case 1: return [ - 'assets/new-ui/crypto_full_icons/ethereum.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', - 'assets/images/deuro_icon.svg', - 'assets/images/more_tokens.svg', + "assets/new-ui/crypto_full_icons/ethereum.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", + "assets/images/deuro_icon.svg", + "assets/images/more_tokens.svg", ]; case 137: return [ - 'assets/new-ui/crypto_full_icons/polygon.svg', - 'assets/images/eth_pol_icon.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', - 'assets/images/more_tokens.svg', + "assets/new-ui/crypto_full_icons/polygon.svg", + "assets/images/eth_pol_icon.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", + "assets/images/more_tokens.svg", ]; case 8453: return [ - 'assets/new-ui/crypto_full_icons/ethereum.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/more_tokens.svg', + "assets/new-ui/crypto_full_icons/ethereum.svg", + "assets/images/usdc_icon.svg", + "assets/images/more_tokens.svg", ]; case 42161: return [ - 'assets/new-ui/crypto_full_icons/arbitrum.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/more_tokens.svg', + "assets/new-ui/crypto_full_icons/arbitrum.svg", + "assets/images/usdc_icon.svg", + "assets/images/more_tokens.svg", ]; case 56: return [ - 'assets/new-ui/crypto_full_icons/bnb.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', - 'assets/images/more_tokens.svg', + "assets/new-ui/crypto_full_icons/bnb.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", + "assets/images/more_tokens.svg", ]; default: return [ - 'assets/new-ui/crypto_full_icons/ethereum.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', + "assets/new-ui/crypto_full_icons/ethereum.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", ]; } } @@ -539,22 +569,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo switch (wallet.type) { case WalletType.solana: return [ - 'assets/images/sol_icon.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', - 'assets/images/more_tokens.svg', + "assets/images/sol_icon.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", + "assets/images/more_tokens.svg", ]; case WalletType.tron: return [ - 'assets/images/trx_icon.svg', - 'assets/images/usdc_icon.svg', - 'assets/images/usdt_wallet_icon.svg', - 'assets/images/more_tokens.svg', + "assets/images/trx_icon.svg", + "assets/images/usdc_icon.svg", + "assets/images/usdt_wallet_icon.svg", + "assets/images/more_tokens.svg", ]; case WalletType.zano: return [ - 'assets/images/zano_icon.svg', - 'assets/images/more_tokens.svg', + "assets/images/zano_icon.svg", + "assets/images/more_tokens.svg", ]; default: return []; @@ -563,7 +593,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @computed String get qrImage { - if (isLightning) return 'assets/images/btc_chain_qr_lightning.svg'; + if (isLightning) { + return "assets/images/btc_chain_qr_lightning.svg"; + } return getQrImage(type); } @@ -582,8 +614,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @computed bool get isLightning => - wallet.type == WalletType.bitcoin && - (wallet.walletAddresses.getPaymentUri(_amount) is LightningPaymentRequest); + wallet.type == WalletType.bitcoin && selectedCurrency == CryptoCurrency.btcln; @computed bool get isZCashTransparent { @@ -698,16 +729,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @action void changeAmount(String amount) { if (selectedCurrency is FiatCurrency) { - this._amount = amount; + _amount = amount; _convertAmountToCrypto(); } else if (selectedCurrency is CryptoCurrency) { - this._amount = _appStore.amountParsingProxy + _amount = _appStore.amountParsingProxy .getCanonicalCryptoAmount(amount, selectedCurrency as CryptoCurrency); } if (isLightning) { - wallet.walletAddresses - .getPaymentRequestUri(this._amount) - .then((uri) => _lnPaymentRequest = uri); + wallet.walletAddresses.getPaymentRequestUri(_amount).then((uri) => _lnPaymentRequest = uri); } } @@ -719,22 +748,24 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @action void _convertAmountToCrypto() { var cryptoCurrency = tokenCurrency ?? wallet.currency; - if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc; + if (cryptoCurrency == CryptoCurrency.btcln) { + cryptoCurrency = CryptoCurrency.btc; + } final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0); if (fiatRate <= 0.0) { dev.log("Invalid Fiat Rate $fiatRate"); - _amount = ''; + _amount = ""; return; } try { - final crypto = (double.parse(_amount.replaceAll(',', '.')) / fiatRate).toStringAsFixed(8); + final crypto = (double.parse(_amount.replaceAll(",", ".")) / fiatRate).toStringAsFixed(8); if (_amount != crypto) { _amount = crypto; } } catch (e) { - _amount = ''; + _amount = ""; } }