CAKE-334 | applied unspent coins control to the app; added unspent_coins_info.dart; reworked createTransaction(), calculateEstimatedFee() and updateUnspent() methods in the electrum_wallet.dart; fixed unspent_coins_list_view_model.dart, unspent_coins_details_view_model.dart, unspent_coins_list_item.dart, unspent_coins_list_page.dart and unspent_coins_details_page.dart; fixed bitcoin_transaction_wrong_balance_exception.dart; added properties to bitcoin_unspent.dart; applied localization to unspent coins pages
OleksandrSobol committed
Jul 5, 2021 at 16:52 UTC
20e0c830cf6d6703fc00d2faeec23f4404a04321
34 files changed
+520
-233
lib/bitcoin/bitcoin_transaction_wrong_balance_exception.dart
+7
-1
@@ -1,4 +1,10 @@
1
+import 'package:cake_wallet/entities/crypto_currency.dart';
2
+
3
class BitcoinTransactionWrongBalanceException implements Exception {
4
+ BitcoinTransactionWrongBalanceException(this.currency);
5
+
6
+ final CryptoCurrency currency;
7
+
8
@override
3
- String toString() => 'Wrong balance. Not enough BTC on your balance.';
9
+ String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.';
10
}
\ No newline at end of file
lib/bitcoin/bitcoin_unspent.dart
+7
-1
@@ -1,7 +1,10 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
2
3
class BitcoinUnspent {
4
- BitcoinUnspent(this.address, this.hash, this.value, this.vout);
4
+ BitcoinUnspent(this.address, this.hash, this.value, this.vout)
5
+ : isSending = true,
6
+ isFrozen = false,
7
+ note = '';
8
9
factory BitcoinUnspent.fromJSON(
10
BitcoinAddressRecord address, Map<String, dynamic> json) =>
@@ -15,4 +18,7 @@ class BitcoinUnspent {
18
19
bool get isP2wpkh =>
20
address.address.startsWith('bc') || address.address.startsWith('ltc');
21
+ bool isSending;
22
+ bool isFrozen;
23
+ String note;
24
}
lib/bitcoin/bitcoin_wallet.dart
+6
@@ -1,3 +1,5 @@
1
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
2
+import 'package:hive/hive.dart';
3
import 'package:mobx/mobx.dart';
4
import 'package:flutter/foundation.dart';
5
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
@@ -17,6 +19,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
19
{@required String mnemonic,
20
@required String password,
21
@required WalletInfo walletInfo,
22
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo,
23
List<BitcoinAddressRecord> initialAddresses,
24
ElectrumBalance initialBalance,
25
int accountIndex = 0})
@@ -24,6 +27,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
27
mnemonic: mnemonic,
28
password: password,
29
walletInfo: walletInfo,
30
+ unspentCoinsInfo: unspentCoinsInfo,
31
networkType: bitcoin.bitcoin,
32
initialAddresses: initialAddresses,
33
initialBalance: initialBalance,
@@ -32,6 +36,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
36
static Future<BitcoinWallet> open({
37
@required String name,
38
@required WalletInfo walletInfo,
39
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo,
40
@required String password,
41
}) async {
42
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
@@ -40,6 +45,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
45
mnemonic: snp.mnemonic,
46
password: password,
47
walletInfo: walletInfo,
48
+ unspentCoinsInfo: unspentCoinsInfo,
49
initialAddresses: snp.addresses,
50
initialBalance: snp.balance,
51
accountIndex: snp.accountIndex);
lib/bitcoin/bitcoin_wallet_service.dart
+9
-4
@@ -2,6 +2,7 @@ import 'dart:io';
2
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
3
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
4
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
5
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
6
import 'package:cake_wallet/core/wallet_base.dart';
7
import 'package:cake_wallet/core/wallet_service.dart';
8
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
@@ -14,9 +15,10 @@ class BitcoinWalletService extends WalletService<
15
BitcoinNewWalletCredentials,
16
BitcoinRestoreWalletFromSeedCredentials,
17
BitcoinRestoreWalletFromWIFCredentials> {
17
- BitcoinWalletService(this.walletInfoSource);
18
+ BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
19
20
final Box<WalletInfo> walletInfoSource;
21
+ final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
22
23
@override
24
WalletType getType() => WalletType.bitcoin;
@@ -26,7 +28,8 @@ class BitcoinWalletService extends WalletService<
28
final wallet = BitcoinWallet(
29
mnemonic: await generateMnemonic(),
30
password: credentials.password,
29
- walletInfo: credentials.walletInfo);
31
+ walletInfo: credentials.walletInfo,
32
+ unspentCoinsInfo: unspentCoinsInfoSource);
33
await wallet.save();
34
await wallet.init();
35
return wallet;
@@ -42,7 +45,8 @@ class BitcoinWalletService extends WalletService<
45
(info) => info.id == WalletBase.idFor(name, getType()),
46
orElse: () => null);
47
final wallet = await BitcoinWalletBase.open(
45
- password: password, name: name, walletInfo: walletInfo);
48
+ password: password, name: name, walletInfo: walletInfo,
49
+ unspentCoinsInfo: unspentCoinsInfoSource);
50
await wallet.init();
51
return wallet;
52
}
@@ -67,7 +71,8 @@ class BitcoinWalletService extends WalletService<
71
final wallet = BitcoinWallet(
72
password: credentials.password,
73
mnemonic: credentials.mnemonic,
70
- walletInfo: credentials.walletInfo);
74
+ walletInfo: credentials.walletInfo,
75
+ unspentCoinsInfo: unspentCoinsInfoSource);
76
await wallet.save();
77
await wallet.init();
78
return wallet;
lib/bitcoin/electrum_wallet.dart
+134
-37
@@ -1,5 +1,7 @@
1
import 'dart:async';
2
import 'dart:convert';
3
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
4
+import 'package:hive/hive.dart';
5
import 'package:mobx/mobx.dart';
6
import 'package:rxdart/subjects.dart';
7
import 'package:flutter/foundation.dart';
@@ -38,6 +40,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
40
ElectrumWalletBase(
41
{@required String password,
42
@required WalletInfo walletInfo,
43
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo,
44
@required List<BitcoinAddressRecord> initialAddresses,
45
@required this.networkType,
46
@required this.mnemonic,
@@ -59,9 +62,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
62
super(walletInfo) {
63
this.electrumClient = electrumClient ?? ElectrumClient();
64
this.walletInfo = walletInfo;
65
+ this.unspentCoinsInfo = unspentCoinsInfo;
66
transactionHistory =
67
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
64
- _unspent = [];
68
+ unspentCoins = [];
69
_scripthashesUpdateSubject = {};
70
}
71
@@ -72,6 +76,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
76
final String mnemonic;
77
78
ElectrumClient electrumClient;
79
+ Box<UnspentCoinsInfo> unspentCoinsInfo;
80
81
@override
82
@observable
@@ -103,7 +108,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
108
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
109
110
final String _password;
106
- List<BitcoinUnspent> _unspent;
111
+ List<BitcoinUnspent> unspentCoins;
112
List<int> _feeRates;
113
int _accountIndex;
114
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
@@ -178,10 +183,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
183
Future<void> startSync() async {
184
try {
185
syncStatus = StartingSyncStatus();
181
- updateTransactions();
186
+ await updateTransactions();
187
_subscribeForUpdates();
188
await _updateBalance();
184
- await _updateUnspent();
189
+ await updateUnspent();
190
_feeRates = await electrumClient.feeRates();
191
192
Timer.periodic(const Duration(minutes: 1),
@@ -218,33 +223,65 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
223
const minAmount = 546;
224
final transactionCredentials = credentials as BitcoinTransactionCredentials;
225
final inputs = <BitcoinUnspent>[];
226
+ var allInputsAmount = 0;
227
+
228
+ if (unspentCoins.isEmpty) {
229
+ await updateUnspent();
230
+ }
231
+
232
+ for (final utx in unspentCoins) {
233
+ if (utx.isSending) {
234
+ allInputsAmount += utx.value;
235
+ inputs.add(utx);
236
+ }
237
+ }
238
+
239
+ if (inputs.isEmpty) {
240
+ throw BitcoinTransactionNoInputsException();
241
+ }
242
+
243
final allAmountFee =
222
- calculateEstimatedFee(transactionCredentials.priority, null);
223
- final allAmount = balance.confirmed - allAmountFee;
224
- var fee = 0;
244
+ feeAmountForPriority(transactionCredentials.priority, inputs.length, 1);
245
+ final allAmount = allInputsAmount - allAmountFee;
246
+
247
final credentialsAmount = transactionCredentials.amount != null
248
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
249
: 0;
250
final amount = transactionCredentials.amount == null ||
229
- allAmount - credentialsAmount < minAmount
251
+ allAmount - credentialsAmount < minAmount
252
? allAmount
253
: credentialsAmount;
254
+ final fee = transactionCredentials.amount == null || amount == allAmount
255
+ ? allAmountFee
256
+ : calculateEstimatedFee(transactionCredentials.priority, amount);
257
+
258
+ if (fee == 0) {
259
+ throw BitcoinTransactionWrongBalanceException(currency);
260
+ }
261
+
262
+ final totalAmount = amount + fee;
263
+
264
+ if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
265
+ throw BitcoinTransactionWrongBalanceException(currency);
266
+ }
267
+
268
final txb = bitcoin.TransactionBuilder(network: networkType);
269
final changeAddress = address;
234
- var leftAmount = amount;
270
+
271
+ var leftAmount = totalAmount;
272
var totalInputAmount = 0;
273
237
- if (_unspent.isEmpty) {
238
- await _updateUnspent();
239
- }
274
+ inputs.clear();
275
241
- for (final utx in _unspent) {
242
- leftAmount = leftAmount - utx.value;
243
- totalInputAmount += utx.value;
244
- inputs.add(utx);
276
+ for (final utx in unspentCoins) {
277
+ if (utx.isSending) {
278
+ leftAmount = leftAmount - utx.value;
279
+ totalInputAmount += utx.value;
280
+ inputs.add(utx);
281
246
- if (leftAmount <= 0) {
247
- break;
282
+ if (leftAmount <= 0) {
283
+ break;
284
+ }
285
}
286
}
287
@@ -252,18 +289,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
289
throw BitcoinTransactionNoInputsException();
290
}
291
255
- final totalAmount = amount + fee;
256
- fee = transactionCredentials.amount != null
257
- ? feeAmountForPriority(transactionCredentials.priority, inputs.length,
258
- amount == allAmount ? 1 : 2)
259
- : allAmountFee;
260
-
261
- if (totalAmount > balance.confirmed) {
262
- throw BitcoinTransactionWrongBalanceException();
263
- }
264
-
265
- if (amount <= 0 || totalInputAmount < amount) {
266
- throw BitcoinTransactionWrongBalanceException();
292
+ if (amount <= 0 || totalInputAmount < totalAmount) {
293
+ throw BitcoinTransactionWrongBalanceException(currency);
294
}
295
296
txb.setVersion(1);
@@ -338,17 +365,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
365
if (amount != null) {
366
int totalValue = 0;
367
341
- for (final input in _unspent) {
368
+ for (final input in unspentCoins) {
369
if (totalValue >= amount) {
370
break;
371
}
372
346
- totalValue += input.value;
347
- inputsCount += 1;
373
+ if (input.isSending) {
374
+ totalValue += input.value;
375
+ inputsCount += 1;
376
+ }
377
}
378
+
379
+ if (totalValue < amount) return 0;
380
} else {
350
- inputsCount = _unspent.length;
381
+ for (final input in unspentCoins) {
382
+ if (input.isSending) {
383
+ inputsCount += 1;
384
+ }
385
+ }
386
}
387
+
388
// If send all, then we have no change value
389
return feeAmountForPriority(
390
priority, inputsCount, amount != null ? 2 : 1);
@@ -382,12 +418,73 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
418
Future<String> makePath() async =>
419
pathForWallet(name: walletInfo.name, type: walletInfo.type);
420
385
- Future<void> _updateUnspent() async {
421
+ Future<void> updateUnspent() async {
422
final unspent = await Future.wait(addresses.map((address) => electrumClient
423
.getListUnspentWithAddress(address.address, networkType)
424
.then((unspent) => unspent
425
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
390
- _unspent = unspent.expand((e) => e).toList();
426
+ unspentCoins = unspent.expand((e) => e).toList();
427
+
428
+ if (unspentCoinsInfo.isEmpty) {
429
+ unspentCoins.forEach((coin) => _addCoinInfo(coin));
430
+ return;
431
+ }
432
+
433
+ if (unspentCoins.isNotEmpty) {
434
+ unspentCoins.forEach((coin) {
435
+ final coinInfoList = unspentCoinsInfo.values.where((element) =>
436
+ element.walletId.contains(id) && element.hash.contains(coin.hash));
437
+
438
+ if (coinInfoList.isNotEmpty) {
439
+ final coinInfo = coinInfoList.first;
440
+
441
+ coin.isFrozen = coinInfo.isFrozen;
442
+ coin.isSending = coinInfo.isSending;
443
+ coin.note = coinInfo.note;
444
+ } else {
445
+ _addCoinInfo(coin);
446
+ }
447
+ });
448
+ }
449
+
450
+ await _refreshUnspentCoinsInfo();
451
+ }
452
+
453
+ Future<void> _addCoinInfo(BitcoinUnspent coin) async {
454
+ final newInfo = UnspentCoinsInfo(
455
+ walletId: id,
456
+ hash: coin.hash,
457
+ isFrozen: coin.isFrozen,
458
+ isSending: coin.isSending,
459
+ note: coin.note
460
+ );
461
+
462
+ await unspentCoinsInfo.add(newInfo);
463
+ }
464
+
465
+ Future<void> _refreshUnspentCoinsInfo() async {
466
+ try {
467
+ final List<dynamic> keys = <dynamic>[];
468
+ final currentWalletUnspentCoins = unspentCoinsInfo.values
469
+ .where((element) => element.walletId.contains(id));
470
+
471
+ if (currentWalletUnspentCoins.isNotEmpty) {
472
+ currentWalletUnspentCoins.forEach((element) {
473
+ final existUnspentCoins = unspentCoins
474
+ ?.where((coin) => element.hash.contains(coin?.hash));
475
+
476
+ if (existUnspentCoins?.isEmpty ?? true) {
477
+ keys.add(element.key);
478
+ }
479
+ });
480
+ }
481
+
482
+ if (keys.isNotEmpty) {
483
+ await unspentCoinsInfo.deleteAll(keys);
484
+ }
485
+ } catch (e) {
486
+ print(e.toString());
487
+ }
488
}
489
490
Future<ElectrumTransactionInfo> fetchTransactionInfo(
@@ -438,7 +535,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
535
_scripthashesUpdateSubject[sh].listen((event) async {
536
try {
537
await _updateBalance();
441
- await _updateUnspent();
538
+ await updateUnspent();
539
await updateTransactions();
540
} catch (e) {
541
print(e.toString());
lib/bitcoin/litecoin_wallet.dart
+6
@@ -1,8 +1,10 @@
1
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
3
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
4
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
5
import 'package:cake_wallet/entities/transaction_priority.dart';
6
import 'package:flutter/foundation.dart';
7
+import 'package:hive/hive.dart';
8
import 'package:mobx/mobx.dart';
9
import 'package:cake_wallet/entities/wallet_info.dart';
10
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
@@ -21,6 +23,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
23
{@required String mnemonic,
24
@required String password,
25
@required WalletInfo walletInfo,
26
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo,
27
List<BitcoinAddressRecord> initialAddresses,
28
ElectrumBalance initialBalance,
29
int accountIndex = 0})
@@ -28,6 +31,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
31
mnemonic: mnemonic,
32
password: password,
33
walletInfo: walletInfo,
34
+ unspentCoinsInfo: unspentCoinsInfo,
35
networkType: litecoinNetwork,
36
initialAddresses: initialAddresses,
37
initialBalance: initialBalance,
@@ -36,6 +40,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
40
static Future<LitecoinWallet> open({
41
@required String name,
42
@required WalletInfo walletInfo,
43
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo,
44
@required String password,
45
}) async {
46
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
@@ -44,6 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
49
mnemonic: snp.mnemonic,
50
password: password,
51
walletInfo: walletInfo,
52
+ unspentCoinsInfo: unspentCoinsInfo,
53
initialAddresses: snp.addresses,
54
initialBalance: snp.balance,
55
accountIndex: snp.accountIndex);
lib/bitcoin/litecoin_wallet_service.dart
+9
-4
@@ -1,4 +1,5 @@
1
import 'dart:io';
2
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
3
import 'package:hive/hive.dart';
4
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
5
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
@@ -14,9 +15,10 @@ class LitecoinWalletService extends WalletService<
15
BitcoinNewWalletCredentials,
16
BitcoinRestoreWalletFromSeedCredentials,
17
BitcoinRestoreWalletFromWIFCredentials> {
17
- LitecoinWalletService(this.walletInfoSource);
18
+ LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
19
20
final Box<WalletInfo> walletInfoSource;
21
+ final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
22
23
@override
24
WalletType getType() => WalletType.litecoin;
@@ -26,7 +28,8 @@ class LitecoinWalletService extends WalletService<
28
final wallet = LitecoinWallet(
29
mnemonic: await generateMnemonic(),
30
password: credentials.password,
29
- walletInfo: credentials.walletInfo);
31
+ walletInfo: credentials.walletInfo,
32
+ unspentCoinsInfo: unspentCoinsInfoSource);
33
await wallet.save();
34
await wallet.init();
35
@@ -43,7 +46,8 @@ class LitecoinWalletService extends WalletService<
46
(info) => info.id == WalletBase.idFor(name, getType()),
47
orElse: () => null);
48
final wallet = await LitecoinWalletBase.open(
46
- password: password, name: name, walletInfo: walletInfo);
49
+ password: password, name: name, walletInfo: walletInfo,
50
+ unspentCoinsInfo: unspentCoinsInfoSource);
51
await wallet.init();
52
return wallet;
53
}
@@ -68,7 +72,8 @@ class LitecoinWalletService extends WalletService<
72
final wallet = LitecoinWallet(
73
password: credentials.password,
74
mnemonic: credentials.mnemonic,
71
- walletInfo: credentials.walletInfo);
75
+ walletInfo: credentials.walletInfo,
76
+ unspentCoinsInfo: unspentCoinsInfoSource);
77
await wallet.save();
78
await wallet.init();
79
return wallet;
lib/bitcoin/unspent_coins_info.dart
new
+32
@@ -0,0 +1,32 @@
1
+import 'package:hive/hive.dart';
2
+
3
+part 'unspent_coins_info.g.dart';
4
+
5
+@HiveType(typeId: UnspentCoinsInfo.typeId)
6
+class UnspentCoinsInfo extends HiveObject {
7
+ UnspentCoinsInfo({
8
+ this.walletId,
9
+ this.hash,
10
+ this.isFrozen,
11
+ this.isSending,
12
+ this.note});
13
+
14
+ static const typeId = 9;
15
+ static const boxName = 'Unspent';
16
+ static const boxKey = 'unspentBoxKey';
17
+
18
+ @HiveField(0)
19
+ String walletId;
20
+
21
+ @HiveField(1)
22
+ String hash;
23
+
24
+ @HiveField(2)
25
+ bool isFrozen;
26
+
27
+ @HiveField(3)
28
+ bool isSending;
29
+
30
+ @HiveField(4)
31
+ String note;
32
+}
\ No newline at end of file
lib/di.dart
+32
-11
@@ -1,5 +1,6 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
2
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
3
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
4
import 'package:cake_wallet/core/backup_service.dart';
5
import 'package:cake_wallet/core/wallet_service.dart';
6
import 'package:cake_wallet/entities/biometric_auth.dart';
@@ -130,6 +131,7 @@ Box<Template> _templates;
131
Box<ExchangeTemplate> _exchangeTemplates;
132
Box<TransactionDescription> _transactionDescriptionBox;
133
Box<Order> _ordersSource;
134
+Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
135
136
Future setup(
137
{Box<WalletInfo> walletInfoSource,
@@ -139,7 +141,8 @@ Future setup(
141
Box<Template> templates,
142
Box<ExchangeTemplate> exchangeTemplates,
143
Box<TransactionDescription> transactionDescriptionBox,
142
- Box<Order> ordersSource}) async {
144
+ Box<Order> ordersSource,
145
+ Box<UnspentCoinsInfo> unspentCoinsInfoSource}) async {
146
_walletInfoSource = walletInfoSource;
147
_nodeSource = nodeSource;
148
_contactSource = contactSource;
@@ -148,6 +151,7 @@ Future setup(
151
_exchangeTemplates = exchangeTemplates;
152
_transactionDescriptionBox = transactionDescriptionBox;
153
_ordersSource = ordersSource;
154
+ _unspentCoinsInfoSource = unspentCoinsInfoSource;
155
156
if (!_isSetupFinished) {
157
getIt.registerSingletonAsync<SharedPreferences>(
@@ -450,9 +454,11 @@ Future setup(
454
455
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
456
453
- getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
457
+ getIt.registerFactory(() =>
458
+ BitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
459
455
- getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
460
+ getIt.registerFactory(() =>
461
+ LitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
462
463
getIt.registerFactoryParam<WalletService, WalletType, void>(
464
(WalletType param1, __) {
@@ -588,20 +594,35 @@ Future setup(
594
595
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
596
591
- getIt.registerFactory(() => UnspentCoinsListViewModel());
597
+ getIt.registerFactory(() {
598
+ final wallet = getIt.get<AppStore>().wallet;
599
+
600
+ return UnspentCoinsListViewModel(
601
+ wallet: wallet,
602
+ unspentCoinsInfo: _unspentCoinsInfoSource);
603
+ });
604
605
getIt.registerFactory(() => UnspentCoinsListPage(
606
unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()
607
));
608
609
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel,
598
- UnspentCoinsItem, void>((item, _) =>
599
- UnspentCoinsDetailsViewModel(unspentCoinsItem: item));
600
-
601
- getIt.registerFactoryParam<UnspentCoinsDetailsPage,
602
- UnspentCoinsItem, void>((UnspentCoinsItem item, _) =>
603
- UnspentCoinsDetailsPage(unspentCoinsDetailsViewModel:
604
- getIt.get<UnspentCoinsDetailsViewModel>(param1: item)));
610
+ UnspentCoinsItem, UnspentCoinsListViewModel>((item, model) =>
611
+ UnspentCoinsDetailsViewModel(
612
+ unspentCoinsItem: item,
613
+ unspentCoinsListViewModel: model));
614
+
615
+ getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>(
616
+ (List args, _) {
617
+ final item = args.first as UnspentCoinsItem;
618
+ final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
619
+
620
+ return UnspentCoinsDetailsPage(
621
+ unspentCoinsDetailsViewModel:
622
+ getIt.get<UnspentCoinsDetailsViewModel>(
623
+ param1: item,
624
+ param2: unspentCoinsListViewModel));
625
+ });
626
627
_isSetupFinished = true;
628
}
lib/main.dart
+11
-1
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
2
import 'package:cake_wallet/entities/language_service.dart';
3
import 'package:cake_wallet/buy/order.dart';
4
import 'package:flutter/material.dart';
@@ -75,6 +76,10 @@ Future<void> main() async {
76
Hive.registerAdapter(OrderAdapter());
77
}
78
79
+ if (!Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
80
+ Hive.registerAdapter(UnspentCoinsInfoAdapter());
81
+ }
82
+
83
final secureStorage = FlutterSecureStorage();
84
final transactionDescriptionsBoxKey = await getEncryptionKey(
85
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
@@ -95,6 +100,8 @@ Future<void> main() async {
100
final templates = await Hive.openBox<Template>(Template.boxName);
101
final exchangeTemplates =
102
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
103
+ final unspentCoinsInfoSource =
104
+ await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
105
await initialSetup(
106
sharedPreferences: await SharedPreferences.getInstance(),
107
nodes: nodes,
@@ -102,6 +109,7 @@ Future<void> main() async {
109
contactSource: contacts,
110
tradesSource: trades,
111
ordersSource: orders,
112
+ unspentCoinsInfoSource: unspentCoinsInfoSource,
113
// fiatConvertationService: fiatConvertationService,
114
templates: templates,
115
exchangeTemplates: exchangeTemplates,
@@ -134,6 +142,7 @@ Future<void> initialSetup(
142
@required Box<Template> templates,
143
@required Box<ExchangeTemplate> exchangeTemplates,
144
@required Box<TransactionDescription> transactionDescriptions,
145
+ @required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
146
FlutterSecureStorage secureStorage,
147
int initialMigrationVersion = 15}) async {
148
LanguageService.loadLocaleList();
@@ -153,7 +162,8 @@ Future<void> initialSetup(
162
templates: templates,
163
exchangeTemplates: exchangeTemplates,
164
transactionDescriptionBox: transactionDescriptions,
156
- ordersSource: ordersSource);
165
+ ordersSource: ordersSource,
166
+ unspentCoinsInfoSource: unspentCoinsInfoSource);
167
await bootstrap(navigatorKey);
168
monero_wallet.onStartup();
169
}
lib/router.dart
+3
-1
@@ -372,10 +372,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
372
builder: (_) => getIt.get<UnspentCoinsListPage>());
373
374
case Routes.unspentCoinsDetails:
375
+ final args = settings.arguments as List;
376
+
377
return MaterialPageRoute<void>(
378
builder: (_) =>
379
getIt.get<UnspentCoinsDetailsPage>(
378
- param1: settings.arguments as UnspentCoinsItem));
380
+ param1: args));
381
382
default:
383
return MaterialPageRoute<void>(
lib/src/screens/send/send_page.dart
+2
-2
@@ -426,7 +426,7 @@ class SendPage extends BasePage {
426
),
427
),
428
)),
429
- if (sendViewModel.isBitcoinWallet) Padding(
429
+ if (sendViewModel.isElectrumWallet) Padding(
430
padding: EdgeInsets.only(top: 6),
431
child: GestureDetector(
432
onTap: () => Navigator.of(context)
@@ -436,7 +436,7 @@ class SendPage extends BasePage {
436
MainAxisAlignment.spaceBetween,
437
children: [
438
Text(
439
- 'Coin control (optional)',
439
+ S.of(context).coin_control,
440
style: TextStyle(
441
fontSize: 12,
442
fontWeight: FontWeight.w600,
lib/src/screens/unspent_coins/unspent_coins_details_page.dart
+2
-1
@@ -9,12 +9,13 @@ import 'package:cake_wallet/src/widgets/standart_list_row.dart';
9
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
10
import 'package:cake_wallet/src/screens/base_page.dart';
11
import 'package:flutter_mobx/flutter_mobx.dart';
12
+import 'package:cake_wallet/generated/i18n.dart';
13
14
class UnspentCoinsDetailsPage extends BasePage {
15
UnspentCoinsDetailsPage({this.unspentCoinsDetailsViewModel});
16
17
@override
17
- String get title => 'Unspent coins details';
18
+ String get title => S.current.unspent_coins_details_title;
19
20
final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel;
21
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
+20
-13
@@ -13,7 +13,7 @@ class UnspentCoinsListPage extends BasePage {
13
UnspentCoinsListPage({this.unspentCoinsListViewModel});
14
15
@override
16
- String get title => 'Unspent coins';
16
+ String get title => S.current.unspent_coins_title;
17
18
@override
19
Widget trailing(BuildContext context) {
@@ -77,18 +77,25 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
77
separatorBuilder: (_, __) =>
78
SizedBox(height: 15),
79
itemBuilder: (_, int index) {
80
- final item = unspentCoinsListViewModel.items[index];
81
-
82
- return GestureDetector(
83
- onTap: () =>
84
- Navigator.of(context).pushNamed(Routes.unspentCoinsDetails,
85
- arguments: item),
86
- child: UnspentCoinsListItem(
87
- address: item.address,
88
- amount: item.amount,
89
- isSending: item.isSending,
90
- onCheckBoxTap: (value) {},
91
- ));
80
+ return Observer(builder: (_) {
81
+ final item = unspentCoinsListViewModel.items[index];
82
+
83
+ return GestureDetector(
84
+ onTap: () =>
85
+ Navigator.of(context)
86
+ .pushNamed(Routes.unspentCoinsDetails,
87
+ arguments: [item, unspentCoinsListViewModel]),
88
+ child: UnspentCoinsListItem(
89
+ address: item.address,
90
+ amount: item.amount,
91
+ isSending: item.isSending,
92
+ onCheckBoxTap: item.isFrozen
93
+ ? null
94
+ : () async {
95
+ item.isSending = !item.isSending;
96
+ await unspentCoinsListViewModel
97
+ .saveUnspentCoinInfo(item);}));
98
+ });
99
}
100
)
101
)
lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart
+64
-74
@@ -1,10 +1,9 @@
1
import 'package:auto_size_text/auto_size_text.dart';
2
import 'package:cake_wallet/palette.dart';
3
-import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
3
import 'package:flutter/material.dart';
4
import 'package:flutter/cupertino.dart';
5
7
-class UnspentCoinsListItem extends StatefulWidget {
6
+class UnspentCoinsListItem extends StatelessWidget {
7
UnspentCoinsListItem({
8
@required this.address,
9
@required this.amount,
@@ -12,29 +11,6 @@ class UnspentCoinsListItem extends StatefulWidget {
11
@required this.onCheckBoxTap,
12
});
13
15
- final String address;
16
- final String amount;
17
- final bool isSending;
18
- final Function(bool) onCheckBoxTap;
19
-
20
- @override UnspentCoinsListItemState createState() =>
21
- UnspentCoinsListItemState(
22
- address: address,
23
- amount: amount,
24
- isSending: isSending,
25
- onCheckBoxTap: onCheckBoxTap
26
- );
27
-
28
-}
29
-
30
-class UnspentCoinsListItemState extends State<UnspentCoinsListItem> {
31
- UnspentCoinsListItemState({
32
- @required this.address,
33
- @required this.amount,
34
- @required this.isSending,
35
- @required this.onCheckBoxTap,
36
- }) : checkBoxValue = isSending;
37
-
14
static const amountColor = Palette.darkBlueCraiola;
15
static const addressColor = Palette.darkGray;
16
static const selectedItemColor = Palette.paleCornflowerBlue;
@@ -43,62 +19,76 @@ class UnspentCoinsListItemState extends State<UnspentCoinsListItem> {
19
final String address;
20
final String amount;
21
final bool isSending;
46
- final Function(bool) onCheckBoxTap;
47
-
48
- bool checkBoxValue;
22
+ final Function() onCheckBoxTap;
23
24
@override
25
Widget build(BuildContext context) {
52
- final itemColor = checkBoxValue? selectedItemColor : unselectedItemColor;
26
+ final itemColor = isSending? selectedItemColor : unselectedItemColor;
27
28
return Container(
55
- height: 62,
56
- padding: EdgeInsets.all(12),
57
- decoration: BoxDecoration(
58
- borderRadius: BorderRadius.all(Radius.circular(12)),
59
- color: itemColor),
60
- child: Row(
61
- mainAxisSize: MainAxisSize.max,
62
- crossAxisAlignment: CrossAxisAlignment.center,
63
- children: [
64
- Padding(
65
- padding: EdgeInsets.only(right: 12),
66
- child: StandardCheckbox(
67
- value: checkBoxValue,
68
- onChanged: (value) {
69
- onCheckBoxTap(value);
70
- checkBoxValue = value;
71
- setState(() {});
72
- }
73
- )
74
- ),
75
- Expanded(
76
- child: Column(
77
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
78
- crossAxisAlignment: CrossAxisAlignment.start,
79
- children: [
80
- AutoSizeText(
81
- amount ?? 'Amount',
82
- style: TextStyle(
83
- color: amountColor,
84
- fontSize: 16,
85
- fontWeight: FontWeight.w600
86
- ),
87
- maxLines: 1,
88
- ),
89
- AutoSizeText(
90
- address ?? 'Address',
91
- style: TextStyle(
92
- color: addressColor,
93
- fontSize: 12,
94
- ),
95
- maxLines: 1,
29
+ height: 62,
30
+ padding: EdgeInsets.all(12),
31
+ decoration: BoxDecoration(
32
+ borderRadius: BorderRadius.all(Radius.circular(12)),
33
+ color: itemColor),
34
+ child: Row(
35
+ mainAxisSize: MainAxisSize.max,
36
+ crossAxisAlignment: CrossAxisAlignment.center,
37
+ children: [
38
+ Padding(
39
+ padding: EdgeInsets.only(right: 12),
40
+ child: GestureDetector(
41
+ onTap: () => onCheckBoxTap?.call(),
42
+ child: Container(
43
+ height: 24.0,
44
+ width: 24.0,
45
+ decoration: BoxDecoration(
46
+ border: Border.all(
47
+ color: Theme.of(context)
48
+ .primaryTextTheme
49
+ .caption
50
+ .color,
51
+ width: 1.0),
52
+ borderRadius: BorderRadius.all(
53
+ Radius.circular(8.0)),
54
+ color: Theme.of(context).backgroundColor),
55
+ child: isSending
56
+ ? Icon(
57
+ Icons.check,
58
+ color: Colors.blue,
59
+ size: 20.0,
60
+ )
61
+ : Offstage(),
62
+ )
63
+ )
64
+ ),
65
+ Expanded(
66
+ child: Column(
67
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
68
+ crossAxisAlignment: CrossAxisAlignment.start,
69
+ children: [
70
+ AutoSizeText(
71
+ amount,
72
+ style: TextStyle(
73
+ color: amountColor,
74
+ fontSize: 16,
75
+ fontWeight: FontWeight.w600
76
+ ),
77
+ maxLines: 1,
78
+ ),
79
+ AutoSizeText(
80
+ address,
81
+ style: TextStyle(
82
+ color: addressColor,
83
+ fontSize: 12,
84
+ ),
85
+ maxLines: 1,
86
+ )
87
+ ]
88
)
97
- ]
89
)
99
- )
100
- ],
101
- )
90
+ ],
91
+ )
92
);
93
}
94
}
\ No newline at end of file
lib/src/widgets/standart_switch.dart
-1
@@ -1,4 +1,3 @@
1
-import 'package:cake_wallet/palette.dart';
1
import 'package:flutter/cupertino.dart';
2
import 'package:flutter/material.dart';
3
lib/view_model/send/send_view_model.dart
+2
-4
@@ -1,7 +1,6 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
3
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
4
-import 'package:cake_wallet/entities/balance_display_mode.dart';
4
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
5
import 'package:cake_wallet/entities/transaction_description.dart';
6
import 'package:cake_wallet/entities/transaction_priority.dart';
@@ -20,7 +19,6 @@ import 'package:cake_wallet/core/pending_transaction.dart';
19
import 'package:cake_wallet/core/validator.dart';
20
import 'package:cake_wallet/core/wallet_base.dart';
21
import 'package:cake_wallet/core/execution_state.dart';
23
-import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
22
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
23
import 'package:cake_wallet/monero/monero_wallet.dart';
24
import 'package:cake_wallet/monero/monero_transaction_creation_credentials.dart';
@@ -55,7 +53,7 @@ abstract class SendViewModelBase with Store {
53
_settingsStore.priority[_wallet.type] = priorities.first;
54
}
55
58
- isBitcoinWallet = _wallet is BitcoinWallet;
56
+ isElectrumWallet = _wallet is ElectrumWallet;
57
58
_setCryptoNumMaximumFractionDigits();
59
}
@@ -185,7 +183,7 @@ abstract class SendViewModelBase with Store {
183
PendingTransaction pendingTransaction;
184
185
@observable
188
- bool isBitcoinWallet;
186
+ bool isElectrumWallet;
187
188
@computed
189
String get balance => _wallet.balance.formattedAvailableBalance ?? '0.0';
lib/view_model/unspent_coins/unspent_coins_details_view_model.dart
+16
-6
@@ -3,6 +3,7 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.
3
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
4
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
5
import 'package:cake_wallet/generated/i18n.dart';
6
+import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
7
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
8
import 'package:mobx/mobx.dart';
9
@@ -12,7 +13,9 @@ class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase
13
with _$UnspentCoinsDetailsViewModel;
14
15
abstract class UnspentCoinsDetailsViewModelBase with Store {
15
- UnspentCoinsDetailsViewModelBase({this.unspentCoinsItem}) {
16
+ UnspentCoinsDetailsViewModelBase({
17
+ this.unspentCoinsItem, this.unspentCoinsListViewModel}) {
18
+
19
final amount = unspentCoinsItem.amount ?? '';
20
final address = unspentCoinsItem.address ?? '';
21
isFrozen = unspentCoinsItem.isFrozen ?? false;
@@ -20,25 +23,31 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
23
24
items = [
25
StandartListItem(
23
- title: 'Amount',
26
+ title: S.current.transaction_details_amount,
27
value: amount
28
),
29
StandartListItem(
27
- title: 'Address',
30
+ title: S.current.widgets_address,
31
value: address
32
),
33
TextFieldListItem(
34
title: S.current.note_tap_to_change,
35
value: note,
36
onSubmitted: (value) {
34
- note = value;
37
+ unspentCoinsItem.note = value;
38
+ unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
39
}),
40
UnspentCoinsSwitchItem(
37
- title: 'Freeze',
41
+ title: S.current.freeze,
42
value: '',
43
switchValue: () => isFrozen,
40
- onSwitchValueChange: (value) {
44
+ onSwitchValueChange: (value) async {
45
isFrozen = value;
46
+ unspentCoinsItem.isFrozen = value;
47
+ if (value) {
48
+ unspentCoinsItem.isSending = !value;
49
+ }
50
+ await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
51
}
52
)
53
];
@@ -51,5 +60,6 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
60
String note;
61
62
final UnspentCoinsItem unspentCoinsItem;
63
+ final UnspentCoinsListViewModel unspentCoinsListViewModel;
64
List<TransactionDetailsListItem> items;
65
}
\ No newline at end of file
lib/view_model/unspent_coins/unspent_coins_item.dart
+27
-8
@@ -1,14 +1,33 @@
1
-class UnspentCoinsItem {
2
- UnspentCoinsItem({
1
+import 'package:mobx/mobx.dart';
2
+
3
+part 'unspent_coins_item.g.dart';
4
+
5
+class UnspentCoinsItem = UnspentCoinsItemBase with _$UnspentCoinsItem;
6
+
7
+abstract class UnspentCoinsItemBase with Store {
8
+ UnspentCoinsItemBase({
9
this.address,
10
this.amount,
11
+ this.hash,
12
this.isFrozen,
13
this.note,
7
- this.isSending = true});
14
+ this.isSending});
15
+
16
+ @observable
17
+ String address;
18
+
19
+ @observable
20
+ String amount;
21
+
22
+ @observable
23
+ String hash;
24
+
25
+ @observable
26
+ bool isFrozen;
27
+
28
+ @observable
29
+ String note;
30
9
- final String address;
10
- final String amount;
11
- final bool isFrozen;
12
- final String note;
13
- final bool isSending;
31
+ @observable
32
+ bool isSending;
33
}
\ No newline at end of file
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+47
-50
@@ -1,61 +1,58 @@
1
+import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
3
+import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
4
+import 'package:cake_wallet/core/wallet_base.dart';
5
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
6
+import 'package:flutter/foundation.dart';
7
+import 'package:hive/hive.dart';
8
import 'package:mobx/mobx.dart';
9
10
part 'unspent_coins_list_view_model.g.dart';
11
6
-const List<Map<String, dynamic>> unspentCoinsMap = [
7
- <String, dynamic>{
8
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
9
- "amount" : "0.00358 BTC",
10
- "isFrozen" : true,
11
- "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
12
- <String, dynamic>{
13
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
14
- "amount" : "0.00567894 BTC",
15
- "note" : "sfjskf"},
16
- <String, dynamic>{
17
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
18
- "amount" : "0.00087 BTC",
19
- "isFrozen" : false},
20
- <String, dynamic>{
21
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
22
- "amount" : "0.00012 BTC",
23
- "isFrozen" : true,
24
- "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
25
- <String, dynamic>{
26
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
27
- "amount" : "0.00574 BTC",
28
- "note" : "sffsfsdsgs"},
29
- <String, dynamic>{
30
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
31
- "amount" : "0.000482 BTC",
32
- "isFrozen" : false},
33
- <String, dynamic>{},
34
- <String, dynamic>{
35
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
36
- "amount" : "0.00012 BTC",
37
- "isFrozen" : true,
38
- "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
39
- <String, dynamic>{
40
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
41
- "amount" : "0.00574 BTC",
42
- "note" : "sffsfsdsgs"},
43
- <String, dynamic>{
44
- "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
45
- "amount" : "0.000482 BTC",
46
- "isFrozen" : false},
47
-];
48
-
12
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
13
14
abstract class UnspentCoinsListViewModelBase with Store {
15
+ UnspentCoinsListViewModelBase({
16
+ @required WalletBase wallet,
17
+ @required Box<UnspentCoinsInfo> unspentCoinsInfo}) {
18
+ _unspentCoinsInfo = unspentCoinsInfo;
19
+ _wallet = wallet as ElectrumWallet;
20
+ _wallet.updateUnspent();
21
+ }
22
+
23
+ ElectrumWallet _wallet;
24
+ Box<UnspentCoinsInfo> _unspentCoinsInfo;
25
+
26
@computed
27
ObservableList<UnspentCoinsItem> get items =>
54
- ObservableList.of(unspentCoinsMap.map((elem) =>
55
- UnspentCoinsItem(
56
- address: elem["address"] as String,
57
- amount: elem["amount"] as String,
58
- isFrozen: elem["isFrozen"] as bool,
59
- note: elem["note"] as String
60
- )));
28
+ ObservableList.of(_wallet.unspentCoins.map((elem) {
29
+ final amount = bitcoinAmountToString(amount: elem.value) +
30
+ ' ${_wallet.currency.title}';
31
+
32
+ return UnspentCoinsItem(
33
+ address: elem.address.address,
34
+ amount: amount,
35
+ hash: elem.hash,
36
+ isFrozen: elem.isFrozen,
37
+ note: elem.note,
38
+ isSending: elem.isSending
39
+ );
40
+ }));
41
+
42
+ Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
43
+ try {
44
+ final info = _unspentCoinsInfo.values
45
+ .firstWhere((element) => element.walletId.contains(_wallet.id) &&
46
+ element.hash.contains(item.hash));
47
+
48
+ info.isFrozen = item.isFrozen;
49
+ info.isSending = item.isSending;
50
+ info.note = item.note;
51
+
52
+ await info.save();
53
+ await _wallet.updateUnspent();
54
+ } catch (e) {
55
+ print(e.toString());
56
+ }
57
+ }
58
}
\ No newline at end of file
res/values/strings_de.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
484
485
"outdated_electrum_wallet_receive_warning": "Wenn diese Brieftasche einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Brieftasche ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher",
486
- "do_not_show_me": "Zeig mir das nicht noch einmal"
486
+ "do_not_show_me": "Zeig mir das nicht noch einmal",
487
+
488
+ "unspent_coins_title" : "Nicht ausgegebene Münzen",
489
+ "unspent_coins_details_title" : "Details zu nicht ausgegebenen Münzen",
490
+ "freeze" : "Einfrieren",
491
+ "coin_control" : "Münzkontrolle (optional)"
492
}
res/values/strings_en.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "If this wallet has a 12-word seed and was created in Cake, DO NOT deposit Bitcoin into this wallet. Any BTC transferred to this wallet may be lost. Create a new 24-word wallet (tap the menu at the top right, select Wallets, choose Create New Wallet, then select Bitcoin) and IMMEDIATELY move your BTC there. New (24-word) BTC wallets from Cake are secure",
486
- "do_not_show_me": "Do not show me this again"
486
+ "do_not_show_me": "Do not show me this again",
487
+
488
+ "unspent_coins_title" : "Unspent coins",
489
+ "unspent_coins_details_title" : "Unspent coins details",
490
+ "freeze" : "Freeze",
491
+ "coin_control" : "Coin control (optional)"
492
}
\ No newline at end of file
res/values/strings_es.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras",
486
- "do_not_show_me": "no me muestres esto otra vez"
486
+ "do_not_show_me": "no me muestres esto otra vez",
487
+
488
+ "unspent_coins_title" : "Monedas no gastadas",
489
+ "unspent_coins_details_title" : "Detalles de monedas no gastadas",
490
+ "freeze" : "Congelar",
491
+ "coin_control" : "Control de monedas (opcional)"
492
}
\ No newline at end of file
res/values/strings_hi.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "अगर इस वॉलेट में 12 शब्दों का बीज है और इसे केक में बनाया गया है, तो इस वॉलेट में बिटकॉइन जमा न करें। इस वॉलेट में स्थानांतरित किया गया कोई भी बीटीसी खो सकता है। एक नया 24-शब्द वॉलेट बनाएं (ऊपर दाईं ओर स्थित मेनू पर टैप करें, वॉलेट चुनें, नया वॉलेट बनाएं चुनें, फिर बिटकॉइन चुनें) और तुरंत अपना बीटीसी वहां ले जाएं। केक से नए (24-शब्द) बीटीसी वॉलेट सुरक्षित हैं",
486
- "do_not_show_me": "मुझे यह फिर न दिखाएं"
486
+ "do_not_show_me": "मुझे यह फिर न दिखाएं",
487
+
488
+ "unspent_coins_title" : "खर्च न किए गए सिक्के",
489
+ "unspent_coins_details_title" : "अव्ययित सिक्कों का विवरण",
490
+ "freeze" : "फ्रीज",
491
+ "coin_control" : "सिक्का नियंत्रण (वैकल्पिक)"
492
}
\ No newline at end of file
res/values/strings_hr.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Ako ovaj novčanik sadrži sjeme od 12 riječi i stvoren je u Torti, NEMOJTE polagati Bitcoin u ovaj novčanik. Bilo koji BTC prebačen u ovaj novčanik može se izgubiti. Stvorite novi novčanik od 24 riječi (taknite izbornik u gornjem desnom dijelu, odaberite Novčanici, odaberite Stvori novi novčanik, a zatim odaberite Bitcoin) i ODMAH premjestite svoj BTC tamo. Novi BTC novčanici (s 24 riječi) tvrtke Cake sigurni su",
486
- "do_not_show_me": "Ne pokazuj mi ovo više"
486
+ "do_not_show_me": "Ne pokazuj mi ovo više",
487
+
488
+ "unspent_coins_title" : "Nepotrošeni novčići",
489
+ "unspent_coins_details_title" : "Nepotrošeni detalji o novčićima",
490
+ "freeze" : "Zamrznuti",
491
+ "coin_control" : "Kontrola novca (nije obavezno)"
492
}
\ No newline at end of file
res/values/strings_it.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Se questo portafoglio ha un seme di 12 parole ed è stato creato in Cake, NON depositare Bitcoin in questo portafoglio. Qualsiasi BTC trasferito su questo portafoglio potrebbe andare perso. Crea un nuovo portafoglio di 24 parole (tocca il menu in alto a destra, seleziona Portafogli, scegli Crea nuovo portafoglio, quindi seleziona Bitcoin) e sposta IMMEDIATAMENTE lì il tuo BTC. I nuovi portafogli BTC (24 parole) di Cake sono sicuri",
486
- "do_not_show_me": "Non mostrarmelo di nuovo"
486
+ "do_not_show_me": "Non mostrarmelo di nuovo",
487
+
488
+ "unspent_coins_title" : "Monete non spese",
489
+ "unspent_coins_details_title" : "Dettagli sulle monete non spese",
490
+ "freeze" : "Congelare",
491
+ "coin_control" : "Controllo monete (opzionale)"
492
}
\ No newline at end of file
res/values/strings_ja.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "このウォレットに 12 ワードのシードがあり、Cake で作成された場合、このウォレットにビットコインを入金しないでください。 このウォレットに転送された BTC は失われる可能性があります。 新しい 24 ワードのウォレットを作成し (右上のメニューをタップし、[ウォレット]、[新しいウォレットの作成]、[ビットコイン] の順に選択)、すぐに BTC をそこに移動します。 Cake の新しい (24 ワード) BTC ウォレットは安全です",
486
- "do_not_show_me": "また僕にこれを見せないでください"
486
+ "do_not_show_me": "また僕にこれを見せないでください",
487
+
488
+ "unspent_coins_title" : "未使用のコイン",
489
+ "unspent_coins_details_title" : "未使用のコインの詳細",
490
+ "freeze" : "氷結",
491
+ "coin_control" : "コインコントロール(オプション)"
492
}
\ No newline at end of file
res/values/strings_ko.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "금액은 다음보다 크거나 같아야합니다 ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "이 지갑에 12 단어 시드가 있고 Cake에서 생성 된 경우이 지갑에 비트 코인을 입금하지 마십시오. 이 지갑으로 전송 된 모든 BTC는 손실 될 수 있습니다. 새로운 24 단어 지갑을 생성하고 (오른쪽 상단의 메뉴를 탭하고 지갑을 선택한 다음 새 지갑 생성을 선택한 다음 비트 코인을 선택하십시오) 즉시 BTC를 그곳으로 이동하십시오. Cake의 새로운 (24 단어) BTC 지갑은 안전합니다",
486
- "do_not_show_me": "나를 다시 표시하지 않음"
486
+ "do_not_show_me": "나를 다시 표시하지 않음",
487
+
488
+ "unspent_coins_title" : "사용하지 않은 동전",
489
+ "unspent_coins_details_title" : "사용하지 않은 동전 세부 정보",
490
+ "freeze" : "얼다",
491
+ "coin_control" : "코인 제어 (옵션)"
492
}
\ No newline at end of file
res/values/strings_nl.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Waarde van het bedrag moet meer of gelijk zijn aan ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Als deze portemonnee een seed van 12 woorden heeft en is gemaakt in Cake, stort dan GEEN Bitcoin in deze portemonnee. Elke BTC die naar deze portemonnee is overgebracht, kan verloren gaan. Maak een nieuwe portemonnee van 24 woorden (tik op het menu rechtsboven, selecteer Portefeuilles, kies Nieuwe portemonnee maken en selecteer vervolgens Bitcoin) en verplaats je BTC ONMIDDELLIJK daar. Nieuwe (24-woorden) BTC-portefeuilles van Cake zijn veilig",
486
- "do_not_show_me": "laat me dit niet opnieuw zien"
486
+ "do_not_show_me": "laat me dit niet opnieuw zien",
487
+
488
+ "unspent_coins_title" : "Ongebruikte munten",
489
+ "unspent_coins_details_title" : "Details van niet-uitgegeven munten",
490
+ "freeze" : "Bevriezen",
491
+ "coin_control" : "Muntcontrole (optioneel)"
492
}
\ No newline at end of file
res/values/strings_pl.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Wartość kwoty musi być większa lub równa ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Jeśli ten portfel ma 12-wyrazowy seed i został utworzony w Cake, NIE Wpłacaj Bitcoina do tego portfela. Wszelkie BTC przeniesione do tego portfela mogą zostać utracone. Utwórz nowy portfel z 24 słowami (dotknij menu w prawym górnym rogu, wybierz Portfele, wybierz Utwórz nowy portfel, a następnie Bitcoin) i NATYCHMIAST przenieś tam swoje BTC. Nowe (24 słowa) portfele BTC firmy Cake są bezpieczne",
486
- "do_not_show_me": "Nie pokazuj mi tego ponownie"
486
+ "do_not_show_me": "Nie pokazuj mi tego ponownie",
487
+
488
+ "unspent_coins_title" : "Niewydane monety",
489
+ "unspent_coins_details_title" : "Szczegóły niewydanych monet",
490
+ "freeze" : "Zamrażać",
491
+ "coin_control" : "Kontrola monet (opcjonalnie)"
492
}
\ No newline at end of file
res/values/strings_pt.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "O valor do montante deve ser maior ou igual a ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Se esta carteira tiver uma semente de 12 palavras e foi criada no Cake, NÃO deposite Bitcoin nesta carteira. Qualquer BTC transferido para esta carteira pode ser perdido. Crie uma nova carteira de 24 palavras (toque no menu no canto superior direito, selecione Carteiras, escolha Criar Nova Carteira e selecione Bitcoin) e mova IMEDIATAMENTE seu BTC para lá. As novas carteiras BTC (24 palavras) da Cake são seguras",
486
- "do_not_show_me": "não me mostre isso novamente"
486
+ "do_not_show_me": "não me mostre isso novamente",
487
+
488
+ "unspent_coins_title" : "Moedas não gastas",
489
+ "unspent_coins_details_title" : "Detalhes de moedas não gastas",
490
+ "freeze" : "Congelar",
491
+ "coin_control" : "Controle de moedas (opcional)"
492
}
\ No newline at end of file
res/values/strings_ru.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Сумма должна быть больше или равна ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Если этот кошелек имеет мнемоническую фразу из 12 слов и был создан в Cake, НЕ переводите биткойны на этот кошелек. Любые BTC, переведенные на этот кошелек, могут быть потеряны. Создайте новый кошелек с мнемоническои фразы из 24 слов (коснитесь меню в правом верхнем углу, выберите «Кошельки», выберите «Создать новый кошелек», затем выберите «Bitcoin») и НЕМЕДЛЕННО переведите туда свои BTC. Новые (24 слова) кошельки BTC от Cake безопасны",
486
- "do_not_show_me": "Не показывай мне это больше"
486
+ "do_not_show_me": "Не показывай мне это больше",
487
+
488
+ "unspent_coins_title" : "Неизрасходованные монеты",
489
+ "unspent_coins_details_title" : "Сведения о неизрасходованных монетах",
490
+ "freeze" : "Заморозить",
491
+ "coin_control" : "Контроль монет (необязательно)"
492
}
\ No newline at end of file
res/values/strings_uk.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "Значення суми має бути більшим або дорівнювати ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "Якщо цей гаманець має мнемонічну фразу з 12 слів і був створений у Cake, НЕ переводьте біткойни на цей гаманець. Будь-які BTC, переведений на цей гаманець, можуть бути втраченими. Створіть новий гаманець з мнемонічною фразою з 24 слів (торкніться меню у верхньому правому куті, виберіть Гаманці, виберіть Створити новий гаманець, потім виберіть Bitcoin) і НЕГАЙНО переведіть туди свії BTC. Нові (з мнемонічною фразою з 24 слів) гаманці BTC від Cake надійно захищені",
486
- "do_not_show_me": "Не показуй мені це знову"
486
+ "do_not_show_me": "Не показуй мені це знову",
487
+
488
+ "unspent_coins_title" : "Невитрачені монети",
489
+ "unspent_coins_details_title" : "Відомості про невитрачені монети",
490
+ "freeze" : "Заморозити",
491
+ "coin_control" : "Контроль монет (необов’язково)"
492
}
\ No newline at end of file
res/values/strings_zh.arb
+6
-1
@@ -483,5 +483,10 @@
483
"moonpay_alert_text" : "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}",
484
485
"outdated_electrum_wallet_receive_warning": "如果这个钱包有一个 12 字的种子并且是在 Cake 中创建的,不要将比特币存入这个钱包。 任何转移到此钱包的 BTC 都可能丢失。 创建一个新的 24 字钱包(点击右上角的菜单,选择钱包,选择创建新钱包,然后选择比特币)并立即将您的 BTC 移到那里。 Cake 的新(24 字)BTC 钱包是安全的",
486
- "do_not_show_me": "不再提示"
486
+ "do_not_show_me": "不再提示",
487
+
488
+ "unspent_coins_title" : "未使用的硬幣",
489
+ "unspent_coins_details_title" : "未使用代幣詳情",
490
+ "freeze" : "凍結",
491
+ "coin_control" : "硬幣控制(可選)"
492
}
\ No newline at end of file