Fixes

M committed Jan 5, 2021 at 20:31 UTC 798d9a1edf24af6cfd60c22f5370c47306ecc19f
11 files changed +181 -85
lib/bitcoin/bitcoin_amount_format.dart
+29 -5
@@ -1,3 +1,5 @@
1 +import 'dart:math';
2 +
3 import 'package:intl/intl.dart';
4 import 'package:cake_wallet/entities/crypto_amount_format.dart';
5
@@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
9 ..maximumFractionDigits = bitcoinAmountLength
10 ..minimumFractionDigits = 1;
11
10 -String bitcoinAmountToString({int amount}) =>
11 - bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
12 +String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
13 + cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
14 +
15 +double bitcoinAmountToDouble({int amount}) =>
16 + cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
17 +
18 +int stringDoubleToBitcoinAmount(String amount) {
19 + final splitted = amount.split('');
20 + final dotIndex = amount.indexOf('.');
21 + int result = 0;
22 +
23 +
24 + for (var i = 0; i < splitted.length; i++) {
25 + try {
26 + if (dotIndex == i) {
27 + continue;
28 + }
29
13 -double bitcoinAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
30 + final char = splitted[i];
31 + final multiplier = dotIndex < i
32 + ? bitcoinAmountDivider ~/ pow(10, (i - dotIndex))
33 + : (bitcoinAmountDivider * pow(10, (dotIndex - i -1))).toInt();
34 + final num = int.parse(char) * multiplier;
35 + result += num;
36 + } catch (_) {}
37 + }
38
15 -int doubleToBitcoinAmount(double amount) =>
16 - (amount * bitcoinAmountDivider).toInt();
\ No newline at end of file
39 + return result;
40 +}
lib/bitcoin/bitcoin_transaction_credentials.dart
+1 -1
@@ -4,6 +4,6 @@ class BitcoinTransactionCredentials {
4 BitcoinTransactionCredentials(this.address, this.amount, this.priority);
5
6 final String address;
7 - final double amount;
7 + final String amount;
8 TransactionPriority priority;
9 }
lib/bitcoin/bitcoin_transaction_info.dart
+2 -2
@@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
47 final out = vin['tx']['vout'][vout] as Map;
48 final outAddresses =
49 (out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
50 - inputsAmount += doubleToBitcoinAmount(out['value'] as double ?? 0);
50 + inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
51
52 if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
53 direction = TransactionDirection.outgoing;
@@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
58 final outAddresses =
59 out['scriptPubKey']['addresses'] as List<Object> ?? [];
60 final ntrs = outAddresses.toSet().intersection(addressesSet);
61 - final value = doubleToBitcoinAmount(out['value'] as double ?? 0.0);
61 + final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
62 totalOutAmount += value;
63
64 if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
lib/bitcoin/bitcoin_wallet.dart
+22 -18
@@ -116,6 +116,19 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
116 walletInfo: walletInfo);
117 }
118
119 + static int feeAmountForPriority(TransactionPriority priority) {
120 + switch (priority) {
121 + case TransactionPriority.slow:
122 + return 6000;
123 + case TransactionPriority.regular:
124 + return 22080;
125 + case TransactionPriority.fast:
126 + return 24000;
127 + default:
128 + return 0;
129 + }
130 + }
131 +
132 @override
133 final BitcoinTransactionHistory transactionHistory;
134 final String path;
@@ -243,16 +256,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
256 Object credentials) async {
257 final transactionCredentials = credentials as BitcoinTransactionCredentials;
258 final inputs = <BitcoinUnspent>[];
246 - final fee = _feeMultiplier(transactionCredentials.priority);
259 + final fee = feeAmountForPriority(transactionCredentials.priority);
260 final amount = transactionCredentials.amount != null
248 - ? doubleToBitcoinAmount(transactionCredentials.amount)
249 - : balance.total - fee;
261 + ? stringDoubleToBitcoinAmount(transactionCredentials.amount)
262 + : balance.availableBalance - fee;
263 final totalAmount = amount + fee;
264 final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
252 - var leftAmount = totalAmount;
265 final changeAddress = address;
266 + var leftAmount = totalAmount;
267 var totalInputAmount = 0;
268
269 + if (totalAmount > balance.availableBalance) {
270 + throw BitcoinTransactionWrongBalanceException();
271 + }
272 +
273 final unspent = addresses.map((address) => eclient
274 .getListUnspentWithAddress(address.address)
275 .then((unspent) => unspent
@@ -334,7 +351,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
351
352 @override
353 double calculateEstimatedFee(TransactionPriority priority) =>
337 - bitcoinAmountToDouble(amount: _feeMultiplier(priority));
354 + bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
355
356 @override
357 Future<void> save() async {
@@ -386,17 +403,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
403
404 String _getAddress({@required int index}) =>
405 generateAddress(hd: hd, index: index);
389 -
390 - int _feeMultiplier(TransactionPriority priority) {
391 - switch (priority) {
392 - case TransactionPriority.slow:
393 - return 6000;
394 - case TransactionPriority.regular:
395 - return 22080;
396 - case TransactionPriority.fast:
397 - return 24000;
398 - default:
399 - return 0;
400 - }
401 - }
406 }
lib/di.dart
+2 -1
@@ -355,7 +355,8 @@ Future setup(
355 getIt.get<AppStore>().wallet,
356 tradesSource,
357 getIt.get<ExchangeTemplateStore>(),
358 - getIt.get<TradesStore>()));
358 + getIt.get<TradesStore>(),
359 + getIt.get<AppStore>().settingsStore));
360
361 getIt.registerFactory(() => ExchangeTradeViewModel(
362 wallet: getIt.get<AppStore>().wallet,
lib/main.dart
+1
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2 import 'package:cake_wallet/themes/theme_base.dart';
3 import 'package:flutter/material.dart';
4 import 'package:flutter/services.dart';
lib/src/screens/exchange/exchange_page.dart
+39 -28
@@ -62,10 +62,11 @@ class ExchangePage extends BasePage {
62
63 @override
64 Widget trailing(BuildContext context) => TrailButton(
65 - caption: S.of(context).reset, onPressed: () {
65 + caption: S.of(context).reset,
66 + onPressed: () {
67 _formKey.currentState.reset();
68 exchangeViewModel.reset();
68 - });
69 + });
70
71 @override
72 Widget body(BuildContext context) {
@@ -95,8 +96,8 @@ class ExchangePage extends BasePage {
96 return KeyboardActions(
97 config: KeyboardActionsConfig(
98 keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
98 - keyboardBarColor: Theme.of(context).accentTextTheme.body2
99 - .backgroundColor,
99 + keyboardBarColor:
100 + Theme.of(context).accentTextTheme.body2.backgroundColor,
101 nextFocus: false,
102 actions: [
103 KeyboardActionsItem(
@@ -160,6 +161,11 @@ class ExchangePage extends BasePage {
161 padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
162 child: Observer(
163 builder: (_) => ExchangeCard(
164 + hasAllAmount: exchangeViewModel.hasAllAmount,
165 + allAmount: exchangeViewModel.hasAllAmount
166 + ? () => exchangeViewModel
167 + .calculateDepositAllAmount()
168 + : null,
169 amountFocusNode: _depositAmountFocus,
170 key: depositKey,
171 title: S.of(context).you_will_send,
@@ -394,30 +400,35 @@ class ExchangePage extends BasePage {
400 }),
401 ),
402 Observer(
397 - builder: (_) => LoadingPrimaryButton(
398 - text: S.of(context).exchange,
399 - onPressed: () {
400 - if (_formKey.currentState.validate()) {
401 - if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr)
402 - &&(!(exchangeViewModel.status is SyncedSyncStatus))) {
403 - showPopUp<void>(
404 - context: context,
405 - builder: (BuildContext context) {
406 - return AlertWithOneAction(
407 - alertTitle: S.of(context).exchange,
408 - alertContent: S.of(context).exchange_sync_alert_content,
409 - buttonText: S.of(context).ok,
410 - buttonAction: () => Navigator.of(context).pop());
411 - });
412 - } else {
413 - exchangeViewModel.createTrade();
414 - }
415 - }
416 - },
417 - color: Theme.of(context).accentTextTheme.body2.color,
418 - textColor: Colors.white,
419 - isLoading: exchangeViewModel.tradeState
420 - is TradeIsCreating)),
403 + builder: (_) => LoadingPrimaryButton(
404 + text: S.of(context).exchange,
405 + onPressed: () {
406 + if (_formKey.currentState.validate()) {
407 + if ((exchangeViewModel.depositCurrency ==
408 + CryptoCurrency.xmr) &&
409 + (!(exchangeViewModel.status
410 + is SyncedSyncStatus))) {
411 + showPopUp<void>(
412 + context: context,
413 + builder: (BuildContext context) {
414 + return AlertWithOneAction(
415 + alertTitle: S.of(context).exchange,
416 + alertContent: S
417 + .of(context)
418 + .exchange_sync_alert_content,
419 + buttonText: S.of(context).ok,
420 + buttonAction: () =>
421 + Navigator.of(context).pop());
422 + });
423 + } else {
424 + exchangeViewModel.createTrade();
425 + }
426 + }
427 + },
428 + color: Theme.of(context).accentTextTheme.body2.color,
429 + textColor: Colors.white,
430 + isLoading:
431 + exchangeViewModel.tradeState is TradeIsCreating)),
432 ]),
433 )),
434 ));
lib/src/screens/exchange/widgets/exchange_card.dart
+51 -19
@@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
27 this.borderColor = Colors.transparent,
28 this.currencyValueValidator,
29 this.addressTextFieldValidator,
30 - this.amountFocusNode})
30 + this.amountFocusNode,
31 + this.hasAllAmount = false,
32 + this.allAmount})
33 : super(key: key);
34
35 final List<CryptoCurrency> currencies;
@@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
49 final FormFieldValidator<String> currencyValueValidator;
50 final FormFieldValidator<String> addressTextFieldValidator;
51 final FocusNode amountFocusNode;
52 + final bool hasAllAmount;
53 + Function allAmount;
54
55 @override
56 ExchangeCardState createState() => ExchangeCardState();
@@ -197,7 +201,36 @@ class ExchangeCardState extends State<ExchangeCard> {
201 ]),
202 ),
203 ),
200 - )
204 + ),
205 + if (widget.hasAllAmount)
206 + Positioned(
207 + top: 5,
208 + right: 55,
209 + child: Container(
210 + height: 32,
211 + width: 32,
212 + margin: EdgeInsets.only(left: 14, top: 4, bottom: 10),
213 + decoration: BoxDecoration(
214 + color: Theme.of(context)
215 + .primaryTextTheme
216 + .display1
217 + .color,
218 + borderRadius: BorderRadius.all(Radius.circular(6))),
219 + child: InkWell(
220 + onTap: () => widget.allAmount?.call(),
221 + child: Center(
222 + child: Text(S.of(context).all,
223 + textAlign: TextAlign.center,
224 + style: TextStyle(
225 + fontSize: 12,
226 + fontWeight: FontWeight.bold,
227 + color: Theme.of(context)
228 + .primaryTextTheme
229 + .display1
230 + .decorationColor)),
231 + ),
232 + ),
233 + ))
234 ],
235 )),
236 Padding(
@@ -232,18 +265,17 @@ class ExchangeCardState extends State<ExchangeCard> {
265 ),
266 !_isAddressEditable && widget.hasRefundAddress
267 ? Padding(
235 - padding: EdgeInsets.only(top: 20),
236 - child: Text(
237 - S.of(context).refund_address,
238 - style: TextStyle(
239 - fontSize: 14,
240 - fontWeight: FontWeight.w500,
241 - color:
242 - Theme.of(context)
243 - .accentTextTheme
244 - .display4
245 - .decorationColor),
246 - ))
268 + padding: EdgeInsets.only(top: 20),
269 + child: Text(
270 + S.of(context).refund_address,
271 + style: TextStyle(
272 + fontSize: 14,
273 + fontWeight: FontWeight.w500,
274 + color: Theme.of(context)
275 + .accentTextTheme
276 + .display4
277 + .decorationColor),
278 + ))
279 : Offstage(),
280 _isAddressEditable
281 ? Padding(
@@ -251,7 +283,8 @@ class ExchangeCardState extends State<ExchangeCard> {
283 child: AddressTextField(
284 controller: addressController,
285 placeholder: widget.hasRefundAddress
254 - ? S.of(context).refund_address : null,
286 + ? S.of(context).refund_address
287 + : null,
288 options: [
289 AddressTextFieldOption.paste,
290 AddressTextFieldOption.qrCode,
@@ -265,8 +298,7 @@ class ExchangeCardState extends State<ExchangeCard> {
298 hintStyle: TextStyle(
299 fontSize: 16,
300 fontWeight: FontWeight.w600,
268 - color:
269 - Theme.of(context)
301 + color: Theme.of(context)
302 .accentTextTheme
303 .display4
304 .decorationColor),
@@ -281,8 +313,8 @@ class ExchangeCardState extends State<ExchangeCard> {
313 onTap: () {
314 Clipboard.setData(
315 ClipboardData(text: addressController.text));
284 - showBar<void>(context,
285 - S.of(context).copied_to_clipboard);
316 + showBar<void>(
317 + context, S.of(context).copied_to_clipboard);
318 },
319 child: Row(
320 mainAxisSize: MainAxisSize.max,
lib/store/settings_store.dart
+1 -1
@@ -153,7 +153,7 @@ abstract class SettingsStoreBase with Store {
153 .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
154 false;
155 final legacyTheme =
156 - sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy)
156 + (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
157 ? ThemeType.dark.index
158 : ThemeType.bright.index;
159 final savedTheme = ThemeList.deserialize(
lib/view_model/exchange/exchange_view_model.dart
+32 -9
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2 +import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
3 import 'package:cake_wallet/core/wallet_base.dart';
4 import 'package:cake_wallet/entities/crypto_currency.dart';
5 import 'package:cake_wallet/entities/sync_status.dart';
@@ -7,6 +9,7 @@ import 'package:cake_wallet/exchange/limits.dart';
9 import 'package:cake_wallet/exchange/trade.dart';
10 import 'package:cake_wallet/exchange/limits_state.dart';
11 import 'package:cake_wallet/store/dashboard/trades_store.dart';
12 +import 'package:cake_wallet/store/settings_store.dart';
13 import 'package:intl/intl.dart';
14 import 'package:mobx/mobx.dart';
15 import 'package:cake_wallet/generated/i18n.dart';
@@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
30 class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
31
32 abstract class ExchangeViewModelBase with Store {
30 - ExchangeViewModelBase(
31 - this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) {
33 + ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
34 + this.tradesStore, this._settingsStore) {
35 providerList = [
36 XMRTOExchangeProvider(),
37 ChangeNowExchangeProvider(),
@@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
107 @observable
108 bool isReceiveAmountEntered;
109
107 - Limits limits;
108 -
109 - NumberFormat _cryptoNumberFormat;
110 -
110 @computed
111 SyncStatus get status => wallet.syncStatus;
112
@@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
114 ObservableList<ExchangeTemplate> get templates =>
115 _exchangeTemplateStore.templates;
116
117 + bool get hasAllAmount =>
118 + wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
119 +
120 + Limits limits;
121 +
122 + NumberFormat _cryptoNumberFormat;
123 +
124 + SettingsStore _settingsStore;
125 +
126 @action
127 void changeProvider({ExchangeProvider provider}) {
128 this.provider = provider;
@@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
272 await trades.add(trade);
273 tradeState = TradeIsCreatedSuccessfully(trade: trade);
274 } catch (e) {
267 - tradeState = TradeIsCreatedFailure(
268 - title: provider.title,
269 - error: e.toString());
275 + tradeState =
276 + TradeIsCreatedFailure(title: provider.title, error: e.toString());
277 }
278 }
279 } else {
@@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
298 _onPairChange();
299 }
300
301 + @action
302 + void calculateDepositAllAmount() {
303 + if (wallet is BitcoinWallet) {
304 + final availableBalance = wallet.balance.availableBalance as int;
305 + final fee = BitcoinWalletBase.feeAmountForPriority(
306 + _settingsStore.transactionPriority);
307 +
308 + if (availableBalance < fee || availableBalance == 0) {
309 + return;
310 + }
311 +
312 + final amount = availableBalance - fee;
313 + depositAmount = bitcoinAmountToString(amount: amount);
314 + }
315 + }
316 +
317 void updateTemplate() => _exchangeTemplateStore.update();
318
319 void addTemplate(
lib/view_model/send/send_view_model.dart
+1 -1
@@ -197,7 +197,7 @@ abstract class SendViewModelBase with Store {
197
198 switch (_wallet.type) {
199 case WalletType.bitcoin:
200 - final amount = !sendAll ? double.parse(_amount) : null;
200 + final amount = !sendAll ? _amount : null;
201
202 return BitcoinTransactionCredentials(
203 address, amount, _settingsStore.transactionPriority);