This commit is contained in:
M 2021-01-05 20:31:03 +02:00
parent 2dc5489a8e
commit 798d9a1edf
11 changed files with 181 additions and 85 deletions

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:cake_wallet/entities/crypto_amount_format.dart'; import 'package:cake_wallet/entities/crypto_amount_format.dart';
@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = bitcoinAmountLength ..maximumFractionDigits = bitcoinAmountLength
..minimumFractionDigits = 1; ..minimumFractionDigits = 1;
String bitcoinAmountToString({int amount}) => String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
double bitcoinAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); double bitcoinAmountToDouble({int amount}) =>
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
int doubleToBitcoinAmount(double amount) => int stringDoubleToBitcoinAmount(String amount) {
(amount * bitcoinAmountDivider).toInt(); final splitted = amount.split('');
final dotIndex = amount.indexOf('.');
int result = 0;
for (var i = 0; i < splitted.length; i++) {
try {
if (dotIndex == i) {
continue;
}
final char = splitted[i];
final multiplier = dotIndex < i
? bitcoinAmountDivider ~/ pow(10, (i - dotIndex))
: (bitcoinAmountDivider * pow(10, (dotIndex - i -1))).toInt();
final num = int.parse(char) * multiplier;
result += num;
} catch (_) {}
}
return result;
}

View file

@ -4,6 +4,6 @@ class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.address, this.amount, this.priority); BitcoinTransactionCredentials(this.address, this.amount, this.priority);
final String address; final String address;
final double amount; final String amount;
TransactionPriority priority; TransactionPriority priority;
} }

View file

@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map; final out = vin['tx']['vout'][vout] as Map;
final outAddresses = final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet(); (out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
inputsAmount += doubleToBitcoinAmount(out['value'] as double ?? 0); inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses = final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? []; out['scriptPubKey']['addresses'] as List<Object> ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet); final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = doubleToBitcoinAmount(out['value'] as double ?? 0.0); final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
totalOutAmount += value; totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||

View file

@ -116,6 +116,19 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
walletInfo: walletInfo); walletInfo: walletInfo);
} }
static int feeAmountForPriority(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
@override @override
final BitcoinTransactionHistory transactionHistory; final BitcoinTransactionHistory transactionHistory;
final String path; final String path;
@ -243,16 +256,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Object credentials) async { Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials; final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[]; final inputs = <BitcoinUnspent>[];
final fee = _feeMultiplier(transactionCredentials.priority); final fee = feeAmountForPriority(transactionCredentials.priority);
final amount = transactionCredentials.amount != null final amount = transactionCredentials.amount != null
? doubleToBitcoinAmount(transactionCredentials.amount) ? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: balance.total - fee; : balance.availableBalance - fee;
final totalAmount = amount + fee; final totalAmount = amount + fee;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin); final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
var leftAmount = totalAmount;
final changeAddress = address; final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0; var totalInputAmount = 0;
if (totalAmount > balance.availableBalance) {
throw BitcoinTransactionWrongBalanceException();
}
final unspent = addresses.map((address) => eclient final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address) .getListUnspentWithAddress(address.address)
.then((unspent) => unspent .then((unspent) => unspent
@ -334,7 +351,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@override @override
double calculateEstimatedFee(TransactionPriority priority) => double calculateEstimatedFee(TransactionPriority priority) =>
bitcoinAmountToDouble(amount: _feeMultiplier(priority)); bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
@override @override
Future<void> save() async { Future<void> save() async {
@ -386,17 +403,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
String _getAddress({@required int index}) => String _getAddress({@required int index}) =>
generateAddress(hd: hd, index: index); generateAddress(hd: hd, index: index);
int _feeMultiplier(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
} }

View file

@ -355,7 +355,8 @@ Future setup(
getIt.get<AppStore>().wallet, getIt.get<AppStore>().wallet,
tradesSource, tradesSource,
getIt.get<ExchangeTemplateStore>(), getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>())); getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel( getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet, wallet: getIt.get<AppStore>().wallet,

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View file

@ -62,7 +62,8 @@ class ExchangePage extends BasePage {
@override @override
Widget trailing(BuildContext context) => TrailButton( Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset, onPressed: () { caption: S.of(context).reset,
onPressed: () {
_formKey.currentState.reset(); _formKey.currentState.reset();
exchangeViewModel.reset(); exchangeViewModel.reset();
}); });
@ -95,8 +96,8 @@ class ExchangePage extends BasePage {
return KeyboardActions( return KeyboardActions(
config: KeyboardActionsConfig( config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2 keyboardBarColor:
.backgroundColor, Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false, nextFocus: false,
actions: [ actions: [
KeyboardActionsItem( KeyboardActionsItem(
@ -160,6 +161,11 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32), padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer( child: Observer(
builder: (_) => ExchangeCard( builder: (_) => ExchangeCard(
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel
.calculateDepositAllAmount()
: null,
amountFocusNode: _depositAmountFocus, amountFocusNode: _depositAmountFocus,
key: depositKey, key: depositKey,
title: S.of(context).you_will_send, title: S.of(context).you_will_send,
@ -398,16 +404,21 @@ class ExchangePage extends BasePage {
text: S.of(context).exchange, text: S.of(context).exchange,
onPressed: () { onPressed: () {
if (_formKey.currentState.validate()) { if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr) if ((exchangeViewModel.depositCurrency ==
&&(!(exchangeViewModel.status is SyncedSyncStatus))) { CryptoCurrency.xmr) &&
(!(exchangeViewModel.status
is SyncedSyncStatus))) {
showPopUp<void>( showPopUp<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: S.of(context).exchange, alertTitle: S.of(context).exchange,
alertContent: S.of(context).exchange_sync_alert_content, alertContent: S
.of(context)
.exchange_sync_alert_content,
buttonText: S.of(context).ok, buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () =>
Navigator.of(context).pop());
}); });
} else { } else {
exchangeViewModel.createTrade(); exchangeViewModel.createTrade();
@ -416,8 +427,8 @@ class ExchangePage extends BasePage {
}, },
color: Theme.of(context).accentTextTheme.body2.color, color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white, textColor: Colors.white,
isLoading: exchangeViewModel.tradeState isLoading:
is TradeIsCreating)), exchangeViewModel.tradeState is TradeIsCreating)),
]), ]),
)), )),
)); ));

