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);