CW-997 coin control enhancements / send ALL fixes ()

* computed sending balance value

* files that didnt get added before

* monero + move unspent calc to view model

* working

* remove old code

* Update lib/view_model/send/send_view_model.dart

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Matthew Fosse 2025-03-30 07:52:46 -07:00 committed by GitHub
parent 7831b421b1
commit 40a0989956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 106 additions and 12 deletions

View file

@ -65,6 +65,6 @@ class ElectrumBalance extends Balance {
'unconfirmed': unconfirmed,
'frozen': frozen,
'secondConfirmed': secondConfirmed,
'secondUnconfirmed': secondUnconfirmed
'secondUnconfirmed': secondUnconfirmed,
});
}

View file

@ -4,6 +4,8 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
@ -2240,10 +2242,11 @@ abstract class ElectrumWalletBase
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
if (info.isFrozen) {
totalFrozen += element.value;
}
}
});
});
@ -2499,6 +2502,12 @@ abstract class ElectrumWalletBase
transactionHistory.addOne(tx);
}
}
@override
String formatCryptoAmount(String amount) {
final amountInt = int.parse(amount);
return bitcoinAmountToString(amount: amountInt);
}
}
class ScanNode {

View file

@ -36,6 +36,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
ObservableMap<CryptoCurrency, BalanceType> get balance;
String formatCryptoAmount(String amount) => amount;
SyncStatus get syncStatus;
set syncStatus(SyncStatus status);

View file

@ -3,6 +3,7 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:cw_core/monero_amount_format.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/account.dart';
@ -905,4 +906,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
enableLedgerExchange(dummyWPtr, connection);
}
@override
String formatCryptoAmount(String amount) {
return moneroAmountToString(amount: int.parse(amount));
}
}

View file

@ -770,4 +770,9 @@ abstract class WowneroWalletBase
return wownero_wallet.verifyMessage(message, address, signature);
}
@override
String formatCryptoAmount(String amount) {
return wowneroAmountToString(amount: int.parse(amount));
}
}

View file

@ -244,7 +244,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
currencyValueValidator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
allAmountCallback: () async => output.setSendAll(sendViewModel.sendingBalance)),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
@ -266,7 +266,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
),
),
Text(
sendViewModel.balance,
sendViewModel.sendingBalance,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -384,10 +384,16 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
),
onTap: () async {
await Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
);
if (mounted) {
// we just got back from the unspent coins list screen, so we need to recompute the sending balance:
sendViewModel.updateSendingBalance();
}
},
child: Container(
color: Colors.transparent,
child: Row(
@ -505,7 +511,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
reaction((_) => sendViewModel.selectedCryptoCurrency, (Currency currency) {
if (output.sendAll) {
output.setSendAll(sendViewModel.balance);
output.setSendAll(sendViewModel.sendingBalance);
}
output.setCryptoAmount(cryptoAmountController.text);

View file

@ -225,6 +225,42 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
}
@action
Future<void> updateSendingBalance() async {
// force the sendingBalance to recompute since unspent coins aren't observable
// or at least mobx can't detect the changes
final currentType = coinTypeToSpendFrom;
if (currentType == UnspentCoinType.any) {
coinTypeToSpendFrom = UnspentCoinType.nonMweb;
} else if (currentType == UnspentCoinType.nonMweb) {
coinTypeToSpendFrom = UnspentCoinType.any;
} else if (currentType == UnspentCoinType.mweb) {
coinTypeToSpendFrom = UnspentCoinType.nonMweb;
}
// set it back to the original value:
coinTypeToSpendFrom = currentType;
}
@computed
String get sendingBalance {
// only for electrum, monero, wownero, decred wallets atm:
switch (wallet.type) {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.monero:
case WalletType.wownero:
case WalletType.decred:
return wallet.formatCryptoAmount(
unspentCoinsListViewModel.getSendingBalance(coinTypeToSpendFrom).toString());
default:
return balance;
}
}
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@ -502,14 +538,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return bitcoin!.createBitcoinTransactionCredentials(
outputs,
priority: priority!,
feeRate:feesViewModel. customBitcoinFeeRate,
feeRate: feesViewModel.customBitcoinFeeRate,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
case WalletType.litecoin:
return bitcoin!.createBitcoinTransactionCredentials(
outputs,
priority: priority!,
feeRate:feesViewModel. customBitcoinFeeRate,
feeRate: feesViewModel.customBitcoinFeeRate,
// if it's an exchange flow then disable sending from mweb coins
coinTypeToSpendFrom: provider != null ? UnspentCoinType.nonMweb : coinTypeToSpendFrom,
);

View file

@ -131,6 +131,36 @@ abstract class UnspentCoinsListViewModelBase with Store {
}
}
List<Unspent> _getSpecificUnspents(UnspentCoinType overrideCoinTypeToSpendFrom) {
switch (wallet.type) {
case WalletType.monero:
return monero!.getUnspents(wallet);
case WalletType.wownero:
return wownero!.getUnspents(wallet);
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: overrideCoinTypeToSpendFrom);
case WalletType.decred:
return decred!.getUnspents(wallet);
default:
return List.empty();
}
}
@action
int getSendingBalance(UnspentCoinType overrideCoinTypeToSpendFrom) {
// return items.where((element) => element.isSending).fold(0, (previousValue, element) => previousValue + element.value);
// go through all unspent coins and add up the value minus frozen and non sending:
int total = 0;
for (final item in _getSpecificUnspents(overrideCoinTypeToSpendFrom)) {
if (item.isFrozen || !item.isSending) continue;
total += item.value;
}
return total;
}
@action
void _updateUnspentCoinsInfo() {
items.clear();