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 <malikowskirobert@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich
2026-07-17 17:49:17 +03:00
committed by GitHub
co-authored by Robert Malikowski Omar Hatem
parent 28c2cc152e
commit 03edd658a5
18 changed files with 1193 additions and 1003 deletions
+5 -5
View File
@@ -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"
+140 -37
View File
@@ -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<LNURLWithdrawRequest?> 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<bool> 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<int> _convert(List<int> 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;
+5 -5
View File
@@ -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"
+4 -4
View File
@@ -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:
+5 -5
View File
@@ -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"
+4 -4
View File
@@ -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:
+4 -4
View File
@@ -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:
+4 -4
View File
@@ -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:
@@ -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(
+21 -15
View File
@@ -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<String?> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> map) =>
factory _LNURLPCallbackResponseDTO.fromJson(Map<String, dynamic> map) =>
_LNURLPCallbackResponseDTO(map["pr"] as String);
final String pr;
}
+121 -115
View File
@@ -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<NewReceivePage> {
.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<NewReceivePage> {
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<CryptoCurrency>()
.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<CryptoCurrency>().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<NewReceivePage> {
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<NewReceivePage> {
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<NewReceivePage> {
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<NewReceivePage> {
widget.addressListViewModel.rotateAddress();
}
}
})
: SizedBox.shrink(),
},
)
: const SizedBox.shrink(),
),
),
),
@@ -269,11 +272,12 @@ class _NewReceivePageState extends State<NewReceivePage> {
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<NewReceivePage> {
: 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<NewReceivePage> {
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<NewReceivePage> {
),
),
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<NewReceivePage> {
void _showLabelModal() {
showMaterialModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withAlpha(80),
builder: (context) {
return getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel);
}).then((value) {
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withAlpha(80),
builder: (_) => getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel),
).then((value) {
_reloadAddressWithLabel(widget.addressListViewModel.uri);
});
}
@@ -359,8 +365,8 @@ class _NewReceivePageState extends State<NewReceivePage> {
// 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?;
});
}
}
+2 -2
View File
@@ -594,7 +594,7 @@ class _NewSendPageState extends State<NewSendPage> {
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<NewSendPage> {
builder: (context) => Material(
child: L2ActionWalletSelector(
showOtherWallets: false,
action: l2actions.withdraw,
action: L2Actions.withdraw,
sendViewModel: widget.sendViewModel,
contactListViewModel:
widget.contactListViewModel,
@@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
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(
@@ -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<TransactionDetailsModal> {
}
@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<ListItem>()
.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<ListItem>()
.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) {
@@ -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<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
// ),
// ),
// ),
if (widget.action == l2actions.withdraw)
if (widget.action == L2Actions.withdraw)
Row(
children: [
Flexible(
@@ -215,12 +209,12 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
);
},
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<L2ActionWalletSelector> {
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<L2ActionWalletSelector> {
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: _isLoading,
),
SizedBox()
const SizedBox()
],
),
],
@@ -339,13 +336,13 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
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: [
@@ -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<void>(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<void>(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<void>(
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<void>(
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<void>(
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<void>(
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(),
),
);
}
});
}
+275 -251
View File
@@ -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<TxDetailRowDefinition> 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<TransactionDetailsListItem> items;
final List<TransactionDetailsListItem> RBFListItems;
final List<TransactionDetailsListItem> 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<void> _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;
}
@@ -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 = <ListItem>[],
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<ListItem> get addressList {
return _computeAddressList();
}
ObservableList<ListItem> get addressList => _computeAddressList();
List<ListItem> get forceRecomputeItems {
// necessary because the addressList contains non-observable items
List<ListItem> recomputed = [];
final recomputed = <ListItem>[];
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<String> 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 = "";
}
}