CAKE-345 | added monero_output.dart to the app; fixed transaction_history.dart; renamed SendItem on Output; calculated formattedCryptoAmount in the output.dart; used outputs list instead sendItemList; fixed bitcoin_transaction_credentials.dart, electrum_wallet.dart, monero_transaction_creation_credentials.dart, monero_wallet.dart, exchange and send pages, view models

This commit is contained in:
OleksandrSobol 2021-08-10 17:52:35 +03:00
parent 1dd3f69b1c
commit 1e3ec8da1c
15 changed files with 191 additions and 188 deletions

View file

@ -0,0 +1,8 @@
import 'package:flutter/foundation.dart';
class MoneroOutput {
MoneroOutput({@required this.address, @required this.amount});
final String address;
final String amount;
}

View file

@ -1,5 +1,6 @@
import 'dart:ffi'; import 'dart:ffi';
import 'package:cw_monero/convert_utf8_to_string.dart'; import 'package:cw_monero/convert_utf8_to_string.dart';
import 'package:cw_monero/monero_output.dart';
import 'package:cw_monero/structs/ut8_box.dart'; import 'package:cw_monero/structs/ut8_box.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -107,21 +108,21 @@ PendingTransactionDescription createTransactionSync(
} }
PendingTransactionDescription createTransactionMultDestSync( PendingTransactionDescription createTransactionMultDestSync(
{List<String> addresses, {List<MoneroOutput> outputs,
String paymentId, String paymentId,
List<String> amounts,
int size,
int priorityRaw, int priorityRaw,
int accountIndex = 0}) { int accountIndex = 0}) {
final List<Pointer<Utf8>> addressesPointers = addresses.map(Utf8.toUtf8).toList(); final int size = outputs.length;
final List<Pointer<Utf8>> addressesPointers = outputs.map((output) =>
Utf8.toUtf8(output.address)).toList();
final Pointer<Pointer<Utf8>> addressesPointerPointer = allocate(count: size); final Pointer<Pointer<Utf8>> addressesPointerPointer = allocate(count: size);
final List<Pointer<Utf8>> amountsPointers = outputs.map((output) =>
final List<Pointer<Utf8>> amountsPointers = amounts.map(Utf8.toUtf8).toList(); Utf8.toUtf8(output.amount)).toList();
final Pointer<Pointer<Utf8>> amountsPointerPointer = allocate(count: size); final Pointer<Pointer<Utf8>> amountsPointerPointer = allocate(count: size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
addressesPointerPointer[ i ] = addressesPointers[ i ]; addressesPointerPointer[i] = addressesPointers[i];
amountsPointerPointer[ i ] = amountsPointers[ i ]; amountsPointerPointer[i] = amountsPointers[i];
} }
final paymentIdPointer = Utf8.toUtf8(paymentId); final paymentIdPointer = Utf8.toUtf8(paymentId);
@ -190,18 +191,14 @@ PendingTransactionDescription _createTransactionSync(Map args) {
} }
PendingTransactionDescription _createTransactionMultDestSync(Map args) { PendingTransactionDescription _createTransactionMultDestSync(Map args) {
final addresses = args['addresses'] as List<String>; final outputs = args['outputs'] as List<MoneroOutput>;
final paymentId = args['paymentId'] as String; final paymentId = args['paymentId'] as String;
final amounts = args['amounts'] as List<String>;
final size = args['size'] as int;
final priorityRaw = args['priorityRaw'] as int; final priorityRaw = args['priorityRaw'] as int;
final accountIndex = args['accountIndex'] as int; final accountIndex = args['accountIndex'] as int;
return createTransactionMultDestSync( return createTransactionMultDestSync(
addresses: addresses, outputs: outputs,
paymentId: paymentId, paymentId: paymentId,
amounts: amounts,
size: size,
priorityRaw: priorityRaw, priorityRaw: priorityRaw,
accountIndex: accountIndex); accountIndex: accountIndex);
} }
@ -221,17 +218,13 @@ Future<PendingTransactionDescription> createTransaction(
}); });
Future<PendingTransactionDescription> createTransactionMultDest( Future<PendingTransactionDescription> createTransactionMultDest(
{List<String> addresses, {List<MoneroOutput> outputs,
String paymentId, String paymentId,
List<String> amounts,
int size,
int priorityRaw, int priorityRaw,
int accountIndex = 0}) => int accountIndex = 0}) =>
compute(_createTransactionMultDestSync, { compute(_createTransactionMultDestSync, {
'addresses': addresses, 'outputs': outputs,
'paymentId': paymentId, 'paymentId': paymentId,
'amounts': amounts,
'size': size,
'priorityRaw': priorityRaw, 'priorityRaw': priorityRaw,
'accountIndex': accountIndex 'accountIndex': accountIndex
}); });

