Cw 1539 lightning enhancements (#3400)
What changed, and why it matters
This is a routine feature and bug-fix update for Cake Wallet's Lightning (Bitcoin layer-2) support. It adds the ability to handle LNURL withdrawal requests, fixes currency selection when receiving Bitcoin vs Lightning, and cleans up transaction-detail display. There is no clear security vulnerability in the diff, but the new network code that calls external Lightning services and parses invoices is a place where future bugs could matter, so it deserves normal review and testing.
Treat as a normal feature/bug-fix commit. Review the new LNURL withdrawal flow for input validation, URL scheme handling, and error paths; ensure callback query parameters are correctly encoded and that invoice amounts are validated against min/max before submission. Verify the receive-page currency fix does not regress other wallet types. No emergency action is indicated.
Security signals we found
New network request path added: LNURL.getWithdrawRequest and LNURL.commitWithdrawRequest fetch and decode JSON from user-supplied LNURL callbacks via ProxyWrapper().get().
LNURL decode/encode logic changed from single-quote to double-quote strings and minor style refactor; functional behavior appears unchanged.
Receive page now explicitly sets selectedCurrency to btcln for Lightning options and btc for standard Bitcoin options, fixing prior regression.
Transaction details modal now uses computed getters (transactionAmount, feeAmount, transactionCopyAmount) from amountParsingProxy instead of direct toStringWithSymbol().
pubspec.lock downgrades meta and test_api and lowers Dart SDK constraint; this is a dependency/versioning change, not a vulnerability fix.
Evidence from the diff
The commit refactors Lightning invoice/LNURL handling in cw_core/lib/lnurl.dart and lib/core/lightning_invoice_service.dart, adds LNURLWithdrawRequest parsing and commitWithdrawRequest submission, fixes receive_page currency selection for BTC/LN, and updates transaction-details view models to use computed amount/fee getters. Most of the diff is lint reformatting, import reordering, and pubspec.lock downgrades (meta 1.18.0→1.17.0, test_api 0.7.11→0.7.10, Dart SDK lower bound 3.10.0-0→3.9.0). No obvious injection, auth bypass, or cryptographic flaw is visible in the supplied diff.
Changed components
cw_core/lib/lnurl.dartlib/core/lightning_invoice_service.dartlib/new-ui/pages/receive_page.dartlib/new-ui/pages/send_page.dartlib/new-ui/widgets/send_page/l2_action_wallet_selector.dartlib/view_model/transaction_details_view_model.dartlib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dartlib/src/screens/transaction_details/rbf_details_page.dartInspect captured patch +1200 / −1010
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 6c15a9ad..b35b2bf0 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -634,10 +634,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -1100,10 +1100,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
@@ -1322,5 +1322,5 @@ packages:
source: hosted
version: "2.2.2"
sdks:
- dart: ">=3.10.0-0 <4.0.0"
+ dart: ">=3.9.0 <4.0.0"
flutter: ">=3.29.0"
diff --git a/cw_core/lib/lnurl.dart b/cw_core/lib/lnurl.dart
index 8ea33b99..e0d17685 100644
--- a/cw_core/lib/lnurl.dart
+++ b/cw_core/lib/lnurl.dart
@@ -1,19 +1,20 @@
-import 'dart:convert';
+import "dart:convert";
-import 'package:bech32/bech32.dart';
-import 'package:cw_core/amount/money.dart';
-import 'package:cw_core/crypto_currency.dart';
-import 'package:cw_core/utils/proxy_wrapper.dart';
+import "package:bech32/bech32.dart";
+import "package:cw_core/amount/money.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:cw_core/utils/proxy_wrapper.dart";
const _BOLT_PREFIXES = ["lnbcrt", "lntbs", "lnbc", "lntb"];
-const _LUD17_PREFIXES = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth'];
+const _LUD17_PREFIXES = ["lnurlw", "lnurlc", "lnurlp", "keyauth"];
bool isBolt11ZeroInvoice(String invoice) {
try {
- final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
+ final request =
+ const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
final prefix =
- _BOLT_PREFIXES.firstWhere((prefix) => request.hrp.startsWith(prefix), orElse: () => "");
+ _BOLT_PREFIXES.firstWhere(request.hrp.startsWith, orElse: () => "");
return request.hrp.length == prefix.length;
} catch (e) {
@@ -23,48 +24,56 @@ bool isBolt11ZeroInvoice(String invoice) {
/// Get the amount of a Bolt 11 Invoice in
int? _getAmountBolt11Msat(String invoice) {
- final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
+ final request =
+ const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
final prefix = _BOLT_PREFIXES.firstWhere(
- (prefix) => request.hrp.startsWith(prefix),
- orElse: () => throw FormatException('Invalid BOLT11 invoice: unknown HRP prefix.'),
+ request.hrp.startsWith,
+ orElse: () => throw const FormatException("Invalid BOLT11 invoice: unknown HRP prefix."),
);
final amountPart = request.hrp.substring(prefix.length);
- if (amountPart.isEmpty) return null; // zero-amount invoice
+ if (amountPart.isEmpty) {
+ return null; // zero-amount invoice
+ }
- final hasMultiplier = RegExp(r'[munp]$').hasMatch(amountPart);
- final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : '';
+ final hasMultiplier = RegExp(r"[munp]$").hasMatch(amountPart);
+ final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : "";
final numberStr = hasMultiplier ? amountPart.substring(0, amountPart.length - 1) : amountPart;
- if (numberStr.isEmpty || !RegExp(r'^\d+$').hasMatch(numberStr)) {
- throw FormatException('Invalid BOLT11 invoice: invalid amount number in HRP.');
+ if (numberStr.isEmpty || !RegExp(r"^\d+$").hasMatch(numberStr)) {
+ throw const FormatException("Invalid BOLT11 invoice: invalid amount number in HRP.");
}
final amount = int.parse(numberStr);
switch (multiplier) {
- case '': // bitcoin
+ case "": // bitcoin
return amount * 100000000000;
- case 'm': // milli-bitcoin
+ case "m": // milli-bitcoin
return amount * 100000000;
- case 'u': // micro-bitcoin
+ case "u": // micro-bitcoin
return amount * 100000;
- case 'n': // nano-bitcoin
+ case "n": // nano-bitcoin
return amount * 100;
- case 'p':
+ case "p":
if (amount % 10 != 0) {
- throw FormatException('Invalid BOLT11 invoice: amount not representable in whole msat.');
+ throw const FormatException(
+ "Invalid BOLT11 invoice: amount not representable in whole msat.",
+ );
}
return amount ~/ 10;
default:
- throw FormatException('Invalid BOLT11 invoice: unknown amount multiplier.');
+ throw const FormatException("Invalid BOLT11 invoice: unknown amount multiplier.");
}
}
Money? getBolt11Amount(String invoice) {
final msat = _getAmountBolt11Msat(invoice);
- if (msat == null || msat % 1000 != 0) return null;
+ if (msat == null || msat % 1000 != 0) {
+ return null;
+ }
+
return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln);
}
@@ -73,30 +82,99 @@ class LNURL {
final url = decode(lnurl);
final response = await ProxyWrapper().get(clearnetUri: url);
- if (response.statusCode != 200) return null;
+ if (response.statusCode != 200) {
+ return null;
+ }
try {
final body = jsonDecode(response.body) as Map;
final tag = body["tag"] as String?;
- if (tag != "payRequest") return null;
+ if (tag != "payRequest") {
+ return null;
+ }
final msat = body["minSendable"] as int?;
final maxSendable = body["maxSendable"] as int?;
// if minSendable and maxSendable are the same we assume a specific payment request
- if (msat != maxSendable) return null;
+ if (msat != maxSendable) {
+ return null;
+ }
- if (msat == null || msat % 1000 != 0) return null;
+ if (msat == null || msat % 1000 != 0) {
+ return null;
+ }
return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln);
} catch (_) {
return null;
}
}
+ static Future<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;
diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock
index 908829bc..8eff1d33 100644
--- a/cw_core/pubspec.lock
+++ b/cw_core/pubspec.lock
@@ -394,10 +394,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -738,10 +738,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: "direct main"
description:
@@ -838,5 +838,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
- dart: ">=3.10.0-0 <4.0.0"
+ dart: ">=3.9.0 <4.0.0"
flutter: ">=3.27.0"
diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock
index 0b460e9f..9a568daa 100644
--- a/cw_decred/pubspec.lock
+++ b/cw_decred/pubspec.lock
@@ -417,10 +417,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -769,10 +769,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index b32f56c5..79da3694 100644
--- a/cw_monero/pubspec.lock
+++ b/cw_monero/pubspec.lock
@@ -505,10 +505,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -906,10 +906,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
@@ -1048,5 +1048,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
- dart: ">=3.10.0-0 <4.0.0"
+ dart: ">=3.9.0 <4.0.0"
flutter: ">=3.24.0"
diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock
index 2dd43698..509d7449 100644
--- a/cw_nano/pubspec.lock
+++ b/cw_nano/pubspec.lock
@@ -462,10 +462,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -887,10 +887,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock
index 7fd7eac2..4b622b35 100644
--- a/cw_wownero/pubspec.lock
+++ b/cw_wownero/pubspec.lock
@@ -417,10 +417,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -786,10 +786,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock
index a4a6c538..c4602f53 100644
--- a/cw_zano/pubspec.lock
+++ b/cw_zano/pubspec.lock
@@ -422,10 +422,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -783,10 +783,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
- version: "0.7.11"
+ version: "0.7.10"
torch_dart:
dependency: transitive
description:
diff --git a/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart b/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart
index 9df2848f..484792bf 100644
--- a/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart
+++ b/lib/cake_pay/src/widgets/rounded_overlay_cards_widget.dart
@@ -15,14 +15,14 @@ class RoundedOverlayCards extends StatelessWidget {
return ClipRRect(
borderRadius:
- BorderRadius.only(bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)),
+ BorderRadius.only(bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25)),
child: Container(
decoration: BoxDecoration(
- borderRadius: const BorderRadius.only(
- bottomLeft: Radius.circular(24),
- bottomRight: Radius.circular(24),
- ),
- color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: const BorderRadius.only(
+ bottomLeft: Radius.circular(24),
+ bottomRight: Radius.circular(24),
+ ),
+ color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -30,8 +30,8 @@ class RoundedOverlayCards extends StatelessWidget {
Flexible(
child: ClipRRect(
borderRadius: const BorderRadius.only(
- bottomLeft: Radius.circular(25.0),
- bottomRight: Radius.circular(25.0),
+ bottomLeft: Radius.circular(25),
+ bottomRight: Radius.circular(25),
),
child: Container(
decoration: BoxDecoration(
diff --git a/lib/core/lightning_invoice_service.dart b/lib/core/lightning_invoice_service.dart
index 995c62d6..8b29f6c5 100644
--- a/lib/core/lightning_invoice_service.dart
+++ b/lib/core/lightning_invoice_service.dart
@@ -1,6 +1,6 @@
-import 'dart:convert';
+import "dart:convert";
-import 'package:cw_core/utils/proxy_wrapper.dart';
+import "package:cw_core/utils/proxy_wrapper.dart";
Future<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;
}
diff --git a/lib/new-ui/pages/receive_page.dart b/lib/new-ui/pages/receive_page.dart
index e1c6afd0..4b7846c0 100644
--- a/lib/new-ui/pages/receive_page.dart
+++ b/lib/new-ui/pages/receive_page.dart
@@ -1,54 +1,54 @@
-import 'package:cake_wallet/core/utilities.dart';
-import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart';
-import 'package:cake_wallet/utils/share_util.dart';
-import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
-import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
-import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
-import 'package:cake_wallet/zcash/zcash.dart';
-import 'package:cw_core/crypto_currency.dart';
-import 'package:cw_core/payment_uris.dart';
-import 'package:cw_core/receive_page_option.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:mobx/mobx.dart';
-import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
-import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
-import 'package:cake_wallet/di.dart';
-import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
-import 'package:cake_wallet/entities/preferences_key.dart';
-import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import "package:cake_wallet/anonpay/anonpay_donation_link_info.dart";
+import "package:cake_wallet/bitcoin/bitcoin.dart";
+import "package:cake_wallet/core/utilities.dart";
+import "package:cake_wallet/di.dart";
+import "package:cake_wallet/entities/auto_generate_subaddress_status.dart";
+import "package:cake_wallet/entities/preferences_key.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/new-ui/widgets/modern_button.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
+import "package:cake_wallet/routes.dart";
+import "package:cake_wallet/src/screens/receive/anonpay_receive_page.dart";
+import "package:cake_wallet/utils/share_util.dart";
+import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
+import "package:cake_wallet/view_model/dashboard/receive_option_view_model.dart";
+import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart";
+import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart";
+import "package:cake_wallet/zcash/zcash.dart";
+import "package:cw_core/crypto_currency.dart";
+import "package:cw_core/payment_uris.dart";
+import "package:cw_core/receive_page_option.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:cw_core/wallet_type.dart";
+import "package:flutter/cupertino.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:flutter_mobx/flutter_mobx.dart";
+import "package:mobx/mobx.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
+import "package:shared_preferences/shared_preferences.dart";
class NewReceivePage extends StatefulWidget {
- NewReceivePage(
- {super.key,
- required this.addressListViewModel,
- required this.receiveOptionViewModel,
- required this.dashboardViewModel,
- required this.lightningMode,
- CryptoCurrency? initialCurrency}) {
+ NewReceivePage({
+ required this.addressListViewModel,
+ required this.receiveOptionViewModel,
+ required this.dashboardViewModel,
+ required this.lightningMode,
+ super.key,
+ CryptoCurrency? initialCurrency,
+ }) {
if (initialCurrency != null && initialCurrency != addressListViewModel.selectedCurrency) {
addressListViewModel.setTokenCurrency(initialCurrency);
}
@@ -78,31 +78,33 @@ class _NewReceivePageState extends State<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?;
});
}
}
diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart
index 994e1345..6ee37d2e 100644
--- a/lib/new-ui/pages/send_page.dart
+++ b/lib/new-ui/pages/send_page.dart
@@ -594,7 +594,7 @@ class _NewSendPageState extends State<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,
diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
index d36d67bf..08eec749 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
@@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State<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(
diff --git a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
index ff224803..fb705ace 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
@@ -1,24 +1,28 @@
-import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
-import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
-import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
-import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
-import 'package:cake_wallet/utils/address_formatter.dart';
-import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
+import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart";
+import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
+import "package:cake_wallet/new-ui/widgets/copy_wrapper.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
+import "package:cake_wallet/routes.dart";
+import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
+import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
+import "package:cake_wallet/utils/address_formatter.dart";
+import "package:cake_wallet/view_model/transaction_details_view_model.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:flutter_mobx/flutter_mobx.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
class TransactionDetailsModal extends StatefulWidget {
const TransactionDetailsModal(
- {super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
+ {
+ required this.transactionDetailsViewModel,
+ this.highlightNoteField = false,
+ super.key,
+ });
final TransactionDetailsViewModel transactionDetailsViewModel;
final bool highlightNoteField;
@@ -48,177 +52,195 @@ class _TransactionDetailsModalState extends State<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) {
diff --git a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart
index cea40e19..835631e9 100644
--- a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart
+++ b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart
@@ -1,41 +1,41 @@
-import 'dart:async';
+import "dart:async";
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/main.dart';
-import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
-import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
-import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
-import 'package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart';
-import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
-import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
-import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
-import 'package:cake_wallet/view_model/send/send_view_model.dart';
-import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
-import 'package:cw_core/currency_for_wallet_type.dart';
-import 'package:cw_core/wallet_info.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
+import "package:cake_wallet/bitcoin/bitcoin.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/main.dart";
+import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
+import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
+import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
+import "package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart";
+import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart";
+import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart";
+import "package:cake_wallet/view_model/send/send_view_model.dart";
+import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
+import "package:cw_core/currency_for_wallet_type.dart";
+import "package:cw_core/wallet_info.dart";
+import "package:cw_core/wallet_type.dart";
+import "package:flutter/cupertino.dart";
+import "package:flutter/material.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
-enum l2actions { deposit, withdraw }
+enum L2Actions { deposit, withdraw }
class L2ActionWalletSelector extends StatefulWidget {
const L2ActionWalletSelector({
- super.key,
required this.showOtherWallets,
required this.sendViewModel,
required this.action,
required this.onSendInitiated,
required this.contactListViewModel,
required this.walletSwitcherViewModel,
+ super.key,
});
final bool showOtherWallets;
final SendViewModel sendViewModel;
- final l2actions action;
+ final L2Actions action;
final VoidCallback onSendInitiated;
final ContactListViewModel contactListViewModel;
final WalletSwitcherViewModel walletSwitcherViewModel;
@@ -56,12 +56,18 @@ class _L2ActionWalletSelectorState extends State<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: [
diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart
index 0ec4d5ac..78a5321d 100644
--- a/lib/src/screens/transaction_details/rbf_details_page.dart
+++ b/lib/src/screens/transaction_details/rbf_details_page.dart
@@ -1,34 +1,36 @@
-import 'package:cake_wallet/core/execution_state.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
-import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
-import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
-import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
-import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
-import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
-import 'package:cake_wallet/src/widgets/list_row.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
-import 'package:cake_wallet/src/widgets/standard_list.dart';
-import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
-import 'package:cake_wallet/utils/show_bar.dart';
-import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
-import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:mobx/mobx.dart';
+import "package:cake_wallet/core/execution_state.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/src/screens/base_page.dart";
+import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
+import "package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart";
+import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
+import "package:cake_wallet/src/widgets/alert_with_two_actions.dart";
+import "package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart";
+import "package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart";
+import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
+import "package:cake_wallet/src/widgets/list_row.dart";
+import "package:cake_wallet/src/widgets/primary_button.dart";
+import "package:cake_wallet/src/widgets/standard_expandable_list.dart";
+import "package:cake_wallet/src/widgets/standard_list.dart";
+import "package:cake_wallet/src/widgets/standard_picker_list.dart";
+import "package:cake_wallet/utils/show_bar.dart";
+import "package:cake_wallet/utils/show_pop_up.dart";
+import "package:cake_wallet/view_model/send/send_view_model_state.dart";
+import "package:cake_wallet/view_model/transaction_details_view_model.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:flutter_mobx/flutter_mobx.dart";
+import "package:mobx/mobx.dart";
class RBFDetailsPage extends BasePage {
RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) {
transactionDetailsViewModel.addBumpFeesListItems(
- transactionDetailsViewModel.transactionInfo, rawTransaction);
+ transactionDetailsViewModel.transactionInfo,
+ rawTransaction,
+ );
}
@override
@@ -46,70 +48,70 @@ class RBFDetailsPage extends BasePage {
children: [
Expanded(
child: SectionStandardList(
- sectionCount: 1,
- itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
- itemBuilder: (__, index) {
- final item = transactionDetailsViewModel.RBFListItems[index];
-
- 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 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,
- );
- }
-
- return Container();
- }),
+ 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 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 TextFieldListItem) {
+ return TextFieldListRow(
+ title: item.title,
+ value: item.value,
+ onSubmitted: item.onSubmitted,
+ );
+ }
+
+ 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(),
+ ),
+ );
}
});
}
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index 16f12ac0..e83feb90 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -1,50 +1,50 @@
-import 'package:cake_wallet/reactions/wallet_connect.dart';
-import 'package:cake_wallet/solana/solana.dart';
-import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
-import 'package:cake_wallet/store/app_store.dart';
-import 'package:cake_wallet/core/address_validator.dart';
-import 'package:cake_wallet/tron/tron.dart';
-import 'package:cake_wallet/zano/zano.dart';
-import 'package:cw_core/crypto_amount_format.dart';
-import 'package:cw_core/crypto_currency.dart';
-import 'package:cw_core/currency_for_wallet_type.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cw_core/transaction_info.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:cake_wallet/evm/evm.dart';
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
-import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
-import 'package:cake_wallet/entities/transaction_description.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/monero/monero.dart';
-import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
-import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
-import 'package:cake_wallet/utils/date_formatter.dart';
-import 'package:cake_wallet/view_model/send/send_view_model.dart';
-import 'package:collection/collection.dart';
-import 'package:cw_core/transaction_direction.dart';
-import 'package:cw_core/transaction_priority.dart';
-import 'package:flutter/foundation.dart';
-import 'package:hive/hive.dart';
-import 'package:intl/intl.dart';
-import 'package:mobx/mobx.dart';
-import 'package:url_launcher/url_launcher.dart';
-
-part 'transaction_details_view_model.g.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";
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;
-
- 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),
+ 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;
+ }
+ 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),
+ 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),
+ 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),
+ 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;
}
diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
index 65ee7aef..0610381c 100644
--- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
@@ -1,59 +1,58 @@
-import 'dart:core';
-import 'dart:developer' as dev;
-
-import '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';
-
-part 'wallet_address_list_view_model.g.dart';
+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/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";
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 = "";
}
}
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.