mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
Sp enhancements (#1672)
* fix: enhance regex, fix multiline * feat: improve scan msg, fix missing txs, use date api * feat: node fixes, enhance send modal, TX list tag & filter, refactors * fix: continuous scanning * fix: missing close * fix: resubscribe tweaks * feat: use mempool api setting toggle * handle any failure of height API and fallback to the old method [skip ci] --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
@@ -361,7 +361,7 @@ class CWBitcoin extends Bitcoin {
|
||||
continue;
|
||||
}
|
||||
|
||||
final sh = scriptHash(address, network: network);
|
||||
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
|
||||
final history = await electrumClient.getHistory(sh);
|
||||
|
||||
final balance = await electrumClient.getBalance(sh);
|
||||
@@ -522,7 +522,20 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
@override
|
||||
int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date);
|
||||
Future<bool> checkIfMempoolAPIIsEnabled(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return await bitcoinWallet.checkIfMempoolAPIIsEnabled();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getHeightByDate({required DateTime date, bool? bitcoinMempoolAPIEnabled}) async {
|
||||
if (bitcoinMempoolAPIEnabled ?? false) {
|
||||
try {
|
||||
return await getBitcoinHeightByDateAPI(date: date);
|
||||
} catch (_) {}
|
||||
}
|
||||
return await getBitcoinHeightByDate(date: date);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||
@@ -547,4 +560,35 @@ class CWBitcoin extends Bitcoin {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateFeeRates();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs) {
|
||||
final pendingTx = pendingTransaction as PendingBitcoinTransaction;
|
||||
|
||||
if (!pendingTx.hasSilentPayment) {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
final updatedOutputs = outputs.map((output) {
|
||||
|
||||
try {
|
||||
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
|
||||
final updatedOutput = output;
|
||||
|
||||
updatedOutput.stealthAddress = P2trAddress.fromScriptPubkey(script: pendingOut.scriptPubKey)
|
||||
.toAddress(BitcoinNetwork.mainnet);
|
||||
return updatedOutput;
|
||||
} catch (_) {}
|
||||
|
||||
return output;
|
||||
}).toList();
|
||||
|
||||
return updatedOutputs;
|
||||
}
|
||||
|
||||
@override
|
||||
bool txIsReceivedSilentPayment(TransactionInfo txInfo) {
|
||||
final tx = txInfo as ElectrumTransactionInfo;
|
||||
return tx.isReceivedSilentPayment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,32 +5,40 @@ import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
||||
const BEFORE_REGEX = '(^|\s)';
|
||||
const AFTER_REGEX = '(\$|\s)';
|
||||
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: type == CryptoCurrency.btc
|
||||
? (String txt) => validateAddress(address: txt, network: BitcoinNetwork.mainnet)
|
||||
? (String txt) => BitcoinAddressUtils.validateAddress(
|
||||
address: txt,
|
||||
network: BitcoinNetwork.mainnet,
|
||||
)
|
||||
: null,
|
||||
pattern: getPattern(type),
|
||||
length: getLength(type));
|
||||
|
||||
static String getPattern(CryptoCurrency type) {
|
||||
var pattern = "";
|
||||
if (type is Erc20Token) {
|
||||
return '0x[0-9a-zA-Z]';
|
||||
pattern = '0x[0-9a-zA-Z]';
|
||||
}
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
|
||||
pattern = '4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}';
|
||||
case CryptoCurrency.ada:
|
||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
||||
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||
pattern = '[0-9a-zA-Z]{59}|[0-9a-zA-Z]{92}|[0-9a-zA-Z]{104}'
|
||||
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
|
||||
case CryptoCurrency.btc:
|
||||
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.nano:
|
||||
return '[0-9a-zA-Z_]';
|
||||
pattern = '[0-9a-zA-Z_]';
|
||||
case CryptoCurrency.banano:
|
||||
return '[0-9a-zA-Z_]';
|
||||
pattern = '[0-9a-zA-Z_]';
|
||||
case CryptoCurrency.usdc:
|
||||
case CryptoCurrency.usdcpoly:
|
||||
case CryptoCurrency.usdtPoly:
|
||||
@@ -66,11 +74,11 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.dydx:
|
||||
case CryptoCurrency.steth:
|
||||
case CryptoCurrency.shib:
|
||||
return '0x[0-9a-zA-Z]';
|
||||
pattern = '0x[0-9a-zA-Z]';
|
||||
case CryptoCurrency.xrp:
|
||||
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
|
||||
pattern = '[0-9a-zA-Z]{34}|X[0-9a-zA-Z]{46}';
|
||||
case CryptoCurrency.xhv:
|
||||
return '^hvx|hvi|hvs[0-9a-zA-Z]';
|
||||
pattern = 'hvx|hvi|hvs[0-9a-zA-Z]';
|
||||
case CryptoCurrency.xag:
|
||||
case CryptoCurrency.xau:
|
||||
case CryptoCurrency.xaud:
|
||||
@@ -92,40 +100,43 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.dash:
|
||||
case CryptoCurrency.eos:
|
||||
case CryptoCurrency.wow:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]';
|
||||
case CryptoCurrency.bch:
|
||||
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
|
||||
pattern =
|
||||
'(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.bnb:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]';
|
||||
case CryptoCurrency.ltc:
|
||||
return '^(?!(ltc|LTC)1)[0-9a-zA-Z]*\$|(^LTC1[A-Z0-9]*\$)|(^ltc1[a-z0-9]*\$)';
|
||||
pattern = '(?!(ltc|LTC)1)[0-9a-zA-Z]*|(LTC1[A-Z0-9]*)|(ltc1[a-z0-9]*)';
|
||||
case CryptoCurrency.hbar:
|
||||
return '[0-9a-zA-Z.]';
|
||||
pattern = '[0-9a-zA-Z.]';
|
||||
case CryptoCurrency.zaddr:
|
||||
return '^zs[0-9a-zA-Z]{75}';
|
||||
pattern = 'zs[0-9a-zA-Z]{75}';
|
||||
case CryptoCurrency.zec:
|
||||
return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$';
|
||||
pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
|
||||
case CryptoCurrency.dcr:
|
||||
return 'D[ksecS]([0-9a-zA-Z])+';
|
||||
pattern = 'D[ksecS]([0-9a-zA-Z])+';
|
||||
case CryptoCurrency.rvn:
|
||||
return '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.near:
|
||||
return '[0-9a-f]{64}';
|
||||
pattern = '[0-9a-f]{64}';
|
||||
case CryptoCurrency.rune:
|
||||
return 'thor1[0-9a-z]{38}';
|
||||
pattern = 'thor1[0-9a-z]{38}';
|
||||
case CryptoCurrency.scrt:
|
||||
return 'secret1[0-9a-z]{38}';
|
||||
pattern = 'secret1[0-9a-z]{38}';
|
||||
case CryptoCurrency.stx:
|
||||
return 'S[MP][0-9a-zA-Z]+';
|
||||
pattern = 'S[MP][0-9a-zA-Z]+';
|
||||
case CryptoCurrency.kmd:
|
||||
return 'R[0-9a-zA-Z]{33}';
|
||||
pattern = 'R[0-9a-zA-Z]{33}';
|
||||
case CryptoCurrency.pivx:
|
||||
return 'D([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.btcln:
|
||||
return '^(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||
default:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]';
|
||||
}
|
||||
|
||||
return '$BEFORE_REGEX$pattern$AFTER_REGEX';
|
||||
}
|
||||
|
||||
static List<int>? getLength(CryptoCurrency type) {
|
||||
@@ -266,56 +277,54 @@ class AddressValidator extends TextValidator {
|
||||
}
|
||||
|
||||
static String? getAddressFromStringPattern(CryptoCurrency type) {
|
||||
String? pattern = null;
|
||||
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
case CryptoCurrency.wow:
|
||||
return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '4[0-9a-zA-Z]{94}'
|
||||
'|8[0-9a-zA-Z]{94}'
|
||||
'|[0-9a-zA-Z]{106}';
|
||||
case CryptoCurrency.btc:
|
||||
return '([^0-9a-zA-Z]|^)([1mn][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2pkhAddress type
|
||||
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type
|
||||
'|${SilentPaymentAddress.regex.pattern}\$';
|
||||
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '^L[a-zA-Z0-9]{26,33}'
|
||||
'|[LM][a-km-zA-HJ-NP-Z1-9]{26,33}'
|
||||
'|ltc[a-zA-Z0-9]{26,45}';
|
||||
case CryptoCurrency.eth:
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.maticpoly:
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.nano:
|
||||
return 'nano_[0-9a-zA-Z]{60}';
|
||||
pattern = 'nano_[0-9a-zA-Z]{60}';
|
||||
case CryptoCurrency.banano:
|
||||
return 'ban_[0-9a-zA-Z]{60}';
|
||||
pattern = 'ban_[0-9a-zA-Z]{60}';
|
||||
case CryptoCurrency.bch:
|
||||
return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
||||
'|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}';
|
||||
case CryptoCurrency.sol:
|
||||
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '[1-9A-HJ-NP-Za-km-z]{43,44}';
|
||||
case CryptoCurrency.trx:
|
||||
return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
default:
|
||||
if (type.tag == CryptoCurrency.eth.title) {
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.maticpoly.tag) {
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.sol.title) {
|
||||
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '[1-9A-HJ-NP-Za-km-z]{43,44}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.trx.title) {
|
||||
return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pattern != null) {
|
||||
return "$BEFORE_REGEX$pattern$AFTER_REGEX";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,11 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
||||
}
|
||||
|
||||
if (syncStatus is StartingScanSyncStatus) {
|
||||
return S.current.sync_status_starting_scan;
|
||||
return S.current.sync_status_starting_scan(syncStatus.beginHeight.toString());
|
||||
}
|
||||
|
||||
if (syncStatus is AttemptingScanSyncStatus) {
|
||||
return S.current.sync_status_attempting_scan;
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
@@ -51,7 +51,7 @@ class AddressResolver {
|
||||
throw Exception('Unexpected token: $type for getAddressFromStringPattern');
|
||||
}
|
||||
|
||||
final match = RegExp(addressPattern).firstMatch(raw);
|
||||
final match = RegExp(addressPattern, multiLine: true).firstMatch(raw);
|
||||
return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'),
|
||||
(Match match) {
|
||||
String group = match.group(0)!;
|
||||
@@ -213,8 +213,7 @@ class AddressResolver {
|
||||
await NostrProfileHandler.processRelays(context, nostrProfile!, text);
|
||||
|
||||
if (nostrUserData != null) {
|
||||
String? addressFromBio = extractAddressByType(
|
||||
raw: nostrUserData.about, type: currency);
|
||||
String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
|
||||
if (addressFromBio != null) {
|
||||
return ParsedAddress.nostrAddress(
|
||||
address: addressFromBio,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
|
||||
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
|
||||
@@ -52,7 +53,7 @@ class TransactionsPage extends StatelessWidget {
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/");
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} catch (_) {}
|
||||
},
|
||||
title: S.of(context).syncing_wallet_alert_title,
|
||||
@@ -84,7 +85,7 @@ class TransactionsPage extends StatelessWidget {
|
||||
|
||||
final transaction = item.transaction;
|
||||
final transactionType = dashboardViewModel.type == WalletType.ethereum &&
|
||||
transaction.evmSignatureName == 'approval'
|
||||
transaction.evmSignatureName == 'approval'
|
||||
? ' (${transaction.evmSignatureName})'
|
||||
: '';
|
||||
|
||||
@@ -100,8 +101,11 @@ class TransactionsPage extends StatelessWidget {
|
||||
? ''
|
||||
: item.formattedFiatAmount,
|
||||
isPending: transaction.isPending,
|
||||
title: item.formattedTitle +
|
||||
item.formattedStatus + ' $transactionType',
|
||||
title:
|
||||
item.formattedTitle + item.formattedStatus + ' $transactionType',
|
||||
isReceivedSilentPayment:
|
||||
dashboardViewModel.type == WalletType.bitcoin &&
|
||||
bitcoin!.txIsReceivedSilentPayment(transaction),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
@@ -5,14 +6,16 @@ import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class TransactionRow extends StatelessWidget {
|
||||
TransactionRow(
|
||||
{required this.direction,
|
||||
required this.formattedDate,
|
||||
required this.formattedAmount,
|
||||
required this.formattedFiatAmount,
|
||||
required this.isPending,
|
||||
required this.title,
|
||||
required this.onTap});
|
||||
TransactionRow({
|
||||
required this.direction,
|
||||
required this.formattedDate,
|
||||
required this.formattedAmount,
|
||||
required this.formattedFiatAmount,
|
||||
required this.isPending,
|
||||
required this.isReceivedSilentPayment,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final TransactionDirection direction;
|
||||
@@ -20,6 +23,7 @@ class TransactionRow extends StatelessWidget {
|
||||
final String formattedAmount;
|
||||
final String formattedFiatAmount;
|
||||
final bool isPending;
|
||||
final bool isReceivedSilentPayment;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
@@ -38,50 +42,80 @@ class TransactionRow extends StatelessWidget {
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor
|
||||
),
|
||||
child: Image.asset(
|
||||
direction == TransactionDirection.incoming
|
||||
? 'assets/images/down_arrow.png'
|
||||
: 'assets/images/up_arrow.png'),
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
|
||||
child: Image.asset(direction == TransactionDirection.incoming
|
||||
? 'assets/images/down_arrow.png'
|
||||
: 'assets/images/up_arrow.png'),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor)),
|
||||
Text(formattedAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
|
||||
]),
|
||||
SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(formattedDate,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
|
||||
Text(formattedFiatAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
|
||||
])
|
||||
],
|
||||
)
|
||||
)
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
)),
|
||||
if (isReceivedSilentPayment) TxTag(tag: S.of(context).silent_payment),
|
||||
],
|
||||
),
|
||||
Text(formattedAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
|
||||
]),
|
||||
SizedBox(height: 5),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
|
||||
Text(formattedDate,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
|
||||
Text(formattedFiatAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
|
||||
])
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// A tag to add context to a transaction
|
||||
// example use: differ silent payments from regular txs
|
||||
class TxTag extends StatelessWidget {
|
||||
TxTag({required this.tag});
|
||||
|
||||
final String tag;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 17,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
tag.toLowerCase(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class RescanPage extends BasePage {
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
walletType: _rescanViewModel.wallet.type,
|
||||
bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
|
||||
@@ -68,11 +68,11 @@ class SendPage extends BasePage {
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) {
|
||||
@@ -212,26 +212,25 @@ class SendPage extends BasePage {
|
||||
final count = sendViewModel.outputs.length;
|
||||
|
||||
return count > 1
|
||||
? Semantics (
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change receiver',
|
||||
excludeSemantics: true,
|
||||
child:
|
||||
SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.indicatorDotColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.templateBackgroundColor),
|
||||
))
|
||||
? Semantics(
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change receiver',
|
||||
excludeSemantics: true,
|
||||
child: SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.indicatorDotColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.templateBackgroundColor),
|
||||
))
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
@@ -478,6 +477,7 @@ class SendPage extends BasePage {
|
||||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: sendViewModel.outputs,
|
||||
change: sendViewModel.pendingTransaction!.change,
|
||||
rightButtonText: S.of(_dialogContext).send,
|
||||
leftButtonText: S.of(_dialogContext).cancel,
|
||||
actionRightButton: () async {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -21,6 +22,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
this.change,
|
||||
required this.leftButtonText,
|
||||
required this.rightButtonText,
|
||||
required this.actionLeftButton,
|
||||
@@ -44,6 +46,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final PendingChange? change;
|
||||
final String leftButtonText;
|
||||
final String rightButtonText;
|
||||
final VoidCallback actionLeftButton;
|
||||
@@ -101,6 +104,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs,
|
||||
change: change,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
@@ -117,6 +121,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
this.change,
|
||||
required this.onDispose}) {}
|
||||
|
||||
final String? paymentId;
|
||||
@@ -130,6 +135,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final PendingChange? change;
|
||||
final Function? onDispose;
|
||||
|
||||
@override
|
||||
@@ -145,6 +151,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs,
|
||||
change: change,
|
||||
onDispose: onDispose);
|
||||
}
|
||||
|
||||
@@ -161,6 +168,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
this.change,
|
||||
this.onDispose})
|
||||
: recipientTitle = '' {
|
||||
recipientTitle = outputs.length > 1
|
||||
@@ -179,6 +187,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final PendingChange? change;
|
||||
final Function? onDispose;
|
||||
|
||||
final double backgroundHeight = 160;
|
||||
@@ -391,100 +400,57 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
outputs.length > 1
|
||||
? ListView.builder(
|
||||
padding: EdgeInsets.only(top: 0),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: outputs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = outputs[index];
|
||||
final _address =
|
||||
item.isParsedAddress ? item.extractedAddress : item.address;
|
||||
final _amount = item.cryptoAmount.replaceAll(',', '.');
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.only(top: 0),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: outputs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = outputs[index];
|
||||
final _address =
|
||||
item.isParsedAddress ? item.extractedAddress : item.address;
|
||||
final _amount = item.cryptoAmount.replaceAll(',', '.');
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (item.isParsedAddress)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
item.parsedAddress.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
_address,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
_amount,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
);
|
||||
})
|
||||
: Column(children: [
|
||||
if (outputs.first.isParsedAddress)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
outputs.first.parsedAddress.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
outputs.first.isParsedAddress
|
||||
? outputs.first.extractedAddress
|
||||
: outputs.first.address,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)),
|
||||
])
|
||||
return Column(
|
||||
children: [
|
||||
if (item.isParsedAddress)
|
||||
AddressText(text: item.parsedAddress.name),
|
||||
AddressText(text: _address, fontSize: 10),
|
||||
if (stealthAddressText(item.stealthAddress) != null)
|
||||
AddressText(
|
||||
text: stealthAddressText(item.stealthAddress)!, fontSize: 10),
|
||||
AmountText(text: _amount),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
if (change != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).send_change_to_you,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
AddressText(text: change!.address, fontSize: 10),
|
||||
AmountText(text: change!.amount),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
))),
|
||||
if (showScrollbar)
|
||||
@@ -539,3 +505,78 @@ class ExpirationTimeWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressText extends StatelessWidget {
|
||||
final String text;
|
||||
final double fontSize;
|
||||
final FontWeight fontWeight;
|
||||
final TextAlign? textAlign;
|
||||
|
||||
const AddressText({
|
||||
required this.text,
|
||||
this.fontSize = 14,
|
||||
this.fontWeight = FontWeight.w600,
|
||||
this.textAlign,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AmountText extends StatelessWidget {
|
||||
final String text;
|
||||
final double fontSize;
|
||||
final FontWeight fontWeight;
|
||||
final TextAlign? textAlign;
|
||||
|
||||
const AmountText({
|
||||
required this.text,
|
||||
this.fontSize = 10,
|
||||
this.fontWeight = FontWeight.w600,
|
||||
this.textAlign,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontFamily: 'Lato',
|
||||
color: PaletteDark.pigeonBlue,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
String? stealthAddressText(String? stealthAddress) {
|
||||
if (stealthAddress == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return stealthAddress.isNotEmpty ? "-> $stealthAddress" : null;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ class PrivacyPage extends BasePage {
|
||||
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
|
||||
SettingsSwitcherCell(
|
||||
title: _privacySettingsViewModel.isMoneroWallet
|
||||
? S.current.auto_generate_subaddresses
|
||||
: S.current.auto_generate_addresses,
|
||||
? S.current.auto_generate_subaddresses
|
||||
: S.current.auto_generate_addresses,
|
||||
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);
|
||||
@@ -115,21 +115,21 @@ class PrivacyPage extends BasePage {
|
||||
),
|
||||
if (_privacySettingsViewModel.canUseMempoolFeeAPI)
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.live_fee_rates,
|
||||
title: S.current.enable_mempool_api,
|
||||
value: _privacySettingsViewModel.useMempoolFeeAPI,
|
||||
onValueChange: (BuildContext _, bool isEnabled) async {
|
||||
if (!isEnabled) {
|
||||
final bool confirmation = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).warning,
|
||||
alertContent: S.of(context).disable_fee_api_warning,
|
||||
rightButtonText: S.of(context).confirm,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () => Navigator.of(context).pop(true),
|
||||
actionLeftButton: () => Navigator.of(context).pop(false));
|
||||
}) ??
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).warning,
|
||||
alertContent: S.of(context).disable_fee_api_warning,
|
||||
rightButtonText: S.of(context).confirm,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () => Navigator.of(context).pop(true),
|
||||
actionLeftButton: () => Navigator.of(context).pop(false));
|
||||
}) ??
|
||||
false;
|
||||
if (confirmation) {
|
||||
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
|
||||
|
||||
@@ -20,6 +20,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||
this.isSilentPaymentsScan = false,
|
||||
this.toggleSingleScan,
|
||||
this.doSingleScan = false,
|
||||
this.bitcoinMempoolAPIEnabled,
|
||||
required this.walletType,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -29,6 +30,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||
final bool hasDatePicker;
|
||||
final bool isSilentPaymentsScan;
|
||||
final bool doSingleScan;
|
||||
final Future<bool>? bitcoinMempoolAPIEnabled;
|
||||
final Function()? toggleSingleScan;
|
||||
final WalletType walletType;
|
||||
|
||||
@@ -79,7 +81,8 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
child: BaseTextFormField(
|
||||
focusNode: widget.focusNode,
|
||||
controller: restoreHeightController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||
hintText: widget.isSilentPaymentsScan
|
||||
? S.of(context).silent_payments_scan_from_height
|
||||
: S.of(context).widgets_restore_from_blockheight,
|
||||
@@ -146,7 +149,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
: S.of(context).restore_from_date_or_blockheight,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).hintColor),
|
||||
),
|
||||
)
|
||||
]
|
||||
@@ -166,7 +171,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
if (date != null) {
|
||||
int height;
|
||||
if (widget.isSilentPaymentsScan) {
|
||||
height = bitcoin!.getHeightByDate(date: date);
|
||||
height = await bitcoin!.getHeightByDate(
|
||||
date: date,
|
||||
bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
|
||||
);
|
||||
} else {
|
||||
if (widget.walletType == WalletType.monero) {
|
||||
height = monero!.getHeightByDate(date: date);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -6,12 +7,13 @@ import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
|
||||
part 'transaction_filter_store.g.dart';
|
||||
|
||||
class TransactionFilterStore = TransactionFilterStoreBase
|
||||
with _$TransactionFilterStore;
|
||||
class TransactionFilterStore = TransactionFilterStoreBase with _$TransactionFilterStore;
|
||||
|
||||
abstract class TransactionFilterStoreBase with Store {
|
||||
TransactionFilterStoreBase() : displayIncoming = true,
|
||||
displayOutgoing = true;
|
||||
TransactionFilterStoreBase()
|
||||
: displayIncoming = true,
|
||||
displayOutgoing = true,
|
||||
displaySilentPayments = true;
|
||||
|
||||
@observable
|
||||
bool displayIncoming;
|
||||
@@ -19,6 +21,9 @@ abstract class TransactionFilterStoreBase with Store {
|
||||
@observable
|
||||
bool displayOutgoing;
|
||||
|
||||
@observable
|
||||
bool displaySilentPayments;
|
||||
|
||||
@observable
|
||||
DateTime? startDate;
|
||||
|
||||
@@ -26,31 +31,36 @@ abstract class TransactionFilterStoreBase with Store {
|
||||
DateTime? endDate;
|
||||
|
||||
@computed
|
||||
bool get displayAll => displayIncoming && displayOutgoing;
|
||||
bool get displayAll => displayIncoming && displayOutgoing && displaySilentPayments;
|
||||
|
||||
@action
|
||||
void toggleAll() {
|
||||
if (displayAll) {
|
||||
displayOutgoing = false;
|
||||
displayIncoming = false;
|
||||
displaySilentPayments = false;
|
||||
} else {
|
||||
displayOutgoing = true;
|
||||
displayIncoming = true;
|
||||
displaySilentPayments = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@action
|
||||
void toggleIncoming() {
|
||||
displayIncoming = !displayIncoming;
|
||||
}
|
||||
|
||||
|
||||
@action
|
||||
void toggleOutgoing() {
|
||||
displayOutgoing = !displayOutgoing;
|
||||
}
|
||||
|
||||
@action
|
||||
void toggleSilentPayments() {
|
||||
displaySilentPayments = !displaySilentPayments;
|
||||
}
|
||||
|
||||
@action
|
||||
void changeStartDate(DateTime date) => startDate = date;
|
||||
|
||||
@@ -59,34 +69,33 @@ abstract class TransactionFilterStoreBase with Store {
|
||||
|
||||
List<ActionListItem> filtered({required List<ActionListItem> transactions}) {
|
||||
var _transactions = <ActionListItem>[];
|
||||
final needToFilter = !displayAll ||
|
||||
(startDate != null && endDate != null);
|
||||
final needToFilter = !displayAll || (startDate != null && endDate != null);
|
||||
|
||||
if (needToFilter) {
|
||||
_transactions = transactions.where((item) {
|
||||
var allowed = true;
|
||||
|
||||
if (allowed && startDate != null && endDate != null) {
|
||||
if(item is TransactionListItem){
|
||||
allowed = (startDate?.isBefore(item.transaction.date) ?? false)
|
||||
&& (endDate?.isAfter(item.transaction.date) ?? false);
|
||||
}else if(item is AnonpayTransactionListItem){
|
||||
allowed = (startDate?.isBefore(item.transaction.createdAt) ?? false)
|
||||
&& (endDate?.isAfter(item.transaction.createdAt) ?? false);
|
||||
}
|
||||
if (item is TransactionListItem) {
|
||||
allowed = (startDate?.isBefore(item.transaction.date) ?? false) &&
|
||||
(endDate?.isAfter(item.transaction.date) ?? false);
|
||||
} else if (item is AnonpayTransactionListItem) {
|
||||
allowed = (startDate?.isBefore(item.transaction.createdAt) ?? false) &&
|
||||
(endDate?.isAfter(item.transaction.createdAt) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowed && (!displayAll)) {
|
||||
if(item is TransactionListItem){
|
||||
allowed = (displayOutgoing &&
|
||||
item.transaction.direction ==
|
||||
TransactionDirection.outgoing) ||
|
||||
(displayIncoming &&
|
||||
item.transaction.direction == TransactionDirection.incoming);
|
||||
} else if(item is AnonpayTransactionListItem){
|
||||
if (item is TransactionListItem) {
|
||||
allowed =
|
||||
(displayOutgoing && item.transaction.direction == TransactionDirection.outgoing) ||
|
||||
(displayIncoming &&
|
||||
item.transaction.direction == TransactionDirection.incoming &&
|
||||
!bitcoin!.txIsReceivedSilentPayment(item.transaction)) ||
|
||||
(displaySilentPayments && bitcoin!.txIsReceivedSilentPayment(item.transaction));
|
||||
} else if (item is AnonpayTransactionListItem) {
|
||||
allowed = displayIncoming;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return allowed;
|
||||
@@ -97,4 +106,4 @@ abstract class TransactionFilterStoreBase with Store {
|
||||
|
||||
return _transactions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,11 @@ abstract class DashboardViewModelBase with Store {
|
||||
value: () => transactionFilterStore.displayOutgoing,
|
||||
caption: S.current.outgoing,
|
||||
onChanged: transactionFilterStore.toggleOutgoing),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displaySilentPayments,
|
||||
caption: S.current.silent_payments,
|
||||
onChanged: transactionFilterStore.toggleSilentPayments,
|
||||
),
|
||||
// FilterItem(
|
||||
// value: () => false,
|
||||
// caption: S.current.transactions_by_date,
|
||||
@@ -376,12 +381,15 @@ abstract class DashboardViewModelBase with Store {
|
||||
// to not cause work duplication, this will do the job as well, it will be slightly less precise
|
||||
// about what happened - but still enough.
|
||||
// if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0",
|
||||
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0",
|
||||
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join(""))
|
||||
"private view key is 0",
|
||||
// if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
|
||||
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) "public view key is 0",
|
||||
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join(""))
|
||||
"public view key is 0",
|
||||
// if (wallet.seed == null) "wallet seed is null",
|
||||
// if (wallet.seed == "") "wallet seed is empty",
|
||||
if (monero!.getSubaddressList(wallet).getAll(wallet)[0].address == "41d7FXjswpK1111111111111111111111111111111111111111111111111111111111111111111111111111112KhNi4")
|
||||
if (monero!.getSubaddressList(wallet).getAll(wallet)[0].address ==
|
||||
"41d7FXjswpK1111111111111111111111111111111111111111111111111111111111111111111111111111112KhNi4")
|
||||
"primary address is invalid, you won't be able to receive / spend funds",
|
||||
];
|
||||
return errors;
|
||||
|
||||
@@ -29,6 +29,10 @@ abstract class RescanViewModelBase with Store {
|
||||
@computed
|
||||
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
Future<bool> get isBitcoinMempoolAPIEnabled async =>
|
||||
wallet.type == WalletType.bitcoin && await bitcoin!.checkIfMempoolAPIIsEnabled(wallet);
|
||||
|
||||
@action
|
||||
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
||||
state = RescanWalletState.rescaning;
|
||||
|
||||
@@ -79,6 +79,9 @@ abstract class OutputBase with Store {
|
||||
bool get isParsedAddress =>
|
||||
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
||||
|
||||
@observable
|
||||
String? stealthAddress;
|
||||
|
||||
@computed
|
||||
int get formattedCryptoAmount {
|
||||
int amount = 0;
|
||||
@@ -134,9 +137,8 @@ abstract class OutputBase with Store {
|
||||
final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? 0;
|
||||
return double.parse(trc20EstimatedFee.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (_wallet.type == WalletType.solana) {
|
||||
return solana!.getEstimateFees(_wallet) ?? 0.0;
|
||||
}
|
||||
@@ -145,16 +147,16 @@ abstract class OutputBase with Store {
|
||||
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
||||
fee = bitcoin!.getEstimatedFeeWithFeeRate(_wallet,
|
||||
_settingsStore.customBitcoinFeeRate,formattedCryptoAmount);
|
||||
if (_settingsStore.priority[_wallet.type] ==
|
||||
bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
||||
fee = bitcoin!.getEstimatedFeeWithFeeRate(
|
||||
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
|
||||
}
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash) {
|
||||
if (_wallet.type == WalletType.litecoin || _wallet.type == WalletType.bitcoinCash) {
|
||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||
}
|
||||
|
||||
@@ -249,7 +251,8 @@ abstract class OutputBase with Store {
|
||||
try {
|
||||
final fiat = calculateFiatAmount(
|
||||
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
|
||||
cryptoAmount: sendAll ? cryptoFullBalance.replaceAll(",", ".") : cryptoAmount.replaceAll(',', '.'));
|
||||
cryptoAmount:
|
||||
sendAll ? cryptoFullBalance.replaceAll(",", ".") : cryptoAmount.replaceAll(',', '.'));
|
||||
if (fiatAmount != fiat) {
|
||||
fiatAmount = fiat;
|
||||
}
|
||||
|
||||
@@ -375,6 +375,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
throw Exception("THORChain does not support Taproot addresses");
|
||||
}
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
final updatedOutputs = bitcoin!.updateOutputs(pendingTransaction!, outputs);
|
||||
|
||||
if (outputs.length == updatedOutputs.length) {
|
||||
outputs = ObservableList.of(updatedOutputs);
|
||||
}
|
||||
}
|
||||
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
@@ -414,8 +423,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
|
||||
Future<void> _executeReplaceByFee(TransactionInfo tx, String newFee) async {
|
||||
|
||||
|
||||
clearOutputs();
|
||||
final output = outputs.first;
|
||||
output.address = tx.outputAddresses?.first ?? '';
|
||||
|
||||
Reference in New Issue
Block a user