View file

@ -1,9 +1,9 @@
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
class BitcoinTransactionCredentials { class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.sendItemList, this.priority); BitcoinTransactionCredentials(this.outputs, this.priority);
final List<SendItem> sendItemList; final List<Output> outputs;
BitcoinTransactionPriority priority; BitcoinTransactionPriority priority;
} }

View file

@ -158,7 +158,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
const minAmount = 546; const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials; final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[]; final inputs = <BitcoinUnspent>[];
final sendItemList = transactionCredentials.sendItemList; final outputs = transactionCredentials.outputs;
final hasMultiDestination = outputs.length > 1;
var allInputsAmount = 0; var allInputsAmount = 0;
if (unspentCoins.isEmpty) { if (unspentCoins.isEmpty) {
@ -177,61 +178,54 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
final allAmountFee = feeAmountForPriority( final allAmountFee = feeAmountForPriority(
transactionCredentials.priority, inputs.length, sendItemList.length); transactionCredentials.priority, inputs.length, outputs.length);
final allAmount = allInputsAmount - allAmountFee; final allAmount = allInputsAmount - allAmountFee;
var credentialsAmount = 0; var credentialsAmount = 0;
var amount = 0; var amount = 0;
var fee = 0; var fee = 0;
if (sendItemList.length > 1) { if (hasMultiDestination) {
final sendAllItems = sendItemList.where((item) => item.sendAll).toList(); final sendAllItems = outputs.where((item) => item.sendAll).toList();
if (sendAllItems?.isNotEmpty ?? false) { if (sendAllItems?.isNotEmpty ?? false) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
final nullAmountItems = sendItemList.where((item) => final nullAmountItems = outputs.where((item) =>
stringDoubleToBitcoinAmount(item.cryptoAmount.replaceAll(',', '.')) <= 0) item.formattedCryptoAmount <= 0).toList();
.toList();
if (nullAmountItems?.isNotEmpty ?? false) { if (nullAmountItems?.isNotEmpty ?? false) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
credentialsAmount = sendItemList.fold(0, (previousValue, element) => credentialsAmount = outputs.fold(0, (acc, value) =>
previousValue + stringDoubleToBitcoinAmount( acc + value.formattedCryptoAmount);
element.cryptoAmount.replaceAll(',', '.')));
if (credentialsAmount > allAmount) { if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
amount = allAmount - credentialsAmount < minAmount amount = credentialsAmount;
? allAmount
: credentialsAmount;
fee = amount == allAmount fee = calculateEstimatedFee(transactionCredentials.priority, amount,
? allAmountFee outputsCount: outputs.length + 1);
: calculateEstimatedFee(transactionCredentials.priority, amount,
outputsCount: sendItemList.length + 1);
} else { } else {
final sendItem = sendItemList.first; final output = outputs.first;
credentialsAmount = !sendItem.sendAll credentialsAmount = !output.sendAll
? stringDoubleToBitcoinAmount( ? output.formattedCryptoAmount
sendItem.cryptoAmount.replaceAll(',', '.'))
: 0; : 0;
if (credentialsAmount > allAmount) { if (credentialsAmount > allAmount) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
amount = sendItem.sendAll || allAmount - credentialsAmount < minAmount amount = output.sendAll || allAmount - credentialsAmount < minAmount
? allAmount ? allAmount
: credentialsAmount; : credentialsAmount;
fee = sendItem.sendAll || amount == allAmount fee = output.sendAll || amount == allAmount
? allAmountFee ? allAmountFee
: calculateEstimatedFee(transactionCredentials.priority, amount); : calculateEstimatedFee(transactionCredentials.priority, amount);
} }
@ -289,18 +283,18 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
}); });
sendItemList.forEach((item) { outputs.forEach((item) {
final _amount = item.sendAll final outputAmount = hasMultiDestination
? amount ? item.formattedCryptoAmount
: stringDoubleToBitcoinAmount(item.cryptoAmount.replaceAll(',', '.')); : amount;
txb.addOutput( txb.addOutput(
addressToOutputScript(item.address, networkType), addressToOutputScript(item.address, networkType),
_amount); outputAmount);
}); });
final estimatedSize = final estimatedSize =
estimatedTransactionSize(inputs.length, sendItemList.length + 1); estimatedTransactionSize(inputs.length, outputs.length + 1);
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize; final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
final changeValue = totalInputAmount - amount - feeAmount; final changeValue = totalInputAmount - amount - feeAmount;

View file

@ -1,11 +1,11 @@
import 'package:cake_wallet/entities/transaction_creation_credentials.dart'; import 'package:cake_wallet/entities/transaction_creation_credentials.dart';
import 'package:cake_wallet/entities/monero_transaction_priority.dart'; import 'package:cake_wallet/entities/monero_transaction_priority.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
class MoneroTransactionCreationCredentials class MoneroTransactionCreationCredentials
extends TransactionCreationCredentials { extends TransactionCreationCredentials {
MoneroTransactionCreationCredentials({this.sendItemList, this.priority}); MoneroTransactionCreationCredentials({this.outputs, this.priority});
final List<SendItem> sendItemList; final List<Output> outputs;
final MoneroTransactionPriority priority; final MoneroTransactionPriority priority;
} }

View file

@ -13,6 +13,7 @@ import 'package:cw_monero/transaction_history.dart'
import 'package:cw_monero/wallet.dart'; import 'package:cw_monero/wallet.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet; import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'package:cw_monero/transaction_history.dart' as transaction_history; import 'package:cw_monero/transaction_history.dart' as transaction_history;
import 'package:cw_monero/monero_output.dart';
import 'package:cake_wallet/monero/monero_transaction_creation_credentials.dart'; import 'package:cake_wallet/monero/monero_transaction_creation_credentials.dart';
import 'package:cake_wallet/monero/pending_monero_transaction.dart'; import 'package:cake_wallet/monero/pending_monero_transaction.dart';
import 'package:cake_wallet/monero/monero_wallet_keys.dart'; import 'package:cake_wallet/monero/monero_wallet_keys.dart';
@ -150,8 +151,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override @override
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials; final _credentials = credentials as MoneroTransactionCreationCredentials;
final sendItemList = _credentials.sendItemList; final outputs = _credentials.outputs;
final listSize = sendItemList.length; final hasMultiDestination = outputs.length > 1;
final unlockedBalance = final unlockedBalance =
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
@ -161,16 +162,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
throw MoneroTransactionCreationException('The wallet is not synced.'); throw MoneroTransactionCreationException('The wallet is not synced.');
} }
if (listSize > 1) { if (hasMultiDestination) {
final sendAllItems = sendItemList.where((item) => item.sendAll).toList(); final sendAllItems = outputs.where((item) => item.sendAll).toList();
if (sendAllItems?.isNotEmpty ?? false) { if (sendAllItems?.isNotEmpty ?? false) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
} }
final nullAmountItems = sendItemList.where((item) => final nullAmountItems = outputs.where((item) =>
moneroParseAmount(amount: item.cryptoAmount.replaceAll(',', '.')) <= 0) item.formattedCryptoAmount <= 0).toList();
.toList();
if (nullAmountItems?.isNotEmpty ?? false) { if (nullAmountItems?.isNotEmpty ?? false) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
@ -178,42 +178,41 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
var credentialsAmount = 0; var credentialsAmount = 0;
credentialsAmount = sendItemList.fold(0, (previousValue, element) => credentialsAmount = outputs.fold(0, (acc, value) =>
previousValue + moneroParseAmount( acc + value.formattedCryptoAmount);
amount: element.cryptoAmount.replaceAll(',', '.')));
if (unlockedBalance < credentialsAmount) { if (unlockedBalance < credentialsAmount) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.'); throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
} }
final addresses = sendItemList.map((e) => e.address).toList(); final moneroOutputs = outputs.map((output) =>
final amounts = sendItemList.map((e) => MoneroOutput(
e.cryptoAmount.replaceAll(',', '.')).toList(); address: output.address,
amount: output.cryptoAmount.replaceAll(',', '.')))
.toList();
pendingTransactionDescription = pendingTransactionDescription =
await transaction_history.createTransactionMultDest( await transaction_history.createTransactionMultDest(
addresses: addresses, outputs: moneroOutputs,
paymentId: '', paymentId: '',
amounts: amounts,
size: listSize,
priorityRaw: _credentials.priority.serialize(), priorityRaw: _credentials.priority.serialize(),
accountIndex: walletAddresses.account.id); accountIndex: walletAddresses.account.id);
} else { } else {
final item = sendItemList.first; final output = outputs.first;
final address = item.address; final address = output.address;
final amount = item.sendAll final amount = output.sendAll
? null ? null
: item.cryptoAmount.replaceAll(',', '.'); : output.cryptoAmount.replaceAll(',', '.');
final formattedAmount = item.sendAll final formattedAmount = output.sendAll
? null ? null
: moneroParseAmount(amount: amount); : output.formattedCryptoAmount;
if ((formattedAmount != null && unlockedBalance < formattedAmount) || if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
(formattedAmount == null && unlockedBalance <= 0)) { (formattedAmount == null && unlockedBalance <= 0)) {
final formattedBalance = moneroAmountToString(amount: unlockedBalance); final formattedBalance = moneroAmountToString(amount: unlockedBalance);
throw MoneroTransactionCreationException( throw MoneroTransactionCreationException(
'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${item.cryptoAmount}.'); 'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
} }
pendingTransactionDescription = pendingTransactionDescription =

View file

@ -385,8 +385,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
.pendingTransactionFiatAmount + .pendingTransactionFiatAmount +
' ' + ' ' +
widget.exchangeTradeViewModel.sendViewModel.fiat.title, widget.exchangeTradeViewModel.sendViewModel.fiat.title,
sendItemList: widget.exchangeTradeViewModel.sendViewModel outputs: widget.exchangeTradeViewModel.sendViewModel
.sendItemList); .outputs);
}); });
}); });
} }

