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:cake_wallet/entities/crypto_amount_format.dart';
@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = bitcoinAmountLength
..minimumFractionDigits = 1;
String bitcoinAmountToString({int amount}) =>
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
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) =>
(amount * bitcoinAmountDivider).toInt();
int stringDoubleToBitcoinAmount(String amount) {
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);
final String address;
final double amount;
final String amount;
TransactionPriority priority;
}

View file

@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(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) {
direction = TransactionDirection.outgoing;
@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? [];
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;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||

View file

@ -116,6 +116,19 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
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
final BitcoinTransactionHistory transactionHistory;
final String path;
@ -243,16 +256,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final fee = _feeMultiplier(transactionCredentials.priority);
final fee = feeAmountForPriority(transactionCredentials.priority);
final amount = transactionCredentials.amount != null
? doubleToBitcoinAmount(transactionCredentials.amount)
: balance.total - fee;
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: balance.availableBalance - fee;
final totalAmount = amount + fee;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
var leftAmount = totalAmount;
final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0;
if (totalAmount > balance.availableBalance) {
throw BitcoinTransactionWrongBalanceException();
}
final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address)
.then((unspent) => unspent
@ -334,7 +351,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@override
double calculateEstimatedFee(TransactionPriority priority) =>
bitcoinAmountToDouble(amount: _feeMultiplier(priority));
bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
@override
Future<void> save() async {
@ -386,17 +403,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
String _getAddress({@required int 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,
tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>()));
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel(
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:flutter/material.dart';
import 'package:flutter/services.dart';

View file

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

View file

@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
this.borderColor = Colors.transparent,
this.currencyValueValidator,
this.addressTextFieldValidator,
this.amountFocusNode})
this.amountFocusNode,
this.hasAllAmount = false,
this.allAmount})
: super(key: key);
final List<CryptoCurrency> currencies;
@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator;
final FocusNode amountFocusNode;
final bool hasAllAmount;
Function allAmount;
@override
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(
@ -232,18 +265,17 @@ class ExchangeCardState extends State<ExchangeCard> {
),
!_isAddressEditable && widget.hasRefundAddress
? Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color:
Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
))
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
))
: Offstage(),
_isAddressEditable
? Padding(
@ -251,7 +283,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: AddressTextField(
controller: addressController,
placeholder: widget.hasRefundAddress
? S.of(context).refund_address : null,
? S.of(context).refund_address
: null,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
@ -265,8 +298,7 @@ class ExchangeCardState extends State<ExchangeCard> {
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color:
Theme.of(context)
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
@ -281,8 +313,8 @@ class ExchangeCardState extends State<ExchangeCard> {
onTap: () {
Clipboard.setData(
ClipboardData(text: addressController.text));
showBar<void>(context,
S.of(context).copied_to_clipboard);
showBar<void>(
context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,

View file

@ -153,7 +153,7 @@ abstract class SettingsStoreBase with Store {
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false;
final legacyTheme =
sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy)
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index
: ThemeType.bright.index;
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/entities/crypto_currency.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/limits_state.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:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) {
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.tradesStore, this._settingsStore) {
providerList = [
XMRTOExchangeProvider(),
ChangeNowExchangeProvider(),
@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
@observable
bool isReceiveAmountEntered;
Limits limits;
NumberFormat _cryptoNumberFormat;
@computed
SyncStatus get status => wallet.syncStatus;
@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
bool get hasAllAmount =>
wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
Limits limits;
NumberFormat _cryptoNumberFormat;
SettingsStore _settingsStore;
@action
void changeProvider({ExchangeProvider provider}) {
this.provider = provider;
@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) {
tradeState = TradeIsCreatedFailure(
title: provider.title,
error: e.toString());
tradeState =
TradeIsCreatedFailure(title: provider.title, error: e.toString());
}
}
} else {
@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
_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 addTemplate(

View file

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