mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Merge branch 'release-4.2.4' into CAKE-345-batch-sending
# Conflicts: # lib/bitcoin/electrum_wallet.dart # lib/src/screens/send/send_page.dart # lib/src/screens/send/send_template_page.dart # lib/view_model/send/send_view_model.dart # res/values/strings_de.arb # res/values/strings_en.arb # res/values/strings_es.arb # res/values/strings_hi.arb # res/values/strings_hr.arb # res/values/strings_it.arb # res/values/strings_ja.arb # res/values/strings_ko.arb # res/values/strings_nl.arb # res/values/strings_pl.arb # res/values/strings_pt.arb # res/values/strings_ru.arb # res/values/strings_uk.arb # res/values/strings_zh.arb
This commit is contained in:
@@ -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.';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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;
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_addresses.dart';
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
@@ -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,14 +27,22 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: bitcoin.bitcoin,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
accountIndex: accountIndex);
|
||||
initialBalance: initialBalance) {
|
||||
walletAddresses = BitcoinWalletAddresses(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd,
|
||||
networkType: networkType);
|
||||
}
|
||||
|
||||
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,12 +51,9 @@ 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);
|
||||
}
|
||||
|
||||
@override
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitcoin_wallet_addresses.g.dart';
|
||||
|
||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase
|
||||
with _$BitcoinWalletAddresses;
|
||||
|
||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||
with Store {
|
||||
BitcoinWalletAddressesBase(
|
||||
WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd,
|
||||
@required this.networkType})
|
||||
: super(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd);
|
||||
|
||||
bitcoin.NetworkType networkType;
|
||||
|
||||
@override
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -38,30 +41,28 @@ 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,
|
||||
ElectrumClient electrumClient,
|
||||
int accountIndex = 0,
|
||||
ElectrumBalance initialBalance})
|
||||
: balance = initialBalance ??
|
||||
const ElectrumBalance(confirmed: 0, unconfirmed: 0),
|
||||
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
|
||||
network: networkType)
|
||||
.derivePath("m/0'/0"),
|
||||
addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? []).toSet()),
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_accountIndex = accountIndex,
|
||||
_feeRates = <int>[],
|
||||
_isTransactionUpdating = false,
|
||||
super(walletInfo) {
|
||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||
this.walletInfo = walletInfo;
|
||||
this.unspentCoinsInfo = unspentCoinsInfo;
|
||||
transactionHistory =
|
||||
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
_unspent = [];
|
||||
unspentCoins = [];
|
||||
_scripthashesUpdateSubject = {};
|
||||
}
|
||||
|
||||
@@ -72,10 +73,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
final String mnemonic;
|
||||
|
||||
ElectrumClient electrumClient;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
ElectrumWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
@@ -85,9 +86,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
ObservableList<BitcoinAddressRecord> addresses;
|
||||
|
||||
List<String> get scriptHashes => addresses
|
||||
List<String> get scriptHashes => walletAddresses.addresses
|
||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
||||
.toList();
|
||||
|
||||
@@ -103,85 +102,26 @@ 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;
|
||||
bool _isTransactionUpdating;
|
||||
|
||||
Future<void> init() async {
|
||||
await generateAddresses();
|
||||
address = addresses[_accountIndex].address;
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> nextAddress() async {
|
||||
_accountIndex += 1;
|
||||
|
||||
if (_accountIndex >= addresses.length) {
|
||||
_accountIndex = 0;
|
||||
}
|
||||
|
||||
address = addresses[_accountIndex].address;
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> generateAddresses() async {
|
||||
if (addresses.length < 33) {
|
||||
final addressesCount = 33 - addresses.length;
|
||||
await generateNewAddresses(addressesCount,
|
||||
startIndex: addresses.length, hd: hd);
|
||||
}
|
||||
}
|
||||
|
||||
Future<BitcoinAddressRecord> generateNewAddress(
|
||||
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
||||
_accountIndex += 1;
|
||||
final _hd = hd ?? this.hd;
|
||||
final address = BitcoinAddressRecord(
|
||||
getAddress(index: _accountIndex, hd: _hd),
|
||||
index: _accountIndex,
|
||||
isHidden: isHidden);
|
||||
addresses.add(address);
|
||||
await save();
|
||||
return address;
|
||||
}
|
||||
|
||||
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
|
||||
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
|
||||
final list = <BitcoinAddressRecord>[];
|
||||
|
||||
for (var i = startIndex; i < count + startIndex; i++) {
|
||||
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
||||
index: i, isHidden: isHidden);
|
||||
list.add(address);
|
||||
}
|
||||
|
||||
addresses.addAll(list);
|
||||
await save();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<void> updateAddress(String address) async {
|
||||
for (final addr in addresses) {
|
||||
if (addr.address == address) {
|
||||
await save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
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),
|
||||
@@ -219,6 +159,23 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
final inputs = <BitcoinUnspent>[];
|
||||
final sendItemList = transactionCredentials.sendItemList;
|
||||
var allInputsAmount = 0;
|
||||
|
||||
if (unspentCoins.isEmpty) {
|
||||
await updateUnspent();
|
||||
}
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
allInputsAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) {
|
||||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
final allAmountFee =
|
||||
calculateEstimatedFee(transactionCredentials.priority, null);
|
||||
final allAmount = balance.confirmed - allAmountFee;
|
||||
@@ -280,22 +237,48 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
feeAmountForPriority(transactionCredentials.priority, inputs.length, 1);
|
||||
final allAmount = allInputsAmount - allAmountFee;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||
final changeAddress = walletAddresses.address;
|
||||
var leftAmount = totalAmount;
|
||||
final changeAddress = address;
|
||||
var leftAmount = totalAmount;
|
||||
var totalInputAmount = 0;
|
||||
|
||||
if (_unspent.isEmpty) {
|
||||
await _updateUnspent();
|
||||
}
|
||||
inputs.clear();
|
||||
|
||||
for (final utx in _unspent) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +286,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
if (amount <= 0 || totalInputAmount < amount) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
@@ -361,8 +346,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': mnemonic,
|
||||
'account_index': _accountIndex.toString(),
|
||||
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
||||
'account_index': walletAddresses.accountIndex.toString(),
|
||||
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
||||
'balance': balance?.toJSON()
|
||||
});
|
||||
|
||||
@@ -387,17 +372,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
|
||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
||||
|
||||
@@ -428,24 +422,84 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
||||
|
||||
Future<String> makePath() async =>
|
||||
pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
Future<void> _updateUnspent() async {
|
||||
final unspent = await Future.wait(addresses.map((address) => electrumClient
|
||||
Future<void> updateUnspent() async {
|
||||
final unspent = await Future.wait(walletAddresses
|
||||
.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(
|
||||
{@required String hash, @required int height}) async {
|
||||
final tx = await electrumClient.getTransactionExpanded(hash: hash);
|
||||
return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type,
|
||||
height: height, addresses: addresses);
|
||||
height: height, addresses: walletAddresses.addresses);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -489,7 +543,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());
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'electrum_wallet_addresses.g.dart';
|
||||
|
||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
||||
with _$ElectrumWalletAddresses;
|
||||
|
||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd})
|
||||
: super(walletInfo) {
|
||||
this.hd = hd;
|
||||
this.accountIndex = accountIndex;
|
||||
addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||
(initialAddresses ?? []).toSet());
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
bitcoin.HDWallet hd;
|
||||
|
||||
ObservableList<BitcoinAddressRecord> addresses;
|
||||
|
||||
int accountIndex;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await generateAddresses();
|
||||
address = addresses[accountIndex].address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> nextAddress() async {
|
||||
accountIndex += 1;
|
||||
|
||||
if (accountIndex >= addresses.length) {
|
||||
accountIndex = 0;
|
||||
}
|
||||
|
||||
address = addresses[accountIndex].address;
|
||||
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
Future<void> generateAddresses() async {
|
||||
if (addresses.length < 33) {
|
||||
final addressesCount = 33 - addresses.length;
|
||||
await generateNewAddresses(addressesCount,
|
||||
startIndex: addresses.length, hd: hd);
|
||||
}
|
||||
}
|
||||
|
||||
Future<BitcoinAddressRecord> generateNewAddress(
|
||||
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
||||
accountIndex += 1;
|
||||
final _hd = hd ?? this.hd;
|
||||
final address = BitcoinAddressRecord(
|
||||
getAddress(index: accountIndex, hd: _hd),
|
||||
index: accountIndex,
|
||||
isHidden: isHidden);
|
||||
addresses.add(address);
|
||||
return address;
|
||||
}
|
||||
|
||||
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
|
||||
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
|
||||
final list = <BitcoinAddressRecord>[];
|
||||
|
||||
for (var i = startIndex; i < count + startIndex; i++) {
|
||||
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
||||
index: i, isHidden: isHidden);
|
||||
list.add(address);
|
||||
}
|
||||
|
||||
addresses.addAll(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/*Future<void> updateAddress(String address) async {
|
||||
for (final addr in addresses) {
|
||||
if (addr.address == address) {
|
||||
await save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
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/bitcoin/litecoin_wallet_addresses.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';
|
||||
@@ -10,7 +11,6 @@ import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
|
||||
part 'litecoin_wallet.g.dart';
|
||||
|
||||
@@ -21,6 +21,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,14 +29,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: litecoinNetwork,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
accountIndex: accountIndex);
|
||||
initialBalance: initialBalance) {
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd,
|
||||
networkType: networkType,
|
||||
mnemonic: mnemonic);
|
||||
}
|
||||
|
||||
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,32 +54,12 @@ 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);
|
||||
}
|
||||
|
||||
@override
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||
|
||||
@override
|
||||
Future<void> generateAddresses() async {
|
||||
if (addresses.length < 33) {
|
||||
final addressesCount = 22 - addresses.length;
|
||||
await generateNewAddresses(addressesCount,
|
||||
hd: hd, startIndex: addresses.length);
|
||||
|
||||
final changeRoot = bitcoin.HDWallet.fromSeed(
|
||||
mnemonicToSeedBytes(mnemonic),
|
||||
network: networkType)
|
||||
.derivePath("m/0'/1");
|
||||
|
||||
await generateNewAddresses(11,
|
||||
startIndex: 0, hd: changeRoot, isHidden: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int feeRate(TransactionPriority priority) {
|
||||
if (priority is LitecoinTransactionPriority) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'litecoin_wallet_addresses.g.dart';
|
||||
|
||||
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
|
||||
with _$LitecoinWalletAddresses;
|
||||
|
||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||
with Store {
|
||||
LitecoinWalletAddressesBase(
|
||||
WalletInfo walletInfo,
|
||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
@required bitcoin.HDWallet hd,
|
||||
@required this.networkType,
|
||||
@required this.mnemonic})
|
||||
: super(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
accountIndex: accountIndex,
|
||||
hd: hd);
|
||||
|
||||
bitcoin.NetworkType networkType;
|
||||
|
||||
final String mnemonic;
|
||||
|
||||
@override
|
||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||
|
||||
@override
|
||||
Future<void> generateAddresses() async {
|
||||
if (addresses.length < 33) {
|
||||
final addressesCount = 22 - addresses.length;
|
||||
await generateNewAddresses(addressesCount,
|
||||
hd: hd, startIndex: addresses.length);
|
||||
|
||||
final changeRoot = bitcoin.HDWallet.fromSeed(
|
||||
mnemonicToSeedBytes(mnemonic),
|
||||
network: networkType)
|
||||
.derivePath("m/0'/1");
|
||||
|
||||
await generateNewAddresses(11,
|
||||
startIndex: 0, hd: changeRoot, isHidden: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ abstract class BuyProvider {
|
||||
String get trackUrl;
|
||||
|
||||
WalletType get walletType => wallet.type;
|
||||
String get walletAddress => wallet.address;
|
||||
String get walletAddress => wallet.walletAddresses.address;
|
||||
String get walletId => wallet.id;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:cake_wallet/entities/balance.dart';
|
||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/entities/currency_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
import 'package:cake_wallet/entities/node.dart';
|
||||
@@ -31,9 +31,9 @@ abstract class WalletBase<
|
||||
|
||||
String get name => walletInfo.name;
|
||||
|
||||
String get address;
|
||||
//String get address;
|
||||
|
||||
set address(String address);
|
||||
//set address(String address);
|
||||
|
||||
BalanceType get balance;
|
||||
|
||||
@@ -45,6 +45,8 @@ abstract class WalletBase<
|
||||
|
||||
Object get keys;
|
||||
|
||||
WalletAddresses get walletAddresses;
|
||||
|
||||
HistoryType transactionHistory;
|
||||
|
||||
Future<void> connectToNode({@required Node node});
|
||||
|
||||
+46
-4
@@ -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';
|
||||
@@ -39,6 +40,8 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
@@ -77,6 +80,9 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
@@ -126,6 +132,7 @@ Box<Template> _templates;
|
||||
Box<ExchangeTemplate> _exchangeTemplates;
|
||||
Box<TransactionDescription> _transactionDescriptionBox;
|
||||
Box<Order> _ordersSource;
|
||||
Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
||||
|
||||
Future setup(
|
||||
{Box<WalletInfo> walletInfoSource,
|
||||
@@ -135,7 +142,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;
|
||||
@@ -144,6 +152,7 @@ Future setup(
|
||||
_exchangeTemplates = exchangeTemplates;
|
||||
_transactionDescriptionBox = transactionDescriptionBox;
|
||||
_ordersSource = ordersSource;
|
||||
_unspentCoinsInfoSource = unspentCoinsInfoSource;
|
||||
|
||||
if (!_isSetupFinished) {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
@@ -370,7 +379,8 @@ Future setup(
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
|
||||
AccountListItem, void>(
|
||||
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
|
||||
(getIt.get<AppStore>().wallet as MoneroWallet).accountList,
|
||||
(getIt.get<AppStore>().wallet as MoneroWallet).walletAddresses.accountList,
|
||||
wallet: getIt.get<AppStore>().wallet,
|
||||
accountListItem: account));
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
|
||||
@@ -454,9 +464,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, __) {
|
||||
@@ -592,5 +604,35 @@ Future setup(
|
||||
|
||||
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
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, UnspentCoinsListViewModel>((item, model) =>
|
||||
UnspentCoinsDetailsViewModel(
|
||||
unspentCoinsItem: item,
|
||||
unspentCoinsListViewModel: model));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
|
||||
|
||||
const topLevelDomain = 'crypto';
|
||||
|
||||
Future<ParsedAddress> parseAddressFromDomain(
|
||||
String domain, String ticker) async {
|
||||
try {
|
||||
final formattedName = OpenaliasRecord.formatDomainName(domain);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
|
||||
if (domainParts.length <= 1 || domainParts.first.isEmpty || name.isEmpty) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
if (name.contains(topLevelDomain)) {
|
||||
final address =
|
||||
await fetchUnstoppableDomainAddress(domain, ticker);
|
||||
|
||||
if (address?.isEmpty ?? true) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
return ParsedAddress(
|
||||
address: address,
|
||||
name: domain,
|
||||
parseFrom: ParseFrom.unstoppableDomains);
|
||||
}
|
||||
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(formattedName);
|
||||
|
||||
if (record == null || record.address.contains(formattedName)) {
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
|
||||
return ParsedAddress(
|
||||
address: record.address,
|
||||
name: record.name,
|
||||
parseFrom: ParseFrom.openAlias);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return ParsedAddress(address: domain);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
enum ParseFrom {unstoppableDomains, openAlias, notParsed}
|
||||
|
||||
class ParsedAddress {
|
||||
ParsedAddress({
|
||||
this.address = '',
|
||||
this.name = '',
|
||||
this.parseFrom = ParseFrom.notParsed});
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
final ParseFrom parseFrom;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
const channel = MethodChannel('com.cake_wallet/native_utils');
|
||||
|
||||
Future<String> fetchUnstoppableDomainAddress(String domain, String ticker) async {
|
||||
var address = '';
|
||||
|
||||
try {
|
||||
address = await channel.invokeMethod(
|
||||
'getUnstoppableDomainAddress',
|
||||
<String, String> {
|
||||
'domain' : domain,
|
||||
'ticker' : ticker
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
print('Unstoppable domain error: ${e.toString()}');
|
||||
address = '';
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
|
||||
abstract class WalletAddresses {
|
||||
WalletAddresses(this.walletInfo) {
|
||||
addressesMap = {};
|
||||
}
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
String get address;
|
||||
|
||||
set address(String address);
|
||||
|
||||
Map<String, String> addressesMap;
|
||||
|
||||
Future<void> init();
|
||||
|
||||
Future<void> updateAddressesInBox();
|
||||
|
||||
Future<void> saveAddressesInBox() async {
|
||||
try {
|
||||
if (walletInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
walletInfo.address = address;
|
||||
walletInfo.addresses = addressesMap;
|
||||
|
||||
if (walletInfo.isInBox) {
|
||||
await walletInfo.save();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,8 @@ class WalletInfo extends HiveObject {
|
||||
@HiveField(8)
|
||||
String address;
|
||||
|
||||
@HiveField(10)
|
||||
Map<String, String> addresses;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
||||
+11
-1
@@ -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,6 +3,8 @@ import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:cake_wallet/monero/monero_amount_format.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_monero/transaction_history.dart'
|
||||
@@ -15,10 +17,7 @@ import 'package:cake_wallet/monero/pending_monero_transaction.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_keys.dart';
|
||||
import 'package:cake_wallet/monero/monero_balance.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_history.dart';
|
||||
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/subaddress.dart';
|
||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
@@ -35,9 +34,7 @@ class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
||||
MoneroWalletBase({WalletInfo walletInfo})
|
||||
: accountList = MoneroAccountList(),
|
||||
subaddressList = MoneroSubaddressList(),
|
||||
super(walletInfo) {
|
||||
: super(walletInfo) {
|
||||
transactionHistory = MoneroTransactionHistory();
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||
@@ -47,33 +44,26 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
_isSavingAfterSync = false;
|
||||
_isSavingAfterNewTransaction = false;
|
||||
_isTransactionUpdating = false;
|
||||
_onAccountChangeReaction = reaction((_) => account, (Account account) {
|
||||
walletAddresses = MoneroWalletAddresses(walletInfo);
|
||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account,
|
||||
(Account account) {
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
||||
subaddressList.update(accountIndex: account.id);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress.address;
|
||||
walletAddresses.updateSubaddressList(accountIndex: account.id);
|
||||
});
|
||||
}
|
||||
|
||||
static const int _autoAfterSyncSaveInterval = 60000;
|
||||
|
||||
@observable
|
||||
Account account;
|
||||
|
||||
@observable
|
||||
Subaddress subaddress;
|
||||
@override
|
||||
MoneroWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
@observable
|
||||
MoneroBalance balance;
|
||||
@@ -88,10 +78,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
publicSpendKey: monero_wallet.getPublicSpendKey(),
|
||||
publicViewKey: monero_wallet.getPublicViewKey());
|
||||
|
||||
final MoneroSubaddressList subaddressList;
|
||||
|
||||
final MoneroAccountList accountList;
|
||||
|
||||
SyncListener _listener;
|
||||
ReactionDisposer _onAccountChangeReaction;
|
||||
int _lastAutosaveTimestamp;
|
||||
@@ -101,15 +87,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
int _lastSaveTimestamp;
|
||||
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
subaddressList.update(accountIndex: account.id ?? 0);
|
||||
subaddress = subaddressList.getAll().first;
|
||||
await walletAddresses.init();
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id));
|
||||
address = subaddress.address;
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id));
|
||||
_setListeners();
|
||||
await updateTransactions();
|
||||
|
||||
@@ -129,24 +111,6 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
_onAccountChangeReaction?.reaction?.dispose();
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
accountList.update();
|
||||
final accountListLength = accountList.accounts?.length ?? 0;
|
||||
|
||||
if (accountListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||
final subaddressListLength = subaddressList.subaddresses?.length ?? 0;
|
||||
|
||||
if (subaddressListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> connectToNode({@required Node node}) async {
|
||||
try {
|
||||
@@ -189,7 +153,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
? moneroParseAmount(amount: _credentials.amount)
|
||||
: null;
|
||||
final unlockedBalance =
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id);
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
if ((amount != null && unlockedBalance < amount) ||
|
||||
(amount == null && unlockedBalance <= 0)) {
|
||||
@@ -209,7 +173,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
paymentId: _credentials.paymentId,
|
||||
amount: _credentials.amount,
|
||||
priorityRaw: _credentials.priority.serialize(),
|
||||
accountIndex: account.id);
|
||||
accountIndex: walletAddresses.account.id);
|
||||
|
||||
return PendingMoneroTransaction(pendingTransactionDescription);
|
||||
}
|
||||
@@ -238,12 +202,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
await backupWalletFiles(name);
|
||||
_lastSaveTimestamp = now;
|
||||
await monero_wallet.store();
|
||||
}
|
||||
@@ -265,7 +232,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
monero_wallet.rescanBlockchainAsync();
|
||||
await startSync();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
await _askForUpdateTransactionHistory();
|
||||
await save();
|
||||
await walletInfo.save();
|
||||
@@ -364,10 +331,10 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
await updateTransactions();
|
||||
|
||||
int _getFullBalance() =>
|
||||
monero_wallet.getFullBalance(accountIndex: account.id);
|
||||
monero_wallet.getFullBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
int _getUnlockedBalance() =>
|
||||
monero_wallet.getUnlockedBalance(accountIndex: account.id);
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
|
||||
|
||||
Future<void> _afterSyncSave() async {
|
||||
try {
|
||||
@@ -414,13 +381,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
if (walletInfo.isRecovery) {
|
||||
await _askForUpdateTransactionHistory();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
}
|
||||
|
||||
if (blocksLeft < 100) {
|
||||
await _askForUpdateTransactionHistory();
|
||||
_askForUpdateBalance();
|
||||
accountList.update();
|
||||
walletAddresses.accountList.update();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
await _afterSyncSave();
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
|
||||
import 'package:cake_wallet/monero/subaddress.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'monero_wallet_addresses.g.dart';
|
||||
|
||||
class MoneroWalletAddresses = MoneroWalletAddressesBase
|
||||
with _$MoneroWalletAddresses;
|
||||
|
||||
abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||
MoneroWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo) {
|
||||
accountList = MoneroAccountList();
|
||||
subaddressList = MoneroSubaddressList();
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
Account account;
|
||||
|
||||
@observable
|
||||
Subaddress subaddress;
|
||||
|
||||
MoneroSubaddressList subaddressList;
|
||||
|
||||
MoneroAccountList accountList;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
final _subaddressList = MoneroSubaddressList();
|
||||
|
||||
addressesMap.clear();
|
||||
|
||||
accountList.accounts.forEach((account) {
|
||||
_subaddressList.update(accountIndex: account.id);
|
||||
_subaddressList.subaddresses.forEach((subaddress) {
|
||||
addressesMap[subaddress.address] = subaddress.label;
|
||||
});
|
||||
});
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
accountList.update();
|
||||
final accountListLength = accountList.accounts?.length ?? 0;
|
||||
|
||||
if (accountListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||
final subaddressListLength = subaddressList.subaddresses?.length ?? 0;
|
||||
|
||||
if (subaddressListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateSubaddressList({int accountIndex}) {
|
||||
subaddressList.update(accountIndex: accountIndex);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress.address;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_utils.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_monero/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||
@@ -55,16 +56,7 @@ class MoneroWalletService extends WalletService<
|
||||
MoneroWalletService(this.walletInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
static Future<void> _removeCache(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool walletFilesExist(String path) =>
|
||||
!File(path).existsSync() && !File('$path.keys').existsSync();
|
||||
|
||||
@@ -117,10 +109,10 @@ class MoneroWalletService extends WalletService<
|
||||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = MoneroWallet(walletInfo: walletInfo);
|
||||
final isValid = wallet.validate();
|
||||
final isValid = wallet.walletAddresses.validate();
|
||||
|
||||
if (!isValid) {
|
||||
await _removeCache(name);
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
return openWallet(name, password);
|
||||
}
|
||||
@@ -135,7 +127,7 @@ class MoneroWalletService extends WalletService<
|
||||
(e is WalletOpeningException &&
|
||||
(e.message == 'std::bad_alloc' ||
|
||||
e.message.contains('bad_alloc')))) {
|
||||
await _removeCache(name);
|
||||
await restoreOrResetWalletFiles(name);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
|
||||
String backupFileName(String originalPath) {
|
||||
final pathParts = originalPath.split('/');
|
||||
final newName = '#_${pathParts.last}';
|
||||
pathParts.removeLast();
|
||||
pathParts.add(newName);
|
||||
return pathParts.join('/');
|
||||
}
|
||||
|
||||
Future<void> backupWalletFiles(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
final keysFile = File('$path.keys');
|
||||
final addressListFile = File('$path.address.txt');
|
||||
final newCacheFilePath = backupFileName(cacheFile.path);
|
||||
final newKeysFilePath = backupFileName(keysFile.path);
|
||||
final newAddressListFilePath = backupFileName(addressListFile.path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
await cacheFile.copy(newCacheFilePath);
|
||||
}
|
||||
|
||||
if (keysFile.existsSync()) {
|
||||
await keysFile.copy(newKeysFilePath);
|
||||
}
|
||||
|
||||
if (addressListFile.existsSync()) {
|
||||
await addressListFile.copy(newAddressListFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreWalletFiles(String name) async {
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
final keysFilePath = '$walletDirPath/$name.keys';
|
||||
final addressListFilePath = '$walletDirPath/$name.address.txt';
|
||||
final backupCacheFile = File(backupFileName(cacheFilePath));
|
||||
final backupKeysFile = File(backupFileName(keysFilePath));
|
||||
final backupAddressListFile = File(backupFileName(addressListFilePath));
|
||||
|
||||
if (backupCacheFile.existsSync()) {
|
||||
await backupCacheFile.copy(cacheFilePath);
|
||||
}
|
||||
|
||||
if (backupKeysFile.existsSync()) {
|
||||
await backupKeysFile.copy(keysFilePath);
|
||||
}
|
||||
|
||||
if (backupAddressListFile.existsSync()) {
|
||||
await backupAddressListFile.copy(addressListFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> backupWalletFilesExists(String name) async {
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
final keysFilePath = '$walletDirPath/$name.keys';
|
||||
final addressListFilePath = '$walletDirPath/$name.address.txt';
|
||||
final backupCacheFile = File(backupFileName(cacheFilePath));
|
||||
final backupKeysFile = File(backupFileName(keysFilePath));
|
||||
final backupAddressListFile = File(backupFileName(addressListFilePath));
|
||||
|
||||
return backupCacheFile.existsSync()
|
||||
&& backupKeysFile.existsSync()
|
||||
&& backupAddressListFile.existsSync();
|
||||
}
|
||||
|
||||
Future<void> removeCache(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreOrResetWalletFiles(String name) async {
|
||||
final backupsExists = await backupWalletFilesExists(name);
|
||||
|
||||
if (backupsExists) {
|
||||
await restoreWalletFiles(name);
|
||||
}
|
||||
|
||||
removeCache(name);
|
||||
}
|
||||
@@ -45,6 +45,7 @@ class Palette {
|
||||
static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0);
|
||||
static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0);
|
||||
static const Color darkBlue = Color.fromRGBO(109, 128, 178, 1.0);
|
||||
static const Color paleCornflowerBlue = Color.fromRGBO(185, 196, 237, 1.0);
|
||||
}
|
||||
|
||||
class PaletteDark {
|
||||
|
||||
@@ -37,7 +37,7 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||
await wallet.connectToNode(node: node);
|
||||
|
||||
if (wallet.walletInfo.address?.isEmpty ?? true) {
|
||||
wallet.walletInfo.address = wallet.address;
|
||||
wallet.walletInfo.address = wallet.walletAddresses.address;
|
||||
|
||||
if (wallet.walletInfo.isInBox) {
|
||||
await wallet.walletInfo.save();
|
||||
|
||||
@@ -11,9 +11,12 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
@@ -364,6 +367,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<SupportPage>());
|
||||
|
||||
case Routes.unspentCoinsList:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<UnspentCoinsListPage>());
|
||||
|
||||
case Routes.unspentCoinsDetails:
|
||||
final args = settings.arguments as List;
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<UnspentCoinsDetailsPage>(
|
||||
param1: args));
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
||||
@@ -55,4 +55,6 @@ class Routes {
|
||||
static const orderDetails = '/order_details';
|
||||
static const preOrder = '/pre_order';
|
||||
static const buyWebView = '/buy_web_view';
|
||||
static const unspentCoinsList = '/unspent_coins_list';
|
||||
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||
}
|
||||
@@ -151,17 +151,19 @@ class ContactListPage extends BasePage {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 12)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
contact.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 12)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
contact.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/sync_status.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -9,8 +11,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/limits_state.dart';
|
||||
@@ -23,7 +23,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
@@ -42,7 +41,9 @@ class ExchangePage extends BasePage {
|
||||
final checkBoxKey = GlobalKey<StandardCheckboxState>();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _depositAmountFocus = FocusNode();
|
||||
final _depositAddressFocus = FocusNode();
|
||||
final _receiveAmountFocus = FocusNode();
|
||||
final _receiveAddressFocus = FocusNode();
|
||||
var _isReactionsSet = false;
|
||||
|
||||
@override
|
||||
@@ -171,6 +172,7 @@ class ExchangePage extends BasePage {
|
||||
.calculateDepositAllAmount()
|
||||
: null,
|
||||
amountFocusNode: _depositAmountFocus,
|
||||
addressFocusNode: _depositAddressFocus,
|
||||
key: depositKey,
|
||||
title: S.of(context).you_will_send,
|
||||
initialCurrency:
|
||||
@@ -179,7 +181,7 @@ class ExchangePage extends BasePage {
|
||||
initialAddress:
|
||||
exchangeViewModel.depositCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable:
|
||||
@@ -223,6 +225,15 @@ class ExchangePage extends BasePage {
|
||||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type: exchangeViewModel.depositCurrency),
|
||||
onPushPasteButton: (context) async {
|
||||
final domain =
|
||||
exchangeViewModel.depositAddress;
|
||||
final ticker = exchangeViewModel
|
||||
.depositCurrency.title.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(
|
||||
context, domain, ticker);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -232,6 +243,7 @@ class ExchangePage extends BasePage {
|
||||
child: Observer(
|
||||
builder: (_) => ExchangeCard(
|
||||
amountFocusNode: _receiveAmountFocus,
|
||||
addressFocusNode: _receiveAddressFocus,
|
||||
key: receiveKey,
|
||||
title: S.of(context).you_will_get,
|
||||
initialCurrency:
|
||||
@@ -240,7 +252,7 @@ class ExchangePage extends BasePage {
|
||||
initialAddress: exchangeViewModel
|
||||
.receiveCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable: exchangeViewModel
|
||||
.isReceiveAmountEditable,
|
||||
@@ -268,6 +280,15 @@ class ExchangePage extends BasePage {
|
||||
AddressValidator(
|
||||
type: exchangeViewModel
|
||||
.receiveCurrency),
|
||||
onPushPasteButton: (context) async {
|
||||
final domain =
|
||||
exchangeViewModel.receiveAddress;
|
||||
final ticker = exchangeViewModel
|
||||
.receiveCurrency.title.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(
|
||||
context, domain, ticker);
|
||||
},
|
||||
)),
|
||||
)
|
||||
],
|
||||
@@ -371,7 +392,7 @@ class ExchangePage extends BasePage {
|
||||
from: template.depositCurrency,
|
||||
to: template.receiveCurrency,
|
||||
onTap: () {
|
||||
applyTemplate(
|
||||
applyTemplate(context,
|
||||
exchangeViewModel, template);
|
||||
},
|
||||
onRemove: () {
|
||||
@@ -472,8 +493,8 @@ class ExchangePage extends BasePage {
|
||||
));
|
||||
}
|
||||
|
||||
void applyTemplate(
|
||||
ExchangeViewModel exchangeViewModel, ExchangeTemplate template) {
|
||||
void applyTemplate(BuildContext context,
|
||||
ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
|
||||
exchangeViewModel.changeDepositCurrency(
|
||||
currency: CryptoCurrency.fromString(template.depositCurrency));
|
||||
exchangeViewModel.changeReceiveCurrency(
|
||||
@@ -491,6 +512,16 @@ class ExchangePage extends BasePage {
|
||||
exchangeViewModel.receiveAddress = template.receiveAddress;
|
||||
exchangeViewModel.isReceiveAmountEntered = false;
|
||||
exchangeViewModel.isFixedRateMode = false;
|
||||
|
||||
var domain = template.depositAddress;
|
||||
var ticker = template.depositCurrency.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
|
||||
domain = template.receiveAddress;
|
||||
ticker = template.receiveCurrency.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
|
||||
void _setReactions(
|
||||
@@ -646,7 +677,8 @@ class ExchangePage extends BasePage {
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => exchangeViewModel.wallet.address, (String address) {
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
|
||||
(String address) {
|
||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||
depositKey.currentState.changeAddress(address: address);
|
||||
}
|
||||
@@ -656,6 +688,26 @@ class ExchangePage extends BasePage {
|
||||
}
|
||||
});
|
||||
|
||||
_depositAddressFocus.addListener(() async {
|
||||
if (!_depositAddressFocus.hasFocus &&
|
||||
depositAddressController.text.isNotEmpty) {
|
||||
final domain = depositAddressController.text;
|
||||
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
||||
exchangeViewModel.depositAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
});
|
||||
|
||||
_receiveAddressFocus.addListener(() async {
|
||||
if (!_receiveAddressFocus.hasFocus &&
|
||||
receiveAddressController.text.isNotEmpty) {
|
||||
final domain = receiveAddressController.text;
|
||||
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
||||
exchangeViewModel.receiveAddress =
|
||||
await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
|
||||
}
|
||||
});
|
||||
|
||||
_receiveAmountFocus.addListener(() {
|
||||
if (_receiveAmountFocus.hasFocus && !exchangeViewModel.isFixedRateMode) {
|
||||
showPopUp<void>(
|
||||
@@ -707,7 +759,8 @@ class ExchangePage extends BasePage {
|
||||
isCurrentTypeWallet ? exchangeViewModel.wallet.name : null);
|
||||
|
||||
key.currentState.changeAddress(
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.address : '');
|
||||
address: isCurrentTypeWallet
|
||||
? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||
|
||||
key.currentState.changeAmount(amount: '');
|
||||
}
|
||||
@@ -719,11 +772,35 @@ class ExchangePage extends BasePage {
|
||||
if (isCurrentTypeWallet) {
|
||||
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
||||
key.currentState.addressController.text =
|
||||
exchangeViewModel.wallet.address;
|
||||
exchangeViewModel.wallet.walletAddresses.address;
|
||||
} else if (key.currentState.addressController.text ==
|
||||
exchangeViewModel.wallet.address) {
|
||||
exchangeViewModel.wallet.walletAddresses.address) {
|
||||
key.currentState.changeWalletName(null);
|
||||
key.currentState.addressController.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> applyOpenaliasOrUnstoppableDomains(
|
||||
BuildContext context, String domain, String ticker) async {
|
||||
final parsedAddress = await parseAddressFromDomain(domain, ticker);
|
||||
|
||||
switch (parsedAddress.parseFrom) {
|
||||
case ParseFrom.unstoppableDomains:
|
||||
showAddressAlert(
|
||||
context,
|
||||
S.of(context).address_detected,
|
||||
S.of(context).address_from_domain(parsedAddress.name));
|
||||
break;
|
||||
case ParseFrom.openAlias:
|
||||
showAddressAlert(
|
||||
context,
|
||||
S.of(context).openalias_alert_title,
|
||||
S.of(context).openalias_alert_content(parsedAddress.name));
|
||||
break;
|
||||
case ParseFrom.notParsed:
|
||||
break;
|
||||
}
|
||||
|
||||
return parsedAddress.address;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class ExchangeTemplatePage extends BasePage {
|
||||
initialAddress: exchangeViewModel
|
||||
.depositCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable: exchangeViewModel
|
||||
@@ -150,9 +150,8 @@ class ExchangeTemplatePage extends BasePage {
|
||||
.color,
|
||||
currencyValueValidator: AmountValidator(
|
||||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type:
|
||||
exchangeViewModel.depositCurrency),
|
||||
//addressTextFieldValidator: AddressValidator(
|
||||
// type: exchangeViewModel.depositCurrency),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -168,7 +167,7 @@ class ExchangeTemplatePage extends BasePage {
|
||||
initialAddress:
|
||||
exchangeViewModel.receiveCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.address
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable:
|
||||
exchangeViewModel.provider is
|
||||
@@ -190,8 +189,8 @@ class ExchangeTemplatePage extends BasePage {
|
||||
.decorationColor,
|
||||
currencyValueValidator: AmountValidator(
|
||||
type: exchangeViewModel.wallet.type),
|
||||
addressTextFieldValidator: AddressValidator(
|
||||
type: exchangeViewModel.receiveCurrency),
|
||||
//addressTextFieldValidator: AddressValidator(
|
||||
// type: exchangeViewModel.receiveCurrency),
|
||||
)),
|
||||
)
|
||||
],
|
||||
@@ -383,7 +382,8 @@ class ExchangeTemplatePage extends BasePage {
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => exchangeViewModel.wallet.address, (String address) {
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
|
||||
(String address) {
|
||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||
depositKey.currentState.changeAddress(address: address);
|
||||
}
|
||||
@@ -405,7 +405,8 @@ class ExchangeTemplatePage extends BasePage {
|
||||
isCurrentTypeWallet ? exchangeViewModel.wallet.name : null);
|
||||
|
||||
key.currentState.changeAddress(
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.address : '');
|
||||
address: isCurrentTypeWallet
|
||||
? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||
|
||||
key.currentState.changeAmount(amount: '');
|
||||
}
|
||||
@@ -417,9 +418,9 @@ class ExchangeTemplatePage extends BasePage {
|
||||
if (isCurrentTypeWallet) {
|
||||
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
|
||||
key.currentState.addressController.text =
|
||||
exchangeViewModel.wallet.address;
|
||||
exchangeViewModel.wallet.walletAddresses.address;
|
||||
} else if (key.currentState.addressController.text ==
|
||||
exchangeViewModel.wallet.address) {
|
||||
exchangeViewModel.wallet.walletAddresses.address) {
|
||||
key.currentState.changeWalletName(null);
|
||||
key.currentState.addressController.text = null;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ class ExchangeCard extends StatefulWidget {
|
||||
this.currencyValueValidator,
|
||||
this.addressTextFieldValidator,
|
||||
this.amountFocusNode,
|
||||
this.addressFocusNode,
|
||||
this.hasAllAmount = false,
|
||||
this.allAmount})
|
||||
this.allAmount,
|
||||
this.onPushPasteButton})
|
||||
: super(key: key);
|
||||
|
||||
final List<CryptoCurrency> currencies;
|
||||
@@ -49,8 +51,10 @@ class ExchangeCard extends StatefulWidget {
|
||||
final FormFieldValidator<String> currencyValueValidator;
|
||||
final FormFieldValidator<String> addressTextFieldValidator;
|
||||
final FocusNode amountFocusNode;
|
||||
final FocusNode addressFocusNode;
|
||||
final bool hasAllAmount;
|
||||
Function allAmount;
|
||||
final Function allAmount;
|
||||
final Function(BuildContext context) onPushPasteButton;
|
||||
|
||||
@override
|
||||
ExchangeCardState createState() => ExchangeCardState();
|
||||
@@ -288,6 +292,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
focusNode: widget.addressFocusNode,
|
||||
controller: addressController,
|
||||
placeholder: widget.hasRefundAddress
|
||||
? S.of(context).refund_address
|
||||
@@ -311,6 +316,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||
.decorationColor),
|
||||
buttonColor: widget.addressButtonsColor,
|
||||
validator: widget.addressTextFieldValidator,
|
||||
onPushPasteButton: widget.onPushPasteButton,
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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 => S.current.unspent_coins_details_title;
|
||||
|
||||
final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => unspentCoinsDetailsViewModel.items.length,
|
||||
itemBuilder: (_, __, index) {
|
||||
final item = unspentCoinsDetailsViewModel.items[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return StandartListRow(
|
||||
title: '${item.title}:',
|
||||
value: item.value);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is UnspentCoinsSwitchItem) {
|
||||
return Observer(builder: (_) => UnspentCoinsSwitchRow(
|
||||
title: item.title,
|
||||
switchValue: item.switchValue(),
|
||||
onSwitchValueChange: item.onSwitchValueChange
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.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 UnspentCoinsListPage extends BasePage {
|
||||
UnspentCoinsListPage({this.unspentCoinsListViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.unspent_coins_title;
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
final questionImage = Image.asset('assets/images/question_mark.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color);
|
||||
|
||||
return SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => showUnspentCoinsAlert(context),
|
||||
child: questionImage),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
UnspentCoinsListForm(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListForm extends StatefulWidget {
|
||||
UnspentCoinsListForm(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
UnspentCoinsListFormState createState() =>
|
||||
UnspentCoinsListFormState(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
UnspentCoinsListFormState(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
|
||||
}
|
||||
|
||||
void afterLayout(dynamic _) {
|
||||
showUnspentCoinsAlert(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(24, 12, 24, 24),
|
||||
child: Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
itemCount: unspentCoinsListViewModel.items.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
SizedBox(height: 15),
|
||||
itemBuilder: (_, int index) {
|
||||
return Observer(builder: (_) {
|
||||
final item = unspentCoinsListViewModel.items[index];
|
||||
|
||||
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);}));
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showUnspentCoinsAlert(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: '',
|
||||
alertContent: 'Information about unspent coins',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class UnspentCoinsListItem extends StatelessWidget {
|
||||
UnspentCoinsListItem({
|
||||
@required this.address,
|
||||
@required this.amount,
|
||||
@required this.isSending,
|
||||
@required this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
static const amountColor = Palette.darkBlueCraiola;
|
||||
static const addressColor = Palette.darkGray;
|
||||
static const selectedItemColor = Palette.paleCornflowerBlue;
|
||||
static const unselectedItemColor = Palette.moderateLavender;
|
||||
|
||||
final String address;
|
||||
final String amount;
|
||||
final bool isSending;
|
||||
final Function() onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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: 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,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:cake_wallet/src/widgets/standart_switch.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UnspentCoinsSwitchRow extends StatelessWidget {
|
||||
UnspentCoinsSwitchRow(
|
||||
{this.title,
|
||||
this.titleFontSize = 14,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange});
|
||||
|
||||
final String title;
|
||||
final double titleFontSize;
|
||||
final bool switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.overline.color),
|
||||
textAlign: TextAlign.left),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: StandartSwitch(
|
||||
value: switchValue,
|
||||
onTaped: () => onSwitchValueChange(!switchValue))
|
||||
)
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ class AddressTextField extends StatelessWidget {
|
||||
this.iconColor,
|
||||
this.textStyle,
|
||||
this.hintStyle,
|
||||
this.validator});
|
||||
this.validator,
|
||||
this.onPushPasteButton});
|
||||
|
||||
static const prefixIconWidth = 34.0;
|
||||
static const prefixIconHeight = 34.0;
|
||||
@@ -43,6 +44,7 @@ class AddressTextField extends StatelessWidget {
|
||||
final TextStyle textStyle;
|
||||
final TextStyle hintStyle;
|
||||
final FocusNode focusNode;
|
||||
final Function(BuildContext context) onPushPasteButton;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -225,5 +227,7 @@ class AddressTextField extends StatelessWidget {
|
||||
if (address?.isNotEmpty ?? false) {
|
||||
controller.text = address;
|
||||
}
|
||||
|
||||
onPushPasteButton?.call(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,6 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
||||
Container(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
margin: EdgeInsets.only(
|
||||
right: 10.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
@@ -65,14 +62,17 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
||||
)
|
||||
: Offstage(),
|
||||
),
|
||||
Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
if (caption.isNotEmpty) Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -16,11 +16,22 @@ class ContactListViewModel = ContactListViewModelBase
|
||||
abstract class ContactListViewModelBase with Store {
|
||||
ContactListViewModelBase(this.contactSource, this.walletInfoSource)
|
||||
: contacts = ObservableList<ContactRecord>(),
|
||||
walletContacts = walletInfoSource.values
|
||||
.where((info) => info.address?.isNotEmpty ?? false)
|
||||
.map((info) => WalletContact(
|
||||
info.address, info.name, walletTypeToCryptoCurrency(info.type)))
|
||||
.toList() {
|
||||
walletContacts = [] {
|
||||
walletInfoSource.values.forEach((info) {
|
||||
if (info.addresses?.isNotEmpty ?? false) {
|
||||
info.addresses?.forEach((address, label) {
|
||||
final name = label.isNotEmpty
|
||||
? info.name + ' ($label)'
|
||||
: info.name;
|
||||
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_subscription = contactSource.bindToListWithTransform(
|
||||
contacts, (Contact contact) => ContactRecord(contactSource, contact),
|
||||
initialFire: true);
|
||||
|
||||
@@ -92,17 +92,17 @@ abstract class DashboardViewModelBase with Store {
|
||||
final _wallet = wallet;
|
||||
|
||||
if (_wallet is MoneroWallet) {
|
||||
subname = _wallet.account?.label;
|
||||
subname = _wallet.walletAddresses.account?.label;
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => _wallet.account,
|
||||
(Account account) => _onMoneroAccountChange(_wallet));
|
||||
_onMoneroAccountChangeReaction = reaction((_) => _wallet.walletAddresses
|
||||
.account, (Account account) => _onMoneroAccountChange(_wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => _wallet.balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
|
||||
|
||||
final _accountTransactions = _wallet
|
||||
.transactionHistory.transactions.values
|
||||
.where((tx) => tx.accountIndex == _wallet.account.id)
|
||||
.where((tx) => tx.accountIndex == _wallet.walletAddresses.account.id)
|
||||
.toList();
|
||||
|
||||
transactions = ObservableList.of(_accountTransactions.map((transaction) =>
|
||||
@@ -131,7 +131,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
filter: (TransactionInfo tx) {
|
||||
final wallet = _wallet;
|
||||
if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
|
||||
return tx.accountIndex == wallet.account.id;
|
||||
return tx.accountIndex == wallet.walletAddresses.account.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -153,7 +153,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
String subname;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
String get address => wallet.walletAddresses.address;
|
||||
|
||||
@computed
|
||||
SyncStatus get status => wallet.syncStatus;
|
||||
@@ -251,13 +251,13 @@ abstract class DashboardViewModelBase with Store {
|
||||
wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
subname = wallet.account?.label;
|
||||
subname = wallet.walletAddresses.account?.label;
|
||||
|
||||
_onMoneroAccountChangeReaction?.reaction?.dispose();
|
||||
_onMoneroBalanceChangeReaction?.reaction?.dispose();
|
||||
|
||||
_onMoneroAccountChangeReaction = reaction((_) => wallet.account,
|
||||
(Account account) => _onMoneroAccountChange(wallet));
|
||||
_onMoneroAccountChangeReaction = reaction((_) => wallet.walletAddresses
|
||||
.account, (Account account) => _onMoneroAccountChange(wallet));
|
||||
|
||||
_onMoneroBalanceChangeReaction = reaction((_) => wallet.balance,
|
||||
(MoneroBalance balance) => _onMoneroTransactionsUpdate(wallet));
|
||||
@@ -284,7 +284,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
settingsStore: appStore.settingsStore),
|
||||
filter: (TransactionInfo tx) {
|
||||
if (tx is MoneroTransactionInfo && wallet is MoneroWallet) {
|
||||
return tx.accountIndex == wallet.account.id;
|
||||
return tx.accountIndex == wallet.walletAddresses.account.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -293,7 +293,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
@action
|
||||
void _onMoneroAccountChange(MoneroWallet wallet) {
|
||||
subname = wallet.account?.label;
|
||||
subname = wallet.walletAddresses.account?.label;
|
||||
_onMoneroTransactionsUpdate(wallet);
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
transactions.clear();
|
||||
|
||||
final _accountTransactions = wallet.transactionHistory.transactions.values
|
||||
.where((tx) => tx.accountIndex == wallet.account.id)
|
||||
.where((tx) => tx.accountIndex == wallet.walletAddresses.account.id)
|
||||
.toList();
|
||||
|
||||
transactions.addAll(_accountTransactions.map((transaction) =>
|
||||
|
||||
@@ -41,7 +41,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
receiveAddress = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
limitsState = LimitsInitialState();
|
||||
tradeState = ExchangeTradeStateInitial();
|
||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
||||
@@ -308,8 +309,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||
isReceiveAmountEntered = false;
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency ? wallet.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency
|
||||
? wallet.walletAddresses.address : '';
|
||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||
isFixedRateMode = false;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/monero/monero_account_list.dart';
|
||||
@@ -10,11 +12,12 @@ class MoneroAccountEditOrCreateViewModel = MoneroAccountEditOrCreateViewModelBas
|
||||
|
||||
abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList,
|
||||
{AccountListItem accountListItem})
|
||||
{@required WalletBase wallet, AccountListItem accountListItem})
|
||||
: state = InitialExecutionState(),
|
||||
isEdit = accountListItem != null,
|
||||
label = accountListItem?.label??'',
|
||||
_accountListItem = accountListItem;
|
||||
_accountListItem = accountListItem,
|
||||
_wallet = wallet;
|
||||
|
||||
final bool isEdit;
|
||||
|
||||
@@ -26,6 +29,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
|
||||
final MoneroAccountList _moneroAccountList;
|
||||
final AccountListItem _accountListItem;
|
||||
final WalletBase _wallet;
|
||||
|
||||
Future<void> save() async {
|
||||
try {
|
||||
@@ -38,6 +42,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store {
|
||||
await _moneroAccountList.addAccount(label: label);
|
||||
}
|
||||
|
||||
await _wallet.save();
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
|
||||
@@ -20,15 +20,16 @@ abstract class MoneroAccountListViewModelBase with Store {
|
||||
}
|
||||
|
||||
@computed
|
||||
List<AccountListItem> get accounts => _moneroWallet.accountList.accounts
|
||||
.map((acc) => AccountListItem(
|
||||
List<AccountListItem> get accounts => _moneroWallet.walletAddresses
|
||||
.accountList.accounts.map((acc) => AccountListItem(
|
||||
label: acc.label,
|
||||
id: acc.id,
|
||||
isSelected: acc.id == _moneroWallet.account.id))
|
||||
isSelected: acc.id == _moneroWallet.walletAddresses.account.id))
|
||||
.toList();
|
||||
|
||||
final MoneroWallet _moneroWallet;
|
||||
|
||||
void select(AccountListItem item) =>
|
||||
_moneroWallet.account = Account(id: item.id, label: item.label);
|
||||
_moneroWallet.walletAddresses.account =
|
||||
Account(id: item.id, label: item.label);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
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';
|
||||
|
||||
part 'unspent_coins_details_view_model.g.dart';
|
||||
|
||||
class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase
|
||||
with _$UnspentCoinsDetailsViewModel;
|
||||
|
||||
abstract class UnspentCoinsDetailsViewModelBase with Store {
|
||||
UnspentCoinsDetailsViewModelBase({
|
||||
this.unspentCoinsItem, this.unspentCoinsListViewModel}) {
|
||||
|
||||
final amount = unspentCoinsItem.amount ?? '';
|
||||
final address = unspentCoinsItem.address ?? '';
|
||||
isFrozen = unspentCoinsItem.isFrozen ?? false;
|
||||
note = unspentCoinsItem.note ?? '';
|
||||
|
||||
items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: amount
|
||||
),
|
||||
StandartListItem(
|
||||
title: S.current.widgets_address,
|
||||
value: address
|
||||
),
|
||||
TextFieldListItem(
|
||||
title: S.current.note_tap_to_change,
|
||||
value: note,
|
||||
onSubmitted: (value) {
|
||||
unspentCoinsItem.note = value;
|
||||
unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}),
|
||||
UnspentCoinsSwitchItem(
|
||||
title: S.current.freeze,
|
||||
value: '',
|
||||
switchValue: () => isFrozen,
|
||||
onSwitchValueChange: (value) async {
|
||||
isFrozen = value;
|
||||
unspentCoinsItem.isFrozen = value;
|
||||
if (value) {
|
||||
unspentCoinsItem.isSending = !value;
|
||||
}
|
||||
await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
final UnspentCoinsItem unspentCoinsItem;
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
List<TransactionDetailsListItem> items;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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});
|
||||
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
@observable
|
||||
String hash;
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
@observable
|
||||
bool isSending;
|
||||
}
|
||||
@@ -0,0 +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';
|
||||
|
||||
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(_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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class UnspentCoinsSwitchItem extends TransactionDetailsListItem {
|
||||
UnspentCoinsSwitchItem({
|
||||
String title,
|
||||
String value,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange}) : super(title: title, value: value);
|
||||
|
||||
final bool Function() switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
}
|
||||
@@ -64,12 +64,15 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is ElectrumWallet) {
|
||||
await wallet.generateNewAddress();
|
||||
await wallet.walletAddresses.generateNewAddress();
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList
|
||||
.addSubaddress(accountIndex: wallet.account.id, label: label);
|
||||
await wallet.walletAddresses.subaddressList
|
||||
.addSubaddress(
|
||||
accountIndex: wallet.walletAddresses.account.id,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
@@ -77,13 +80,14 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
||||
Future<void> _update() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.updateAddress(_item.address as String);
|
||||
}
|
||||
/*if (wallet is BitcoinWallet) {
|
||||
await wallet.walletAddresses.updateAddress(_item.address as String);
|
||||
await wallet.save();
|
||||
}*/
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.account.id,
|
||||
await wallet.walletAddresses.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.walletAddresses.account.id,
|
||||
addressIndex: _item.id as int,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
|
||||
@@ -80,7 +80,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
WalletAddressListItem get address =>
|
||||
WalletAddressListItem(address: _wallet.address);
|
||||
WalletAddressListItem(address: _wallet.walletAddresses.address);
|
||||
|
||||
@computed
|
||||
PaymentURI get uri {
|
||||
@@ -105,8 +105,10 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
final addressList = ObservableList<ListItem>();
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
final primaryAddress = wallet.subaddressList.subaddresses.first;
|
||||
addressList.addAll(wallet.subaddressList.subaddresses.map((subaddress) {
|
||||
final primaryAddress =
|
||||
wallet.walletAddresses.subaddressList.subaddresses.first;
|
||||
addressList.addAll(wallet.walletAddresses.subaddressList.subaddresses
|
||||
.map((subaddress) {
|
||||
final isPrimary = subaddress == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
@@ -118,8 +120,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
}
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
final primaryAddress = wallet.addresses.first;
|
||||
final bitcoinAddresses = wallet.addresses.map((addr) {
|
||||
final primaryAddress = wallet.walletAddresses.addresses.first;
|
||||
final bitcoinAddresses = wallet.walletAddresses.addresses.map((addr) {
|
||||
final isPrimary = addr == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
@@ -139,7 +141,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
return wallet.account.label;
|
||||
return wallet.walletAddresses.account.label;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -160,7 +162,7 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
|
||||
@action
|
||||
void setAddress(WalletAddressListItem address) =>
|
||||
_wallet.address = address.address;
|
||||
_wallet.walletAddresses.address = address.address;
|
||||
|
||||
void _init() {
|
||||
_baseItems = [];
|
||||
@@ -177,7 +179,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is ElectrumWallet) {
|
||||
wallet.nextAddress();
|
||||
wallet.walletAddresses.nextAddress();
|
||||
wallet.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ abstract class WalletCreationVMBase with Store {
|
||||
dirPath: dirPath);
|
||||
credentials.walletInfo = walletInfo;
|
||||
final wallet = await process(credentials);
|
||||
walletInfo.address = wallet.address;
|
||||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await _walletInfoSource.add(walletInfo);
|
||||
_appStore.changeCurrentWallet(wallet);
|
||||
_appStore.authenticationStore.allowed();
|
||||
|
||||
Reference in New Issue
Block a user