View file

@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_a
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -55,16 +55,16 @@ class SendPage extends BasePage {
onPressed: () { onPressed: () {
var pageToJump = controller.page.round() - 1; var pageToJump = controller.page.round() - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0; pageToJump = pageToJump > 0 ? pageToJump : 0;
final item = _defineCurrentSendItem(); final output = _defineCurrentOutput();
sendViewModel.removeSendItem(item); sendViewModel.removeOutput(output);
controller.jumpToPage(pageToJump); controller.jumpToPage(pageToJump);
}) })
: TrailButton( : TrailButton(
caption: S.of(context).clear, caption: S.of(context).clear,
onPressed: () { onPressed: () {
final item = _defineCurrentSendItem(); final output = _defineCurrentOutput();
_formKey.currentState.reset(); _formKey.currentState.reset();
item.reset(); output.reset();
}); });
}); });
@ -85,13 +85,13 @@ class SendPage extends BasePage {
return PageView.builder( return PageView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: controller, controller: controller,
itemCount: sendViewModel.sendItemList.length, itemCount: sendViewModel.outputs.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = sendViewModel.sendItemList[index]; final output = sendViewModel.outputs[index];
return SendCard( return SendCard(
key: item.key, key: output.key,
item: item, output: output,
sendViewModel: sendViewModel, sendViewModel: sendViewModel,
); );
} }
@ -104,7 +104,7 @@ class SendPage extends BasePage {
child: Container( child: Container(
height: 10, height: 10,
child: Observer(builder: (_) { child: Observer(builder: (_) {
final count = sendViewModel.sendItemList.length; final count = sendViewModel.outputs.length;
return count > 1 return count > 1
? SmoothPageIndicator( ? SmoothPageIndicator(
@ -211,11 +211,11 @@ class SendPage extends BasePage {
amount: template.amount, amount: template.amount,
from: template.cryptoCurrency, from: template.cryptoCurrency,
onTap: () async { onTap: () async {
final item = _defineCurrentSendItem(); final output = _defineCurrentOutput();
item.address = output.address =
template.address; template.address;
item.setCryptoAmount(template.amount); output.setCryptoAmount(template.amount);
final parsedAddress = await item final parsedAddress = await output
.applyOpenaliasOrUnstoppableDomains(); .applyOpenaliasOrUnstoppableDomains();
showAddressAlert(context, parsedAddress); showAddressAlert(context, parsedAddress);
}, },
@ -263,7 +263,7 @@ class SendPage extends BasePage {
padding: EdgeInsets.only(bottom: 12), padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton( child: PrimaryButton(
onPressed: () { onPressed: () {
sendViewModel.addSendItem(); sendViewModel.addOutput();
}, },
text: S.of(context).add_receiver, text: S.of(context).add_receiver,
color: Colors.green, color: Colors.green,
@ -274,16 +274,16 @@ class SendPage extends BasePage {
return LoadingPrimaryButton( return LoadingPrimaryButton(
onPressed: () async { onPressed: () async {
if (!_formKey.currentState.validate()) { if (!_formKey.currentState.validate()) {
if (sendViewModel.sendItemList.length > 1) { if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context); showErrorValidationAlert(context);
} }
return; return;
} }
final notValidItems = sendViewModel.sendItemList final notValidItems = sendViewModel.outputs
.where((item) => .where((item) =>
item.address.isEmpty || item.cryptoAmount.isEmpty) item.address.isEmpty || item.cryptoAmount.isEmpty)
.toList(); .toList();
if (notValidItems?.isNotEmpty ?? false) { if (notValidItems?.isNotEmpty ?? false) {
@ -343,7 +343,7 @@ class SendPage extends BasePage {
feeValue: sendViewModel.pendingTransaction.feeFormatted, feeValue: sendViewModel.pendingTransaction.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
+ ' ' + sendViewModel.fiat.title, + ' ' + sendViewModel.fiat.title,
sendItemList: sendViewModel.sendItemList, outputs: sendViewModel.outputs,
rightButtonText: S.of(context).ok, rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
actionRightButton: () { actionRightButton: () {
@ -381,7 +381,7 @@ class SendPage extends BasePage {
if (state is TransactionCommitted) { if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
sendViewModel.clearSendItemList(); sendViewModel.clearOutputs();
}); });
} }
}); });
@ -389,9 +389,9 @@ class SendPage extends BasePage {
_effectsInstalled = true; _effectsInstalled = true;
} }
SendItem _defineCurrentSendItem() { Output _defineCurrentOutput() {
final itemCount = controller.page.round(); final itemCount = controller.page.round();
return sendViewModel.sendItemList[itemCount]; return sendViewModel.outputs[itemCount];
} }
void showErrorValidationAlert(BuildContext context) async { void showErrorValidationAlert(BuildContext context) async {

View file

@ -14,7 +14,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class SendTemplatePage extends BasePage { class SendTemplatePage extends BasePage {
SendTemplatePage({@required this.sendTemplateViewModel}) { SendTemplatePage({@required this.sendTemplateViewModel}) {
sendTemplateViewModel.sendItem.reset(); sendTemplateViewModel.output.reset();
} }
final SendTemplateViewModel sendTemplateViewModel; final SendTemplateViewModel sendTemplateViewModel;
@ -258,21 +258,21 @@ class SendTemplatePage extends BasePage {
return; return;
} }
final item = sendTemplateViewModel.sendItem; final output = sendTemplateViewModel.output;
reaction((_) => item.fiatAmount, (String amount) { reaction((_) => output.fiatAmount, (String amount) {
if (amount != _fiatAmountController.text) { if (amount != _fiatAmountController.text) {
_fiatAmountController.text = amount; _fiatAmountController.text = amount;
} }
}); });
reaction((_) => item.cryptoAmount, (String amount) { reaction((_) => output.cryptoAmount, (String amount) {
if (amount != _cryptoAmountController.text) { if (amount != _cryptoAmountController.text) {
_cryptoAmountController.text = amount; _cryptoAmountController.text = amount;
} }
}); });
reaction((_) => item.address, (String address) { reaction((_) => output.address, (String address) {
if (address != _addressController.text) { if (address != _addressController.text) {
_addressController.text = address; _addressController.text = address;
} }
@ -281,24 +281,24 @@ class SendTemplatePage extends BasePage {
_cryptoAmountController.addListener(() { _cryptoAmountController.addListener(() {
final amount = _cryptoAmountController.text; final amount = _cryptoAmountController.text;
if (amount != item.cryptoAmount) { if (amount != output.cryptoAmount) {
item.setCryptoAmount(amount); output.setCryptoAmount(amount);
} }
}); });
_fiatAmountController.addListener(() { _fiatAmountController.addListener(() {
final amount = _fiatAmountController.text; final amount = _fiatAmountController.text;
if (amount != item.fiatAmount) { if (amount != output.fiatAmount) {
item.setFiatAmount(amount); output.setFiatAmount(amount);
} }
}); });
_addressController.addListener(() { _addressController.addListener(() {
final address = _addressController.text; final address = _addressController.text;
if (item.address != address) { if (output.address != address) {
item.address = address; output.address = address;
} }
}); });

View file

@ -1,5 +1,5 @@
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart'; import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -13,14 +13,14 @@ class ConfirmSendingAlert extends BaseAlertDialog {
@required this.fee, @required this.fee,
@required this.feeValue, @required this.feeValue,
@required this.feeFiatAmount, @required this.feeFiatAmount,
@required this.sendItemList, @required this.outputs,
@required this.leftButtonText, @required this.leftButtonText,
@required this.rightButtonText, @required this.rightButtonText,
@required this.actionLeftButton, @required this.actionLeftButton,
@required this.actionRightButton, @required this.actionRightButton,
this.alertBarrierDismissible = true this.alertBarrierDismissible = true
}) { }) {
itemCount = sendItemList.length; itemCount = outputs.length;
recipientTitle = itemCount > 1 recipientTitle = itemCount > 1
? S.current.transaction_details_recipient_address ? S.current.transaction_details_recipient_address
: S.current.recipient_address; : S.current.recipient_address;
@ -33,7 +33,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
final String fee; final String fee;
final String feeValue; final String feeValue;
final String feeFiatAmount; final String feeFiatAmount;
final List<SendItem> sendItemList; final List<Output> outputs;
final String leftButtonText; final String leftButtonText;
final String rightButtonText; final String rightButtonText;
final VoidCallback actionLeftButton; final VoidCallback actionLeftButton;
@ -179,10 +179,10 @@ class ConfirmSendingAlert extends BaseAlertDialog {
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
itemCount: itemCount, itemCount: itemCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = sendItemList[index]; final item = outputs[index];
final _address = item.address; final _address = item.address;
final _amount = final _amount =
item.cryptoAmount.replaceAll(',', '.'); item.cryptoAmount.replaceAll(',', '.');
return Column( return Column(
children: [ children: [
@ -225,7 +225,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
: Padding( : Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: Text( child: Text(
sendItemList.first.address, outputs.first.address,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart'; import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -19,21 +19,21 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class SendCard extends StatefulWidget { class SendCard extends StatefulWidget {
SendCard({Key key, @required this.item, @required this.sendViewModel}) : super(key: key); SendCard({Key key, @required this.output, @required this.sendViewModel}) : super(key: key);
final SendItem item; final Output output;
final SendViewModel sendViewModel; final SendViewModel sendViewModel;
@override @override
SendCardState createState() => SendCardState( SendCardState createState() => SendCardState(
item: item, output: output,
sendViewModel: sendViewModel sendViewModel: sendViewModel
); );
} }
class SendCardState extends State<SendCard> class SendCardState extends State<SendCard>
with AutomaticKeepAliveClientMixin<SendCard> { with AutomaticKeepAliveClientMixin<SendCard> {
SendCardState({@required this.item, @required this.sendViewModel}) SendCardState({@required this.output, @required this.sendViewModel})
: addressController = TextEditingController(), : addressController = TextEditingController(),
cryptoAmountController = TextEditingController(), cryptoAmountController = TextEditingController(),
fiatAmountController = TextEditingController(), fiatAmountController = TextEditingController(),
@ -45,7 +45,7 @@ class SendCardState extends State<SendCard>
static const prefixIconWidth = 34.0; static const prefixIconWidth = 34.0;
static const prefixIconHeight = 34.0; static const prefixIconHeight = 34.0;
final SendItem item; final Output output;
final SendViewModel sendViewModel; final SendViewModel sendViewModel;
final TextEditingController addressController; final TextEditingController addressController;
@ -143,7 +143,7 @@ class SendCardState extends State<SendCard>
.decorationColor), .decorationColor),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
final parsedAddress = final parsedAddress =
await item.applyOpenaliasOrUnstoppableDomains(); await output.applyOpenaliasOrUnstoppableDomains();
showAddressAlert(context, parsedAddress); showAddressAlert(context, parsedAddress);
}, },
validator: sendViewModel.addressValidator, validator: sendViewModel.addressValidator,
@ -189,7 +189,7 @@ class SendCardState extends State<SendCard>
.decorationColor, .decorationColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14), fontSize: 14),
validator: item.sendAll validator: output.sendAll
? sendViewModel.allAmountValidator ? sendViewModel.allAmountValidator
: sendViewModel : sendViewModel
.amountValidator), .amountValidator),
@ -201,7 +201,7 @@ class SendCardState extends State<SendCard>
height: prefixIconHeight, height: prefixIconHeight,
child: InkWell( child: InkWell(
onTap: () async => onTap: () async =>
item.setSendAll(), output.setSendAll(),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context) color: Theme.of(context)
@ -349,7 +349,7 @@ class SendCardState extends State<SendCard>
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(
item output
.estimatedFee .estimatedFee
.toString() + .toString() +
' ' + ' ' +
@ -366,7 +366,7 @@ class SendCardState extends State<SendCard>
padding: padding:
EdgeInsets.only(top: 5), EdgeInsets.only(top: 5),
child: Text( child: Text(
item output
.estimatedFeeFiatAmount .estimatedFeeFiatAmount
+ ' ' + + ' ' +
sendViewModel sendViewModel
@ -435,10 +435,10 @@ class SendCardState extends State<SendCard>
} }
void _setEffects(BuildContext context) { void _setEffects(BuildContext context) {
addressController.text = item.address; addressController.text = output.address;
cryptoAmountController.text = item.cryptoAmount; cryptoAmountController.text = output.cryptoAmount;
fiatAmountController.text = item.fiatAmount; fiatAmountController.text = output.fiatAmount;
noteController.text = item.note; noteController.text = output.note;
if (_effectsInstalled) { if (_effectsInstalled) {
return; return;
@ -447,48 +447,48 @@ class SendCardState extends State<SendCard>
cryptoAmountController.addListener(() { cryptoAmountController.addListener(() {
final amount = cryptoAmountController.text; final amount = cryptoAmountController.text;
if (item.sendAll && amount != S.current.all) { if (output.sendAll && amount != S.current.all) {
item.sendAll = false; output.sendAll = false;
} }
if (amount != item.cryptoAmount) { if (amount != output.cryptoAmount) {
item.setCryptoAmount(amount); output.setCryptoAmount(amount);
} }
}); });
fiatAmountController.addListener(() { fiatAmountController.addListener(() {
final amount = fiatAmountController.text; final amount = fiatAmountController.text;
if (amount != item.fiatAmount) { if (amount != output.fiatAmount) {
item.sendAll = false; output.sendAll = false;
item.setFiatAmount(amount); output.setFiatAmount(amount);
} }
}); });
noteController.addListener(() { noteController.addListener(() {
final note = noteController.text ?? ''; final note = noteController.text ?? '';
if (note != item.note) { if (note != output.note) {
item.note = note; output.note = note;
} }
}); });
reaction((_) => item.sendAll, (bool all) { reaction((_) => output.sendAll, (bool all) {
if (all) { if (all) {
cryptoAmountController.text = S.current.all; cryptoAmountController.text = S.current.all;
fiatAmountController.text = null; fiatAmountController.text = null;
} }
}); });
reaction((_) => item.fiatAmount, (String amount) { reaction((_) => output.fiatAmount, (String amount) {
if (amount != fiatAmountController.text) { if (amount != fiatAmountController.text) {
fiatAmountController.text = amount; fiatAmountController.text = amount;
} }
}); });
reaction((_) => item.cryptoAmount, (String amount) { reaction((_) => output.cryptoAmount, (String amount) {
if (item.sendAll && amount != S.current.all) { if (output.sendAll && amount != S.current.all) {
item.sendAll = false; output.sendAll = false;
} }
if (amount != cryptoAmountController.text) { if (amount != cryptoAmountController.text) {
@ -496,7 +496,7 @@ class SendCardState extends State<SendCard>
} }
}); });
reaction((_) => item.address, (String address) { reaction((_) => output.address, (String address) {
if (address != addressController.text) { if (address != addressController.text) {
addressController.text = address; addressController.text = address;
} }
@ -505,12 +505,12 @@ class SendCardState extends State<SendCard>
addressController.addListener(() { addressController.addListener(() {
final address = addressController.text; final address = addressController.text;
if (item.address != address) { if (output.address != address) {
item.address = address; output.address = address;
} }
}); });
reaction((_) => item.note, (String note) { reaction((_) => output.note, (String note) {
if (note != noteController.text) { if (note != noteController.text) {
noteController.text = note; noteController.text = note;
} }
@ -518,7 +518,7 @@ class SendCardState extends State<SendCard>
addressFocusNode.addListener(() async { addressFocusNode.addListener(() async {
if (!addressFocusNode.hasFocus && addressController.text.isNotEmpty) { if (!addressFocusNode.hasFocus && addressController.text.isNotEmpty) {
final parsedAddress = await item.applyOpenaliasOrUnstoppableDomains(); final parsedAddress = await output.applyOpenaliasOrUnstoppableDomains();
showAddressAlert(context, parsedAddress); showAddressAlert(context, parsedAddress);
} }
}); });

View file

@ -79,11 +79,11 @@ abstract class ExchangeTradeViewModelBase with Store {
return; return;
} }
sendViewModel.clearSendItemList(); sendViewModel.clearOutputs();
final item = sendViewModel.sendItemList.first; final output = sendViewModel.outputs.first;
item.address = trade.inputAddress; output.address = trade.inputAddress;
item.setCryptoAmount(trade.amount); output.setCryptoAmount(trade.amount);
await sendViewModel.createTransaction(); await sendViewModel.createTransaction();
} }

View file

@ -15,14 +15,14 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
part 'send_item.g.dart'; part 'output.g.dart';
const String cryptoNumberPattern = '0.0'; const String cryptoNumberPattern = '0.0';
class SendItem = SendItemBase with _$SendItem; class Output = OutputBase with _$Output;
abstract class SendItemBase with Store { abstract class OutputBase with Store {
SendItemBase(this._wallet, this._settingsStore, this._fiatConversationStore) OutputBase(this._wallet, this._settingsStore, this._fiatConversationStore)
:_cryptoNumberFormat = NumberFormat(cryptoNumberPattern) { :_cryptoNumberFormat = NumberFormat(cryptoNumberPattern) {
reset(); reset();
_setCryptoNumMaximumFractionDigits(); _setCryptoNumMaximumFractionDigits();
@ -47,8 +47,8 @@ abstract class SendItemBase with Store {
bool sendAll; bool sendAll;
@computed @computed
double get estimatedFee { int get formattedCryptoAmount {
int amount; int amount = 0;
try { try {
if (cryptoAmount?.isNotEmpty ?? false) { if (cryptoAmount?.isNotEmpty ?? false) {
@ -72,9 +72,18 @@ abstract class SendItemBase with Store {
amount = _amount; amount = _amount;
} }
} }
} catch(e) {
amount = 0;
}
return amount;
}
@computed
double get estimatedFee {
try {
final fee = _wallet.calculateEstimatedFee( final fee = _wallet.calculateEstimatedFee(
_settingsStore.priority[_wallet.type], amount); _settingsStore.priority[_wallet.type], formattedCryptoAmount);
if (_wallet is ElectrumWallet) { if (_wallet is ElectrumWallet) {
return bitcoinAmountToDouble(amount: fee); return bitcoinAmountToDouble(amount: fee);

View file

@ -1,4 +1,4 @@
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart'; import 'package:cake_wallet/store/templates/send_template_store.dart';
@ -21,10 +21,10 @@ abstract class SendTemplateViewModelBase with Store {
SendTemplateViewModelBase(this._wallet, this._settingsStore, SendTemplateViewModelBase(this._wallet, this._settingsStore,
this._sendTemplateStore, this._fiatConversationStore) { this._sendTemplateStore, this._fiatConversationStore) {
sendItem = SendItem(_wallet, _settingsStore, _fiatConversationStore); output = Output(_wallet, _settingsStore, _fiatConversationStore);
} }
SendItem sendItem; Output output;
Validator get amountValidator => AmountValidator(type: _wallet.type); Validator get amountValidator => AmountValidator(type: _wallet.type);

View file

@ -2,7 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:cake_wallet/view_model/send/send_item.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -42,33 +42,33 @@ abstract class SendViewModelBase with Store {
_settingsStore.priority[_wallet.type] = priorities.first; _settingsStore.priority[_wallet.type] = priorities.first;
} }
sendItemList = ObservableList<SendItem>() outputs = ObservableList<Output>()
..add(SendItem(_wallet, _settingsStore, _fiatConversationStore)); ..add(Output(_wallet, _settingsStore, _fiatConversationStore));
} }
@observable @observable
ExecutionState state; ExecutionState state;
ObservableList<SendItem> sendItemList; ObservableList<Output> outputs;
@action @action
void addSendItem() { void addOutput() {
sendItemList.add(SendItem(_wallet, _settingsStore, _fiatConversationStore)); outputs.add(Output(_wallet, _settingsStore, _fiatConversationStore));
} }
@action @action
void removeSendItem(SendItem item) { void removeOutput(Output output) {
sendItemList.remove(item); outputs.remove(output);
} }
@action @action
void clearSendItemList() { void clearOutputs() {
sendItemList.clear(); outputs.clear();
addSendItem(); addOutput();
} }
@computed @computed
bool get isBatchSending => sendItemList.length > 1; bool get isBatchSending => outputs.length > 1;
@computed @computed
String get pendingTransactionFiatAmount { String get pendingTransactionFiatAmount {
@ -150,14 +150,14 @@ abstract class SendViewModelBase with Store {
@action @action
Future<void> commitTransaction() async { Future<void> commitTransaction() async {
String address = sendItemList.fold('', (previousValue, item) { String address = outputs.fold('', (acc, value) {
return previousValue + item.address + '\n'; return acc + value.address + '\n';
}); });
address = address.trim(); address = address.trim();
String note = sendItemList.fold('', (previousValue, item) { String note = outputs.fold('', (acc, value) {
return previousValue + item.note + '\n'; return acc + value.note + '\n';
}); });
note = note.trim(); note = note.trim();
@ -192,17 +192,17 @@ abstract class SendViewModelBase with Store {
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
sendItemList, priority as BitcoinTransactionPriority); outputs, priority as BitcoinTransactionPriority);
case WalletType.litecoin: case WalletType.litecoin:
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
sendItemList, priority as BitcoinTransactionPriority); outputs, priority as BitcoinTransactionPriority);
case WalletType.monero: case WalletType.monero:
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
return MoneroTransactionCreationCredentials( return MoneroTransactionCreationCredentials(
sendItemList: sendItemList, outputs: outputs,
priority: priority as MoneroTransactionPriority); priority: priority as MoneroTransactionPriority);
default: default:
return null; return null;