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

This commit is contained in:
OleksandrSobol
2021-07-05 16:52:24 +03:00
parent 3f81f708e5
commit 20e0c830cf
34 changed files with 525 additions and 238 deletions
@@ -1,4 +1,10 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
class BitcoinTransactionWrongBalanceException implements Exception {
BitcoinTransactionWrongBalanceException(this.currency);
final CryptoCurrency currency;
@override
String toString() => 'Wrong balance. Not enough BTC on your balance.';
String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.';
}
+7 -1
View File
@@ -1,7 +1,10 @@
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
class BitcoinUnspent {
BitcoinUnspent(this.address, this.hash, this.value, this.vout);
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
: isSending = true,
isFrozen = false,
note = '';
factory BitcoinUnspent.fromJSON(
BitcoinAddressRecord address, Map<String, dynamic> json) =>
@@ -15,4 +18,7 @@ class BitcoinUnspent {
bool get isP2wpkh =>
address.address.startsWith('bc') || address.address.startsWith('ltc');
bool isSending;
bool isFrozen;
String note;
}
+6
View File
@@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
@@ -17,6 +19,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
{@required String mnemonic,
@required String password,
@required WalletInfo walletInfo,
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord> initialAddresses,
ElectrumBalance initialBalance,
int accountIndex = 0})
@@ -24,6 +27,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
@@ -32,6 +36,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
static Future<BitcoinWallet> open({
@required String name,
@required WalletInfo walletInfo,
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
@required String password,
}) async {
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
@@ -40,6 +45,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
accountIndex: snp.accountIndex);
+9 -4
View File
@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
@@ -14,9 +15,10 @@ class BitcoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
BitcoinWalletService(this.walletInfoSource);
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
@override
WalletType getType() => WalletType.bitcoin;
@@ -26,7 +28,8 @@ class BitcoinWalletService extends WalletService<
final wallet = BitcoinWallet(
mnemonic: await generateMnemonic(),
password: credentials.password,
walletInfo: credentials.walletInfo);
walletInfo: credentials.walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
return wallet;
@@ -42,7 +45,8 @@ class BitcoinWalletService extends WalletService<
(info) => info.id == WalletBase.idFor(name, getType()),
orElse: () => null);
final wallet = await BitcoinWalletBase.open(
password: password, name: name, walletInfo: walletInfo);
password: password, name: name, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
@@ -67,7 +71,8 @@ class BitcoinWalletService extends WalletService<
final wallet = BitcoinWallet(
password: credentials.password,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo);
walletInfo: credentials.walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
return wallet;
+141 -44
View File
@@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:flutter/foundation.dart';
@@ -38,6 +40,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
ElectrumWalletBase(
{@required String password,
@required WalletInfo walletInfo,
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
@required List<BitcoinAddressRecord> initialAddresses,
@required this.networkType,
@required this.mnemonic,
@@ -59,9 +62,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
this.unspentCoinsInfo = unspentCoinsInfo;
transactionHistory =
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
_unspent = [];
unspentCoins = [];
_scripthashesUpdateSubject = {};
}
@@ -72,6 +76,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final String mnemonic;
ElectrumClient electrumClient;
Box<UnspentCoinsInfo> unspentCoinsInfo;
@override
@observable
@@ -103,7 +108,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
final String _password;
List<BitcoinUnspent> _unspent;
List<BitcoinUnspent> unspentCoins;
List<int> _feeRates;
int _accountIndex;
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
@@ -178,10 +183,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> startSync() async {
try {
syncStatus = StartingSyncStatus();
updateTransactions();
await updateTransactions();
_subscribeForUpdates();
await _updateBalance();
await _updateUnspent();
await updateUnspent();
_feeRates = await electrumClient.feeRates();
Timer.periodic(const Duration(minutes: 1),
@@ -218,33 +223,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final allAmountFee =
calculateEstimatedFee(transactionCredentials.priority, null);
final allAmount = balance.confirmed - allAmountFee;
var fee = 0;
final credentialsAmount = transactionCredentials.amount != null
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: 0;
final amount = transactionCredentials.amount == null ||
allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
final txb = bitcoin.TransactionBuilder(network: networkType);
final changeAddress = address;
var leftAmount = amount;
var totalInputAmount = 0;
var allInputsAmount = 0;
if (_unspent.isEmpty) {
await _updateUnspent();
if (unspentCoins.isEmpty) {
await updateUnspent();
}
for (final utx in _unspent) {
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
break;
for (final utx in unspentCoins) {
if (utx.isSending) {
allInputsAmount += utx.value;
inputs.add(utx);
}
}
@@ -252,18 +240,57 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
throw BitcoinTransactionNoInputsException();
}
final totalAmount = amount + fee;
fee = transactionCredentials.amount != null
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
amount == allAmount ? 1 : 2)
: allAmountFee;
final allAmountFee =
feeAmountForPriority(transactionCredentials.priority, inputs.length, 1);
final allAmount = allInputsAmount - allAmountFee;
if (totalAmount > balance.confirmed) {
throw BitcoinTransactionWrongBalanceException();
final credentialsAmount = transactionCredentials.amount != null
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: 0;
final amount = transactionCredentials.amount == null ||
allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
final fee = transactionCredentials.amount == null || amount == allAmount
? allAmountFee
: calculateEstimatedFee(transactionCredentials.priority, amount);
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency);
}
if (amount <= 0 || totalInputAmount < amount) {
throw BitcoinTransactionWrongBalanceException();
final totalAmount = amount + fee;
if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final txb = bitcoin.TransactionBuilder(network: networkType);
final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0;
inputs.clear();
for (final utx in unspentCoins) {
if (utx.isSending) {
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
break;
}
}
}
if (inputs.isEmpty) {
throw BitcoinTransactionNoInputsException();
}
if (amount <= 0 || totalInputAmount < totalAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
txb.setVersion(1);
@@ -338,17 +365,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
if (amount != null) {
int totalValue = 0;
for (final input in _unspent) {
for (final input in unspentCoins) {
if (totalValue >= amount) {
break;
}
totalValue += input.value;
inputsCount += 1;
if (input.isSending) {
totalValue += input.value;
inputsCount += 1;
}
}
if (totalValue < amount) return 0;
} else {
inputsCount = _unspent.length;
for (final input in unspentCoins) {
if (input.isSending) {
inputsCount += 1;
}
}
}
// If send all, then we have no change value
return feeAmountForPriority(
priority, inputsCount, amount != null ? 2 : 1);
@@ -382,12 +418,73 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<String> makePath() async =>
pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> _updateUnspent() async {
Future<void> updateUnspent() async {
final unspent = await Future.wait(addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
_unspent = unspent.expand((e) => e).toList();
unspentCoins = unspent.expand((e) => e).toList();
if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin));
return;
}
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) && element.hash.contains(coin.hash));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note;
} else {
_addCoinInfo(coin);
}
});
}
await _refreshUnspentCoinsInfo();
}
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
final newInfo = UnspentCoinsInfo(
walletId: id,
hash: coin.hash,
isFrozen: coin.isFrozen,
isSending: coin.isSending,
note: coin.note
);
await unspentCoinsInfo.add(newInfo);
}
Future<void> _refreshUnspentCoinsInfo() async {
try {
final List<dynamic> keys = <dynamic>[];
final currentWalletUnspentCoins = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id));
if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) {
final existUnspentCoins = unspentCoins
?.where((coin) => element.hash.contains(coin?.hash));
if (existUnspentCoins?.isEmpty ?? true) {
keys.add(element.key);
}
});
}
if (keys.isNotEmpty) {
await unspentCoinsInfo.deleteAll(keys);
}
} catch (e) {
print(e.toString());
}
}
Future<ElectrumTransactionInfo> fetchTransactionInfo(
@@ -438,7 +535,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
_scripthashesUpdateSubject[sh].listen((event) async {
try {
await _updateBalance();
await _updateUnspent();
await updateUnspent();
await updateTransactions();
} catch (e) {
print(e.toString());
+6
View File
@@ -1,8 +1,10 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
@@ -21,6 +23,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
{@required String mnemonic,
@required String password,
@required WalletInfo walletInfo,
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord> initialAddresses,
ElectrumBalance initialBalance,
int accountIndex = 0})
@@ -28,6 +31,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: litecoinNetwork,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
@@ -36,6 +40,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
static Future<LitecoinWallet> open({
@required String name,
@required WalletInfo walletInfo,
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
@required String password,
}) async {
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
@@ -44,6 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
accountIndex: snp.accountIndex);
+9 -4
View File
@@ -1,4 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
@@ -14,9 +15,10 @@ class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
LitecoinWalletService(this.walletInfoSource);
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
@override
WalletType getType() => WalletType.litecoin;
@@ -26,7 +28,8 @@ class LitecoinWalletService extends WalletService<
final wallet = LitecoinWallet(
mnemonic: await generateMnemonic(),
password: credentials.password,
walletInfo: credentials.walletInfo);
walletInfo: credentials.walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
@@ -43,7 +46,8 @@ class LitecoinWalletService extends WalletService<
(info) => info.id == WalletBase.idFor(name, getType()),
orElse: () => null);
final wallet = await LitecoinWalletBase.open(
password: password, name: name, walletInfo: walletInfo);
password: password, name: name, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
@@ -68,7 +72,8 @@ class LitecoinWalletService extends WalletService<
final wallet = LitecoinWallet(
password: credentials.password,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo);
walletInfo: credentials.walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
return wallet;
+32
View File
@@ -0,0 +1,32 @@
import 'package:hive/hive.dart';
part 'unspent_coins_info.g.dart';
@HiveType(typeId: UnspentCoinsInfo.typeId)
class UnspentCoinsInfo extends HiveObject {
UnspentCoinsInfo({
this.walletId,
this.hash,
this.isFrozen,
this.isSending,
this.note});
static const typeId = 9;
static const boxName = 'Unspent';
static const boxKey = 'unspentBoxKey';
@HiveField(0)
String walletId;
@HiveField(1)
String hash;
@HiveField(2)
bool isFrozen;
@HiveField(3)
bool isSending;
@HiveField(4)
String note;
}
+31 -10
View File
@@ -1,5 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/entities/biometric_auth.dart';
@@ -130,6 +131,7 @@ Box<Template> _templates;
Box<ExchangeTemplate> _exchangeTemplates;
Box<TransactionDescription> _transactionDescriptionBox;
Box<Order> _ordersSource;
Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
Future setup(
{Box<WalletInfo> walletInfoSource,
@@ -139,7 +141,8 @@ Future setup(
Box<Template> templates,
Box<ExchangeTemplate> exchangeTemplates,
Box<TransactionDescription> transactionDescriptionBox,
Box<Order> ordersSource}) async {
Box<Order> ordersSource,
Box<UnspentCoinsInfo> unspentCoinsInfoSource}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_contactSource = contactSource;
@@ -148,6 +151,7 @@ Future setup(
_exchangeTemplates = exchangeTemplates;
_transactionDescriptionBox = transactionDescriptionBox;
_ordersSource = ordersSource;
_unspentCoinsInfoSource = unspentCoinsInfoSource;
if (!_isSetupFinished) {
getIt.registerSingletonAsync<SharedPreferences>(
@@ -450,9 +454,11 @@ Future setup(
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
getIt.registerFactory(() =>
BitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
getIt.registerFactory(() =>
LitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
getIt.registerFactoryParam<WalletService, WalletType, void>(
(WalletType param1, __) {
@@ -588,20 +594,35 @@ Future setup(
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() => UnspentCoinsListViewModel());
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return UnspentCoinsListViewModel(
wallet: wallet,
unspentCoinsInfo: _unspentCoinsInfoSource);
});
getIt.registerFactory(() => UnspentCoinsListPage(
unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()
));
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel,
UnspentCoinsItem, void>((item, _) =>
UnspentCoinsDetailsViewModel(unspentCoinsItem: item));
UnspentCoinsItem, UnspentCoinsListViewModel>((item, model) =>
UnspentCoinsDetailsViewModel(
unspentCoinsItem: item,
unspentCoinsListViewModel: model));
getIt.registerFactoryParam<UnspentCoinsDetailsPage,
UnspentCoinsItem, void>((UnspentCoinsItem item, _) =>
UnspentCoinsDetailsPage(unspentCoinsDetailsViewModel:
getIt.get<UnspentCoinsDetailsViewModel>(param1: item)));
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>(
(List args, _) {
final item = args.first as UnspentCoinsItem;
final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
return UnspentCoinsDetailsPage(
unspentCoinsDetailsViewModel:
getIt.get<UnspentCoinsDetailsViewModel>(
param1: item,
param2: unspentCoinsListViewModel));
});
_isSetupFinished = true;
}
+11 -1
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:flutter/material.dart';
@@ -75,6 +76,10 @@ Future<void> main() async {
Hive.registerAdapter(OrderAdapter());
}
if (!Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
@@ -95,6 +100,8 @@ Future<void> main() async {
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final unspentCoinsInfoSource =
await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
@@ -102,6 +109,7 @@ Future<void> main() async {
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
@@ -134,6 +142,7 @@ Future<void> initialSetup(
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
@required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
FlutterSecureStorage secureStorage,
int initialMigrationVersion = 15}) async {
LanguageService.loadLocaleList();
@@ -153,7 +162,8 @@ Future<void> initialSetup(
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions,
ordersSource: ordersSource);
ordersSource: ordersSource,
unspentCoinsInfoSource: unspentCoinsInfoSource);
await bootstrap(navigatorKey);
monero_wallet.onStartup();
}
+3 -1
View File
@@ -372,10 +372,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<UnspentCoinsListPage>());
case Routes.unspentCoinsDetails:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<UnspentCoinsDetailsPage>(
param1: settings.arguments as UnspentCoinsItem));
param1: args));
default:
return MaterialPageRoute<void>(
+2 -2
View File
@@ -426,7 +426,7 @@ class SendPage extends BasePage {
),
),
)),
if (sendViewModel.isBitcoinWallet) Padding(
if (sendViewModel.isElectrumWallet) Padding(
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
onTap: () => Navigator.of(context)
@@ -436,7 +436,7 @@ class SendPage extends BasePage {
MainAxisAlignment.spaceBetween,
children: [
Text(
'Coin control (optional)',
S.of(context).coin_control,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@@ -9,12 +9,13 @@ import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class UnspentCoinsDetailsPage extends BasePage {
UnspentCoinsDetailsPage({this.unspentCoinsDetailsViewModel});
@override
String get title => 'Unspent coins details';
String get title => S.current.unspent_coins_details_title;
final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel;
@@ -13,7 +13,7 @@ class UnspentCoinsListPage extends BasePage {
UnspentCoinsListPage({this.unspentCoinsListViewModel});
@override
String get title => 'Unspent coins';
String get title => S.current.unspent_coins_title;
@override
Widget trailing(BuildContext context) {
@@ -77,18 +77,25 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
separatorBuilder: (_, __) =>
SizedBox(height: 15),
itemBuilder: (_, int index) {
final item = unspentCoinsListViewModel.items[index];
return Observer(builder: (_) {
final item = unspentCoinsListViewModel.items[index];
return GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(Routes.unspentCoinsDetails,
arguments: item),
child: UnspentCoinsListItem(
address: item.address,
amount: item.amount,
isSending: item.isSending,
onCheckBoxTap: (value) {},
));
return GestureDetector(
onTap: () =>
Navigator.of(context)
.pushNamed(Routes.unspentCoinsDetails,
arguments: [item, unspentCoinsListViewModel]),
child: UnspentCoinsListItem(
address: item.address,
amount: item.amount,
isSending: item.isSending,
onCheckBoxTap: item.isFrozen
? null
: () async {
item.isSending = !item.isSending;
await unspentCoinsListViewModel
.saveUnspentCoinInfo(item);}));
});
}
)
)
@@ -1,10 +1,9 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class UnspentCoinsListItem extends StatefulWidget {
class UnspentCoinsListItem extends StatelessWidget {
UnspentCoinsListItem({
@required this.address,
@required this.amount,
@@ -12,29 +11,6 @@ class UnspentCoinsListItem extends StatefulWidget {
@required this.onCheckBoxTap,
});
final String address;
final String amount;
final bool isSending;
final Function(bool) onCheckBoxTap;
@override UnspentCoinsListItemState createState() =>
UnspentCoinsListItemState(
address: address,
amount: amount,
isSending: isSending,
onCheckBoxTap: onCheckBoxTap
);
}
class UnspentCoinsListItemState extends State<UnspentCoinsListItem> {
UnspentCoinsListItemState({
@required this.address,
@required this.amount,
@required this.isSending,
@required this.onCheckBoxTap,
}) : checkBoxValue = isSending;
static const amountColor = Palette.darkBlueCraiola;
static const addressColor = Palette.darkGray;
static const selectedItemColor = Palette.paleCornflowerBlue;
@@ -43,62 +19,76 @@ class UnspentCoinsListItemState extends State<UnspentCoinsListItem> {
final String address;
final String amount;
final bool isSending;
final Function(bool) onCheckBoxTap;
bool checkBoxValue;
final Function() onCheckBoxTap;
@override
Widget build(BuildContext context) {
final itemColor = checkBoxValue? selectedItemColor : unselectedItemColor;
final itemColor = isSending? selectedItemColor : unselectedItemColor;
return Container(
height: 62,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: itemColor),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(right: 12),
child: StandardCheckbox(
value: checkBoxValue,
onChanged: (value) {
onCheckBoxTap(value);
checkBoxValue = value;
setState(() {});
}
)
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
amount ?? 'Amount',
style: TextStyle(
color: amountColor,
fontSize: 16,
fontWeight: FontWeight.w600
),
maxLines: 1,
),
AutoSizeText(
address ?? 'Address',
style: TextStyle(
color: addressColor,
fontSize: 12,
),
maxLines: 1,
height: 62,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: itemColor),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(right: 12),
child: GestureDetector(
onTap: () => onCheckBoxTap?.call(),
child: Container(
height: 24.0,
width: 24.0,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
width: 1.0),
borderRadius: BorderRadius.all(
Radius.circular(8.0)),
color: Theme.of(context).backgroundColor),
child: isSending
? Icon(
Icons.check,
color: Colors.blue,
size: 20.0,
)
: Offstage(),
)
)
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
amount,
style: TextStyle(
color: amountColor,
fontSize: 16,
fontWeight: FontWeight.w600
),
maxLines: 1,
),
AutoSizeText(
address,
style: TextStyle(
color: addressColor,
fontSize: 12,
),
maxLines: 1,
)
]
)
]
)
)
],
)
],
)
);
}
}
-1
View File
@@ -1,4 +1,3 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
+2 -4
View File
@@ -1,7 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/transaction_priority.dart';
@@ -20,7 +19,6 @@ import 'package:cake_wallet/core/pending_transaction.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/monero/monero_transaction_creation_credentials.dart';
@@ -55,7 +53,7 @@ abstract class SendViewModelBase with Store {
_settingsStore.priority[_wallet.type] = priorities.first;
}
isBitcoinWallet = _wallet is BitcoinWallet;
isElectrumWallet = _wallet is ElectrumWallet;
_setCryptoNumMaximumFractionDigits();
}
@@ -185,7 +183,7 @@ abstract class SendViewModelBase with Store {
PendingTransaction pendingTransaction;
@observable
bool isBitcoinWallet;
bool isElectrumWallet;
@computed
String get balance => _wallet.balance.formattedAvailableBalance ?? '0.0';
@@ -3,6 +3,7 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
import 'package:mobx/mobx.dart';
@@ -12,7 +13,9 @@ class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase
with _$UnspentCoinsDetailsViewModel;
abstract class UnspentCoinsDetailsViewModelBase with Store {
UnspentCoinsDetailsViewModelBase({this.unspentCoinsItem}) {
UnspentCoinsDetailsViewModelBase({
this.unspentCoinsItem, this.unspentCoinsListViewModel}) {
final amount = unspentCoinsItem.amount ?? '';
final address = unspentCoinsItem.address ?? '';
isFrozen = unspentCoinsItem.isFrozen ?? false;
@@ -20,25 +23,31 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
items = [
StandartListItem(
title: 'Amount',
title: S.current.transaction_details_amount,
value: amount
),
StandartListItem(
title: 'Address',
title: S.current.widgets_address,
value: address
),
TextFieldListItem(
title: S.current.note_tap_to_change,
value: note,
onSubmitted: (value) {
note = value;
unspentCoinsItem.note = value;
unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
}),
UnspentCoinsSwitchItem(
title: 'Freeze',
title: S.current.freeze,
value: '',
switchValue: () => isFrozen,
onSwitchValueChange: (value) {
onSwitchValueChange: (value) async {
isFrozen = value;
unspentCoinsItem.isFrozen = value;
if (value) {
unspentCoinsItem.isSending = !value;
}
await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
}
)
];
@@ -51,5 +60,6 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
String note;
final UnspentCoinsItem unspentCoinsItem;
final UnspentCoinsListViewModel unspentCoinsListViewModel;
List<TransactionDetailsListItem> items;
}
@@ -1,14 +1,33 @@
class UnspentCoinsItem {
UnspentCoinsItem({
import 'package:mobx/mobx.dart';
part 'unspent_coins_item.g.dart';
class UnspentCoinsItem = UnspentCoinsItemBase with _$UnspentCoinsItem;
abstract class UnspentCoinsItemBase with Store {
UnspentCoinsItemBase({
this.address,
this.amount,
this.hash,
this.isFrozen,
this.note,
this.isSending = true});
this.isSending});
final String address;
final String amount;
final bool isFrozen;
final String note;
final bool isSending;
@observable
String address;
@observable
String amount;
@observable
String hash;
@observable
bool isFrozen;
@observable
String note;
@observable
bool isSending;
}
@@ -1,61 +1,58 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'unspent_coins_list_view_model.g.dart';
const List<Map<String, dynamic>> unspentCoinsMap = [
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00358 BTC",
"isFrozen" : true,
"note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00567894 BTC",
"note" : "sfjskf"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00087 BTC",
"isFrozen" : false},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00012 BTC",
"isFrozen" : true,
"note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00574 BTC",
"note" : "sffsfsdsgs"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.000482 BTC",
"isFrozen" : false},
<String, dynamic>{},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00012 BTC",
"isFrozen" : true,
"note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.00574 BTC",
"note" : "sffsfsdsgs"},
<String, dynamic>{
"address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr",
"amount" : "0.000482 BTC",
"isFrozen" : false},
];
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
abstract class UnspentCoinsListViewModelBase with Store {
UnspentCoinsListViewModelBase({
@required WalletBase wallet,
@required Box<UnspentCoinsInfo> unspentCoinsInfo}) {
_unspentCoinsInfo = unspentCoinsInfo;
_wallet = wallet as ElectrumWallet;
_wallet.updateUnspent();
}
ElectrumWallet _wallet;
Box<UnspentCoinsInfo> _unspentCoinsInfo;
@computed
ObservableList<UnspentCoinsItem> get items =>
ObservableList.of(unspentCoinsMap.map((elem) =>
UnspentCoinsItem(
address: elem["address"] as String,
amount: elem["amount"] as String,
isFrozen: elem["isFrozen"] as bool,
note: elem["note"] as String
)));
ObservableList.of(_wallet.unspentCoins.map((elem) {
final amount = bitcoinAmountToString(amount: elem.value) +
' ${_wallet.currency.title}';
return UnspentCoinsItem(
address: elem.address.address,
amount: amount,
hash: elem.hash,
isFrozen: elem.isFrozen,
note: elem.note,
isSending: elem.isSending
);
}));
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
try {
final info = _unspentCoinsInfo.values
.firstWhere((element) => element.walletId.contains(_wallet.id) &&
element.hash.contains(item.hash));
info.isFrozen = item.isFrozen;
info.isSending = item.isSending;
info.note = item.note;
await info.save();
await _wallet.updateUnspent();
} catch (e) {
print(e.toString());
}
}
}