mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Cw 1539 lightning enhancements (#3400)
* feat: add Lightning transaction URL support in transaction details view model * fix: update currency selection logic in WalletAddressListViewModel and ReceivePage * feat: integrate computed transaction amount and fee getters in TransactionDetailsViewModel, refactor CopyWrapper logic in TransactionDetailsModal * refactor: apply lint rules * refactor: apply lint rules and improve LNURL handling with new methods for withdrawal requests and error checks * auto-reformat * fix: receive_page regression * fix: Withdraw lightning to other btc wallet gives LN address * chore: update .lock files [skip ci] --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Robert Malikowski
Omar Hatem
parent
28c2cc152e
commit
03edd658a5
@@ -634,10 +634,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1100,10 +1100,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1322,5 +1322,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.10.0-0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
+140
-37
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -422,10 +422,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -783,10 +783,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -15,14 +15,14 @@ class RoundedOverlayCards extends StatelessWidget {
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.only(bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)),
|
||||
BorderRadius.only(bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -30,8 +30,8 @@ class RoundedOverlayCards extends StatelessWidget {
|
||||
Flexible(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25.0),
|
||||
bottomRight: Radius.circular(25.0),
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import "dart:convert";
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
|
||||
Future<String?> getBolt11FromLightingAddress(String lightningAddress, {int amount = 0}) async {
|
||||
try {
|
||||
@@ -31,28 +31,34 @@ Uri getURlOfLightningAddress(String lightningAddress) {
|
||||
}
|
||||
|
||||
class _LNURLPResponseDTO {
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback,
|
||||
this.maxSendable,
|
||||
this.minSendable,
|
||||
this.tag,
|
||||
this.metadata,
|
||||
);
|
||||
|
||||
factory _LNURLPResponseDTO.fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String,
|
||||
);
|
||||
|
||||
final String callback;
|
||||
final int maxSendable;
|
||||
final int minSendable;
|
||||
final String tag;
|
||||
final String metadata;
|
||||
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback, this.maxSendable, this.minSendable, this.tag, this.metadata);
|
||||
|
||||
static _LNURLPResponseDTO fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String);
|
||||
}
|
||||
|
||||
class _LNURLPCallbackResponseDTO {
|
||||
final String pr;
|
||||
|
||||
const _LNURLPCallbackResponseDTO(this.pr);
|
||||
|
||||
static _LNURLPCallbackResponseDTO fromJson(Map<String, dynamic> map) =>
|
||||
factory _LNURLPCallbackResponseDTO.fromJson(Map<String, dynamic> map) =>
|
||||
_LNURLPCallbackResponseDTO(map["pr"] as String);
|
||||
|
||||
final String pr;
|
||||
}
|
||||
|
||||
+121
-115
@@ -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?;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
showOtherWallets: false,
|
||||
action: l2actions.deposit,
|
||||
action: L2Actions.deposit,
|
||||
sendViewModel: widget.sendViewModel,
|
||||
contactListViewModel:
|
||||
widget.contactListViewModel,
|
||||
@@ -609,7 +609,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
showOtherWallets: false,
|
||||
action: l2actions.withdraw,
|
||||
action: L2Actions.withdraw,
|
||||
sendViewModel: widget.sendViewModel,
|
||||
contactListViewModel:
|
||||
widget.contactListViewModel,
|
||||
|
||||
@@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
AssetsSection(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
hasAssetsButton ?
|
||||
AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
|
||||
() {
|
||||
Navigator.of(context).pushNamed(
|
||||
hasAssetsButton ? AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
|
||||
|
||||
() {Navigator.of(context).pushNamed(
|
||||
Routes.homeSettings,
|
||||
arguments: widget.dashboardViewModel.balanceViewModel,
|
||||
);
|
||||
}) : null),
|
||||
}): null),
|
||||
AssetsHistorySectionTab(
|
||||
S.current.history,
|
||||
HistorySection(
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/utils/address_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/copy_wrapper.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/routes.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/utils/address_formatter.dart";
|
||||
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class TransactionDetailsModal extends StatefulWidget {
|
||||
const TransactionDetailsModal(
|
||||
{super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
|
||||
{
|
||||
required this.transactionDetailsViewModel,
|
||||
this.highlightNoteField = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TransactionDetailsViewModel transactionDetailsViewModel;
|
||||
final bool highlightNoteField;
|
||||
@@ -48,177 +52,195 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final transactionInfoAmount = widget.transactionDetailsViewModel.transactionInfo.amount;
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Column(
|
||||
children: [
|
||||
TokenImageWidget(
|
||||
imageUrl:
|
||||
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
|
||||
size: 64,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(text: transactionInfoAmount.toString()),
|
||||
builder: (context, copied) => AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied
|
||||
? S.of(context).copied
|
||||
: transactionInfoAmount.toStringWithSymbol(),
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: copied
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Column(
|
||||
children: [
|
||||
TokenImageWidget(
|
||||
imageUrl:
|
||||
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
|
||||
size: 64,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(
|
||||
text: widget.transactionDetailsViewModel.transactionCopyAmount,
|
||||
),
|
||||
builder: (context, copied) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied
|
||||
? S.of(context).copied
|
||||
: widget.transactionDetailsViewModel.transactionAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: copied
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewListSections(sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) return null;
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewListSections(
|
||||
sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final shouldBuildBottomWidget = item.value.length > 25;
|
||||
final shouldBuildBottomWidget = item.value.length > 25;
|
||||
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue: ((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWIdget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
}),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue: ((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWidget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null,
|
||||
);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
},
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).add_a_note,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
)
|
||||
],
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => NewListSections(sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label:
|
||||
widget.transactionDetailsViewModel.explorerDescription,
|
||||
onTap: widget.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8)
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.bumpFeePage, arguments: [
|
||||
widget.transactionDetailsViewModel.transactionInfo,
|
||||
widget.transactionDetailsViewModel.rawTransaction
|
||||
]);
|
||||
})
|
||||
]
|
||||
}),
|
||||
)
|
||||
],
|
||||
Observer(
|
||||
builder: (_) => NewListSections(
|
||||
sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label: widget
|
||||
.transactionDetailsViewModel.explorerDescription,
|
||||
onTap: widget.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8,
|
||||
),
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.bumpFeePage,
|
||||
arguments: [
|
||||
widget
|
||||
.transactionDetailsViewModel.transactionInfo,
|
||||
widget.transactionDetailsViewModel.rawTransaction,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||
],
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Widget _buildTrailingWIdget(TransactionDetailsListItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: switch (item.runtimeType) {
|
||||
ConfirmationsListItem => Row(
|
||||
children: [
|
||||
Text((item as ConfirmationsListItem).current.toString(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary)),
|
||||
if (item.needed > 0)
|
||||
Text("/${item.needed}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant))
|
||||
],
|
||||
),
|
||||
_ => Text(
|
||||
item.value,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
Widget _buildTrailingWidget(TransactionDetailsListItem item) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: switch (item.runtimeType) {
|
||||
ConfirmationsListItem => Row(
|
||||
children: [
|
||||
Text(
|
||||
(item as ConfirmationsListItem).current.toString(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
if (item.needed > 0)
|
||||
Text(
|
||||
"/${item.needed}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => Text(
|
||||
item.value,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBottomWidget(TransactionDetailsListItem item) {
|
||||
return switch (item.runtimeType) {
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import 'dart:async';
|
||||
import "dart:async";
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/main.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart";
|
||||
import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
enum l2actions { deposit, withdraw }
|
||||
enum L2Actions { deposit, withdraw }
|
||||
|
||||
class L2ActionWalletSelector extends StatefulWidget {
|
||||
const L2ActionWalletSelector({
|
||||
super.key,
|
||||
required this.showOtherWallets,
|
||||
required this.sendViewModel,
|
||||
required this.action,
|
||||
required this.onSendInitiated,
|
||||
required this.contactListViewModel,
|
||||
required this.walletSwitcherViewModel,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool showOtherWallets;
|
||||
final SendViewModel sendViewModel;
|
||||
final l2actions action;
|
||||
final L2Actions action;
|
||||
final VoidCallback onSendInitiated;
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final WalletSwitcherViewModel walletSwitcherViewModel;
|
||||
@@ -56,12 +56,18 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
super.initState();
|
||||
if (widget.showOtherWallets) {
|
||||
() async {
|
||||
items.addAll((await WalletInfo.getAll()).where((item) =>
|
||||
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null));
|
||||
items.addAll(
|
||||
(await WalletInfo.getAll()).where(
|
||||
(item) =>
|
||||
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null,
|
||||
),
|
||||
);
|
||||
items.sort((a, b) {
|
||||
if (a.name == widget.sendViewModel.wallet.name)
|
||||
if (a.name == widget.sendViewModel.wallet.name) {
|
||||
return -1;
|
||||
else if (b.name == widget.sendViewModel.wallet.name) return 1;
|
||||
} else if (b.name == widget.sendViewModel.wallet.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
setState(() {});
|
||||
@@ -80,16 +86,16 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: widget.action == l2actions.deposit
|
||||
title: widget.action == L2Actions.deposit
|
||||
? "${S.of(context).send_from}..."
|
||||
: "${S.of(context).receive_to}...",
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Flexible(
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -123,29 +129,17 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: WalletRow(
|
||||
currencyIconPath: getCryptoCurrencyIconForWalletListItem(item.type),
|
||||
walletName: item.name,
|
||||
isCurrent: item.name == widget.sendViewModel.wallet.name,
|
||||
isSelected: _selectedWalletIndex == index && !textEntered,
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
setState(() {
|
||||
addressController.text = "";
|
||||
_selectedWalletIndex = index;
|
||||
});
|
||||
// if (widget.action == l2actions.withdraw) {
|
||||
// widget.sendViewModel.outputs.first.address = item.address;
|
||||
// widget.onSendInitiated();
|
||||
// } else if (widget.action == l2actions.deposit) {
|
||||
// setState(() {
|
||||
// loadingWalletName = item.name;
|
||||
// });
|
||||
// await _handleChangeWallet(item);
|
||||
// widget.onSendInitiated();
|
||||
// setState(() {
|
||||
// loadingWalletName = null;
|
||||
// });
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -198,7 +192,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
if (widget.action == l2actions.withdraw)
|
||||
if (widget.action == L2Actions.withdraw)
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@@ -215,12 +209,12 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (widget.action == l2actions.deposit && widget.showOtherWallets) ...[
|
||||
if (widget.action == L2Actions.deposit && widget.showOtherWallets) ...[
|
||||
Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(64),
|
||||
),
|
||||
SizedBox(),
|
||||
const SizedBox(),
|
||||
Container(
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
@@ -232,7 +226,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
showCupertinoModalBottomSheet(
|
||||
context: navigatorKey.currentContext ?? context,
|
||||
@@ -246,7 +240,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 10,
|
||||
@@ -275,19 +269,22 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
onPressed: () async {
|
||||
if (widget.sendViewModel.wallet.type == WalletType.bitcoin ||
|
||||
widget.sendViewModel.wallet.type == WalletType.litecoin) {
|
||||
if (widget.action == l2actions.withdraw) {
|
||||
if (widget.action == L2Actions.withdraw) {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
bitcoin!.getUnusedSegwitAddress(widget.sendViewModel.wallet)!;
|
||||
}
|
||||
|
||||
if (widget.showOtherWallets) {
|
||||
if (widget.action == l2actions.deposit) {
|
||||
if (widget.action == L2Actions.deposit) {
|
||||
await _handleChangeWallet(items[_selectedWalletIndex]);
|
||||
} else {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
addressController.text.isNotEmpty
|
||||
? addressController.text
|
||||
: items[_selectedWalletIndex].address;
|
||||
if (items[_selectedWalletIndex].name !=
|
||||
widget.sendViewModel.wallet.name) {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
addressController.text.isNotEmpty
|
||||
? addressController.text
|
||||
: items[_selectedWalletIndex].address;
|
||||
}
|
||||
}
|
||||
}
|
||||
widget.onSendInitiated();
|
||||
@@ -298,7 +295,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: _isLoading,
|
||||
),
|
||||
SizedBox()
|
||||
const SizedBox()
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -339,13 +336,13 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
|
||||
class WalletRow extends StatelessWidget {
|
||||
const WalletRow({
|
||||
super.key,
|
||||
required this.currencyIconPath,
|
||||
required this.walletName,
|
||||
required this.onTap,
|
||||
this.isLoading = false,
|
||||
this.isCurrent = false,
|
||||
this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String currencyIconPath;
|
||||
@@ -370,7 +367,7 @@ class WalletRow extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "package:cake_wallet/core/execution_state.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/src/screens/base_page.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_two_actions.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/list_row.dart";
|
||||
import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_expandable_list.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_list.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_picker_list.dart";
|
||||
import "package:cake_wallet/utils/show_bar.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model_state.dart";
|
||||
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
class RBFDetailsPage extends BasePage {
|
||||
RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) {
|
||||
transactionDetailsViewModel.addBumpFeesListItems(
|
||||
transactionDetailsViewModel.transactionInfo, rawTransaction);
|
||||
transactionDetailsViewModel.transactionInfo,
|
||||
rawTransaction,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -46,70 +48,70 @@ class RBFDetailsPage extends BasePage {
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.RBFListItems[index];
|
||||
sectionCount: 1,
|
||||
itemCounter: (_) => transactionDetailsViewModel.rbfListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.rbfListItems[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: "${item.title}:", value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: '${item.title}: ${item.expandableItems.length}',
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: "${item.title}: ${item.expandableItems.length}",
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
maxValue: item.maxValue,
|
||||
);
|
||||
}
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
maxValue: item.maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
}),
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString());
|
||||
},
|
||||
text: S.of(context).send,
|
||||
isLoading:
|
||||
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
isDisabled: transactionDetailsViewModel.sendViewModel.state
|
||||
is ExecutedSuccessfullyState,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
))),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () => transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString()),
|
||||
text: S.of(context).send,
|
||||
isLoading: transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
isDisabled:
|
||||
transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -121,7 +123,7 @@ class RBFDetailsPage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (state) {
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
@@ -131,35 +133,35 @@ class RBFDetailsPage extends BasePage {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
if (state is AwaitingConfirmationState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: state.title ?? '',
|
||||
alertContent: state.message ?? '',
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
});
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithTwoActions(
|
||||
alertTitle: state.title ?? "",
|
||||
alertContent: state.message ?? "",
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -169,7 +171,7 @@ class RBFDetailsPage extends BasePage {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: S.of(context).generating_transaction,
|
||||
@@ -187,42 +189,42 @@ class RBFDetailsPage extends BasePage {
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('rbf_confirm_sending_bottom_sheet'),
|
||||
titleText: S.of(bottomSheetContext).confirm_transaction,
|
||||
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
|
||||
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
||||
titleIconPath:
|
||||
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||
footerType: FooterType.slideActionButton,
|
||||
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
|
||||
onSlideActionComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
builder: (bottomSheetContext) => ConfirmSendingBottomSheet(
|
||||
key: const ValueKey("rbf_confirm_sending_bottom_sheet"),
|
||||
titleText: S.of(bottomSheetContext).confirm_transaction,
|
||||
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
|
||||
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
||||
titleIconPath:
|
||||
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||
footerType: FooterType.slideActionButton,
|
||||
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
|
||||
onSlideActionComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
if (bottomSheetContext.mounted) {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
} catch (_) {}
|
||||
},
|
||||
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
|
||||
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
|
||||
);
|
||||
},
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
|
||||
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
|
||||
),
|
||||
);
|
||||
if (result == null) {
|
||||
transactionDetailsViewModel.sendViewModel.dismissTransaction();
|
||||
await transactionDetailsViewModel.sendViewModel.dismissTransaction();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -232,14 +234,14 @@ class RBFDetailsPage extends BasePage {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/address_validator.dart";
|
||||
import "package:cake_wallet/entities/priority_for_wallet_type.dart";
|
||||
import "package:cake_wallet/entities/transaction_description.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/monero/monero.dart";
|
||||
import "package:cake_wallet/reactions/wallet_connect.dart";
|
||||
import "package:cake_wallet/solana/solana.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/tron/tron.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:cake_wallet/zano/zano.dart";
|
||||
import "package:collection/collection.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/transaction_direction.dart";
|
||||
import "package:cw_core/transaction_info.dart";
|
||||
import "package:cw_core/transaction_priority.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:hive/hive.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:url_launcher/url_launcher.dart";
|
||||
|
||||
part 'transaction_details_view_model.g.dart';
|
||||
part "transaction_details_view_model.g.dart";
|
||||
|
||||
bool _trueFunc(_) => true;
|
||||
|
||||
/// We're adding a regex here so we can remove any already saved address that has the account in it.
|
||||
/// In the refactor, we will make another separate variable for accounts and the UI would handle it as needed.
|
||||
String _moneroRecipientAddressForDisplay(String raw, WalletType walletType) {
|
||||
if (walletType != WalletType.monero || raw.isEmpty) return raw;
|
||||
if (walletType != WalletType.monero || raw.isEmpty) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
final compact = raw.replaceAll(RegExp(r'\s'), '');
|
||||
final compact = raw.replaceAll(RegExp(r"\s"), "");
|
||||
final match = RegExp(
|
||||
r'4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}',
|
||||
r"4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}",
|
||||
caseSensitive: false,
|
||||
).firstMatch(compact);
|
||||
return match?.group(0) ?? raw.trim();
|
||||
@@ -55,6 +55,14 @@ bool isLightning(TransactionInfo tx) => (tx.additionalInfo["isLightning"] as boo
|
||||
bool hasLightningPreimage(TransactionInfo tx) => (tx.additionalInfo["preimage"] as String?) != null;
|
||||
|
||||
class TxDetailRowDefinition {
|
||||
TxDetailRowDefinition({
|
||||
required this.keyString,
|
||||
required this.title,
|
||||
required this.valueGetter,
|
||||
this.applicable = _trueFunc,
|
||||
this.listItemBuilder = StandartListItem.new,
|
||||
});
|
||||
|
||||
final String keyString;
|
||||
final String title;
|
||||
final String Function(TransactionDetailsViewModelBase) valueGetter;
|
||||
@@ -65,20 +73,13 @@ class TxDetailRowDefinition {
|
||||
required Key key,
|
||||
}) listItemBuilder;
|
||||
|
||||
TxDetailRowDefinition(
|
||||
{required this.keyString,
|
||||
required this.title,
|
||||
required this.valueGetter,
|
||||
this.applicable = _trueFunc,
|
||||
this.listItemBuilder = StandartListItem.new});
|
||||
|
||||
static final List<TxDetailRowDefinition> defs = [
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_date_key",
|
||||
title: S.current.transaction_details_date,
|
||||
valueGetter: (vm) =>
|
||||
DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode)
|
||||
.format(vm.transactionInfo.date)),
|
||||
keyString: "standard_list_item_transaction_details_date_key",
|
||||
title: S.current.transaction_details_date,
|
||||
valueGetter: (vm) => DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode)
|
||||
.format(vm.transactionInfo.date),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_height_key",
|
||||
title: S.current.transaction_details_height,
|
||||
@@ -88,125 +89,137 @@ class TxDetailRowDefinition {
|
||||
!isLightning(vm.transactionInfo),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_fee_key",
|
||||
title: S.current.transaction_details_fee,
|
||||
valueGetter: (vm) => vm.transactionInfo.fee!.toStringWithSymbol(),
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type != WalletType.nano &&
|
||||
(vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty),
|
||||
keyString: "standard_list_item_transaction_details_fee_key",
|
||||
title: S.current.transaction_details_fee,
|
||||
valueGetter: (vm) => vm.feeAmount,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type != WalletType.nano &&
|
||||
(vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_confirmations_key",
|
||||
title: S.current.confirmations,
|
||||
valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}",
|
||||
applicable: (vm) =>
|
||||
[...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero]
|
||||
.contains(vm.wallet.type) &&
|
||||
!isLightning(vm.transactionInfo),
|
||||
listItemBuilder: ConfirmationsListItem.new),
|
||||
keyString: "standard_list_item_transaction_confirmations_key",
|
||||
title: S.current.confirmations,
|
||||
valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}",
|
||||
applicable: (vm) =>
|
||||
[...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero]
|
||||
.contains(vm.wallet.type) &&
|
||||
!isLightning(vm.transactionInfo),
|
||||
listItemBuilder: ConfirmationsListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_recipient_address_key",
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
valueGetter: (vm) {
|
||||
String? ret = null;
|
||||
keyString: "standard_list_item_transaction_details_recipient_address_key",
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
valueGetter: (vm) {
|
||||
String? ret;
|
||||
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.monero:
|
||||
if (vm.transactionInfo.direction == TransactionDirection.incoming) {
|
||||
ret = monero!.getTransactionAddress(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo['accountIndex'] as int,
|
||||
vm.transactionInfo.additionalInfo['addressIndex'] as int);
|
||||
}
|
||||
case WalletType.bitcoin:
|
||||
ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? [])
|
||||
.firstOrNull ??
|
||||
"";
|
||||
case WalletType.tron:
|
||||
if (vm.transactionInfo.to != null)
|
||||
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ret == null) {
|
||||
ret = vm.transactionInfo.to ?? "";
|
||||
}
|
||||
final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type);
|
||||
vm.isRecipientAddressShown = resolvedAddress.isNotEmpty;
|
||||
return resolvedAddress;
|
||||
},
|
||||
applicable: (vm) =>
|
||||
vm.showRecipientAddress &&
|
||||
(vm.transactionInfo.to != null ||
|
||||
[WalletType.monero, WalletType.tron].contains(vm.wallet.type) ||
|
||||
vm.wallet.type == WalletType.bitcoin &&
|
||||
vm.transactionInfo.direction == TransactionDirection.incoming),
|
||||
listItemBuilder: AddressListItem.new),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_source_address_key",
|
||||
title: S.current.transaction_details_source_address,
|
||||
valueGetter: (vm) {
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.tron:
|
||||
return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet);
|
||||
default:
|
||||
return vm.transactionInfo.from!;
|
||||
}
|
||||
},
|
||||
applicable: (vm) => vm.transactionInfo.from != null,
|
||||
listItemBuilder: AddressListItem.new),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_address_label_key",
|
||||
title: S.current.address_label,
|
||||
valueGetter: (vm) => monero!.getSubaddressLabel(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo['accountIndex'] as int,
|
||||
vm.transactionInfo.additionalInfo['addressIndex'] as int),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_key",
|
||||
title: S.current.transaction_key,
|
||||
valueGetter: (vm) {
|
||||
final descriptionKey =
|
||||
'${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}';
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.monero:
|
||||
if (vm.transactionInfo.direction == TransactionDirection.incoming) {
|
||||
ret = monero!.getTransactionAddress(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo["accountIndex"] as int,
|
||||
vm.transactionInfo.additionalInfo["addressIndex"] as int,
|
||||
);
|
||||
}
|
||||
case WalletType.bitcoin:
|
||||
ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? [])
|
||||
.firstOrNull ??
|
||||
"";
|
||||
case WalletType.tron:
|
||||
if (vm.transactionInfo.to != null) {
|
||||
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ret ??= vm.transactionInfo.to ?? "";
|
||||
|
||||
final description = vm.transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
return vm.transactionInfo.additionalInfo['key'] as String? ??
|
||||
description.transactionKey ??
|
||||
"";
|
||||
},
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero),
|
||||
final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type);
|
||||
vm.isRecipientAddressShown = resolvedAddress.isNotEmpty;
|
||||
return resolvedAddress;
|
||||
},
|
||||
applicable: (vm) =>
|
||||
vm.showRecipientAddress &&
|
||||
(vm.transactionInfo.to != null ||
|
||||
[WalletType.monero, WalletType.tron].contains(vm.wallet.type) ||
|
||||
vm.wallet.type == WalletType.bitcoin &&
|
||||
vm.transactionInfo.direction == TransactionDirection.incoming),
|
||||
listItemBuilder: AddressListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_source_address_key",
|
||||
title: S.current.transaction_details_source_address,
|
||||
valueGetter: (vm) {
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.tron:
|
||||
return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet);
|
||||
default:
|
||||
return vm.transactionInfo.from!;
|
||||
}
|
||||
},
|
||||
applicable: (vm) => vm.transactionInfo.from != null,
|
||||
listItemBuilder: AddressListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_address_label_key",
|
||||
title: S.current.address_label,
|
||||
valueGetter: (vm) => monero!.getSubaddressLabel(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo["accountIndex"] as int,
|
||||
vm.transactionInfo.additionalInfo["addressIndex"] as int,
|
||||
),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_key",
|
||||
title: S.current.transaction_key,
|
||||
valueGetter: (vm) {
|
||||
final descriptionKey =
|
||||
"${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}";
|
||||
|
||||
final description = vm.transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
return vm.transactionInfo.additionalInfo["key"] as String? ??
|
||||
description.transactionKey ??
|
||||
"";
|
||||
},
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_lightning_preimage",
|
||||
title: S.current.transaction_preimage,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['preimage'] as String? ?? "",
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["preimage"] as String? ?? "",
|
||||
applicable: (vm) =>
|
||||
hasLightningPreimage(vm.transactionInfo) && isLightning(vm.transactionInfo),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_confirmed_key",
|
||||
title: S.current.confirmed_tx,
|
||||
valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.nano),
|
||||
keyString: "standard_list_item_transaction_confirmed_key",
|
||||
title: S.current.confirmed_tx,
|
||||
valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.nano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_memo_key",
|
||||
title: S.current.memo,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['memo'] as String,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type == WalletType.zcash &&
|
||||
vm.transactionInfo.additionalInfo["memo"] != null),
|
||||
keyString: "standard_list_item_transaction_details_memo_key",
|
||||
title: S.current.memo,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["memo"] as String,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type == WalletType.zcash && vm.transactionInfo.additionalInfo["memo"] != null,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_asset_id_key",
|
||||
title: "Asset ID",
|
||||
valueGetter: (vm) =>
|
||||
vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano),
|
||||
keyString: "standard_list_item_transaction_details_asset_id_key",
|
||||
title: "Asset ID",
|
||||
valueGetter: (vm) =>
|
||||
vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_comment_key",
|
||||
title: S.current.transaction_details_title,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['comment'] as String? ?? "",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano),
|
||||
keyString: "standard_list_item_transaction_details_comment_key",
|
||||
title: S.current.transaction_details_title,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["comment"] as String? ?? "",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_id_key",
|
||||
title: S.current.transaction_details_transaction_id,
|
||||
@@ -227,7 +240,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
required this.sendViewModel,
|
||||
this.canReplaceByFee = false,
|
||||
}) : items = [],
|
||||
RBFListItems = [],
|
||||
rbfListItems = [],
|
||||
newFee = 0,
|
||||
isRecipientAddressShown = false,
|
||||
_appStore = appStore,
|
||||
@@ -236,19 +249,23 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
|
||||
for (final def in TxDetailRowDefinition.defs) {
|
||||
if (def.applicable(this)) {
|
||||
items.add(def.listItemBuilder(
|
||||
items.add(
|
||||
def.listItemBuilder(
|
||||
title: def.title,
|
||||
value: def.valueGetter(this),
|
||||
key: ValueKey(def.keyString)) as TransactionDetailsListItem);
|
||||
key: ValueKey(def.keyString),
|
||||
) as TransactionDetailsListItem,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_checkForRBF(tx);
|
||||
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
|
||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||
final recipientAddress = description.recipientAddress;
|
||||
@@ -260,7 +277,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
AddressListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: recipientAddressForDisplay,
|
||||
key: ValueKey('standard_list_item_${recipientAddressForDisplay}_key'),
|
||||
key: ValueKey("standard_list_item_${recipientAddressForDisplay}_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -268,10 +285,11 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
void updateNote(String note) {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
|
||||
description.transactionNote = note;
|
||||
|
||||
@@ -283,9 +301,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
String get note {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash);
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
);
|
||||
return description?.transactionNote ?? "";
|
||||
}
|
||||
|
||||
@@ -296,7 +315,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final AppStore _appStore;
|
||||
|
||||
final List<TransactionDetailsListItem> items;
|
||||
final List<TransactionDetailsListItem> RBFListItems;
|
||||
final List<TransactionDetailsListItem> rbfListItems;
|
||||
bool showRecipientAddress;
|
||||
bool isRecipientAddressShown;
|
||||
int newFee;
|
||||
@@ -304,9 +323,13 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
TransactionPriority? transactionPriority;
|
||||
|
||||
CryptoCurrency get transactionAsset {
|
||||
if (isEVMCompatibleChain(wallet.type)) return evm!.assetOfTransaction(wallet, transactionInfo);
|
||||
if (isEVMCompatibleChain(wallet.type)) {
|
||||
return evm!.assetOfTransaction(wallet, transactionInfo);
|
||||
}
|
||||
|
||||
if (isLightning(transactionInfo)) return CryptoCurrency.btcln;
|
||||
if (isLightning(transactionInfo)) {
|
||||
return CryptoCurrency.btcln;
|
||||
}
|
||||
|
||||
return switch (wallet.type) {
|
||||
WalletType.solana => solana!.assetOfTransaction(wallet, transactionInfo),
|
||||
@@ -316,26 +339,38 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO integrate these getters with the TransactionInfo object
|
||||
@computed
|
||||
String get transactionAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.amount);
|
||||
|
||||
@computed
|
||||
String get feeAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.fee!);
|
||||
|
||||
@computed
|
||||
String get transactionCopyAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayString(transactionInfo.amount);
|
||||
|
||||
// TODO(malik1004x): integrate these getters with the TransactionInfo object
|
||||
String get formattedPendingStatus {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
case WalletType.haven:
|
||||
case WalletType.zano:
|
||||
if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 10) {
|
||||
return ' (${transactionInfo.confirmations}/10)';
|
||||
return " (${transactionInfo.confirmations}/10)";
|
||||
}
|
||||
break;
|
||||
case WalletType.wownero:
|
||||
if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 3) {
|
||||
return ' (${transactionInfo.confirmations}/3)';
|
||||
return " (${transactionInfo.confirmations}/3)";
|
||||
}
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
bool isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false;
|
||||
bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
String str = '';
|
||||
final isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false;
|
||||
final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
String str = "";
|
||||
if (transactionInfo.confirmations <= 0) {
|
||||
str = S.current.pending;
|
||||
}
|
||||
@@ -352,10 +387,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
return str;
|
||||
default:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
String get formattedStatus {
|
||||
@@ -369,7 +404,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
return formattedPendingStatus;
|
||||
}
|
||||
|
||||
return transactionInfo.isPending ? S.current.pending : '';
|
||||
return transactionInfo.isPending ? S.current.pending : "";
|
||||
}
|
||||
|
||||
int get neededConfirmations {
|
||||
@@ -381,9 +416,11 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
case WalletType.wownero:
|
||||
return 3;
|
||||
case WalletType.litecoin:
|
||||
bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
if (isPegOut || fromPegOut) return 6;
|
||||
final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
if (isPegOut || fromPegOut) {
|
||||
return 6;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -408,52 +445,56 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final txId = transactionInfo.txHash;
|
||||
if (wallet.chainId != null) {
|
||||
final explorerUrl = evm!.getExplorerUrlForChainId(wallet.chainId!);
|
||||
if (explorerUrl != null) return '$explorerUrl/tx/${txId}';
|
||||
if (explorerUrl != null) {
|
||||
return "$explorerUrl/tx/${txId}";
|
||||
}
|
||||
}
|
||||
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return 'https://monero.com/tx/${txId}';
|
||||
return "https://monero.com/tx/${txId}";
|
||||
case WalletType.bitcoin:
|
||||
return 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}';
|
||||
return isLightning(transactionInfo)
|
||||
? "https://sparkscan.io/tx/${txId}"
|
||||
: 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}';
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.txIsMweb(transactionInfo)
|
||||
? "https://www.mwebexplorer.com/blocks/block/${transactionInfo.height}"
|
||||
: 'https://blockchair.com/litecoin/transaction/${txId}';
|
||||
: "https://blockchair.com/litecoin/transaction/${txId}";
|
||||
case WalletType.bitcoinCash:
|
||||
return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
|
||||
return "https://blockchair.com/bitcoin-cash/transaction/${txId}";
|
||||
case WalletType.haven:
|
||||
return 'https://explorer.havenprotocol.org/search?value=${txId}';
|
||||
return "https://explorer.havenprotocol.org/search?value=${txId}";
|
||||
case WalletType.ethereum:
|
||||
return 'https://etherscan.io/tx/${txId}';
|
||||
return "https://etherscan.io/tx/${txId}";
|
||||
case WalletType.base:
|
||||
return 'https://basescan.org/tx/${txId}';
|
||||
return "https://basescan.org/tx/${txId}";
|
||||
case WalletType.arbitrum:
|
||||
return 'https://arbiscan.io/tx/${txId}';
|
||||
return "https://arbiscan.io/tx/${txId}";
|
||||
case WalletType.bsc:
|
||||
return 'https://bscscan.com/tx/${txId}';
|
||||
return "https://bscscan.com/tx/${txId}";
|
||||
case WalletType.polygon:
|
||||
return 'https://polygonscan.com/tx/${txId}';
|
||||
return "https://polygonscan.com/tx/${txId}";
|
||||
case WalletType.nano:
|
||||
return 'https://nanexplorer.com/nano/block/${txId}';
|
||||
return "https://nanexplorer.com/nano/block/${txId}";
|
||||
case WalletType.banano:
|
||||
return 'https://nanexplorer.com/banano/block/${txId}';
|
||||
return "https://nanexplorer.com/banano/block/${txId}";
|
||||
case WalletType.solana:
|
||||
return 'https://solscan.io/tx/${txId}';
|
||||
return "https://solscan.io/tx/${txId}";
|
||||
case WalletType.tron:
|
||||
return 'https://tronscan.org/#/transaction/${txId}';
|
||||
return "https://tronscan.org/#/transaction/${txId}";
|
||||
case WalletType.wownero:
|
||||
return 'https://explore.wownero.com/tx/${txId}';
|
||||
return "https://explore.wownero.com/tx/${txId}";
|
||||
case WalletType.zano:
|
||||
return 'https://explorer.zano.org/transaction/${txId}';
|
||||
return "https://explorer.zano.org/transaction/${txId}";
|
||||
case WalletType.decred:
|
||||
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
|
||||
case WalletType.dogecoin:
|
||||
return 'https://blockchair.com/dogecoin/transaction/${txId}';
|
||||
return "https://blockchair.com/dogecoin/transaction/${txId}";
|
||||
case WalletType.zcash:
|
||||
return 'https://blockchair.com/zcash/transaction/${txId}';
|
||||
return "https://blockchair.com/zcash/transaction/${txId}";
|
||||
case WalletType.none:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,13 +514,17 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
: transactionInfo.outputAddresses!.length;
|
||||
|
||||
newFee = bitcoin!.getFeeAmountForPriority(
|
||||
wallet, bitcoin!.getBitcoinTransactionPriorityMedium(), inputsCount, outputsCount);
|
||||
wallet,
|
||||
bitcoin!.getBitcoinTransactionPriorityMedium(),
|
||||
inputsCount,
|
||||
outputsCount,
|
||||
);
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandartListItem(
|
||||
title: S.current.old_fee,
|
||||
value: tx.fee?.toStringWithSymbol() ?? '0.0',
|
||||
key: ValueKey('standard_list_item_rbf_old_fee_key'),
|
||||
value: tx.fee?.toStringWithSymbol() ?? "0.0",
|
||||
key: const ValueKey("standard_list_item_rbf_old_fee_key"),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -488,32 +533,33 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final recommendedRate = (transactionInfo.fee! / BigInt.from(size)) +
|
||||
transactionInfo.fee!.copyWith(amount: BigInt.one);
|
||||
|
||||
RBFListItems.add(
|
||||
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
|
||||
rbfListItems.add(
|
||||
StandartListItem(title: "New recommended fee rate", value: "$recommendedRate sat/byte"),
|
||||
);
|
||||
}
|
||||
|
||||
final priorities = priorityForWalletType(wallet.type);
|
||||
final selectedItem = priorities.indexOf(sendViewModel.feesViewModel.transactionPriority);
|
||||
final customItem = priorities.firstWhereOrNull(
|
||||
(element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom);
|
||||
(element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom,
|
||||
);
|
||||
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
|
||||
final maxCustomFeeRate = sendViewModel.feesViewModel.maxCustomFeeRate?.toDouble();
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardPickerListItem(
|
||||
key: ValueKey('standard_picker_list_item_transaction_priorities_key'),
|
||||
key: const ValueKey("standard_picker_list_item_transaction_priorities_key"),
|
||||
title: S.current.estimated_new_fee,
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${wallet.currency}',
|
||||
value: "${bitcoin!.formatterBitcoinAmountToString(amount: newFee)} ${wallet.currency}",
|
||||
items: priorityForWalletType(wallet.type),
|
||||
customValue: _appStore.settingsStore.customBitcoinFeeRate.toDouble(),
|
||||
maxValue: maxCustomFeeRate,
|
||||
selectedIdx: selectedItem,
|
||||
customItemIndex: customItemIndex ?? 0,
|
||||
displayItem: (dynamic priority, double sliderValue) =>
|
||||
displayItem: (dynamic priority, sliderValue) =>
|
||||
sendViewModel.feesViewModel.displayFeeRate(priority, sliderValue.round()),
|
||||
onSliderChanged: (double newValue) =>
|
||||
setNewFee(value: newValue, priority: transactionPriority!),
|
||||
onItemSelected: (dynamic item, double sliderValue) {
|
||||
onSliderChanged: (newValue) => setNewFee(value: newValue, priority: transactionPriority!),
|
||||
onItemSelected: (dynamic item, sliderValue) {
|
||||
transactionPriority = item as TransactionPriority;
|
||||
return setNewFee(value: sliderValue, priority: transactionPriority!);
|
||||
},
|
||||
@@ -521,9 +567,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
);
|
||||
|
||||
if (transactionInfo.inputAddresses != null && transactionInfo.inputAddresses!.isNotEmpty) {
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardExpandableListItem(
|
||||
key: ValueKey('standard_expandable_list_item_transaction_input_addresses_key'),
|
||||
key: const ValueKey("standard_expandable_list_item_transaction_input_addresses_key"),
|
||||
title: S.current.inputs,
|
||||
expandableItems: transactionInfo.inputAddresses!,
|
||||
),
|
||||
@@ -532,17 +578,17 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
|
||||
if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
|
||||
final outputAddresses = transactionInfo.outputAddresses!.map((element) {
|
||||
if (element.contains('OP_RETURN:') && element.length > 40) {
|
||||
return element.substring(0, 40) + '...';
|
||||
if (element.contains("OP_RETURN:") && element.length > 40) {
|
||||
return "${element.substring(0, 40)}...";
|
||||
}
|
||||
return element;
|
||||
}).toList();
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardExpandableListItem(
|
||||
title: S.current.outputs,
|
||||
expandableItems: outputAddresses,
|
||||
key: ValueKey('standard_expandable_list_item_transaction_output_addresses_key'),
|
||||
key: const ValueKey("standard_expandable_list_item_transaction_output_addresses_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -552,7 +598,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
Future<void> _checkForRBF(TransactionInfo tx) async {
|
||||
if (wallet.type == WalletType.bitcoin &&
|
||||
transactionInfo.direction == TransactionDirection.outgoing) {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash);
|
||||
|
||||
@@ -569,45 +615,23 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
String setNewFee({double? value, required TransactionPriority priority}) {
|
||||
String setNewFee({required TransactionPriority priority, double? value}) {
|
||||
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
|
||||
? bitcoin!.feeAmountWithFeeRate(
|
||||
wallet,
|
||||
value.round(),
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1)
|
||||
transactionInfo.outputAddresses?.length ?? 1,
|
||||
)
|
||||
: bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
transactionInfo.outputAddresses?.length ?? 1,
|
||||
);
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
|
||||
}
|
||||
|
||||
String get formattedCryptoAmount {
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
final crypto = isLightning(transactionInfo) ? CryptoCurrency.btcln : CryptoCurrency.btc;
|
||||
final amount = _appStore.amountParsingProxy
|
||||
.asDisplayString(transactionInfo.amount)
|
||||
.withMaxDecimals(8)
|
||||
.withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
|
||||
return '$amount ${_appStore.amountParsingProxy.getCryptoSymbol(crypto)}';
|
||||
}
|
||||
|
||||
return transactionInfo.amount.toStringWithSymbol();
|
||||
}
|
||||
|
||||
void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee);
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
}
|
||||
|
||||
@@ -1,59 +1,58 @@
|
||||
import 'dart:core';
|
||||
import 'dart:developer' as dev;
|
||||
import "dart:core";
|
||||
import "dart:developer" as dev;
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/address_resolver/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/utils/qr_util.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/address_resolver/yat/yat_store.dart";
|
||||
import "package:cake_wallet/core/amount_parsing_proxy.dart";
|
||||
import "package:cake_wallet/core/fiat_conversion_service.dart";
|
||||
import "package:cake_wallet/core/wallet_change_listener_view_model.dart";
|
||||
import "package:cake_wallet/decred/decred.dart";
|
||||
import "package:cake_wallet/entities/auto_generate_subaddress_status.dart";
|
||||
import "package:cake_wallet/entities/fiat_api_mode.dart";
|
||||
import "package:cake_wallet/entities/fiat_currency.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/monero/monero.dart";
|
||||
import "package:cake_wallet/reactions/wallet_connect.dart";
|
||||
import "package:cake_wallet/reactions/wallet_utils.dart";
|
||||
import "package:cake_wallet/solana/solana.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/store/dashboard/fiat_conversion_store.dart";
|
||||
import "package:cake_wallet/tron/tron.dart";
|
||||
import "package:cake_wallet/utils/list_item.dart";
|
||||
import "package:cake_wallet/utils/qr_util.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart";
|
||||
import "package:cake_wallet/wownero/wownero.dart";
|
||||
import "package:cake_wallet/zano/zano.dart";
|
||||
import "package:cake_wallet/zcash/zcash.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/erc20_token.dart";
|
||||
import "package:cw_core/payment_uris.dart";
|
||||
import "package:cw_core/spl_token.dart";
|
||||
import "package:cw_core/tron_token.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
part 'wallet_address_list_view_model.g.dart';
|
||||
part "wallet_address_list_view_model.g.dart";
|
||||
|
||||
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
|
||||
|
||||
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||
WalletAddressListViewModelBase({
|
||||
required AppStore appStore,
|
||||
required super.appStore,
|
||||
required this.yatStore,
|
||||
required this.fiatConversionStore,
|
||||
}) : _baseItems = <ListItem>[],
|
||||
selectedCurrency = appStore.wallet!.currency,
|
||||
hasAccounts = [WalletType.monero, WalletType.wownero].contains(appStore.wallet!.type),
|
||||
_appStore = appStore,
|
||||
receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? '',
|
||||
super(appStore: appStore) {
|
||||
receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? "" {
|
||||
_init();
|
||||
}
|
||||
|
||||
@@ -81,8 +80,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
CryptoCurrency? tokenCurrency;
|
||||
|
||||
@computed
|
||||
String get cryptoCurrencySymbol =>
|
||||
_appStore.amountParsingProxy.getCryptoSymbol(tokenCurrency ?? wallet.currency);
|
||||
String get cryptoCurrencySymbol => _appStore.amountParsingProxy.getCryptoSymbol(
|
||||
tokenCurrency ??
|
||||
(selectedCurrency is CryptoCurrency
|
||||
? selectedCurrency as CryptoCurrency
|
||||
: wallet.currency),
|
||||
);
|
||||
|
||||
void setTokenCurrency(Currency curr) {
|
||||
if (curr == wallet.currency || curr == CryptoCurrency.btcln) {
|
||||
@@ -122,7 +125,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
_appStore.amountParsingProxy.useSatoshi(selectedCurrency as CryptoCurrency);
|
||||
|
||||
@observable
|
||||
String searchText = '';
|
||||
String searchText = "";
|
||||
|
||||
@computed
|
||||
int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
|
||||
@@ -131,30 +134,45 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
int get tokenCurrencyIndex => tokenCurrency == null ? 0 : tokenCurrencies.indexOf(tokenCurrency!);
|
||||
|
||||
@observable
|
||||
String _amount = '';
|
||||
String _amount = "";
|
||||
|
||||
@computed
|
||||
String get displayAmount => _appStore.amountParsingProxy
|
||||
.getDisplayCryptoAmount(_amount, tokenCurrency ?? wallet.currency);
|
||||
String get displayAmount => _appStore.amountParsingProxy.getDisplayCryptoAmount(
|
||||
_amount,
|
||||
tokenCurrency ??
|
||||
(selectedCurrency is CryptoCurrency
|
||||
? selectedCurrency as CryptoCurrency
|
||||
: wallet.currency),
|
||||
);
|
||||
|
||||
// NOT PRECISE! just for display purposes.
|
||||
@computed
|
||||
String get fiatAmount {
|
||||
if (_amount.isEmpty) return "";
|
||||
if (_amount.isEmpty) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var cryptoCurrency = tokenCurrency ?? wallet.currency;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) {
|
||||
cryptoCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
|
||||
if (selectedCurrency is FiatCurrency && _fiatRate != null) {
|
||||
return selectedCurrencyFiatAmount;
|
||||
}
|
||||
|
||||
if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) return "";
|
||||
if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) {
|
||||
return "";
|
||||
}
|
||||
final amount = double.tryParse(_amount) ?? 0;
|
||||
return (amount * fiatConversionStore.prices[cryptoCurrency]!).toStringAsFixed(2);
|
||||
}
|
||||
|
||||
@computed
|
||||
String get selectedCurrencyFiatAmount {
|
||||
if (_fiatRate == null) return "";
|
||||
if (_fiatRate == null) {
|
||||
return "";
|
||||
}
|
||||
final amount = double.tryParse(_amount) ?? 0;
|
||||
return (amount * _fiatRate!).toStringAsFixed(2);
|
||||
}
|
||||
@@ -201,26 +219,29 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
PaymentURI get uri {
|
||||
if (tokenCurrency != null && isEVMCompatibleChain(wallet.type)) {
|
||||
return ERC681URI(
|
||||
chainId: wallet.chainId ?? 1,
|
||||
address: wallet.walletAddresses.address,
|
||||
amount: _amount,
|
||||
contractAddress: (tokenCurrency as Erc20Token).contractAddress);
|
||||
chainId: wallet.chainId ?? 1,
|
||||
address: wallet.walletAddresses.address,
|
||||
amount: _amount,
|
||||
contractAddress: (tokenCurrency! as Erc20Token).contractAddress,
|
||||
);
|
||||
}
|
||||
if (tokenCurrency is TronToken && wallet.type == WalletType.tron) {
|
||||
return TronURI(
|
||||
amount: _amount,
|
||||
address: wallet.walletAddresses.address,
|
||||
contractAddress: (tokenCurrency as TronToken).contractAddress,
|
||||
contractAddress: (tokenCurrency! as TronToken).contractAddress,
|
||||
);
|
||||
}
|
||||
if (tokenCurrency is SPLToken && wallet.type == WalletType.solana) {
|
||||
return SolanaURI(
|
||||
amount: _amount,
|
||||
address: wallet.walletAddresses.address,
|
||||
contractAddress: (tokenCurrency as SPLToken).mintAddress,
|
||||
contractAddress: (tokenCurrency! as SPLToken).mintAddress,
|
||||
);
|
||||
}
|
||||
if (isLightning && _lnPaymentRequest != null) return _lnPaymentRequest!;
|
||||
if (isLightning && _lnPaymentRequest != null) {
|
||||
return _lnPaymentRequest!;
|
||||
}
|
||||
return wallet.walletAddresses.getPaymentUri(_amount);
|
||||
}
|
||||
|
||||
@@ -257,10 +278,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
final isPrimary = subaddress == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address);
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address,
|
||||
);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
@@ -285,37 +307,37 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
addressList.addAll(addressItems);
|
||||
addressList.add(WalletAddressListHeader(title: S.current.received));
|
||||
|
||||
final receivedAddressItems =
|
||||
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
derivationPath: address.derivationPath,
|
||||
);
|
||||
});
|
||||
final receivedAddressItems = bitcoin!.getSilentPaymentReceivedAddresses(wallet).map(
|
||||
(address) => WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
derivationPath: address.derivationPath,
|
||||
),
|
||||
);
|
||||
addressList.addAll(receivedAddressItems);
|
||||
} else {
|
||||
var addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: subaddress.isChange,
|
||||
isLegacyDerivation: subaddress.isLegacyDerivation,
|
||||
derivationPath: subaddress.derivationPath);
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: subaddress.isChange,
|
||||
isLegacyDerivation: subaddress.isLegacyDerivation,
|
||||
derivationPath: subaddress.derivationPath,
|
||||
);
|
||||
});
|
||||
|
||||
// don't show all 1000+ mweb addresses:
|
||||
@@ -346,11 +368,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.nano) {
|
||||
addressList.add(WalletAddressListItem(
|
||||
isPrimary: true,
|
||||
name: null,
|
||||
address: wallet.walletAddresses.address,
|
||||
));
|
||||
addressList.add(
|
||||
WalletAddressListItem(
|
||||
isPrimary: true,
|
||||
name: null,
|
||||
address: wallet.walletAddresses.address,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
@@ -361,29 +385,35 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
if (wallet.type == WalletType.decred) {
|
||||
final addrInfos = decred!.getAddressInfos(wallet);
|
||||
addrInfos.forEach((info) {
|
||||
for (final info in addrInfos) {
|
||||
addressList.add(
|
||||
new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
|
||||
});
|
||||
WalletAddressListItem(isPrimary: false, address: info.address, name: info.label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.zcash) {
|
||||
final addrInfos = zcash!.getAddressInfos(wallet);
|
||||
addrInfos.forEach((info) {
|
||||
for (final info in addrInfos) {
|
||||
addressList.add(
|
||||
new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
|
||||
});
|
||||
WalletAddressListItem(isPrimary: false, address: info.address, name: info.label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
if (addressList[i] is! WalletAddressListItem) {
|
||||
continue;
|
||||
}
|
||||
final item = addressList[i] as WalletAddressListItem;
|
||||
item.isHidden = wallet.walletAddresses.hiddenAddresses.contains(item.address) ||
|
||||
(isElectrumWallet && item.isLegacyDerivation);
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
if (addressList[i] is! WalletAddressListItem) {
|
||||
continue;
|
||||
}
|
||||
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
|
||||
.contains((addressList[i] as WalletAddressListItem).address);
|
||||
}
|
||||
@@ -395,25 +425,25 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (searchText.isNotEmpty) {
|
||||
return ObservableList.of(addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
return item.address.toLowerCase().contains(searchText.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
return ObservableList.of(
|
||||
addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
return item.address.toLowerCase().contains(searchText.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get addressList {
|
||||
return _computeAddressList();
|
||||
}
|
||||
ObservableList<ListItem> get addressList => _computeAddressList();
|
||||
|
||||
List<ListItem> get forceRecomputeItems {
|
||||
// necessary because the addressList contains non-observable items
|
||||
List<ListItem> recomputed = [];
|
||||
final recomputed = <ListItem>[];
|
||||
recomputed.addAll(_baseItems);
|
||||
recomputed.addAll(_computeAddressList());
|
||||
return recomputed;
|
||||
@@ -451,9 +481,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
case WalletType.wownero:
|
||||
wownero!.getCurrentAccount(wallet).label;
|
||||
default:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -473,7 +503,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
WalletType.litecoin,
|
||||
WalletType.decred,
|
||||
WalletType.dogecoin,
|
||||
WalletType.zcash
|
||||
WalletType.zcash,
|
||||
].contains(wallet.type) &&
|
||||
!isLightning &&
|
||||
isZCashTransparent;
|
||||
@@ -486,7 +516,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin
|
||||
WalletType.dogecoin,
|
||||
].contains(wallet.type);
|
||||
|
||||
List<String> getWalletImages(int? chainId) {
|
||||
@@ -494,44 +524,44 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
switch (chainId) {
|
||||
case 1:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/deuro_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/deuro_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 137:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/polygon.svg',
|
||||
'assets/images/eth_pol_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/polygon.svg",
|
||||
"assets/images/eth_pol_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 8453:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 42161:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/arbitrum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/arbitrum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 56:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/bnb.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/bnb.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
default:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -539,22 +569,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
switch (wallet.type) {
|
||||
case WalletType.solana:
|
||||
return [
|
||||
'assets/images/sol_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/sol_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
'assets/images/trx_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/trx_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case WalletType.zano:
|
||||
return [
|
||||
'assets/images/zano_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/zano_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
@@ -563,7 +593,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
@computed
|
||||
String get qrImage {
|
||||
if (isLightning) return 'assets/images/btc_chain_qr_lightning.svg';
|
||||
if (isLightning) {
|
||||
return "assets/images/btc_chain_qr_lightning.svg";
|
||||
}
|
||||
return getQrImage(type);
|
||||
}
|
||||
|
||||
@@ -582,8 +614,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
@computed
|
||||
bool get isLightning =>
|
||||
wallet.type == WalletType.bitcoin &&
|
||||
(wallet.walletAddresses.getPaymentUri(_amount) is LightningPaymentRequest);
|
||||
wallet.type == WalletType.bitcoin && selectedCurrency == CryptoCurrency.btcln;
|
||||
|
||||
@computed
|
||||
bool get isZCashTransparent {
|
||||
@@ -698,16 +729,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
@action
|
||||
void changeAmount(String amount) {
|
||||
if (selectedCurrency is FiatCurrency) {
|
||||
this._amount = amount;
|
||||
_amount = amount;
|
||||
_convertAmountToCrypto();
|
||||
} else if (selectedCurrency is CryptoCurrency) {
|
||||
this._amount = _appStore.amountParsingProxy
|
||||
_amount = _appStore.amountParsingProxy
|
||||
.getCanonicalCryptoAmount(amount, selectedCurrency as CryptoCurrency);
|
||||
}
|
||||
if (isLightning) {
|
||||
wallet.walletAddresses
|
||||
.getPaymentRequestUri(this._amount)
|
||||
.then((uri) => _lnPaymentRequest = uri);
|
||||
wallet.walletAddresses.getPaymentRequestUri(_amount).then((uri) => _lnPaymentRequest = uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,22 +748,24 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
@action
|
||||
void _convertAmountToCrypto() {
|
||||
var cryptoCurrency = tokenCurrency ?? wallet.currency;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) {
|
||||
cryptoCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
|
||||
|
||||
if (fiatRate <= 0.0) {
|
||||
dev.log("Invalid Fiat Rate $fiatRate");
|
||||
_amount = '';
|
||||
_amount = "";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final crypto = (double.parse(_amount.replaceAll(',', '.')) / fiatRate).toStringAsFixed(8);
|
||||
final crypto = (double.parse(_amount.replaceAll(",", ".")) / fiatRate).toStringAsFixed(8);
|
||||
if (_amount != crypto) {
|
||||
_amount = crypto;
|
||||
}
|
||||
} catch (e) {
|
||||
_amount = '';
|
||||
_amount = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user