View file

@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
this.borderColor = Colors.transparent, this.borderColor = Colors.transparent,
this.currencyValueValidator, this.currencyValueValidator,
this.addressTextFieldValidator, this.addressTextFieldValidator,
this.amountFocusNode}) this.amountFocusNode,
this.hasAllAmount = false,
this.allAmount})
: super(key: key); : super(key: key);
final List<CryptoCurrency> currencies; final List<CryptoCurrency> currencies;
@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
final FormFieldValidator<String> currencyValueValidator; final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator; final FormFieldValidator<String> addressTextFieldValidator;
final FocusNode amountFocusNode; final FocusNode amountFocusNode;
final bool hasAllAmount;
Function allAmount;
@override @override
ExchangeCardState createState() => ExchangeCardState(); ExchangeCardState createState() => ExchangeCardState();
@ -197,7 +201,36 @@ class ExchangeCardState extends State<ExchangeCard> {
]), ]),
), ),
), ),
) ),
if (widget.hasAllAmount)
Positioned(
top: 5,
right: 55,
child: Container(
height: 32,
width: 32,
margin: EdgeInsets.only(left: 14, top: 4, bottom: 10),
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: InkWell(
onTap: () => widget.allAmount?.call(),
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.display1
.decorationColor)),
),
),
))
], ],
)), )),
Padding( Padding(
@ -238,8 +271,7 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color: Theme.of(context)
Theme.of(context)
.accentTextTheme .accentTextTheme
.display4 .display4
.decorationColor), .decorationColor),
@ -251,7 +283,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: AddressTextField( child: AddressTextField(
controller: addressController, controller: addressController,
placeholder: widget.hasRefundAddress placeholder: widget.hasRefundAddress
? S.of(context).refund_address : null, ? S.of(context).refund_address
: null,
options: [ options: [
AddressTextFieldOption.paste, AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode, AddressTextFieldOption.qrCode,
@ -265,8 +298,7 @@ class ExchangeCardState extends State<ExchangeCard> {
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: color: Theme.of(context)
Theme.of(context)
.accentTextTheme .accentTextTheme
.display4 .display4
.decorationColor), .decorationColor),
@ -281,8 +313,8 @@ class ExchangeCardState extends State<ExchangeCard> {
onTap: () { onTap: () {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: addressController.text)); ClipboardData(text: addressController.text));
showBar<void>(context, showBar<void>(
S.of(context).copied_to_clipboard); context, S.of(context).copied_to_clipboard);
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,

View file

@ -153,7 +153,7 @@ abstract class SettingsStoreBase with Store {
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false; false;
final legacyTheme = final legacyTheme =
sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index ? ThemeType.dark.index
: ThemeType.bright.index; : ThemeType.bright.index;
final savedTheme = ThemeList.deserialize( final savedTheme = ThemeList.deserialize(

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/sync_status.dart';
@ -7,6 +9,7 @@ import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits_state.dart'; import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store { abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase( ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) { this.tradesStore, this._settingsStore) {
providerList = [ providerList = [
XMRTOExchangeProvider(), XMRTOExchangeProvider(),
ChangeNowExchangeProvider(), ChangeNowExchangeProvider(),
@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
@observable @observable
bool isReceiveAmountEntered; bool isReceiveAmountEntered;
Limits limits;
NumberFormat _cryptoNumberFormat;
@computed @computed
SyncStatus get status => wallet.syncStatus; SyncStatus get status => wallet.syncStatus;
@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates => ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates; _exchangeTemplateStore.templates;
bool get hasAllAmount =>
wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
Limits limits;
NumberFormat _cryptoNumberFormat;
SettingsStore _settingsStore;
@action @action
void changeProvider({ExchangeProvider provider}) { void changeProvider({ExchangeProvider provider}) {
this.provider = provider; this.provider = provider;
@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
await trades.add(trade); await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade); tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) { } catch (e) {
tradeState = TradeIsCreatedFailure( tradeState =
title: provider.title, TradeIsCreatedFailure(title: provider.title, error: e.toString());
error: e.toString());
} }
} }
} else { } else {
@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
_onPairChange(); _onPairChange();
} }
@action
void calculateDepositAllAmount() {
if (wallet is BitcoinWallet) {
final availableBalance = wallet.balance.availableBalance as int;
final fee = BitcoinWalletBase.feeAmountForPriority(
_settingsStore.transactionPriority);
if (availableBalance < fee || availableBalance == 0) {
return;
}
final amount = availableBalance - fee;
depositAmount = bitcoinAmountToString(amount: amount);
}
}
void updateTemplate() => _exchangeTemplateStore.update(); void updateTemplate() => _exchangeTemplateStore.update();
void addTemplate( void addTemplate(

View file

@ -197,7 +197,7 @@ abstract class SendViewModelBase with Store {
switch (_wallet.type) { switch (_wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
final amount = !sendAll ? double.parse(_amount) : null; final amount = !sendAll ? _amount : null;
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
address, amount, _settingsStore.transactionPriority); address, amount, _settingsStore.transactionPriority);