mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 03:29:36 +00:00
TMP 4
This commit is contained in:
parent
719842964b
commit
81cee186db
94 changed files with 3786 additions and 3001 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -88,3 +88,5 @@ android/key.properties
|
|||
|
||||
**/tool/.secrets-prod.json
|
||||
**/lib/.secrets.g.dart
|
||||
|
||||
vendor/
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "inject.dart"]
|
||||
path = inject.dart
|
||||
url = https://github.com/google/inject.dart
|
||||
[submodule ".vendor/inject.dart"]
|
||||
path = .vendor/inject.dart
|
||||
url = https://github.com/google/inject.dart
|
17
lib/bitcoin/bitcoin_address_record.dart
Normal file
17
lib/bitcoin/bitcoin_address_record.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class BitcoinAddressRecord {
|
||||
BitcoinAddressRecord(this.address, {this.label});
|
||||
|
||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinAddressRecord(decoded['address'] as String,
|
||||
label: decoded['label'] as String);
|
||||
}
|
||||
|
||||
final String address;
|
||||
String label;
|
||||
|
||||
String toJSON() => json.encode({'label': label, 'address': address});
|
||||
}
|
|
@ -1,14 +1,35 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cake_wallet/src/domain/common/balance.dart';
|
||||
|
||||
class BitcoinBalance extends Balance {
|
||||
BitcoinBalance({@required this.confirmed, @required this.unconfirmed});
|
||||
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) : super();
|
||||
|
||||
factory BitcoinBalance.fromJSON(String jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinBalance(
|
||||
confirmed: decoded['confirmed'] as int ?? 0,
|
||||
unconfirmed: decoded['unconfirmed'] as int ?? 0);
|
||||
}
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
|
||||
int get total => confirmed + unconfirmed;
|
||||
|
||||
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
|
||||
|
||||
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
|
||||
|
||||
String get totalFormatted => bitcoinAmountToString(amount: total);
|
||||
|
||||
String toJSON() =>
|
||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});
|
||||
}
|
||||
|
|
|
@ -1,61 +1,78 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||
|
||||
class BitcoinTransactionHistory extends TransactionHistory {
|
||||
BitcoinTransactionHistory(
|
||||
{@required this.eclient,
|
||||
@required this.path,
|
||||
@required String password,
|
||||
@required this.wallet})
|
||||
: _transactions = BehaviorSubject<List<TransactionInfo>>.seeded([]),
|
||||
part 'bitcoin_transaction_history.g.dart';
|
||||
|
||||
// TODO: Think about another transaction store for bitcoin transaction history..
|
||||
|
||||
const _transactionsHistoryFileName = 'transactions.json';
|
||||
|
||||
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
|
||||
with _$BitcoinTransactionHistory;
|
||||
|
||||
abstract class BitcoinTransactionHistoryBase
|
||||
extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
|
||||
BitcoinTransactionHistoryBase(
|
||||
{this.eclient, String dirPath, @required String password})
|
||||
: path = '$dirPath/$_transactionsHistoryFileName',
|
||||
_password = password,
|
||||
_height = 0;
|
||||
|
||||
final BitcoinWallet wallet;
|
||||
BitcoinWallet wallet;
|
||||
final ElectrumClient eclient;
|
||||
final String path;
|
||||
final String _password;
|
||||
int _height;
|
||||
|
||||
@override
|
||||
Observable<List<TransactionInfo>> get transactions => _transactions.stream;
|
||||
List<TransactionInfo> get transactionsAll => _transactions.value;
|
||||
final BehaviorSubject<List<TransactionInfo>> _transactions;
|
||||
bool _isUpdating = false;
|
||||
|
||||
Future<void> init() async {
|
||||
// TODO: throw exeption if wallet is null;
|
||||
final info = await _read();
|
||||
_height = (info['height'] as int) ?? _height;
|
||||
_transactions.value = info['transactions'] as List<TransactionInfo>;
|
||||
// FIXME: remove hardcoded value
|
||||
transactions = ObservableList.of([
|
||||
BitcoinTransactionInfo(
|
||||
id: 'test',
|
||||
height: 12,
|
||||
amount: 12,
|
||||
direction: TransactionDirection.incoming,
|
||||
date: DateTime.now(),
|
||||
isPending: false)
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TransactionInfo>> getAll() async => _transactions.value;
|
||||
Future update() async {
|
||||
await super.update();
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
@override
|
||||
Future update() async {
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||
final addresses = wallet.addresses;
|
||||
final histories =
|
||||
addresses.map((record) => eclient.getHistory(address: record.address));
|
||||
final _historiesWithDetails = await Future.wait(histories)
|
||||
.then((histories) => histories
|
||||
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
||||
.expand((i) => i)
|
||||
.toList())
|
||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||
|
||||
try {
|
||||
_isUpdating = true;
|
||||
final newTransasctions = await fetchTransactions();
|
||||
_transactions.value = _transactions.value + newTransasctions;
|
||||
_updateHeight();
|
||||
await save();
|
||||
_isUpdating = false;
|
||||
} catch (e) {
|
||||
_isUpdating = false;
|
||||
rethrow;
|
||||
}
|
||||
return historiesWithDetails
|
||||
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||
addresses: addresses.map((record) => record.address).toList()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Map<String, Object>> fetchTransactionInfo(
|
||||
|
@ -69,51 +86,20 @@ class BitcoinTransactionHistory extends TransactionHistory {
|
|||
return {'raw': raw, 'header': header};
|
||||
}
|
||||
|
||||
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||
final addresses = wallet.getAddresses();
|
||||
final histories =
|
||||
addresses.map((address) => eclient.getHistory(address: address));
|
||||
final _historiesWithDetails = await Future.wait(histories)
|
||||
.then((histories) => histories
|
||||
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
||||
.expand((i) => i)
|
||||
.toList())
|
||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||
|
||||
return historiesWithDetails
|
||||
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||
addresses: addresses))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
||||
final txs = await getAll()
|
||||
..addAll(transactions);
|
||||
await writeData(
|
||||
path: path,
|
||||
password: _password,
|
||||
data: json
|
||||
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
||||
this.transactions.addAll(transactions);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||
final txs = await getAll()
|
||||
..add(tx);
|
||||
await writeData(
|
||||
path: path,
|
||||
password: _password,
|
||||
data: json
|
||||
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
||||
transactions.add(tx);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> save() async => writeData(
|
||||
path: path,
|
||||
password: _password,
|
||||
data: json
|
||||
.encode({'height': _height, 'transactions': _transactions.value}));
|
||||
data: json.encode({'height': _height, 'transactions': transactions}));
|
||||
|
||||
Future<Map<String, Object>> _read() async {
|
||||
try {
|
||||
|
@ -133,13 +119,13 @@ class BitcoinTransactionHistory extends TransactionHistory {
|
|||
|
||||
return {'transactions': transactions, 'height': height};
|
||||
} catch (_) {
|
||||
return {'transactions': List<TransactionInfo>(), 'height': 0};
|
||||
return {'transactions': <TransactionInfo>[], 'height': 0};
|
||||
}
|
||||
}
|
||||
|
||||
void _updateHeight() {
|
||||
final int newHeight = _transactions.value
|
||||
.fold(0, (acc, val) => val.height > acc ? val.height : acc);
|
||||
final newHeight = transactions.fold(
|
||||
0, (int acc, val) => val.height > acc ? val.height : acc);
|
||||
_height = newHeight > _height ? newHeight : _height;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
|||
String amountFormatted() => bitcoinAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
String fiatAmount() => '';
|
||||
String fiatAmount() => '\$ 24.5';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final m = Map<String, dynamic>();
|
||||
|
|
|
@ -1,251 +1,184 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pending_transaction.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
|
||||
class BitcoinWallet extends Wallet {
|
||||
BitcoinWallet(
|
||||
{@required this.hdwallet,
|
||||
@required this.eclient,
|
||||
@required this.path,
|
||||
@required String password,
|
||||
int accountIndex = 0,
|
||||
this.mnemonic})
|
||||
: _accountIndex = accountIndex,
|
||||
_password = password,
|
||||
_syncStatus = BehaviorSubject<SyncStatus>(),
|
||||
_onBalanceChange = BehaviorSubject<BitcoinBalance>(),
|
||||
_onAddressChange = BehaviorSubject<String>(),
|
||||
_onNameChange = BehaviorSubject<String>();
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
@override
|
||||
Observable<BitcoinBalance> get onBalanceChange => _onBalanceChange.stream;
|
||||
/* TODO: Save balance to a wallet file.
|
||||
Load balance from the wallet file in `init` method.
|
||||
*/
|
||||
|
||||
@override
|
||||
Observable<SyncStatus> get syncStatus => _syncStatus.stream;
|
||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||
|
||||
@override
|
||||
String get name => path.split('/').last ?? '';
|
||||
@override
|
||||
String get address => hdwallet.address;
|
||||
String get xpub => hdwallet.base58;
|
||||
|
||||
final String path;
|
||||
final bitcoin.HDWallet hdwallet;
|
||||
final ElectrumClient eclient;
|
||||
final String mnemonic;
|
||||
BitcoinTransactionHistory history;
|
||||
|
||||
final BehaviorSubject<SyncStatus> _syncStatus;
|
||||
final BehaviorSubject<BitcoinBalance> _onBalanceChange;
|
||||
final BehaviorSubject<String> _onAddressChange;
|
||||
final BehaviorSubject<String> _onNameChange;
|
||||
BehaviorSubject<Object> _addressUpdatesSubject;
|
||||
StreamSubscription<Object> _addressUpdatesSubscription;
|
||||
final String _password;
|
||||
int _accountIndex;
|
||||
|
||||
static Future<BitcoinWallet> load(
|
||||
{@required String name, @required String password}) async {
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
||||
final jsoned = json.decode(walletJSONRaw) as Map<String, Object>;
|
||||
final mnemonic = jsoned['mnemonic'] as String;
|
||||
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||
static BitcoinWallet fromJSON(
|
||||
{@required String password,
|
||||
@required String name,
|
||||
@required String dirPath,
|
||||
String jsonSource}) {
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String;
|
||||
final accountIndex =
|
||||
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
|
||||
(data['account_index'] == "null" || data['account_index'] == null)
|
||||
? 0
|
||||
: int.parse(jsoned['account_index'] as String);
|
||||
: int.parse(data['account_index'] as String);
|
||||
final _addresses = data['addresses'] as List;
|
||||
final addresses = <BitcoinAddressRecord>[];
|
||||
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
|
||||
BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
||||
|
||||
return await build(
|
||||
_addresses?.forEach((Object el) {
|
||||
if (el is String) {
|
||||
addresses.add(BitcoinAddressRecord.fromJSON(el));
|
||||
}
|
||||
});
|
||||
|
||||
return BitcoinWalletBase.build(
|
||||
dirPath: dirPath,
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
name: name,
|
||||
accountIndex: accountIndex);
|
||||
accountIndex: accountIndex,
|
||||
initialAddresses: addresses,
|
||||
initialBalance: balance);
|
||||
}
|
||||
|
||||
static Future<BitcoinWallet> build(
|
||||
static BitcoinWallet build(
|
||||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required String name,
|
||||
int accountIndex = 0}) async {
|
||||
final hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||
network: bitcoin.bitcoin);
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final historyPath = '$walletDirPath/transactions.json';
|
||||
@required String dirPath,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
BitcoinBalance initialBalance,
|
||||
int accountIndex = 0}) {
|
||||
final walletPath = '$dirPath/$name';
|
||||
final eclient = ElectrumClient();
|
||||
final wallet = BitcoinWallet(
|
||||
hdwallet: hd,
|
||||
final history = BitcoinTransactionHistory(
|
||||
eclient: eclient, dirPath: dirPath, password: password);
|
||||
|
||||
return BitcoinWallet._internal(
|
||||
eclient: eclient,
|
||||
path: walletPath,
|
||||
name: name,
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
accountIndex: accountIndex);
|
||||
final history = BitcoinTransactionHistory(
|
||||
eclient: eclient,
|
||||
path: historyPath,
|
||||
password: password,
|
||||
wallet: wallet);
|
||||
wallet.history = history;
|
||||
await history.init();
|
||||
await wallet.updateInfo();
|
||||
|
||||
return wallet;
|
||||
accountIndex: accountIndex,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
transactionHistory: history);
|
||||
}
|
||||
|
||||
List<String> getAddresses() => _accountIndex == 0
|
||||
? [address]
|
||||
: List<String>.generate(
|
||||
_accountIndex, (i) => _getAddress(hd: hdwallet, index: i));
|
||||
BitcoinWalletBase._internal(
|
||||
{@required this.eclient,
|
||||
@required this.path,
|
||||
@required String password,
|
||||
@required this.name,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
int accountIndex = 0,
|
||||
this.transactionHistory,
|
||||
this.mnemonic,
|
||||
BitcoinBalance initialBalance}) {
|
||||
balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
||||
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||
network: bitcoin.bitcoin);
|
||||
addresses = initialAddresses != null
|
||||
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
|
||||
: ObservableList<BitcoinAddressRecord>();
|
||||
|
||||
Future<String> newAddress() async {
|
||||
if (addresses.isEmpty) {
|
||||
addresses.add(BitcoinAddressRecord(hd.address));
|
||||
}
|
||||
|
||||
address = addresses.first.address;
|
||||
|
||||
_password = password;
|
||||
_accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
final BitcoinTransactionHistory transactionHistory;
|
||||
final String path;
|
||||
bitcoin.HDWallet hd;
|
||||
final ElectrumClient eclient;
|
||||
final String mnemonic;
|
||||
int _accountIndex;
|
||||
String _password;
|
||||
|
||||
@override
|
||||
String name;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
@observable
|
||||
BitcoinBalance balance;
|
||||
|
||||
@override
|
||||
final type = WalletType.bitcoin;
|
||||
|
||||
ObservableList<BitcoinAddressRecord> addresses;
|
||||
|
||||
String get xpub => hd.base58;
|
||||
|
||||
Future<void> init() async {
|
||||
await transactionHistory.init();
|
||||
}
|
||||
|
||||
Future<BitcoinAddressRecord> generateNewAddress({String label}) async {
|
||||
_accountIndex += 1;
|
||||
final address = _getAddress(hd: hdwallet, index: _accountIndex);
|
||||
final address = BitcoinAddressRecord(
|
||||
_getAddress(hd: hd, index: _accountIndex),
|
||||
label: label);
|
||||
addresses.add(address);
|
||||
|
||||
await save();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@override
|
||||
Future close() async {
|
||||
await _addressUpdatesSubscription?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Future connectToNode(
|
||||
{Node node, bool useSSL = false, bool isLightWallet = false}) async {
|
||||
try {
|
||||
// FIXME: Hardcoded server address
|
||||
// final uri = Uri.parse(node.uri);
|
||||
// https://electrum2.hodlister.co:50002
|
||||
await eclient.connect(host: 'electrum2.hodlister.co', port: 50002);
|
||||
_syncStatus.value = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
_syncStatus.value = FailedSyncStatus();
|
||||
Future<void> updateAddress(String address, {String label}) async {
|
||||
for (final addr in addresses) {
|
||||
if (addr.address == address) {
|
||||
addr.label = label;
|
||||
await save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(
|
||||
TransactionCreationCredentials credentials) async {
|
||||
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
|
||||
final transactions = history.transactionsAll;
|
||||
history.transactionsAll.sort((q, w) => q.height.compareTo(w.height));
|
||||
final prevTx = transactions.first;
|
||||
|
||||
txb.setVersion(1);
|
||||
txb.addInput(prevTx, 0);
|
||||
txb.addOutput('address', 112);
|
||||
txb.sign(vin: null, keyPair: null);
|
||||
|
||||
final hex = txb.build().toHex();
|
||||
|
||||
// broadcast transaction to electrum
|
||||
return null;
|
||||
}
|
||||
Future<void> startSync() async {}
|
||||
|
||||
@override
|
||||
Future<String> getAddress() async => address;
|
||||
Future<void> connectToNode({@required Node node}) async {}
|
||||
|
||||
@override
|
||||
Future<int> getCurrentHeight() async => 0;
|
||||
Future<void> createTransaction(Object credentials) async {}
|
||||
|
||||
@override
|
||||
Future<String> getFilename() async => path.split('/').last ?? '';
|
||||
Future<void> save() async =>
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
|
||||
@override
|
||||
Future<String> getFullBalance() async =>
|
||||
bitcoinAmountToString(amount: _onBalanceChange.value.total);
|
||||
|
||||
@override
|
||||
TransactionHistory getHistory() => history;
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> getKeys() async =>
|
||||
{'publicKey': hdwallet.pubKey, 'privateKey': hdwallet.privKey};
|
||||
|
||||
@override
|
||||
Future<String> getName() async => path.split('/').last ?? '';
|
||||
|
||||
@override
|
||||
Future<int> getNodeHeight() async => 0;
|
||||
|
||||
@override
|
||||
Future<String> getSeed() async => mnemonic;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
||||
@override
|
||||
Future<String> getUnlockedBalance() async =>
|
||||
bitcoinAmountToString(amount: _onBalanceChange.value.total);
|
||||
|
||||
@override
|
||||
Future<bool> isConnected() async => eclient.isConnected;
|
||||
|
||||
@override
|
||||
Observable<String> get onAddressChange => _onAddressChange.stream;
|
||||
|
||||
@override
|
||||
Observable<String> get onNameChange => _onNameChange.stream;
|
||||
|
||||
@override
|
||||
Future rescan({int restoreHeight = 0}) {
|
||||
// TODO: implement rescan
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future startSync() async {
|
||||
_addressUpdatesSubject = eclient.addressUpdate(address: address);
|
||||
_addressUpdatesSubscription =
|
||||
_addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
|
||||
_onBalanceChange.value = await fetchBalance();
|
||||
getHistory().update();
|
||||
}
|
||||
|
||||
@override
|
||||
Future updateInfo() async {
|
||||
_onNameChange.value = await getName();
|
||||
// _addressUpdatesSubject = eclient.addressUpdate(address: address);
|
||||
// _addressUpdatesSubscription =
|
||||
// _addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
|
||||
_onBalanceChange.value = BitcoinBalance(confirmed: 0, unconfirmed: 0);
|
||||
print(await getKeys());
|
||||
}
|
||||
|
||||
Future<BitcoinBalance> fetchBalance() async {
|
||||
final balance = await _fetchBalances();
|
||||
|
||||
return BitcoinBalance(
|
||||
confirmed: balance['confirmed'], unconfirmed: balance['unconfirmed']);
|
||||
}
|
||||
|
||||
Future<void> save() async => await write(
|
||||
path: path,
|
||||
password: _password,
|
||||
obj: {'mnemonic': mnemonic, 'account_index': _accountIndex.toString()});
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': mnemonic,
|
||||
'account_index': _accountIndex.toString(),
|
||||
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
||||
'balance': balance?.toJSON()
|
||||
});
|
||||
|
||||
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
||||
.P2PKH(
|
||||
|
@ -256,9 +189,8 @@ class BitcoinWallet extends Wallet {
|
|||
|
||||
Future<Map<String, int>> _fetchBalances() async {
|
||||
final balances = await Future.wait(
|
||||
getAddresses().map((address) => eclient.getBalance(address: address)));
|
||||
final balance =
|
||||
balances.fold(Map<String, int>(), (Map<String, int> acc, val) {
|
||||
addresses.map((record) => eclient.getBalance(address: record.address)));
|
||||
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
|
||||
acc['confirmed'] =
|
||||
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
||||
acc['unconfirmed'] =
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallets_manager.dart';
|
||||
|
||||
class BitcoinWalletManager extends WalletsManager {
|
||||
@override
|
||||
Future<Wallet> create(String name, String password, String language) async {
|
||||
final wallet = await BitcoinWallet.build(
|
||||
mnemonic: bip39.generateMnemonic(), password: password, name: name);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<Wallet> openWallet(String name, String password) async {
|
||||
return BitcoinWallet.load(
|
||||
name: name, password: password);
|
||||
}
|
||||
|
||||
@override
|
||||
Future remove(WalletDescription wallet) async {
|
||||
final path = await pathForWalletDir(name: wallet.name, type: wallet.type);
|
||||
final f = File(path);
|
||||
|
||||
if (!f.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
f.deleteSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Wallet> restoreFromKeys(String name, String password, String language,
|
||||
int restoreHeight, String address, String viewKey, String spendKey) {
|
||||
// TODO: implement restoreFromKeys
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Wallet> restoreFromSeed(
|
||||
String name, String password, String seed, int restoreHeight) async {
|
||||
final wallet = await BitcoinWallet.build(
|
||||
name: name, password: password, mnemonic: seed);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
21
lib/bitcoin/bitcoin_wallet_creation_credentials.dart
Normal file
21
lib/bitcoin/bitcoin_wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,21 @@
|
|||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||
BitcoinNewWalletCredentials({String name}) : super(name: name);
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromSeedCredentials(
|
||||
{String name, String password, this.mnemonic})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromWIFCredentials(
|
||||
{String name, String password, this.wif})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String wif;
|
||||
}
|
79
lib/bitcoin/bitcoin_wallet_service.dart
Normal file
79
lib/bitcoin/bitcoin_wallet_service.dart
Normal file
|
@ -0,0 +1,79 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class BitcoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
@override
|
||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
||||
final dirPath = await pathForWalletDir(
|
||||
type: WalletType.bitcoin, name: credentials.name);
|
||||
final wallet = BitcoinWalletBase.build(
|
||||
dirPath: dirPath,
|
||||
mnemonic: bip39.generateMnemonic(),
|
||||
password: credentials.password,
|
||||
name: credentials.name);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
||||
final wallet = BitcoinWalletBase.fromJSON(
|
||||
password: password,
|
||||
name: name,
|
||||
dirPath: walletDirPath,
|
||||
jsonSource: walletJSONRaw);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) {
|
||||
// TODO: implement remove
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials) async {
|
||||
// TODO: implement restoreFromKeys
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
final dirPath = await pathForWalletDir(
|
||||
type: WalletType.bitcoin, name: credentials.name);
|
||||
final wallet = BitcoinWalletBase.build(
|
||||
dirPath: dirPath,
|
||||
name: credentials.name,
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
|
@ -7,12 +7,11 @@ import 'package:flutter/foundation.dart';
|
|||
Future<void> write(
|
||||
{@required String path,
|
||||
@required String password,
|
||||
@required Map<String, String> obj}) async {
|
||||
final jsoned = json.encode(obj);
|
||||
@required String data}) async {
|
||||
final keys = extractKeys(password);
|
||||
final key = encrypt.Key.fromBase64(keys.first);
|
||||
final iv = encrypt.IV.fromBase64(keys.last);
|
||||
final encrypted = await encode(key: key, iv: iv, data: jsoned);
|
||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||
final f = File(path);
|
||||
f.writeAsStringSync(encrypted);
|
||||
}
|
||||
|
|
12
lib/core/AddressLabelValidator.dart
Normal file
12
lib/core/AddressLabelValidator.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class AddressLabelValidator extends TextValidator {
|
||||
AddressLabelValidator({WalletType type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_subaddress_name,
|
||||
pattern: '''^[^`,'"]{1,20}\$''',
|
||||
minLength: 1,
|
||||
maxLength: 20);
|
||||
}
|
24
lib/core/amount_validator.dart
Normal file
24
lib/core/amount_validator.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
class AmountValidator extends TextValidator {
|
||||
AmountValidator({WalletType type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_amount,
|
||||
pattern: _pattern(type),
|
||||
minLength: 0,
|
||||
maxLength: 0);
|
||||
|
||||
static String _pattern(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$';
|
||||
case WalletType.bitcoin:
|
||||
// FIXME: Incorrect pattern for bitcoin
|
||||
return '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
|
||||
part 'app_service.g.dart';
|
||||
|
||||
class AppService = AppServiceBase with _$AppService;
|
||||
|
||||
abstract class AppServiceBase with Store {
|
||||
AppServiceBase({this.walletCreationService, this.authService, this.wallet});
|
||||
|
||||
WalletCreationService walletCreationService;
|
||||
AuthService authService;
|
||||
WalletBase wallet;
|
||||
}
|
|
@ -1,21 +1,41 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/setup_pin_code_state.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/encrypt.dart';
|
||||
|
||||
part 'auth_service.g.dart';
|
||||
class AuthService with Store {
|
||||
AuthService({this.secureStorage, this.sharedPreferences});
|
||||
|
||||
class AuthService = AuthServiceBase with _$AuthService;
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
abstract class AuthServiceBase with Store {
|
||||
@observable
|
||||
SetupPinCodeState setupPinCodeState;
|
||||
|
||||
Future<void> setupPinCode({@required String pin}) async {}
|
||||
|
||||
Future<bool> authenticate({@required String pin}) async {
|
||||
return false;
|
||||
Future setPassword(String password) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPassword = encodedPinCode(pin: password);
|
||||
await secureStorage.write(key: key, value: encodedPassword);
|
||||
}
|
||||
|
||||
void resetSetupPinCodeState() =>
|
||||
setupPinCodeState = InitialSetupPinCodeState();
|
||||
Future<bool> canAuthenticate() async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final walletName = sharedPreferences.getString('current_wallet_name') ?? '';
|
||||
var password = '';
|
||||
|
||||
try {
|
||||
password = await secureStorage.read(key: key);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
return walletName.isNotEmpty && password.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<bool> authenticate(String pin) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPin = await secureStorage.read(key: key);
|
||||
final decodedPin = decodedPinCode(pin: encodedPin);
|
||||
|
||||
return decodedPin == pin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/core/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
|
||||
part 'bitcoin_transaction_history.g.dart';
|
||||
|
||||
// TODO: Think about another transaction store for bitcoin transaction history..
|
||||
|
||||
const _transactionsHistoryFileName = 'transactions.json';
|
||||
|
||||
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
|
||||
with _$BitcoinTransactionHistory;
|
||||
|
||||
abstract class BitcoinTransactionHistoryBase
|
||||
extends TranasctionHistoryBase<BitcoinTransactionInfo> with Store {
|
||||
BitcoinTransactionHistoryBase(
|
||||
{this.eclient, String dirPath, @required String password})
|
||||
: path = '$dirPath/$_transactionsHistoryFileName',
|
||||
_password = password,
|
||||
_height = 0;
|
||||
|
||||
BitcoinWallet wallet;
|
||||
final ElectrumClient eclient;
|
||||
final String path;
|
||||
final String _password;
|
||||
int _height;
|
||||
|
||||
Future<void> init() async {
|
||||
// TODO: throw exeption if wallet is null;
|
||||
final info = await _read();
|
||||
_height = (info['height'] as int) ?? _height;
|
||||
transactions = info['transactions'] as List<BitcoinTransactionInfo>;
|
||||
}
|
||||
|
||||
@override
|
||||
Future update() async {
|
||||
await super.update();
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||
final addresses = wallet.getAddresses();
|
||||
final histories =
|
||||
addresses.map((address) => eclient.getHistory(address: address));
|
||||
final _historiesWithDetails = await Future.wait(histories)
|
||||
.then((histories) => histories
|
||||
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
||||
.expand((i) => i)
|
||||
.toList())
|
||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
||||
|
||||
return historiesWithDetails
|
||||
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||
addresses: addresses))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Map<String, Object>> fetchTransactionInfo(
|
||||
{@required String hash, @required int height}) async {
|
||||
final rawFetching = eclient.getTransactionRaw(hash: hash);
|
||||
final headerFetching = eclient.getHeader(height: height);
|
||||
final result = await Future.wait([rawFetching, headerFetching]);
|
||||
final raw = result.first as String;
|
||||
final header = result[1] as Map<String, Object>;
|
||||
|
||||
return {'raw': raw, 'header': header};
|
||||
}
|
||||
|
||||
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
||||
this.transactions.addAll(transactions);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||
transactions.add(tx);
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> save() async => writeData(
|
||||
path: path,
|
||||
password: _password,
|
||||
data: json.encode({'height': _height, 'transactions': transactions}));
|
||||
|
||||
Future<Map<String, Object>> _read() async {
|
||||
try {
|
||||
final content = await read(path: path, password: _password);
|
||||
final jsoned = json.decode(content) as Map<String, Object>;
|
||||
final height = jsoned['height'] as int;
|
||||
final transactions = (jsoned['transactions'] as List<dynamic>)
|
||||
.map((dynamic row) {
|
||||
if (row is Map<String, Object>) {
|
||||
return BitcoinTransactionInfo.fromJson(row);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.where((el) => el != null)
|
||||
.toList();
|
||||
|
||||
return {'transactions': transactions, 'height': height};
|
||||
} catch (_) {
|
||||
return {'transactions': <TransactionInfo>[], 'height': 0};
|
||||
}
|
||||
}
|
||||
|
||||
void _updateHeight() {
|
||||
final newHeight = transactions.fold(
|
||||
0, (int acc, val) => val.height > acc ? val.height : acc);
|
||||
_height = newHeight > _height ? newHeight : _height;
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cake_wallet/core/bitcoin_transaction_history.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:cake_wallet/bitcoin/file.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'wallet_base.dart';
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
/* TODO: Save balance to a wallet file.
|
||||
Load balance from the wallet file in `init` method.
|
||||
*/
|
||||
|
||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||
|
||||
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||
static Future<BitcoinWalletBase> load(
|
||||
{@required String name, @required String password}) async {
|
||||
final walletDirPath =
|
||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
||||
final walletPath = '$walletDirPath/$name';
|
||||
final walletJSONRaw = await read(path: walletPath, password: password);
|
||||
final jsoned = json.decode(walletJSONRaw) as Map<String, Object>;
|
||||
final mnemonic = jsoned['mnemonic'] as String;
|
||||
final accountIndex =
|
||||
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
|
||||
? 0
|
||||
: int.parse(jsoned['account_index'] as String);
|
||||
|
||||
return BitcoinWalletBase.build(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
name: name,
|
||||
accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
factory BitcoinWalletBase.build(
|
||||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required String name,
|
||||
@required String dirPath,
|
||||
int accountIndex = 0}) {
|
||||
final walletPath = '$dirPath/$name';
|
||||
final eclient = ElectrumClient();
|
||||
final history = BitcoinTransactionHistory(
|
||||
eclient: eclient, dirPath: dirPath, password: password);
|
||||
|
||||
return BitcoinWallet._internal(
|
||||
eclient: eclient,
|
||||
path: walletPath,
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
accountIndex: accountIndex,
|
||||
transactionHistory: history);
|
||||
}
|
||||
|
||||
BitcoinWalletBase._internal(
|
||||
{@required this.eclient,
|
||||
@required this.path,
|
||||
@required String password,
|
||||
int accountIndex = 0,
|
||||
this.transactionHistory,
|
||||
this.mnemonic}) {
|
||||
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
||||
network: bitcoin.bitcoin);
|
||||
_password = password;
|
||||
_accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
final BitcoinTransactionHistory transactionHistory;
|
||||
final String path;
|
||||
bitcoin.HDWallet hd;
|
||||
final ElectrumClient eclient;
|
||||
final String mnemonic;
|
||||
int _accountIndex;
|
||||
String _password;
|
||||
|
||||
@override
|
||||
String get name => path.split('/').last ?? '';
|
||||
|
||||
@override
|
||||
String get filename => hd.address;
|
||||
|
||||
String get xpub => hd.base58;
|
||||
|
||||
List<String> getAddresses() => _accountIndex == 0
|
||||
? [address]
|
||||
: List<String>.generate(
|
||||
_accountIndex, (i) => _getAddress(hd: hd, index: i));
|
||||
|
||||
Future<void> init() async {
|
||||
await transactionHistory.init();
|
||||
}
|
||||
|
||||
Future<String> newAddress() async {
|
||||
_accountIndex += 1;
|
||||
final address = _getAddress(hd: hd, index: _accountIndex);
|
||||
await save();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startSync() async {}
|
||||
|
||||
@override
|
||||
Future<void> connectToNode({@required Node node}) async {}
|
||||
|
||||
@override
|
||||
Future<void> createTransaction(Object credentials) async {}
|
||||
|
||||
@override
|
||||
Future<void> save() async => await write(
|
||||
path: path,
|
||||
password: _password,
|
||||
obj: {'mnemonic': mnemonic, 'account_index': _accountIndex.toString()});
|
||||
|
||||
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
||||
.P2PKH(
|
||||
data: PaymentData(
|
||||
pubkey: Uint8List.fromList(hd.derive(index).pubKey.codeUnits)))
|
||||
.data
|
||||
.address;
|
||||
|
||||
Future<Map<String, int>> _fetchBalances() async {
|
||||
final balances = await Future.wait(
|
||||
getAddresses().map((address) => eclient.getBalance(address: address)));
|
||||
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
|
||||
acc['confirmed'] =
|
||||
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
||||
acc['unconfirmed'] =
|
||||
(val['unconfirmed'] as int ?? 0) + (acc['unconfirmed'] ?? 0);
|
||||
|
||||
return acc;
|
||||
});
|
||||
|
||||
return balance;
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_description.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallets_manager.dart';
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromSeedCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromSeedCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
const BitcoinRestoreWalletFromSeedCredentials(
|
||||
{String name, String password, this.mnemonic})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinRestoreWalletFromWIFCredentials
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||
const BitcoinRestoreWalletFromWIFCredentials(
|
||||
{String name, String password, this.wif})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* BitcoinWalletListService
|
||||
*
|
||||
* */
|
||||
|
||||
class BitcoinWalletListService extends WalletListService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
@override
|
||||
Future<void> create(BitcoinNewWalletCredentials credentials) async {
|
||||
final wallet = await BitcoinWalletBase.build(
|
||||
mnemonic: bip39.generateMnemonic(),
|
||||
password: credentials.password,
|
||||
name: credentials.name);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: WalletType.bitcoin))
|
||||
.existsSync();
|
||||
|
||||
@override
|
||||
Future<void> openWallet(String name, String password) async {
|
||||
// TODO: implement openWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<void> remove(String wallet) {
|
||||
// TODO: implement remove
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials) async {
|
||||
// TODO: implement restoreFromKeys
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
final wallet = await BitcoinWalletBase.build(
|
||||
name: credentials.name,
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic);
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
}
|
12
lib/core/generate_wallet_password.dart
Normal file
12
lib/core/generate_wallet_password.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:uuid/uuid.dart';
|
||||
import 'package:cake_wallet/bitcoin/key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
String generateWalletPassword(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return generateKey();
|
||||
default:
|
||||
return Uuid().v4();
|
||||
}
|
||||
}
|
17
lib/core/mnemonic_length.dart
Normal file
17
lib/core/mnemonic_length.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
const bitcoinMnemonicLength = 12;
|
||||
const moneroMnemonicLength = 25;
|
||||
|
||||
int mnemonicLength(WalletType type) {
|
||||
// TODO: need to have only one place for get(set) mnemonic string lenth;
|
||||
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return moneroMnemonicLength;
|
||||
case WalletType.bitcoin:
|
||||
return bitcoinMnemonicLength;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
73
lib/core/seed_validator.dart
Normal file
73
lib/core/seed_validator.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import 'package:bip39/src/wordlists/english.dart' as bitcoin_english;
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/chinese_simplified.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/dutch.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/german.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/japanese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/portuguese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/russian.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/spanish.dart';
|
||||
|
||||
class SeedValidator extends Validator<MnemonicItem> {
|
||||
SeedValidator({this.type, this.language})
|
||||
: _words = getWordList(type: type, language: language);
|
||||
|
||||
final WalletType type;
|
||||
final String language;
|
||||
final List<String> _words;
|
||||
|
||||
static List<String> getWordList({WalletType type, String language}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return getBitcoinWordList(language);
|
||||
case WalletType.monero:
|
||||
return getMoneroWordList(language);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> getMoneroWordList(String language) {
|
||||
// FIXME: Unnamed constants; Need to be sure that string are in same case;
|
||||
|
||||
switch (language) {
|
||||
case 'English':
|
||||
return EnglishMnemonics.words;
|
||||
break;
|
||||
case 'Chinese (simplified)':
|
||||
return ChineseSimplifiedMnemonics.words;
|
||||
break;
|
||||
case 'Dutch':
|
||||
return DutchMnemonics.words;
|
||||
break;
|
||||
case 'German':
|
||||
return GermanMnemonics.words;
|
||||
break;
|
||||
case 'Japanese':
|
||||
return JapaneseMnemonics.words;
|
||||
break;
|
||||
case 'Portuguese':
|
||||
return PortugueseMnemonics.words;
|
||||
break;
|
||||
case 'Russian':
|
||||
return RussianMnemonics.words;
|
||||
break;
|
||||
case 'Spanish':
|
||||
return SpanishMnemonics.words;
|
||||
break;
|
||||
default:
|
||||
return EnglishMnemonics.words;
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> getBitcoinWordList(String language) {
|
||||
assert(language.toLowerCase() == 'english');
|
||||
return bitcoin_english.WORDLIST;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isValid(MnemonicItem value) => _words.contains(value.text);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class SetupPinCodeState {}
|
||||
|
||||
class InitialSetupPinCodeState extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeInProgress extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeFinishedSuccessfully extends SetupPinCodeState {}
|
||||
|
||||
class SetupPinCodeFinishedFailure extends SetupPinCodeState {
|
||||
SetupPinCodeFinishedFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
|
||||
abstract class TranasctionHistoryBase<TransactionType> {
|
||||
TranasctionHistoryBase() : _isUpdating = false;
|
||||
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
||||
TransactionHistoryBase() : _isUpdating = false;
|
||||
|
||||
@observable
|
||||
List<TransactionType> transactions;
|
||||
ObservableList<TransactionType> transactions;
|
||||
|
||||
bool _isUpdating;
|
||||
|
||||
|
@ -15,7 +16,7 @@ abstract class TranasctionHistoryBase<TransactionType> {
|
|||
|
||||
try {
|
||||
_isUpdating = false;
|
||||
transactions = await fetchTransactions();
|
||||
transactions.addAll(await fetchTransactions());
|
||||
_isUpdating = true;
|
||||
} catch (e) {
|
||||
_isUpdating = false;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class Validator<T> {
|
||||
Validator({@required this.errorMessage});
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
bool isValid(T value);
|
||||
|
||||
String call(T value) => !isValid(value) ? errorMessage : null;
|
||||
}
|
||||
|
||||
class TextValidator extends Validator<String> {
|
||||
TextValidator(
|
||||
{this.minLength, this.maxLength, this.pattern, String errorMessage})
|
||||
: super(errorMessage: errorMessage);
|
||||
|
||||
final int minLength;
|
||||
final int maxLength;
|
||||
String pattern;
|
||||
|
||||
@override
|
||||
bool isValid(String value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value.length > minLength &&
|
||||
(maxLength > 0 ? (value.length <= maxLength) : true) &&
|
||||
(pattern != null ? match(value) : true);
|
||||
}
|
||||
|
||||
bool match(String value) => RegExp(pattern).hasMatch(value);
|
||||
}
|
||||
|
||||
class WalletNameValidator extends TextValidator {
|
||||
WalletNameValidator()
|
||||
: super(minLength: 1, maxLength: 15, pattern: '^[a-zA-Z0-9_]\$');
|
||||
}
|
|
@ -1,20 +1,24 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
||||
abstract class WalletBase<BalaceType> {
|
||||
WalletType type;
|
||||
|
||||
String get name;
|
||||
|
||||
String get filename;
|
||||
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
BalaceType balance;
|
||||
|
||||
TransactionHistoryBase transactionHistory;
|
||||
|
||||
Future<void> connectToNode({@required Node node});
|
||||
|
||||
Future<void> startSync();
|
||||
|
||||
Future<void> createTransaction(Object credentials);
|
||||
|
||||
Future<void> save();
|
||||
}
|
|
@ -1,34 +1,47 @@
|
|||
import 'package:cake_wallet/core/wallet_creation_state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/core/bitcoin_wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/monero_wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/encrypt.dart';
|
||||
|
||||
part 'wallet_creation_service.g.dart';
|
||||
|
||||
class WalletCreationService = WalletCreationServiceBase
|
||||
with _$WalletCreationService;
|
||||
|
||||
abstract class WalletCreationServiceBase with Store {
|
||||
@observable
|
||||
WalletCreationState state;
|
||||
class WalletCreationService {
|
||||
WalletCreationService(
|
||||
{WalletType initialType,
|
||||
this.appStore,
|
||||
this.secureStorage,
|
||||
this.sharedPreferences})
|
||||
: type = initialType {
|
||||
if (type != null) {
|
||||
changeWalletType(type: type);
|
||||
}
|
||||
}
|
||||
|
||||
WalletType type;
|
||||
final AppStore appStore;
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
WalletListService _service;
|
||||
// final WalletService walletService;
|
||||
// final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
WalletService _service;
|
||||
|
||||
void changeWalletType({@required WalletType type}) {
|
||||
this.type = type;
|
||||
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
_service = MoneroWalletListService();
|
||||
_service = MoneroWalletService();
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
_service = BitcoinWalletListService();
|
||||
_service = BitcoinWalletService();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -36,32 +49,45 @@ abstract class WalletCreationServiceBase with Store {
|
|||
}
|
||||
|
||||
Future<void> create(WalletCredentials credentials) async {
|
||||
try {
|
||||
state = WalletCreating();
|
||||
await _service.create(credentials);
|
||||
state = WalletCreatedSuccessfully();
|
||||
} catch (e) {
|
||||
state = WalletCreationFailure(error: e.toString());
|
||||
}
|
||||
final password = generateWalletPassword(type);
|
||||
credentials.password = password;
|
||||
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||
final wallet = await _service.create(credentials);
|
||||
appStore.wallet = wallet;
|
||||
appStore.authenticationStore.allowed();
|
||||
}
|
||||
|
||||
Future<void> restoreFromKeys(WalletCredentials credentials) async {
|
||||
try {
|
||||
state = WalletCreating();
|
||||
await _service.restoreFromKeys(credentials);
|
||||
state = WalletCreatedSuccessfully();
|
||||
} catch (e) {
|
||||
state = WalletCreationFailure(error: e.toString());
|
||||
}
|
||||
final password = generateWalletPassword(type);
|
||||
credentials.password = password;
|
||||
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||
final wallet = await _service.restoreFromKeys(credentials);
|
||||
appStore.wallet = wallet;
|
||||
appStore.authenticationStore.allowed();
|
||||
}
|
||||
|
||||
Future<void> restoreFromSeed(WalletCredentials credentials) async {
|
||||
try {
|
||||
state = WalletCreating();
|
||||
await _service.restoreFromSeed(credentials);
|
||||
state = WalletCreatedSuccessfully();
|
||||
} catch (e) {
|
||||
state = WalletCreationFailure(error: e.toString());
|
||||
}
|
||||
final password = generateWalletPassword(type);
|
||||
credentials.password = password;
|
||||
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||
final wallet = await _service.restoreFromSeed(credentials);
|
||||
appStore.wallet = wallet;
|
||||
appStore.authenticationStore.allowed();
|
||||
}
|
||||
|
||||
Future<String> getWalletPassword({String walletName}) async {
|
||||
final key = generateStoreKeyFor(
|
||||
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
|
||||
final encodedPassword = await secureStorage.read(key: key);
|
||||
|
||||
return decodeWalletPassword(password: encodedPassword);
|
||||
}
|
||||
|
||||
Future<void> saveWalletPassword({String walletName, String password}) async {
|
||||
final key = generateStoreKeyFor(
|
||||
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
|
||||
final encodedPassword = encodeWalletPassword(password: password);
|
||||
|
||||
await secureStorage.write(key: key, value: encodedPassword);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
abstract class WalletCredentials {
|
||||
const WalletCredentials({this.name, this.password});
|
||||
WalletCredentials({this.name, this.password});
|
||||
|
||||
final String name;
|
||||
final String password;
|
||||
String password;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
abstract class WalletListService<N extends WalletCredentials,
|
||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||
Future<void> create(N credentials);
|
||||
|
||||
Future<void> restoreFromSeed(RFS credentials);
|
||||
|
||||
Future<void> restoreFromKeys(RFK credentials);
|
||||
|
||||
Future<void> openWallet(String name, String password);
|
||||
|
||||
Future<bool> isWalletExit(String name);
|
||||
|
||||
Future<void> remove(String wallet);
|
||||
}
|
17
lib/core/wallet_service.dart
Normal file
17
lib/core/wallet_service.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
|
||||
abstract class WalletService<N extends WalletCredentials,
|
||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||
Future<WalletBase> create(N credentials);
|
||||
|
||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
||||
|
||||
Future<WalletBase> restoreFromKeys(RFK credentials);
|
||||
|
||||
Future<WalletBase> openWallet(String name, String password);
|
||||
|
||||
Future<bool> isWalletExit(String name);
|
||||
|
||||
Future<void> remove(String wallet);
|
||||
}
|
105
lib/di.dart
Normal file
105
lib/di.dart
Normal file
|
@ -0,0 +1,105 @@
|
|||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
ReactionDisposer _onCurrentWalletChangeReaction;
|
||||
|
||||
void setup() {
|
||||
getIt.registerSingleton(AuthenticationStore());
|
||||
getIt.registerSingleton<AppStore>(
|
||||
AppStore(authenticationStore: getIt.get<AuthenticationStore>()));
|
||||
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
() => SharedPreferences.getInstance());
|
||||
getIt.registerFactoryParam<WalletCreationService, WalletType, void>(
|
||||
(type, _) => WalletCreationService(
|
||||
initialType: type,
|
||||
appStore: getIt.get<AppStore>(),
|
||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
|
||||
WalletNewVM(getIt.get<WalletCreationService>(param1: type), type: type));
|
||||
|
||||
getIt
|
||||
.registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) {
|
||||
final type = args.first as WalletType;
|
||||
final language = args[1] as String;
|
||||
final mnemonic = args[2] as String;
|
||||
|
||||
return WalletRestorationFromSeedVM(
|
||||
getIt.get<WalletCreationService>(param1: type),
|
||||
type: type,
|
||||
language: language,
|
||||
seed: mnemonic);
|
||||
});
|
||||
|
||||
getIt.registerFactory<AddressListViewModel>(
|
||||
() => AddressListViewModel(wallet: getIt.get<AppStore>().wallet));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => DashboardViewModel(appStore: getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactory<AuthService>(() => AuthService(
|
||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(
|
||||
authService: getIt.get<AuthService>(),
|
||||
sharedPreferences: getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory<AuthPage>(() => AuthPage(
|
||||
authViewModel: getIt.get<AuthViewModel>(),
|
||||
onAuthenticationFinished: (isAuthenticated, __) {
|
||||
if (isAuthenticated) {
|
||||
getIt.get<AuthenticationStore>().allowed();
|
||||
}
|
||||
},
|
||||
closable: false));
|
||||
|
||||
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||
walletViewModel: getIt.get<DashboardViewModel>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory<ReceivePage>(() =>
|
||||
ReceivePage(addressListViewModel: getIt.get<AddressListViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<AddressEditOrCreateViewModel, dynamic, void>(
|
||||
(dynamic item, _) => AddressEditOrCreateViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet, item: item));
|
||||
|
||||
getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>(
|
||||
(dynamic item, _) => AddressEditOrCreatePage(
|
||||
addressEditOrCreateViewModel:
|
||||
getIt.get<AddressEditOrCreateViewModel>(param1: item)));
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
_onCurrentWalletChangeReaction ??=
|
||||
reaction((_) => appStore.wallet, (WalletBase wallet) async {
|
||||
print('Wallet name ${wallet.name}');
|
||||
await getIt
|
||||
.get<SharedPreferences>()
|
||||
.setString('current_wallet_name', wallet.name);
|
||||
});
|
||||
}
|
|
@ -1,7 +1,13 @@
|
|||
import 'package:cake_wallet/core/app_service.dart';
|
||||
import 'package:cake_wallet/reactions/bootstrap.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
@ -27,7 +33,8 @@ import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
|
|||
import 'package:cake_wallet/src/stores/send_template/send_template_store.dart';
|
||||
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
|
||||
import 'package:cake_wallet/src/screens/root/root.dart';
|
||||
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
|
||||
|
||||
//import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'package:cake_wallet/src/stores/price/price_store.dart';
|
||||
import 'package:cake_wallet/src/domain/services/user_service.dart';
|
||||
|
@ -47,6 +54,8 @@ import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
|||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setup();
|
||||
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(appDir.path);
|
||||
Hive.registerAdapter(ContactAdapter());
|
||||
|
@ -87,13 +96,13 @@ void main() async {
|
|||
sharedPreferences: sharedPreferences);
|
||||
final userService = UserService(
|
||||
sharedPreferences: sharedPreferences, secureStorage: secureStorage);
|
||||
final authenticationStore = AuthenticationStore(userService: userService);
|
||||
// final authenticationStore = AuthenticationStore(userService: userService);
|
||||
|
||||
await initialSetup(
|
||||
sharedPreferences: sharedPreferences,
|
||||
walletListService: walletListService,
|
||||
nodes: nodes,
|
||||
authStore: authenticationStore,
|
||||
// authStore: authenticationStore,
|
||||
initialMigrationVersion: 2);
|
||||
|
||||
final settingsStore = await SettingsStoreBase.load(
|
||||
|
@ -119,8 +128,7 @@ void main() async {
|
|||
|
||||
final walletCreationService = WalletCreationService();
|
||||
final authService = AuthService();
|
||||
final appStore = AppService(
|
||||
walletCreationService: walletCreationService, authService: authService);
|
||||
|
||||
|
||||
setReactions(
|
||||
settingsStore: settingsStore,
|
||||
|
@ -128,7 +136,7 @@ void main() async {
|
|||
syncStore: syncStore,
|
||||
walletStore: walletStore,
|
||||
walletService: walletService,
|
||||
authenticationStore: authenticationStore,
|
||||
// authenticationStore: authenticationStore,
|
||||
loginStore: loginStore);
|
||||
|
||||
runApp(MultiProvider(providers: [
|
||||
|
@ -141,7 +149,7 @@ void main() async {
|
|||
Provider(create: (_) => walletStore),
|
||||
Provider(create: (_) => syncStore),
|
||||
Provider(create: (_) => balanceStore),
|
||||
Provider(create: (_) => authenticationStore),
|
||||
// Provider(create: (_) => authenticationStore),
|
||||
Provider(create: (_) => contacts),
|
||||
Provider(create: (_) => nodes),
|
||||
Provider(create: (_) => transactionDescriptions),
|
||||
|
@ -149,7 +157,7 @@ void main() async {
|
|||
Provider(create: (_) => seedLanguageStore),
|
||||
Provider(create: (_) => sendTemplateStore),
|
||||
Provider(create: (_) => exchangeTemplateStore),
|
||||
Provider(create: (_) => appStore),
|
||||
// Provider(create: (_) => appStore),
|
||||
Provider(create: (_) => walletCreationService),
|
||||
Provider(create: (_) => authService)
|
||||
], child: CakeWalletApp()));
|
||||
|
@ -159,7 +167,7 @@ Future<void> initialSetup(
|
|||
{WalletListService walletListService,
|
||||
SharedPreferences sharedPreferences,
|
||||
Box<Node> nodes,
|
||||
AuthenticationStore authStore,
|
||||
// AuthenticationStore authStore,
|
||||
int initialMigrationVersion = 1,
|
||||
WalletType initialWalletType = WalletType.bitcoin}) async {
|
||||
await walletListService.changeWalletManger(walletType: initialWalletType);
|
||||
|
@ -167,7 +175,12 @@ Future<void> initialSetup(
|
|||
version: initialMigrationVersion,
|
||||
sharedPreferences: sharedPreferences,
|
||||
nodes: nodes);
|
||||
await authStore.started();
|
||||
// await authStore.started();
|
||||
await bootstrap();
|
||||
// final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
// FIXME
|
||||
// authenticationStore.state = AuthenticationState.denied;
|
||||
|
||||
monero_wallet.onStartup();
|
||||
}
|
||||
|
||||
|
@ -241,6 +254,8 @@ class MaterialAppWithTheme extends StatelessWidget {
|
|||
nodes: nodes,
|
||||
trades: trades,
|
||||
transactionDescriptions: transactionDescriptions),
|
||||
home: Root());
|
||||
home: Root(
|
||||
authenticationStore: getIt.get<AuthenticationStore>(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
76
lib/monero/monero_subaddress_list.dart
Normal file
76
lib/monero/monero_subaddress_list.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_monero/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
|
||||
part 'monero_subaddress_list.g.dart';
|
||||
|
||||
class MoneroSubaddressList = MoneroSubaddressListBase
|
||||
with _$MoneroSubaddressList;
|
||||
|
||||
abstract class MoneroSubaddressListBase with Store {
|
||||
MoneroSubaddressListBase() {
|
||||
_isRefreshing = false;
|
||||
_isUpdating = false;
|
||||
subaddresses = ObservableList<Subaddress>();
|
||||
}
|
||||
|
||||
@observable
|
||||
ObservableList<Subaddress> subaddresses;
|
||||
|
||||
bool _isRefreshing;
|
||||
bool _isUpdating;
|
||||
|
||||
void update({int accountIndex}) {
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_isUpdating = true;
|
||||
refresh(accountIndex: accountIndex);
|
||||
subaddresses.clear();
|
||||
subaddresses.addAll(getAll());
|
||||
_isUpdating = false;
|
||||
} catch (e) {
|
||||
_isUpdating = false;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
List<Subaddress> getAll() {
|
||||
return subaddress_list
|
||||
.getAllSubaddresses()
|
||||
.map((subaddressRow) => Subaddress.fromRow(subaddressRow))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future addSubaddress({int accountIndex, String label}) async {
|
||||
await subaddress_list.addSubaddress(
|
||||
accountIndex: accountIndex, label: label);
|
||||
update(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
Future setLabelSubaddress(
|
||||
{int accountIndex, int addressIndex, String label}) async {
|
||||
await subaddress_list.setLabelForSubaddress(
|
||||
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||
update(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
void refresh({int accountIndex}) {
|
||||
if (_isRefreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_isRefreshing = true;
|
||||
subaddress_list.refreshSubaddresses(accountIndex: accountIndex);
|
||||
_isRefreshing = false;
|
||||
} on PlatformException catch (e) {
|
||||
_isRefreshing = false;
|
||||
print(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
|
|||
|
||||
part 'monero_transaction_history.g.dart';
|
||||
|
||||
List<TransactionInfo> _getAllTransactions(dynamic _) =>
|
||||
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
|
||||
monero_transaction_history
|
||||
.getAllTransations()
|
||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||
|
@ -18,9 +18,13 @@ class MoneroTransactionHistory = MoneroTransactionHistoryBase
|
|||
with _$MoneroTransactionHistory;
|
||||
|
||||
abstract class MoneroTransactionHistoryBase
|
||||
extends TranasctionHistoryBase<TransactionInfo> with Store {
|
||||
extends TransactionHistoryBase<MoneroTransactionInfo> with Store {
|
||||
MoneroTransactionHistoryBase() {
|
||||
transactions = ObservableList<MoneroTransactionInfo>();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TransactionInfo>> fetchTransactions() async {
|
||||
Future<List<MoneroTransactionInfo>> fetchTransactions() async {
|
||||
monero_transaction_history.refreshTransactions();
|
||||
return _getAllTransactions(null);
|
||||
}
|
|
@ -1,35 +1,36 @@
|
|||
import 'package:cake_wallet/core/monero_balance.dart';
|
||||
import 'package:cake_wallet/core/monero_transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.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/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/account.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/account_list.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress_list.dart';
|
||||
import 'package:cw_monero/wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||
import 'wallet_base.dart';
|
||||
|
||||
part 'monero_wallet.g.dart';
|
||||
|
||||
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||
|
||||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||
MoneroWalletBase({String filename, this.isRecovery = false}) {
|
||||
transactionHistory = MoneroTransactionHistory();
|
||||
MoneroWalletBase({String filename, this.isRecovery = false})
|
||||
: transactionHistory = MoneroTransactionHistory() {
|
||||
_filename = filename;
|
||||
accountList = AccountList();
|
||||
subaddressList = SubaddressList();
|
||||
subaddressList = MoneroSubaddressList();
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
|
||||
}
|
||||
|
||||
MoneroTransactionHistory transactionHistory;
|
||||
SubaddressList subaddressList;
|
||||
AccountList accountList;
|
||||
@override
|
||||
final MoneroTransactionHistory transactionHistory;
|
||||
|
||||
@observable
|
||||
Account account;
|
||||
|
@ -41,30 +42,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
|||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
String get name => filename.split('/').last;
|
||||
String get name => _filename.split('/').last;
|
||||
|
||||
@override
|
||||
String get filename => _filename;
|
||||
final type = WalletType.monero;
|
||||
|
||||
String _filename;
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
bool isRecovery;
|
||||
|
||||
SyncListner _listner;
|
||||
MoneroSubaddressList subaddressList;
|
||||
|
||||
void init() {
|
||||
AccountList accountList;
|
||||
|
||||
String _filename;
|
||||
|
||||
SyncListner _listener;
|
||||
|
||||
Future<void> init() async {
|
||||
await accountList.update();
|
||||
account = accountList.getAll().first;
|
||||
subaddressList.refresh(accountIndex: account.id ?? 0);
|
||||
subaddressList.update(accountIndex: account.id ?? 0);
|
||||
subaddress = subaddressList.getAll().first;
|
||||
balance = MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getFullBalance(accountIndex: account.id));
|
||||
address = subaddress.address;
|
||||
_setListeners();
|
||||
}
|
||||
|
||||
void close() {
|
||||
_listner?.stop();
|
||||
_listener?.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -133,8 +144,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
|||
Future<bool> isConnected() async => monero_wallet.isConnected();
|
||||
|
||||
void _setListeners() {
|
||||
_listner?.stop();
|
||||
_listner = monero_wallet.setListeners(
|
||||
_listener?.stop();
|
||||
_listener = monero_wallet.setListeners(
|
||||
_onNewBlock, _onNeedToRefresh, _onNewTransaction);
|
||||
}
|
||||
|
|
@ -1,21 +1,20 @@
|
|||
import 'package:cake_wallet/core/monero_wallet.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cw_monero/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||
|
||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
const MoneroNewWalletCredentials(
|
||||
{String name, String password, this.language})
|
||||
MoneroNewWalletCredentials({String name, String password, this.language})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String language;
|
||||
}
|
||||
|
||||
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
const MoneroRestoreWalletFromSeedCredentials(
|
||||
MoneroRestoreWalletFromSeedCredentials(
|
||||
{String name, String password, this.mnemonic, this.height})
|
||||
: super(name: name, password: password);
|
||||
|
||||
|
@ -24,7 +23,7 @@ class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
|||
}
|
||||
|
||||
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||
const MoneroRestoreWalletFromKeysCredentials(
|
||||
MoneroRestoreWalletFromKeysCredentials(
|
||||
{String name,
|
||||
String password,
|
||||
this.language,
|
||||
|
@ -41,12 +40,12 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
|||
final int height;
|
||||
}
|
||||
|
||||
class MoneroWalletListService extends WalletListService<
|
||||
class MoneroWalletService extends WalletService<
|
||||
MoneroNewWalletCredentials,
|
||||
MoneroRestoreWalletFromSeedCredentials,
|
||||
MoneroRestoreWalletFromKeysCredentials> {
|
||||
@override
|
||||
Future<void> create(MoneroNewWalletCredentials credentials) async {
|
||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
||||
try {
|
||||
final path =
|
||||
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
||||
|
@ -56,7 +55,10 @@ class MoneroWalletListService extends WalletListService<
|
|||
password: credentials.password,
|
||||
language: credentials.language);
|
||||
|
||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
||||
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception fop wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
||||
|
@ -77,7 +79,7 @@ class MoneroWalletListService extends WalletListService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> openWallet(String name, String password) async {
|
||||
Future<MoneroWallet> openWallet(String name, String password) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
monero_wallet_manager.openWallet(path: path, password: password);
|
||||
|
@ -86,7 +88,10 @@ class MoneroWalletListService extends WalletListService<
|
|||
// final walletInfo = walletInfoSource.values
|
||||
// .firstWhere((info) => info.id == id, orElse: () => null);
|
||||
|
||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
||||
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception fop wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
||||
|
@ -100,7 +105,7 @@ class MoneroWalletListService extends WalletListService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromKeys(
|
||||
Future<MoneroWallet> restoreFromKeys(
|
||||
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
||||
try {
|
||||
final path =
|
||||
|
@ -115,7 +120,10 @@ class MoneroWalletListService extends WalletListService<
|
|||
viewKey: credentials.viewKey,
|
||||
spendKey: credentials.spendKey);
|
||||
|
||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
||||
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception fop wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
||||
|
@ -124,7 +132,7 @@ class MoneroWalletListService extends WalletListService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> restoreFromSeed(
|
||||
Future<MoneroWallet> restoreFromSeed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
try {
|
||||
final path =
|
||||
|
@ -136,7 +144,10 @@ class MoneroWalletListService extends WalletListService<
|
|||
seed: credentials.mnemonic,
|
||||
restoreHeight: credentials.height);
|
||||
|
||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
||||
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception fop wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
|
@ -12,6 +12,7 @@ class Palette {
|
|||
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
|
||||
static const Color darkLavender = Color.fromRGBO(225, 238, 250, 1.0);
|
||||
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0);
|
||||
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
|
||||
}
|
||||
|
||||
class PaletteDark {
|
||||
|
|
66
lib/reactions/bootstrap.dart
Normal file
66
lib/reactions/bootstrap.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/encrypt.dart';
|
||||
|
||||
// FIXME: move me
|
||||
Future<String> getWalletPassword({String walletName}) async {
|
||||
final secureStorage = getIt.get<FlutterSecureStorage>();
|
||||
final key = generateStoreKeyFor(
|
||||
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
|
||||
final encodedPassword = await secureStorage.read(key: key);
|
||||
|
||||
return decodeWalletPassword(password: encodedPassword);
|
||||
}
|
||||
|
||||
// FIXME: move me
|
||||
Future<void> loadCurrentWallet() async {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final name = getIt.get<SharedPreferences>().getString('current_wallet_name');
|
||||
final type = WalletType.monero; // FIXME
|
||||
final password = await getWalletPassword(walletName: name);
|
||||
|
||||
WalletService _service;
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
_service = MoneroWalletService();
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
_service = BitcoinWalletService();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final wallet = await _service.openWallet(name, password);
|
||||
appStore.wallet = wallet;
|
||||
}
|
||||
|
||||
ReactionDisposer _initialAuthReaction;
|
||||
|
||||
Future<void> bootstrap() async {
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
|
||||
if (authenticationStore.state == AuthenticationState.uninitialized) {
|
||||
authenticationStore.state =
|
||||
getIt.get<SharedPreferences>().getString('current_wallet_name') == null
|
||||
? AuthenticationState.denied
|
||||
: AuthenticationState.installed;
|
||||
}
|
||||
|
||||
_initialAuthReaction ??= autorun((_) async {
|
||||
final state = authenticationStore.state;
|
||||
|
||||
if (state == AuthenticationState.installed) {
|
||||
await loadCurrentWallet();
|
||||
}
|
||||
});
|
||||
}
|
182
lib/router.dart
182
lib/router.dart
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
@ -6,7 +8,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
import 'di.dart';
|
||||
// MARK: Import domains
|
||||
|
||||
import 'package:cake_wallet/src/domain/common/contact.dart';
|
||||
|
@ -21,7 +23,7 @@ import 'package:cake_wallet/src/domain/common/node.dart';
|
|||
import 'package:cake_wallet/src/domain/monero/transaction_description.dart';
|
||||
import 'package:cake_wallet/src/domain/exchange/trade.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/account.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
|
@ -56,7 +58,7 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
|||
import 'package:cake_wallet/src/screens/nodes/new_node_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/subaddress/new_subaddress_page.dart';
|
||||
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
|
||||
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||
|
@ -126,24 +128,18 @@ class Router {
|
|||
Navigator.pushNamed(context, Routes.newWalletType))));
|
||||
|
||||
case Routes.newWalletType:
|
||||
return CupertinoPageRoute<void>(builder: (_) => NewWalletTypePage());
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => NewWalletTypePage(
|
||||
onTypeSelected: (context, type) => Navigator.of(context)
|
||||
.pushNamed(Routes.newWallet, arguments: type),
|
||||
));
|
||||
|
||||
case Routes.newWallet:
|
||||
final type = settings.arguments as WalletType;
|
||||
walletListService.changeWalletManger(walletType: type);
|
||||
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
builder:
|
||||
(_) =>
|
||||
ProxyProvider<AuthenticationStore, WalletCreationStore>(
|
||||
update: (_, authStore, __) => WalletCreationStore(
|
||||
authStore: authStore,
|
||||
sharedPreferences: sharedPreferences,
|
||||
walletListService: walletListService),
|
||||
child: NewWalletPage(
|
||||
walletsService: walletListService,
|
||||
walletService: walletService,
|
||||
sharedPreferences: sharedPreferences)));
|
||||
builder: (_) => NewWalletPage(walletNewVM));
|
||||
|
||||
case Routes.setupPin:
|
||||
Function(BuildContext, String) callback;
|
||||
|
@ -163,6 +159,13 @@ class Router {
|
|||
callback == null ? null : callback(context, pin))),
|
||||
fullscreenDialog: true);
|
||||
|
||||
case Routes.restoreWalletType:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => NewWalletTypePage(
|
||||
onTypeSelected: (context, type) => Navigator.of(context)
|
||||
.pushNamed(Routes.restoreWalletOptions, arguments: type),
|
||||
));
|
||||
|
||||
case Routes.restoreOptions:
|
||||
final type = settings.arguments as WalletType;
|
||||
walletListService.changeWalletManger(walletType: type);
|
||||
|
@ -175,7 +178,28 @@ class Router {
|
|||
walletListService.changeWalletManger(walletType: type);
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => RestoreWalletOptionsPage(type: type));
|
||||
builder: (_) => RestoreWalletOptionsPage(
|
||||
type: type,
|
||||
onRestoreFromSeed: (context) {
|
||||
final route = type == WalletType.monero
|
||||
? Routes.seedLanguage
|
||||
: Routes.restoreWalletFromSeed;
|
||||
final args = type == WalletType.monero
|
||||
? [type, Routes.restoreWalletFromSeed]
|
||||
: [type];
|
||||
|
||||
Navigator.of(context).pushNamed(route, arguments: args);
|
||||
},
|
||||
onRestoreFromKeys: (context) {
|
||||
final route = type == WalletType.monero
|
||||
? Routes.seedLanguage
|
||||
: Routes.restoreWalletFromKeys;
|
||||
final args = type == WalletType.monero
|
||||
? [type, Routes.restoreWalletFromSeed]
|
||||
: [type];
|
||||
|
||||
Navigator.of(context).pushNamed(route, arguments: args);
|
||||
}));
|
||||
|
||||
case Routes.restoreWalletOptionsFromWelcome:
|
||||
return CupertinoPageRoute<void>(
|
||||
|
@ -186,7 +210,7 @@ class Router {
|
|||
sharedPreferences: sharedPreferences)),
|
||||
child: SetupPinCodePage(
|
||||
onPinCodeSetup: (context, _) => Navigator.pushNamed(
|
||||
context, Routes.restoreWalletOptions))));
|
||||
context, Routes.restoreWalletType))));
|
||||
|
||||
case Routes.seed:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -196,8 +220,11 @@ class Router {
|
|||
callback: settings.arguments as void Function()));
|
||||
|
||||
case Routes.restoreWalletFromSeed:
|
||||
final type = settings.arguments as WalletType;
|
||||
walletListService.changeWalletManger(walletType: type);
|
||||
final args = settings.arguments as List<dynamic>;
|
||||
final type = args.first as WalletType;
|
||||
final language = type == WalletType.monero
|
||||
? args[1] as String
|
||||
: 'English'; // FIXME: Unnamed constant; English default and only one language for bitcoin.
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) =>
|
||||
|
@ -207,11 +234,15 @@ class Router {
|
|||
sharedPreferences: sharedPreferences,
|
||||
walletListService: walletListService),
|
||||
child: RestoreWalletFromSeedPage(
|
||||
walletsService: walletListService,
|
||||
walletService: walletService,
|
||||
sharedPreferences: sharedPreferences)));
|
||||
type: type, language: language)));
|
||||
|
||||
case Routes.restoreWalletFromKeys:
|
||||
final args = settings.arguments as List<dynamic>;
|
||||
final type = args.first as WalletType;
|
||||
final language = type == WalletType.monero
|
||||
? args[1] as String
|
||||
: 'English'; // FIXME: Unnamed constant; English default and only one language for bitcoin.
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) =>
|
||||
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
||||
|
@ -256,22 +287,16 @@ class Router {
|
|||
|
||||
case Routes.sendTemplate:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => Provider(
|
||||
create: (_) => SendStore(
|
||||
walletService: walletService,
|
||||
priceStore: priceStore,
|
||||
transactionDescriptions: transactionDescriptions),
|
||||
child: SendTemplatePage())
|
||||
);
|
||||
builder: (_) => Provider(
|
||||
create: (_) => SendStore(
|
||||
walletService: walletService,
|
||||
priceStore: priceStore,
|
||||
transactionDescriptions: transactionDescriptions),
|
||||
child: SendTemplatePage()));
|
||||
|
||||
case Routes.receive:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => MultiProvider(providers: [
|
||||
Provider(
|
||||
create: (_) =>
|
||||
SubaddressListStore(walletService: walletService))
|
||||
], child: ReceivePage()));
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<ReceivePage>());
|
||||
|
||||
case Routes.transactionDetails:
|
||||
return CupertinoPageRoute<void>(
|
||||
|
@ -281,10 +306,8 @@ class Router {
|
|||
|
||||
case Routes.newSubaddress:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => Provider(
|
||||
create: (_) =>
|
||||
SubadrressCreationStore(walletService: walletService),
|
||||
child: NewSubaddressPage()));
|
||||
builder: (_) =>
|
||||
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
|
||||
|
||||
case Routes.disclaimer:
|
||||
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
|
||||
|
@ -294,7 +317,15 @@ class Router {
|
|||
builder: (_) => DisclaimerPage(isReadOnly: true));
|
||||
|
||||
case Routes.seedLanguage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => SeedLanguage());
|
||||
final args = settings.arguments as List<dynamic>;
|
||||
final type = args.first as WalletType;
|
||||
final redirectRoute = args[1] as String;
|
||||
|
||||
return CupertinoPageRoute<void>(builder: (_) {
|
||||
return SeedLanguage(
|
||||
onConfirm: (context, lang) => Navigator.of(context)
|
||||
.popAndPushNamed(redirectRoute, arguments: [type, lang]));
|
||||
});
|
||||
|
||||
case Routes.walletList:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -306,17 +337,18 @@ class Router {
|
|||
child: WalletListPage()));
|
||||
|
||||
case Routes.auth:
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => Provider(
|
||||
create: (_) => AuthStore(
|
||||
sharedPreferences: sharedPreferences,
|
||||
userService: userService,
|
||||
walletService: walletService),
|
||||
child: AuthPage(
|
||||
onAuthenticationFinished:
|
||||
settings.arguments as OnAuthenticationFinished),
|
||||
));
|
||||
return null;
|
||||
// return MaterialPageRoute<void>(
|
||||
// fullscreenDialog: true,
|
||||
// builder: (_) => Provider(
|
||||
// create: (_) => AuthStore(
|
||||
// sharedPreferences: sharedPreferences,
|
||||
// userService: userService,
|
||||
// walletService: walletService),
|
||||
// child: AuthPage(
|
||||
// onAuthenticationFinished:
|
||||
// settings.arguments as OnAuthenticationFinished),
|
||||
// ));
|
||||
|
||||
case Routes.unlock:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -455,15 +487,13 @@ class Router {
|
|||
], child: SubaddressListPage()));
|
||||
|
||||
case Routes.restoreWalletFromSeedDetails:
|
||||
final args = settings.arguments as List;
|
||||
final walletRestorationFromSeedVM =
|
||||
getIt.get<WalletRestorationFromSeedVM>(param1: args);
|
||||
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) =>
|
||||
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
||||
update: (_, authStore, __) => WalletRestorationStore(
|
||||
authStore: authStore,
|
||||
sharedPreferences: sharedPreferences,
|
||||
walletListService: walletListService,
|
||||
seed: settings.arguments as List<MnemoticItem>),
|
||||
child: RestoreWalletFromSeedDetailsPage()));
|
||||
builder: (_) => RestoreWalletFromSeedDetailsPage(
|
||||
walletRestorationFromSeedVM: walletRestorationFromSeedVM));
|
||||
|
||||
case Routes.exchange:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -487,22 +517,24 @@ class Router {
|
|||
|
||||
case Routes.exchangeTemplate:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Provider(create: (_) {
|
||||
final xmrtoprovider = XMRTOExchangeProvider();
|
||||
builder: (_) => Provider(
|
||||
create: (_) {
|
||||
final xmrtoprovider = XMRTOExchangeProvider();
|
||||
|
||||
return ExchangeStore(
|
||||
initialProvider: xmrtoprovider,
|
||||
initialDepositCurrency: CryptoCurrency.xmr,
|
||||
initialReceiveCurrency: CryptoCurrency.btc,
|
||||
trades: trades,
|
||||
providerList: [
|
||||
xmrtoprovider,
|
||||
ChangeNowExchangeProvider(),
|
||||
MorphTokenExchangeProvider(trades: trades)
|
||||
],
|
||||
walletStore: walletStore);
|
||||
}, child: ExchangeTemplatePage(),)
|
||||
);
|
||||
return ExchangeStore(
|
||||
initialProvider: xmrtoprovider,
|
||||
initialDepositCurrency: CryptoCurrency.xmr,
|
||||
initialReceiveCurrency: CryptoCurrency.btc,
|
||||
trades: trades,
|
||||
providerList: [
|
||||
xmrtoprovider,
|
||||
ChangeNowExchangeProvider(),
|
||||
MorphTokenExchangeProvider(trades: trades)
|
||||
],
|
||||
walletStore: walletStore);
|
||||
},
|
||||
child: ExchangeTemplatePage(),
|
||||
));
|
||||
|
||||
case Routes.settings:
|
||||
return MaterialPageRoute<void>(
|
||||
|
|
|
@ -46,4 +46,5 @@ class Routes {
|
|||
static const newWalletType = '/new_wallet_type';
|
||||
static const sendTemplate = '/send_template';
|
||||
static const exchangeTemplate = '/exchange_template';
|
||||
static const restoreWalletType = '/restore_wallet_type';
|
||||
}
|
|
@ -1 +1,3 @@
|
|||
abstract class Balance {}
|
||||
abstract class Balance {
|
||||
const Balance();
|
||||
}
|
||||
|
|
11
lib/src/domain/common/mnemonic_item.dart
Normal file
11
lib/src/domain/common/mnemonic_item.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
class MnemonicItem {
|
||||
MnemonicItem({String text}) : _text = text;
|
||||
|
||||
String get text => _text;
|
||||
String _text;
|
||||
|
||||
void changeText(String text) => _text = text;
|
||||
|
||||
@override
|
||||
String toString() => text;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
class MnemoticItem {
|
||||
MnemoticItem({String text, this.dic}) : _text = text;
|
||||
|
||||
String get text => _text;
|
||||
final List<String> dic;
|
||||
|
||||
String _text;
|
||||
|
||||
bool isCorrect() => dic.contains(text);
|
||||
|
||||
void changeText(String text) {
|
||||
_text = text;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => text;
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.manager.dart';
|
||||
import 'package:cake_wallet/bitcoin/key.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -119,7 +118,7 @@ class WalletListService {
|
|||
MoneroWalletsManager(walletInfoSource: walletInfoSource);
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
walletsManager = BitcoinWalletManager();
|
||||
// walletsManager = BitcoinWalletManager();
|
||||
break;
|
||||
case WalletType.none:
|
||||
walletsManager = null;
|
||||
|
|
|
@ -35,10 +35,10 @@ void setReactions(
|
|||
settingsStore: settingsStore,
|
||||
priceStore: priceStore);
|
||||
autorun((_) async {
|
||||
if (authenticationStore.state == AuthenticationState.allowed) {
|
||||
await loginStore.loadCurrentWallet();
|
||||
authenticationStore.state = AuthenticationState.readyToLogin;
|
||||
}
|
||||
// if (authenticationStore.state == AuthenticationState.allowed) {
|
||||
// await loginStore.loadCurrentWallet();
|
||||
// authenticationStore.state = AuthenticationState.readyToLogin;
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
|
||||
import 'package:cake_wallet/src/stores/auth/auth_store.dart';
|
||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
|
||||
|
@ -12,8 +11,12 @@ import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
|
|||
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
||||
|
||||
class AuthPage extends StatefulWidget {
|
||||
AuthPage({this.onAuthenticationFinished, this.closable = true});
|
||||
AuthPage(
|
||||
{this.onAuthenticationFinished,
|
||||
this.authViewModel,
|
||||
this.closable = true});
|
||||
|
||||
final AuthViewModel authViewModel;
|
||||
final OnAuthenticationFinished onAuthenticationFinished;
|
||||
final bool closable;
|
||||
|
||||
|
@ -25,40 +28,13 @@ class AuthPageState extends State<AuthPage> {
|
|||
final _key = GlobalKey<ScaffoldState>();
|
||||
final _pinCodeKey = GlobalKey<PinCodeState>();
|
||||
final _backArrowImageDarkTheme =
|
||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||
|
||||
void changeProcessText(String text) {
|
||||
_key.currentState.showSnackBar(
|
||||
SnackBar(content: Text(text), backgroundColor: Colors.green));
|
||||
}
|
||||
|
||||
void close() => Navigator.of(_key.currentContext).pop();
|
||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||
ReactionDisposer _reaction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authStore = Provider.of<AuthStore>(context);
|
||||
final settingsStore = Provider.of<SettingsStore>(context);
|
||||
|
||||
if (settingsStore.allowBiometricalAuthentication) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final biometricAuth = BiometricAuth();
|
||||
biometricAuth.isAuthenticated().then(
|
||||
(isAuth) {
|
||||
if (isAuth) {
|
||||
authStore.biometricAuth();
|
||||
_key.currentState.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(S.of(context).authenticated),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
reaction((_) => authStore.state, (AuthState state) {
|
||||
void initState() {
|
||||
_reaction ??=
|
||||
reaction((_) => widget.authViewModel.state, (AuthState state) {
|
||||
if (state is AuthenticatedSuccessfully) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.onAuthenticationFinished != null) {
|
||||
|
@ -119,32 +95,69 @@ class AuthPageState extends State<AuthPage> {
|
|||
});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reaction.reaction.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void changeProcessText(String text) => _key.currentState.showSnackBar(
|
||||
SnackBar(content: Text(text), backgroundColor: Colors.green));
|
||||
|
||||
void close() => Navigator.of(_key.currentContext).pop();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final authStore = Provider.of<AuthStore>(context);
|
||||
// final settingsStore = Provider.of<SettingsStore>(context);
|
||||
|
||||
// if (settingsStore.allowBiometricalAuthentication) {
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// final biometricAuth = BiometricAuth();
|
||||
// biometricAuth.isAuthenticated().then(
|
||||
// (isAuth) {
|
||||
// if (isAuth) {
|
||||
// authStore.biometricAuth();
|
||||
// _key.currentState.showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text(S.of(context).authenticated),
|
||||
// backgroundColor: Colors.green,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
return Scaffold(
|
||||
key: _key,
|
||||
appBar: CupertinoNavigationBar(
|
||||
leading: widget.closable
|
||||
? SizedBox(
|
||||
height: 37,
|
||||
width: 20,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: _backArrowImageDarkTheme),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
? SizedBox(
|
||||
height: 37,
|
||||
width: 20,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: _backArrowImageDarkTheme),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
border: null,
|
||||
),
|
||||
resizeToAvoidBottomPadding: false,
|
||||
body: PinCode(
|
||||
(pin, _) => authStore.auth(
|
||||
password: pin.fold('', (ac, val) => ac + '$val')),
|
||||
(pin, _) => widget.authViewModel
|
||||
.auth(password: pin.fold('', (ac, val) => ac + '$val')),
|
||||
false,
|
||||
_pinCodeKey));
|
||||
}
|
||||
|
|
|
@ -14,15 +14,16 @@ Widget createLoginPage(
|
|||
@required WalletService walletService,
|
||||
@required WalletListService walletListService,
|
||||
@required AuthenticationStore authenticationStore}) =>
|
||||
Provider(
|
||||
create: (_) => AuthStore(
|
||||
sharedPreferences: sharedPreferences,
|
||||
userService: userService,
|
||||
walletService: walletService),
|
||||
child: AuthPage(
|
||||
onAuthenticationFinished: (isAuthenticated, state) {
|
||||
if (isAuthenticated) {
|
||||
authenticationStore.loggedIn();
|
||||
}
|
||||
},
|
||||
closable: false));
|
||||
null;
|
||||
// Provider(
|
||||
// create: (_) => AuthStore(
|
||||
// sharedPreferences: sharedPreferences,
|
||||
// userService: userService,
|
||||
// walletService: walletService),
|
||||
// child: AuthPage(
|
||||
// onAuthenticationFinished: (isAuthenticated, state) {
|
||||
// if (isAuthenticated) {
|
||||
// authenticationStore.loggedIn();
|
||||
// }
|
||||
// },
|
||||
// closable: false));
|
||||
|
|
|
@ -11,13 +11,14 @@ Widget createUnlockPage(
|
|||
@required UserService userService,
|
||||
@required WalletService walletService,
|
||||
@required Function(bool, AuthPageState) onAuthenticationFinished}) =>
|
||||
WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Provider(
|
||||
create: (_) => AuthStore(
|
||||
sharedPreferences: sharedPreferences,
|
||||
userService: userService,
|
||||
walletService: walletService),
|
||||
child: AuthPage(
|
||||
onAuthenticationFinished: onAuthenticationFinished,
|
||||
closable: false)));
|
||||
null;
|
||||
// WillPopScope(
|
||||
// onWillPop: () async => false,
|
||||
// child: Provider(
|
||||
// create: (_) => AuthStore(
|
||||
// sharedPreferences: sharedPreferences,
|
||||
// userService: userService,
|
||||
// walletService: walletService),
|
||||
// child: AuthPage(
|
||||
// onAuthenticationFinished: onAuthenticationFinished,
|
||||
// closable: false)));
|
|
@ -10,12 +10,19 @@ enum AppBarStyle { regular, withShadow }
|
|||
|
||||
abstract class BasePage extends StatelessWidget {
|
||||
String get title => null;
|
||||
|
||||
bool get isModalBackButton => false;
|
||||
|
||||
Color get backgroundLightColor => Colors.white;
|
||||
|
||||
Color get backgroundDarkColor => PaletteDark.darkNightBlue;
|
||||
|
||||
bool get resizeToAvoidBottomPadding => true;
|
||||
|
||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||
|
||||
Widget Function(BuildContext, Widget) get rootWrapper => null;
|
||||
|
||||
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
|
||||
final _backArrowImageDarkTheme =
|
||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||
|
@ -83,9 +90,8 @@ abstract class BasePage extends StatelessWidget {
|
|||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? backgroundDarkColor
|
||||
: backgroundLightColor);
|
||||
backgroundColor:
|
||||
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||
|
||||
case AppBarStyle.withShadow:
|
||||
return NavBar.withShadow(
|
||||
|
@ -93,9 +99,8 @@ abstract class BasePage extends StatelessWidget {
|
|||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? backgroundDarkColor
|
||||
: backgroundLightColor);
|
||||
backgroundColor:
|
||||
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||
|
||||
default:
|
||||
return NavBar(
|
||||
|
@ -103,9 +108,8 @@ abstract class BasePage extends StatelessWidget {
|
|||
leading: leading(context),
|
||||
middle: middle(context),
|
||||
trailing: trailing(context),
|
||||
backgroundColor: _isDarkTheme
|
||||
? backgroundDarkColor
|
||||
: backgroundLightColor);
|
||||
backgroundColor:
|
||||
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,13 +120,14 @@ abstract class BasePage extends StatelessWidget {
|
|||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _isDarkTheme
|
||||
? backgroundDarkColor
|
||||
: backgroundLightColor,
|
||||
final root = Scaffold(
|
||||
backgroundColor:
|
||||
_isDarkTheme ? backgroundDarkColor : backgroundLightColor,
|
||||
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||
appBar: appBar(context),
|
||||
body: SafeArea(child: body(context)),
|
||||
floatingActionButton: floatingActionButton(context));
|
||||
|
||||
return rootWrapper?.call(context, root) ?? root;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/wallet_card.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_history_panel.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||
|
||||
class DashboardPage extends StatelessWidget {
|
||||
DashboardPage({@required this.walletViewModel});
|
||||
|
||||
final DashboardViewModel walletViewModel;
|
||||
final _bodyKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DashboardPageBody(key: _bodyKey);
|
||||
Widget build(BuildContext context) =>
|
||||
DashboardPageBody(key: _bodyKey, walletViewModel: walletViewModel);
|
||||
}
|
||||
|
||||
class DashboardPageBody extends StatefulWidget {
|
||||
DashboardPageBody({Key key}) : super(key: key);
|
||||
DashboardPageBody({Key key, @required this.walletViewModel})
|
||||
: super(key: key);
|
||||
|
||||
final DashboardViewModel walletViewModel;
|
||||
|
||||
@override
|
||||
DashboardPageBodyState createState() => DashboardPageBodyState();
|
||||
}
|
||||
|
||||
class DashboardPageBodyState extends State<DashboardPageBody> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final menuButton = Image.asset('assets/images/header.png',
|
||||
final menuButton = Image.asset(
|
||||
'assets/images/header.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
);
|
||||
|
||||
|
@ -30,15 +38,10 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
|
|||
child: Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor
|
||||
],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight
|
||||
)
|
||||
),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor
|
||||
], begin: Alignment.centerLeft, end: Alignment.centerRight)),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
|
@ -55,22 +58,17 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
|
|||
padding: EdgeInsets.all(0),
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
builder: (_) => MenuWidget(),
|
||||
context: context
|
||||
);
|
||||
builder: (_) => MenuWidget(), context: context);
|
||||
},
|
||||
child: menuButton),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, top: 20),
|
||||
child: WalletCard(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 28,
|
||||
),
|
||||
Expanded(child: TradeHistoryPanel())
|
||||
padding: EdgeInsets.only(left: 20, top: 20),
|
||||
child: WalletCard(walletVM: widget.walletViewModel)),
|
||||
SizedBox(height: 28),
|
||||
Expanded(child: TradeHistoryPanel(dashboardViewModel: widget.walletViewModel))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -17,7 +17,7 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
final actionListStore = Provider.of<ActionListStore>(context);
|
||||
// final actionListStore = Provider.of<ActionListStore>(context);
|
||||
final historyPanelWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
|
@ -97,44 +97,44 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color))),
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).incoming),
|
||||
Checkbox(
|
||||
value: actionListStore
|
||||
.transactionFilterStore
|
||||
.displayIncoming,
|
||||
onChanged: (value) =>
|
||||
actionListStore
|
||||
.transactionFilterStore
|
||||
.toggleIncoming(),
|
||||
)
|
||||
]))),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).outgoing),
|
||||
Checkbox(
|
||||
value: actionListStore
|
||||
.transactionFilterStore
|
||||
.displayOutgoing,
|
||||
onChanged: (value) =>
|
||||
actionListStore
|
||||
.transactionFilterStore
|
||||
.toggleOutgoing(),
|
||||
)
|
||||
]))),
|
||||
// PopupMenuItem(
|
||||
// value: 0,
|
||||
// child: Observer(
|
||||
// builder: (_) => Row(
|
||||
// mainAxisAlignment:
|
||||
// MainAxisAlignment
|
||||
// .spaceBetween,
|
||||
// children: [
|
||||
// Text(S.of(context).incoming),
|
||||
// Checkbox(
|
||||
// value: actionListStore
|
||||
// .transactionFilterStore
|
||||
// .displayIncoming,
|
||||
// onChanged: (value) =>
|
||||
// actionListStore
|
||||
// .transactionFilterStore
|
||||
// .toggleIncoming(),
|
||||
// )
|
||||
// ]))),
|
||||
// PopupMenuItem(
|
||||
// value: 1,
|
||||
// child: Observer(
|
||||
// builder: (_) => Row(
|
||||
// mainAxisAlignment:
|
||||
// MainAxisAlignment
|
||||
// .spaceBetween,
|
||||
// children: [
|
||||
// Text(S.of(context).outgoing),
|
||||
// Checkbox(
|
||||
// value: actionListStore
|
||||
// .transactionFilterStore
|
||||
// .displayOutgoing,
|
||||
// onChanged: (value) =>
|
||||
// actionListStore
|
||||
// .transactionFilterStore
|
||||
// .toggleOutgoing(),
|
||||
// )
|
||||
// ]))),
|
||||
PopupMenuItem(
|
||||
value: 2,
|
||||
child:
|
||||
|
@ -156,17 +156,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
.spaceBetween,
|
||||
children: [
|
||||
Text('XMR.TO'),
|
||||
Checkbox(
|
||||
value: actionListStore
|
||||
.tradeFilterStore
|
||||
.displayXMRTO,
|
||||
onChanged: (value) =>
|
||||
actionListStore
|
||||
.tradeFilterStore
|
||||
.toggleDisplayExchange(
|
||||
ExchangeProviderDescription
|
||||
.xmrto),
|
||||
)
|
||||
// Checkbox(
|
||||
// value: actionListStore
|
||||
// .tradeFilterStore
|
||||
// .displayXMRTO,
|
||||
// onChanged: (value) =>
|
||||
// actionListStore
|
||||
// .tradeFilterStore
|
||||
// .toggleDisplayExchange(
|
||||
// ExchangeProviderDescription
|
||||
// .xmrto),
|
||||
// )
|
||||
]))),
|
||||
PopupMenuItem(
|
||||
value: 4,
|
||||
|
@ -177,17 +177,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
.spaceBetween,
|
||||
children: [
|
||||
Text('Change.NOW'),
|
||||
Checkbox(
|
||||
value: actionListStore
|
||||
.tradeFilterStore
|
||||
.displayChangeNow,
|
||||
onChanged: (value) =>
|
||||
actionListStore
|
||||
.tradeFilterStore
|
||||
.toggleDisplayExchange(
|
||||
ExchangeProviderDescription
|
||||
.changeNow),
|
||||
)
|
||||
// Checkbox(
|
||||
// value: actionListStore
|
||||
// .tradeFilterStore
|
||||
// .displayChangeNow,
|
||||
// onChanged: (value) =>
|
||||
// actionListStore
|
||||
// .tradeFilterStore
|
||||
// .toggleDisplayExchange(
|
||||
// ExchangeProviderDescription
|
||||
// .changeNow),
|
||||
// )
|
||||
]))),
|
||||
PopupMenuItem(
|
||||
value: 5,
|
||||
|
@ -198,17 +198,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
.spaceBetween,
|
||||
children: [
|
||||
Text('MorphToken'),
|
||||
Checkbox(
|
||||
value: actionListStore
|
||||
.tradeFilterStore
|
||||
.displayMorphToken,
|
||||
onChanged: (value) =>
|
||||
actionListStore
|
||||
.tradeFilterStore
|
||||
.toggleDisplayExchange(
|
||||
ExchangeProviderDescription
|
||||
.morphToken),
|
||||
)
|
||||
// Checkbox(
|
||||
// value: actionListStore
|
||||
// .tradeFilterStore
|
||||
// .displayMorphToken,
|
||||
// onChanged: (value) =>
|
||||
// actionListStore
|
||||
// .tradeFilterStore
|
||||
// .toggleDisplayExchange(
|
||||
// ExchangeProviderDescription
|
||||
// .morphToken),
|
||||
// )
|
||||
])))
|
||||
],
|
||||
child: filterButton,
|
||||
|
@ -225,10 +225,10 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
|||
.add(Duration(days: 1)));
|
||||
|
||||
if (picked != null && picked.length == 2) {
|
||||
actionListStore.transactionFilterStore
|
||||
.changeStartDate(picked.first);
|
||||
actionListStore.transactionFilterStore
|
||||
.changeEndDate(picked.last);
|
||||
// actionListStore.transactionFilterStore
|
||||
// .changeStartDate(picked.first);
|
||||
// actionListStore.transactionFilterStore
|
||||
// .changeEndDate(picked.last);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/action_list_store.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/date_section_item.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/action_list_store.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/date_section_item.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'date_section_raw.dart';
|
||||
import 'trade_row.dart';
|
||||
import 'transaction_raw.dart';
|
||||
import 'button_header.dart';
|
||||
|
||||
class TradeHistoryPanel extends StatefulWidget {
|
||||
TradeHistoryPanel({this.dashboardViewModel});
|
||||
|
||||
DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
TradeHistoryPanelState createState() => TradeHistoryPanelState();
|
||||
}
|
||||
|
@ -44,119 +49,127 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actionListStore = Provider.of<ActionListStore>(context);
|
||||
final settingsStore = Provider.of<SettingsStore>(context);
|
||||
final transactionDateFormat = DateFormat("HH:mm");
|
||||
// final actionListStore = Provider.of<ActionListStore>(context);
|
||||
// final settingsStore = Provider.of<SettingsStore>(context);
|
||||
final transactionDateFormat = DateFormat('HH:mm');
|
||||
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: AnimatedContainer(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: panelHeight,
|
||||
duration: Duration(milliseconds: 1000),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverPersistentHeader(
|
||||
delegate: ButtonHeader(),
|
||||
pinned: true,
|
||||
floating: false,
|
||||
),
|
||||
Observer(
|
||||
key: _listObserverKey,
|
||||
builder: (_) {
|
||||
final items = actionListStore.items == null
|
||||
? <String>[]
|
||||
: actionListStore.items;
|
||||
final itemsCount = items.length + 1;
|
||||
final symbol = settingsStore.fiatCurrency.toString();
|
||||
double freeSpaceHeight = MediaQuery.of(context).size.height - 496;
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: AnimatedContainer(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: panelHeight,
|
||||
duration: Duration(milliseconds: 1000),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20)),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverPersistentHeader(
|
||||
delegate: ButtonHeader(),
|
||||
pinned: true,
|
||||
floating: false,
|
||||
),
|
||||
Observer(
|
||||
key: _listObserverKey,
|
||||
builder: (_) {
|
||||
// final items = actionListStore.items == null
|
||||
// ? <String>[]
|
||||
// : actionListStore.items;
|
||||
final items = widget.dashboardViewModel.transactions;
|
||||
final itemsCount = items.length + 1;
|
||||
final symbol =
|
||||
'\$'; // settingsStore.fiatCurrency.toString();
|
||||
var freeSpaceHeight =
|
||||
MediaQuery.of(context).size.height - 496;
|
||||
|
||||
return SliverList(
|
||||
key: _listKey,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return SliverList(
|
||||
key: _listKey,
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
if (index == itemsCount - 1) {
|
||||
freeSpaceHeight = freeSpaceHeight >= 0
|
||||
? freeSpaceHeight
|
||||
: 0;
|
||||
|
||||
if (index == itemsCount - 1) {
|
||||
freeSpaceHeight = freeSpaceHeight >= 0 ? freeSpaceHeight : 0;
|
||||
return Container(
|
||||
height: freeSpaceHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: Theme.of(context).backgroundColor);
|
||||
}
|
||||
|
||||
final item = items[index];
|
||||
|
||||
if (item is DateSectionItem) {
|
||||
freeSpaceHeight -= 38;
|
||||
return DateSectionRaw(date: item.date);
|
||||
}
|
||||
|
||||
if (item is TransactionListItem) {
|
||||
freeSpaceHeight -= 62;
|
||||
final transaction = item.transaction;
|
||||
final savedDisplayMode =
|
||||
BalanceDisplayMode.all;
|
||||
//settingsStore
|
||||
// .balanceDisplayMode;
|
||||
final formattedAmount = savedDisplayMode ==
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: transaction.amountFormatted();
|
||||
final formattedFiatAmount =
|
||||
savedDisplayMode ==
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: transaction
|
||||
.fiatAmount(); // symbol ???
|
||||
|
||||
return TransactionRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.transactionDetails,
|
||||
arguments: transaction),
|
||||
direction: transaction.direction,
|
||||
formattedDate: transactionDateFormat
|
||||
.format(transaction.date),
|
||||
formattedAmount: formattedAmount,
|
||||
formattedFiatAmount: formattedFiatAmount,
|
||||
isPending: transaction.isPending);
|
||||
}
|
||||
|
||||
if (item is TradeListItem) {
|
||||
freeSpaceHeight -= 62;
|
||||
final trade = item.trade;
|
||||
final savedDisplayMode =
|
||||
BalanceDisplayMode.all;
|
||||
//settingsStore
|
||||
// .balanceDisplayMode;
|
||||
final formattedAmount = trade.amount != null
|
||||
? savedDisplayMode ==
|
||||
BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: trade.amountFormatted()
|
||||
: trade.amount;
|
||||
|
||||
return TradeRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.tradeDetails,
|
||||
arguments: trade),
|
||||
provider: trade.provider,
|
||||
from: trade.from,
|
||||
to: trade.to,
|
||||
createdAtFormattedDate:
|
||||
transactionDateFormat
|
||||
.format(trade.createdAt),
|
||||
formattedAmount: formattedAmount);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: freeSpaceHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
final item = items[index];
|
||||
|
||||
if (item is DateSectionItem) {
|
||||
freeSpaceHeight -= 38;
|
||||
return DateSectionRaw(date: item.date);
|
||||
}
|
||||
|
||||
if (item is TransactionListItem) {
|
||||
freeSpaceHeight -= 62;
|
||||
final transaction = item.transaction;
|
||||
final savedDisplayMode = settingsStore.balanceDisplayMode;
|
||||
final formattedAmount =
|
||||
savedDisplayMode == BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: transaction.amountFormatted();
|
||||
final formattedFiatAmount =
|
||||
savedDisplayMode == BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: transaction.fiatAmount(); // symbol ???
|
||||
|
||||
return TransactionRow(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.transactionDetails,
|
||||
arguments: transaction),
|
||||
direction: transaction.direction,
|
||||
formattedDate:
|
||||
transactionDateFormat.format(transaction.date),
|
||||
formattedAmount: formattedAmount,
|
||||
formattedFiatAmount: formattedFiatAmount,
|
||||
isPending: transaction.isPending);
|
||||
}
|
||||
|
||||
if (item is TradeListItem) {
|
||||
freeSpaceHeight -= 62;
|
||||
final trade = item.trade;
|
||||
final savedDisplayMode = settingsStore.balanceDisplayMode;
|
||||
final formattedAmount = trade.amount != null
|
||||
? savedDisplayMode == BalanceDisplayMode.hiddenBalance
|
||||
? '---'
|
||||
: trade.amountFormatted()
|
||||
: trade.amount;
|
||||
|
||||
return TradeRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.tradeDetails, arguments: trade),
|
||||
provider: trade.provider,
|
||||
from: trade.from,
|
||||
to: trade.to,
|
||||
createdAtFormattedDate:
|
||||
transactionDateFormat.format(trade.createdAt),
|
||||
formattedAmount: formattedAmount);
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).backgroundColor
|
||||
);
|
||||
},
|
||||
|
||||
childCount: itemsCount
|
||||
)
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
color: Theme.of(context).backgroundColor);
|
||||
}, childCount: itemsCount));
|
||||
})
|
||||
],
|
||||
)))); //,
|
||||
}
|
||||
}
|
|
@ -1,21 +1,27 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/src/stores/balance/balance_store.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'package:cake_wallet/src/stores/sync/sync_store.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||
|
||||
class WalletCard extends StatefulWidget {
|
||||
WalletCard({this.walletVM});
|
||||
|
||||
DashboardViewModel walletVM;
|
||||
|
||||
@override
|
||||
WalletCardState createState() => WalletCardState();
|
||||
}
|
||||
|
@ -50,14 +56,12 @@ class WalletCardState extends State<WalletCard> {
|
|||
cardWidth = screenWidth;
|
||||
opacity = 1;
|
||||
});
|
||||
Timer(Duration(milliseconds: 500), () =>
|
||||
setState(() => isDraw = true)
|
||||
);
|
||||
Timer(Duration(milliseconds: 500), () => setState(() => isDraw = true));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Color> colorsSync = [
|
||||
final colorsSync = [
|
||||
Theme.of(context).cardTheme.color,
|
||||
Theme.of(context).hoverColor
|
||||
];
|
||||
|
@ -67,77 +71,67 @@ class WalletCardState extends State<WalletCard> {
|
|||
height: cardHeight,
|
||||
alignment: Alignment.centerRight,
|
||||
child: AnimatedContainer(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
padding: EdgeInsets.only(
|
||||
top: 1,
|
||||
left: 1,
|
||||
bottom: 1
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
color: Theme.of(context).focusColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: PaletteDark.darkNightBlue.withOpacity(0.5),
|
||||
blurRadius: 8,
|
||||
offset: Offset(5, 5))
|
||||
]
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
child: Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
color: Theme.of(context).cardColor,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => isFrontSide = !isFrontSide),
|
||||
child: isFrontSide
|
||||
? frontSide(colorsSync)
|
||||
: backSide(colorsSync)
|
||||
alignment: Alignment.centerLeft,
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
padding: EdgeInsets.only(top: 1, left: 1, bottom: 1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10)),
|
||||
color: Theme.of(context).focusColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: PaletteDark.darkNightBlue.withOpacity(0.5),
|
||||
blurRadius: 8,
|
||||
offset: Offset(5, 5))
|
||||
]),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
child: Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
color: Theme.of(context).cardColor,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => isFrontSide = !isFrontSide),
|
||||
child: isFrontSide
|
||||
? frontSide(colorsSync)
|
||||
: backSide(colorsSync)),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget frontSide(List<Color> colorsSync) {
|
||||
final syncStore = Provider.of<SyncStore>(context);
|
||||
final walletStore = Provider.of<WalletStore>(context);
|
||||
// final syncStore = Provider.of<SyncStore>(context);
|
||||
// final walletStore = Provider.of<WalletStore>(context);
|
||||
final settingsStore = Provider.of<SettingsStore>(context);
|
||||
final balanceStore = Provider.of<BalanceStore>(context);
|
||||
final triangleButton = Image.asset('assets/images/triangle.png',
|
||||
// final balanceStore = Provider.of<BalanceStore>(context);
|
||||
final triangleButton = Image.asset(
|
||||
'assets/images/triangle.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
);
|
||||
|
||||
return Observer(
|
||||
key: _syncingObserverKey,
|
||||
builder: (_) {
|
||||
final status = syncStore.status;
|
||||
final status = widget.walletVM.status;
|
||||
final statusText = status.title();
|
||||
final progress = syncStore.status.progress();
|
||||
final progress = status.progress();
|
||||
final indicatorWidth = progress * cardWidth;
|
||||
|
||||
String shortAddress = walletStore.subaddress.address;
|
||||
shortAddress = shortAddress.replaceRange(4, shortAddress.length - 4, '...');
|
||||
|
||||
final shortAddress = widget.walletVM.address
|
||||
.replaceRange(4, widget.walletVM.address.length - 4, '...');
|
||||
var descriptionText = '';
|
||||
|
||||
if (status is SyncingSyncStatus) {
|
||||
descriptionText = S
|
||||
.of(context)
|
||||
.Blocks_remaining(
|
||||
syncStore.status.toString());
|
||||
descriptionText = S.of(context).Blocks_remaining(status.toString());
|
||||
}
|
||||
|
||||
if (status is FailedSyncStatus) {
|
||||
descriptionText = S
|
||||
.of(context)
|
||||
.please_try_to_connect_to_another_node;
|
||||
descriptionText = S.of(context).please_try_to_connect_to_another_node;
|
||||
}
|
||||
|
||||
return Container(
|
||||
|
@ -149,183 +143,206 @@ class WalletCardState extends State<WalletCard> {
|
|||
height: cardHeight,
|
||||
width: indicatorWidth,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10)),
|
||||
gradient: LinearGradient(
|
||||
colors: colorsSync,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter
|
||||
)
|
||||
),
|
||||
end: Alignment.bottomCenter)),
|
||||
),
|
||||
progress != 1
|
||||
? Positioned(
|
||||
left: indicatorWidth,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: 1,
|
||||
height: cardHeight,
|
||||
color: Theme.of(context).focusColor,
|
||||
)
|
||||
)
|
||||
: Offstage(),
|
||||
isDraw ? Positioned(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
child: Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
? Positioned(
|
||||
left: indicatorWidth,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
width: 1,
|
||||
height: cardHeight,
|
||||
color: Theme.of(context).focusColor,
|
||||
))
|
||||
: Offstage(),
|
||||
isDraw
|
||||
? Positioned(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
child: Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
walletStore.name,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.walletVM.name,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
triangleButton
|
||||
],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
triangleButton
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
if (widget.walletVM.subname?.isNotEmpty ?? false)
|
||||
Text(
|
||||
widget.walletVM.subname,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
walletStore.account.label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
Container(
|
||||
width: 98,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.subtitle
|
||||
.backgroundColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16))),
|
||||
child: Text(
|
||||
shortAddress,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 98,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16))
|
||||
),
|
||||
child: Text(
|
||||
shortAddress,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
status is SyncedSyncStatus
|
||||
? Observer(
|
||||
key: _balanceObserverKey,
|
||||
builder: (_) {
|
||||
final balanceDisplayMode = settingsStore.balanceDisplayMode;
|
||||
final symbol = settingsStore
|
||||
.fiatCurrency
|
||||
.toString();
|
||||
var balance = '---';
|
||||
var fiatBalance = '---';
|
||||
status is SyncedSyncStatus
|
||||
? Observer(
|
||||
key: _balanceObserverKey,
|
||||
builder: (_) {
|
||||
final balanceDisplayMode =
|
||||
BalanceDisplayMode.fullBalance;
|
||||
// settingsStore.balanceDisplayMode;
|
||||
final symbol =
|
||||
settingsStore.fiatCurrency.toString();
|
||||
var balance = '---';
|
||||
var fiatBalance = '---';
|
||||
|
||||
if (balanceDisplayMode ==
|
||||
BalanceDisplayMode.availableBalance) {
|
||||
balance =
|
||||
balanceStore.unlockedBalance ??
|
||||
'0.0';
|
||||
fiatBalance =
|
||||
'$symbol ${balanceStore.fiatUnlockedBalance}';
|
||||
}
|
||||
if (balanceDisplayMode ==
|
||||
BalanceDisplayMode.availableBalance) {
|
||||
balance = widget.walletVM.balance
|
||||
.unlockedBalance ??
|
||||
'0.0';
|
||||
fiatBalance = '\$ 123.43';
|
||||
// '$symbol ${balanceStore.fiatUnlockedBalance}';
|
||||
}
|
||||
|
||||
if (balanceDisplayMode ==
|
||||
BalanceDisplayMode.fullBalance) {
|
||||
balance =
|
||||
balanceStore.fullBalance ?? '0.0';
|
||||
fiatBalance =
|
||||
'$symbol ${balanceStore.fiatFullBalance}';
|
||||
}
|
||||
if (balanceDisplayMode ==
|
||||
BalanceDisplayMode.fullBalance) {
|
||||
balance = widget.walletVM.balance
|
||||
.totalBalance ??
|
||||
'0.0';
|
||||
fiatBalance = '\$ 123.43';
|
||||
// '$symbol ${balanceStore.fiatFullBalance}';
|
||||
}
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
balanceDisplayMode.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
balance,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
fiatBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
],
|
||||
);
|
||||
})
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
balanceDisplayMode.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
balance,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
descriptionText,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
fiatBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
)
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
descriptionText,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
: Offstage()
|
||||
),
|
||||
))
|
||||
: Offstage()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -334,149 +351,157 @@ class WalletCardState extends State<WalletCard> {
|
|||
}
|
||||
|
||||
Widget backSide(List<Color> colorsSync) {
|
||||
final walletStore = Provider.of<WalletStore>(context);
|
||||
final rightArrow = Image.asset('assets/images/right_arrow.png',
|
||||
final rightArrow = Image.asset(
|
||||
'assets/images/right_arrow.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
);
|
||||
double messageBoxHeight = 0;
|
||||
double messageBoxWidth = cardWidth - 10;
|
||||
var messageBoxHeight = 0.0;
|
||||
var messageBoxWidth = cardWidth - 10;
|
||||
|
||||
return Observer(
|
||||
key: _addressObserverKey,
|
||||
builder: (_) {
|
||||
return Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
gradient: LinearGradient(
|
||||
colors: colorsSync,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter
|
||||
)
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
key: _addressObserverKey,
|
||||
builder: (_) {
|
||||
return Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding:
|
||||
EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10)),
|
||||
gradient: LinearGradient(
|
||||
colors: colorsSync,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter)),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 90,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.current.card_address,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: walletStore.subaddress.address));
|
||||
_addressObserverKey.currentState.setState(() {
|
||||
text: widget.walletVM.address));
|
||||
_addressObserverKey.currentState
|
||||
.setState(() {
|
||||
messageBoxHeight = 20;
|
||||
messageBoxWidth = cardWidth;
|
||||
});
|
||||
Timer(Duration(milliseconds: 1000), () {
|
||||
try {
|
||||
_addressObserverKey.currentState.setState(() {
|
||||
_addressObserverKey.currentState
|
||||
.setState(() {
|
||||
messageBoxHeight = 0;
|
||||
messageBoxWidth = cardWidth - 10;
|
||||
});
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
print('${e.toString()}');
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
walletStore.subaddress.address,
|
||||
widget.walletVM.address,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
SizedBox(width: 10),
|
||||
Container(
|
||||
width: 90,
|
||||
height: 90,
|
||||
child: QrImage(
|
||||
data: widget.walletVM.address,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
)
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Container(
|
||||
width: 90,
|
||||
height: 90,
|
||||
child: QrImage(
|
||||
data: walletStore.subaddress.address,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: 44,
|
||||
padding: EdgeInsets.only(left: 20, right: 20),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(22)),
|
||||
color: Theme.of(context).primaryTextTheme.overline.color
|
||||
],
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.of(context,
|
||||
rootNavigator: true)
|
||||
.pushNamed(Routes.receive),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).accounts_subaddresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
Container(
|
||||
height: 44,
|
||||
padding: EdgeInsets.only(left: 20, right: 20),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(22)),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.overline
|
||||
.color),
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pushNamed(Routes.receive),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).accounts_subaddresses,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
),
|
||||
),
|
||||
rightArrow
|
||||
],
|
||||
rightArrow
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
width: messageBoxWidth,
|
||||
height: messageBoxHeight,
|
||||
alignment: Alignment.center,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10)),
|
||||
color: Colors.green
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).copied_to_clipboard,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
AnimatedContainer(
|
||||
width: messageBoxWidth,
|
||||
height: messageBoxHeight,
|
||||
alignment: Alignment.center,
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.only(topLeft: Radius.circular(10)),
|
||||
color: Colors.green),
|
||||
child: Text(
|
||||
S.of(context).copied_to_clipboard,
|
||||
style: TextStyle(fontSize: 10, color: Colors.white),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,85 +1,54 @@
|
|||
import 'package:cake_wallet/core/monero_wallet_list_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_store.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_state.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||
|
||||
class NewWalletPage extends BasePage {
|
||||
NewWalletPage(
|
||||
{@required this.walletsService,
|
||||
@required this.walletService,
|
||||
@required this.sharedPreferences});
|
||||
NewWalletPage(this._walletNewVM);
|
||||
|
||||
final WalletListService walletsService;
|
||||
final WalletService walletService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
final WalletNewVM _walletNewVM;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletNameForm();
|
||||
Widget body(BuildContext context) => WalletNameForm(_walletNewVM);
|
||||
}
|
||||
|
||||
class WalletNameForm extends StatefulWidget {
|
||||
WalletNameForm(this._walletNewVM);
|
||||
|
||||
final WalletNewVM _walletNewVM;
|
||||
|
||||
@override
|
||||
_WalletNameFormState createState() => _WalletNameFormState();
|
||||
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
|
||||
}
|
||||
|
||||
class _WalletNameFormState extends State<WalletNameForm> {
|
||||
_WalletNameFormState(this._walletNewVM);
|
||||
|
||||
static const aspectRatioImage = 1.22;
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final nameController = TextEditingController();
|
||||
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
|
||||
ReactionDisposer _stateReaction;
|
||||
final WalletNewVM _walletNewVM;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletCreationStore = Provider.of<WalletCreationStore>(context);
|
||||
final walletCreationService = Provider.of<WalletCreationService>(context);
|
||||
|
||||
// FIXME: Does seed language store is really needed ???
|
||||
|
||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
||||
|
||||
final seedLocales = [
|
||||
S.current.seed_language_english,
|
||||
S.current.seed_language_chinese,
|
||||
S.current.seed_language_dutch,
|
||||
S.current.seed_language_german,
|
||||
S.current.seed_language_japanese,
|
||||
S.current.seed_language_portuguese,
|
||||
S.current.seed_language_russian,
|
||||
S.current.seed_language_spanish
|
||||
];
|
||||
|
||||
nameController.addListener(() =>
|
||||
walletCreationStore.setDisabledStatus(!nameController.text.isNotEmpty));
|
||||
|
||||
reaction((_) => walletCreationStore.state, (WalletCreationState state) {
|
||||
void initState() {
|
||||
_stateReaction ??=
|
||||
reaction((_) => _walletNewVM.state, (WalletCreationState state) {
|
||||
if (state is WalletCreatedSuccessfully) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
@ -98,7 +67,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
|||
});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
|
@ -116,94 +89,76 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
|||
child: Form(
|
||||
key: _formKey,
|
||||
child: TextFormField(
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
hintText: S.of(context).wallet_name,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
validator: (value) {
|
||||
walletCreationStore.validateWalletName(value);
|
||||
return walletCreationStore.errorMessage;
|
||||
},
|
||||
)),
|
||||
onChanged: (value) => _walletNewVM.name = value,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.title.color),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color),
|
||||
hintText: S.of(context).wallet_name,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
validator: WalletNameValidator())),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 40),
|
||||
child: Text(
|
||||
S.of(context).seed_language_choose,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
if (_walletNewVM.hasLanguageSelector) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 40),
|
||||
child: Text(
|
||||
S.of(context).seed_language_choose,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Observer(
|
||||
builder: (_) => SelectButton(
|
||||
image: null,
|
||||
text: seedLocales[seedLanguages
|
||||
.indexOf(seedLanguageStore.selectedSeedLanguage)],
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.title
|
||||
.backgroundColor,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
onTap: () async => await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
SeedLanguagePicker()))),
|
||||
)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: SeedLanguageSelector(
|
||||
key: _languageSelectorKey,
|
||||
initialSelected: defaultSeedLanguage),
|
||||
)
|
||||
]
|
||||
]),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Observer(
|
||||
builder: (context) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: () => _confirmForm(walletCreationService,
|
||||
seedLanguageStore.selectedSeedLanguage),
|
||||
onPressed: _confirmForm,
|
||||
text: S.of(context).continue_text,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading: walletCreationStore.state is WalletIsCreating,
|
||||
isDisabled: walletCreationStore.isDisabledStatus,
|
||||
isLoading: _walletNewVM.state is WalletCreatedSuccessfully,
|
||||
isDisabled: _walletNewVM.name.isEmpty,
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmForm(
|
||||
WalletCreationService walletCreationService, String language) {
|
||||
void _confirmForm() {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
WalletCredentials credentials;
|
||||
|
||||
if (walletCreationService.type == WalletType.monero) {
|
||||
credentials = MoneroNewWalletCredentials(
|
||||
name: nameController.text, language: language);
|
||||
}
|
||||
|
||||
walletCreationService.create(credentials);
|
||||
_walletNewVM.create(
|
||||
options: _walletNewVM.hasLanguageSelector
|
||||
? _languageSelectorKey.currentState.selected
|
||||
: null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -8,14 +9,23 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
|
||||
class NewWalletTypePage extends BasePage {
|
||||
NewWalletTypePage({this.onTypeSelected});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletTypeForm();
|
||||
Widget body(BuildContext context) =>
|
||||
WalletTypeForm(onTypeSelected: onTypeSelected);
|
||||
}
|
||||
|
||||
class WalletTypeForm extends StatefulWidget {
|
||||
WalletTypeForm({this.onTypeSelected});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
|
||||
@override
|
||||
WalletTypeFormState createState() => WalletTypeFormState();
|
||||
}
|
||||
|
@ -23,35 +33,19 @@ class WalletTypeForm extends StatefulWidget {
|
|||
class WalletTypeFormState extends State<WalletTypeForm> {
|
||||
static const aspectRatioImage = 1.22;
|
||||
|
||||
final moneroIcon = Image.asset('assets/images/monero.png', height: 24, width: 24);
|
||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final moneroIcon =
|
||||
Image.asset('assets/images/monero.png', height: 24, width: 24);
|
||||
final bitcoinIcon =
|
||||
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||
|
||||
bool isDisabledButton;
|
||||
bool isMoneroSelected;
|
||||
bool isBitcoinSelected;
|
||||
|
||||
Color moneroBackgroundColor = Colors.transparent;
|
||||
Color moneroTextColor = Colors.transparent;
|
||||
Color bitcoinBackgroundColor = Colors.transparent;
|
||||
Color bitcoinTextColor = Colors.transparent;
|
||||
WalletType selected;
|
||||
List<WalletType> types;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDisabledButton = true;
|
||||
isMoneroSelected = false;
|
||||
isBitcoinSelected = false;
|
||||
|
||||
types = [WalletType.bitcoin, WalletType.monero];
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
|
||||
}
|
||||
|
||||
void afterLayout(dynamic _) {
|
||||
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||
moneroTextColor = Theme.of(context).primaryTextTheme.title.color;
|
||||
bitcoinBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.color;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -75,71 +69,52 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
|||
S.of(context).choose_wallet_currency,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: SelectButton(
|
||||
image: bitcoinIcon,
|
||||
text: 'Bitcoin',
|
||||
color: bitcoinBackgroundColor,
|
||||
textColor: bitcoinTextColor,
|
||||
onTap: () {}),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: SelectButton(
|
||||
image: moneroIcon,
|
||||
text: 'Monero',
|
||||
color: moneroBackgroundColor,
|
||||
textColor: moneroTextColor,
|
||||
onTap: () => onSelectMoneroButton(context)),
|
||||
)
|
||||
...types.map((type) => Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: SelectButton(
|
||||
image: _iconFor(type),
|
||||
text: walletTypeToString(type),
|
||||
color: _backgroundColorFor(selected == type),
|
||||
textColor: _textColorFor(selected == type),
|
||||
onTap: () => setState(() => selected = type)),
|
||||
))
|
||||
],
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
bottom: 24
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: PrimaryButton(
|
||||
onPressed: () => Navigator.of(context).pushNamed(Routes.newWallet),
|
||||
onPressed: () => widget.onTypeSelected(context, selected),
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isDisabled: isDisabledButton,
|
||||
isDisabled: selected == null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onSelectMoneroButton(BuildContext context) {
|
||||
isMoneroSelected = true;
|
||||
isBitcoinSelected = false;
|
||||
isDisabledButton = false;
|
||||
// FIXME: Move color selection inside ui element; add isSelected to buttons.
|
||||
|
||||
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
|
||||
moneroTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
|
||||
bitcoinBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.color;
|
||||
Color _backgroundColorFor(bool isSelected) => isSelected
|
||||
? Theme.of(context).accentTextTheme.title.decorationColor
|
||||
: Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
Color _textColorFor(bool isSelected) => isSelected
|
||||
? Theme.of(context).primaryTextTheme.title.backgroundColor
|
||||
: Theme.of(context).primaryTextTheme.title.color;
|
||||
|
||||
void onSelectBitcoinButton(BuildContext context) {
|
||||
isMoneroSelected = false;
|
||||
isBitcoinSelected = true;
|
||||
isDisabledButton = false;
|
||||
|
||||
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||
moneroTextColor = Theme.of(context).primaryTextTheme.title.color;
|
||||
bitcoinBackgroundColor = moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
|
||||
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
|
||||
|
||||
setState(() {});
|
||||
Image _iconFor(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return moneroIcon;
|
||||
case WalletType.bitcoin:
|
||||
return bitcoinIcon;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -16,346 +18,255 @@ import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
|||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/themes.dart';
|
||||
import 'package:cake_wallet/theme_changer.dart';
|
||||
import 'package:cake_wallet/core/amount_validator.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
|
||||
|
||||
class ReceivePage extends StatefulWidget {
|
||||
@override
|
||||
ReceivePageState createState() => ReceivePageState();
|
||||
}
|
||||
class ReceivePage extends BasePage {
|
||||
ReceivePage({this.addressListViewModel})
|
||||
: amountController = TextEditingController(),
|
||||
_formKey = GlobalKey<FormState>() {
|
||||
amountController.addListener(() => addressListViewModel.amount =
|
||||
_formKey.currentState.validate() ? amountController.text : '');
|
||||
}
|
||||
|
||||
class ReceivePageState extends State<ReceivePage> {
|
||||
final amountController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
|
||||
final _backArrowImageDarkTheme =
|
||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||
final AddressListViewModel addressListViewModel;
|
||||
final TextEditingController amountController;
|
||||
final GlobalKey<FormState> _formKey;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
amountController.dispose();
|
||||
super.dispose();
|
||||
Color get backgroundLightColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
Color get backgroundDarkColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||
(BuildContext context, Widget scaffold) => Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||
child: scaffold);
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => Text(
|
||||
S.of(context).receive,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
final shareImage = Image.asset('assets/images/share.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color);
|
||||
|
||||
return SizedBox(
|
||||
height: 20.0,
|
||||
width: 14.0,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Share.text(S.current.share_address,
|
||||
addressListViewModel.address.address, 'text/plain'),
|
||||
child: shareImage),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletStore = Provider.of<WalletStore>(context);
|
||||
final subaddressListStore = Provider.of<SubaddressListStore>(context);
|
||||
final accountListStore = Provider.of<AccountListStore>(context);
|
||||
return super.build(context);
|
||||
}
|
||||
|
||||
final shareImage = Image.asset('assets/images/share.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
);
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final copyImage = Image.asset('assets/images/copy_content.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
);
|
||||
color: Theme.of(context).primaryTextTheme.title.color);
|
||||
|
||||
final currentColor = Theme.of(context).accentTextTheme.subtitle.decorationColor;
|
||||
final notCurrentColor = Theme.of(context).backgroundColor;
|
||||
|
||||
final currentTextColor = Colors.blue;
|
||||
final notCurrentTextColor = Theme.of(context).primaryTextTheme.caption.color;
|
||||
|
||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||
Image _backButton;
|
||||
|
||||
if (_themeChanger.getTheme() == Themes.darkTheme) {
|
||||
_backButton = _backArrowImageDarkTheme;
|
||||
} else {
|
||||
_backButton = _backArrowImage;
|
||||
}
|
||||
|
||||
amountController.addListener(() {
|
||||
if (_formKey.currentState.validate()) {
|
||||
walletStore.onChangedAmountValue(amountController.text);
|
||||
} else {
|
||||
walletStore.onChangedAmountValue('');
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomPadding: false,
|
||||
body: Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
Theme.of(context).primaryColor
|
||||
],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight
|
||||
)
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(height: 25),
|
||||
Row(children: <Widget>[
|
||||
Spacer(flex: 4),
|
||||
Observer(
|
||||
builder: (_) => Flexible(
|
||||
flex: 6,
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: QrImage(
|
||||
data: addressListViewModel.uri.toString(),
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.display4
|
||||
.color,
|
||||
))))),
|
||||
Spacer(flex: 4)
|
||||
]),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 40, 24, 0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
controller: amountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
BlacklistingTextInputFormatter(
|
||||
RegExp('[\\-|\\ |\\,]'))
|
||||
],
|
||||
textAlign: TextAlign.center,
|
||||
hintText: S.of(context).receive_amount,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline5
|
||||
.color
|
||||
.withOpacity(0.4),
|
||||
validator: AmountValidator(),
|
||||
autovalidate: true,
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline5
|
||||
.color,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600))))
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 20,
|
||||
left: 5,
|
||||
right: 10
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 44,
|
||||
width: 44,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: _backButton),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
S.of(context).receive,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
SizedBox(
|
||||
height: 44.0,
|
||||
width: 44.0,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => Share.text(
|
||||
S.current.share_address, walletStore.subaddress.address, 'text/plain'),
|
||||
child: shareImage),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Observer(builder: (_) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: QrImage(
|
||||
data: walletStore.subaddress.address +
|
||||
walletStore.amountValue,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context).primaryTextTheme.display4.color,
|
||||
),
|
||||
),
|
||||
)),
|
||||
Spacer(
|
||||
flex: 1,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: BaseTextFormField(
|
||||
controller: amountController,
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
BlacklistingTextInputFormatter(
|
||||
RegExp('[\\-|\\ |\\,]'))
|
||||
],
|
||||
textAlign: TextAlign.center,
|
||||
hintText: S.of(context).receive_amount,
|
||||
borderColor: Theme.of(context).primaryTextTheme.caption.color,
|
||||
validator: (value) {
|
||||
walletStore.validateAmount(value);
|
||||
return walletStore.errorMessage;
|
||||
},
|
||||
autovalidate: true,
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: Builder(
|
||||
builder: (context) => Observer(
|
||||
builder: (context) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: walletStore.subaddress.address));
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
S.of(context).copied_to_clipboard,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(milliseconds: 500),
|
||||
));
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
color: Theme.of(context).primaryTextTheme.overline.color
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
walletStore.subaddress.address,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 12),
|
||||
child: copyImage,
|
||||
)
|
||||
],
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 40, top: 40),
|
||||
child: Builder(
|
||||
builder: (context) => Observer(
|
||||
builder: (context) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: addressListViewModel.address.address));
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
S.of(context).copied_to_clipboard,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(milliseconds: 500),
|
||||
));
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(24)),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.overline
|
||||
.color),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
addressListViewModel.address.address,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: subaddressListStore.subaddresses.length + 2,
|
||||
itemBuilder: (context, index) {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 12),
|
||||
child: copyImage,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
))),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
separatorBuilder: (context, _) =>
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: addressListViewModel.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = addressListViewModel.items[index];
|
||||
Widget cell = Container();
|
||||
|
||||
if (index == 0) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24)
|
||||
),
|
||||
child: HeaderTile(
|
||||
onTap: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AccountListPage(accountListStore: accountListStore);
|
||||
}
|
||||
);
|
||||
},
|
||||
title: walletStore.account.label,
|
||||
icon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
if (item is AccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
onTap: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
// return AccountListPage(
|
||||
// accountListStore:
|
||||
// accountListStore);
|
||||
});
|
||||
},
|
||||
title: addressListViewModel.accountLabel,
|
||||
icon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.title.color,
|
||||
));
|
||||
}
|
||||
|
||||
if (index == 1) {
|
||||
return HeaderTile(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress),
|
||||
title: S.of(context).subaddresses,
|
||||
icon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
)
|
||||
);
|
||||
}
|
||||
if (item is AddressListHeader) {
|
||||
cell = HeaderTile(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress),
|
||||
title: S.of(context).subaddresses,
|
||||
icon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.title.color,
|
||||
));
|
||||
}
|
||||
|
||||
index -= 2;
|
||||
if (item is AddressListItem) {
|
||||
cell = Observer(
|
||||
builder: (_) => AddressCell.fromItem(item,
|
||||
isCurrent: item.address ==
|
||||
addressListViewModel.address.address,
|
||||
onTap: (_) =>
|
||||
addressListViewModel.address = item,
|
||||
onEdit: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress, arguments: item)));
|
||||
}
|
||||
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final subaddress = subaddressListStore.subaddresses[index];
|
||||
final isCurrent =
|
||||
walletStore.subaddress.address == subaddress.address;
|
||||
|
||||
final label = subaddress.label.isNotEmpty
|
||||
? subaddress.label
|
||||
: subaddress.address;
|
||||
|
||||
final content = InkWell(
|
||||
onTap: () => walletStore.setSubaddress(subaddress),
|
||||
child: Container(
|
||||
color: isCurrent ? currentColor : notCurrentColor,
|
||||
padding: EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
top: 28,
|
||||
bottom: 28
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: subaddress.label.isNotEmpty
|
||||
? 18 : 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isCurrent
|
||||
? currentTextColor
|
||||
: notCurrentTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return isCurrent
|
||||
? content
|
||||
: Slidable(
|
||||
key: Key(subaddress.address),
|
||||
actionPane: SlidableDrawerActionPane(),
|
||||
child: content,
|
||||
secondaryActions: <Widget>[
|
||||
IconSlideAction(
|
||||
caption: S.of(context).edit,
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
icon: Icons.edit,
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress, arguments: subaddress),
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24)),
|
||||
child: cell,
|
||||
);
|
||||
})),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
70
lib/src/screens/receive/widgets/address_cell.dart
Normal file
70
lib/src/screens/receive/widgets/address_cell.dart
Normal file
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
|
||||
|
||||
class AddressCell extends StatelessWidget {
|
||||
factory AddressCell.fromItem(AddressListItem item,
|
||||
{@required bool isCurrent,
|
||||
Function(String) onTap,
|
||||
Function() onEdit}) =>
|
||||
AddressCell(
|
||||
address: item.address,
|
||||
name: item.name,
|
||||
isCurrent: isCurrent,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit);
|
||||
|
||||
AddressCell(
|
||||
{@required this.address,
|
||||
@required this.name,
|
||||
@required this.isCurrent,
|
||||
this.onTap,
|
||||
this.onEdit});
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
final bool isCurrent;
|
||||
final Function(String) onTap;
|
||||
final Function() onEdit;
|
||||
|
||||
String get label => name ?? address;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const currentTextColor = Colors.blue; // FIXME: Why it's defined here ?
|
||||
final currentColor =
|
||||
Theme.of(context).accentTextTheme.subtitle.decorationColor;
|
||||
final notCurrentColor = Theme.of(context).backgroundColor;
|
||||
final notCurrentTextColor =
|
||||
Theme.of(context).primaryTextTheme.caption.color;
|
||||
final Widget cell = InkWell(
|
||||
onTap: () => onTap(address),
|
||||
child: Container(
|
||||
color: isCurrent ? currentColor : notCurrentColor,
|
||||
padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28),
|
||||
child: Text(
|
||||
name ?? address,
|
||||
style: TextStyle(
|
||||
fontSize: name?.isNotEmpty ?? false ? 18 : 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isCurrent ? currentTextColor : notCurrentTextColor,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
return isCurrent
|
||||
? cell
|
||||
: Slidable(
|
||||
key: Key(address),
|
||||
actionPane: SlidableDrawerActionPane(),
|
||||
child: cell,
|
||||
secondaryActions: <Widget>[
|
||||
IconSlideAction(
|
||||
caption: S.of(context).edit,
|
||||
color: Theme.of(context).primaryTextTheme.overline.color,
|
||||
icon: Icons.edit,
|
||||
onTap: () => onEdit?.call())
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,26 +1,35 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||
|
||||
class RestoreWalletFromSeedDetailsPage extends BasePage {
|
||||
RestoreWalletFromSeedDetailsPage(
|
||||
{@required this.walletRestorationFromSeedVM});
|
||||
|
||||
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_wallet_restore_description;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => RestoreFromSeedDetailsForm();
|
||||
Widget body(BuildContext context) => RestoreFromSeedDetailsForm(
|
||||
walletRestorationFromSeedVM: walletRestorationFromSeedVM);
|
||||
}
|
||||
|
||||
class RestoreFromSeedDetailsForm extends StatefulWidget {
|
||||
RestoreFromSeedDetailsForm({@required this.walletRestorationFromSeedVM});
|
||||
|
||||
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
|
||||
|
||||
@override
|
||||
_RestoreFromSeedDetailsFormState createState() =>
|
||||
_RestoreFromSeedDetailsFormState();
|
||||
|
@ -31,31 +40,17 @@ class _RestoreFromSeedDetailsFormState
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
final _blockchainHeightKey = GlobalKey<BlockchainHeightState>();
|
||||
final _nameController = TextEditingController();
|
||||
ReactionDisposer _stateReaction;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletRestorationStore = Provider.of<WalletRestorationStore>(context);
|
||||
|
||||
_nameController.addListener(() {
|
||||
if (_nameController.text.isNotEmpty) {
|
||||
walletRestorationStore.setDisabledState(false);
|
||||
} else {
|
||||
walletRestorationStore.setDisabledState(true);
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => walletRestorationStore.state, (WalletRestorationState state) {
|
||||
if (state is WalletRestoredSuccessfully) {
|
||||
void initState() {
|
||||
_stateReaction = reaction((_) => widget.walletRestorationFromSeedVM.state,
|
||||
(WalletCreationState state) {
|
||||
if (state is WalletCreatedSuccessfully) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
||||
if (state is WalletRestorationFailure) {
|
||||
if (state is WalletCreationFailure) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
|
@ -64,73 +59,87 @@ class _RestoreFromSeedDetailsFormState
|
|||
alertTitle: S.current.restore_title_from_seed,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop()
|
||||
);
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_nameController.addListener(
|
||||
() => widget.walletRestorationFromSeedVM.name = _nameController.text);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_stateReaction.reaction.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24.0),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
child: Column(children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: TextFormField(
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16
|
||||
),
|
||||
hintText: S.of(context).restore_wallet_name,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
validator: (value) {
|
||||
walletRestorationStore
|
||||
.validateWalletName(value);
|
||||
return walletRestorationStore.errorMessage;
|
||||
},
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
BlockchainHeightWidget(key: _blockchainHeightKey),
|
||||
]),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: TextFormField(
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
fontSize: 16),
|
||||
hintText: S.of(context).restore_wallet_name,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
validator: WalletNameValidator(),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
if (widget.walletRestorationFromSeedVM.hasRestorationHeight)
|
||||
BlockchainHeightWidget(
|
||||
key: _blockchainHeightKey,
|
||||
onHeightChange: (height) {
|
||||
widget.walletRestorationFromSeedVM.height = height;
|
||||
print(height);
|
||||
}),
|
||||
]),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
bottomSection: Observer(builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
walletRestorationStore.restoreFromSeed(
|
||||
name: _nameController.text,
|
||||
restoreHeight: _blockchainHeightKey.currentState.height);
|
||||
widget.walletRestorationFromSeedVM.create();
|
||||
}
|
||||
},
|
||||
isLoading: walletRestorationStore.state is WalletIsRestoring,
|
||||
isLoading:
|
||||
widget.walletRestorationFromSeedVM.state is WalletCreating,
|
||||
text: S.of(context).restore_recover,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isDisabled: walletRestorationStore.disabledState,
|
||||
isDisabled: _nameController.text.isNotEmpty,
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart';
|
||||
import 'package:cake_wallet/src/widgets/seed_widget.dart';
|
||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/core/seed_validator.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/core/mnemonic_length.dart';
|
||||
|
||||
class RestoreWalletFromSeedPage extends BasePage {
|
||||
RestoreWalletFromSeedPage(
|
||||
{@required this.walletsService,
|
||||
@required this.walletService,
|
||||
@required this.sharedPreferences});
|
||||
RestoreWalletFromSeedPage({@required this.type, @required this.language});
|
||||
|
||||
final WalletListService walletsService;
|
||||
final WalletService walletService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
final WalletType type;
|
||||
final String language;
|
||||
final formKey = GlobalKey<_RestoreFromSeedFormState>();
|
||||
|
||||
@override
|
||||
|
@ -34,11 +26,14 @@ class RestoreWalletFromSeedPage extends BasePage {
|
|||
Color get backgroundDarkColor => PaletteDark.lightNightBlue;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => RestoreFromSeedForm(key: formKey);
|
||||
Widget body(BuildContext context) =>
|
||||
RestoreFromSeedForm(key: formKey, type: type, language: language);
|
||||
}
|
||||
|
||||
class RestoreFromSeedForm extends StatefulWidget {
|
||||
RestoreFromSeedForm({Key key}) : super(key: key);
|
||||
RestoreFromSeedForm({Key key, this.type, this.language}) : super(key: key);
|
||||
final WalletType type;
|
||||
final String language;
|
||||
|
||||
@override
|
||||
_RestoreFromSeedFormState createState() => _RestoreFromSeedFormState();
|
||||
|
@ -46,13 +41,11 @@ class RestoreFromSeedForm extends StatefulWidget {
|
|||
|
||||
class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
|
||||
final _seedKey = GlobalKey<SeedWidgetState>();
|
||||
void clear() => _seedKey.currentState.clear();
|
||||
|
||||
String mnemonic() => _seedKey.currentState.items.map((e) => e.text).join(' ');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletRestorationStore = Provider.of<WalletRestorationStore>(context);
|
||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
SystemChannels.textInput.invokeMethod<void>('TextInput.hide'),
|
||||
|
@ -60,11 +53,13 @@ class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
|
|||
color: Theme.of(context).backgroundColor,
|
||||
child: SeedWidget(
|
||||
key: _seedKey,
|
||||
onMnemoticChange: (seed) => walletRestorationStore.setSeed(seed),
|
||||
maxLength: mnemonicLength(widget.type),
|
||||
onMnemonicChange: (seed) => null,
|
||||
onFinish: () => Navigator.of(context).pushNamed(
|
||||
Routes.restoreWalletFromSeedDetails,
|
||||
arguments: _seedKey.currentState.items),
|
||||
seedLanguage: seedLanguageStore.selectedSeedLanguage,
|
||||
arguments: [widget.type, widget.language, mnemonic()]),
|
||||
validator:
|
||||
SeedValidator(type: widget.type, language: widget.language),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class RestoreWalletOptionsPage extends BasePage {
|
||||
RestoreWalletOptionsPage({@required this.type});
|
||||
RestoreWalletOptionsPage(
|
||||
{@required this.type,
|
||||
@required this.onRestoreFromSeed,
|
||||
@required this.onRestoreFromKeys});
|
||||
|
||||
static const _aspectRatioImage = 2.086;
|
||||
final WalletType type;
|
||||
final Function(BuildContext context) onRestoreFromSeed;
|
||||
final Function(BuildContext context) onRestoreFromKeys;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_seed_keys_restore;
|
||||
|
@ -23,8 +25,6 @@ class RestoreWalletOptionsPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
|
@ -33,28 +33,56 @@ class RestoreWalletOptionsPage extends BasePage {
|
|||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () {
|
||||
seedLanguageStore
|
||||
.setCurrentRoute(Routes.restoreWalletFromSeed);
|
||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
||||
},
|
||||
onPressed: () => onRestoreFromSeed(context),
|
||||
image: imageSeed,
|
||||
title: S.of(context).restore_title_from_seed,
|
||||
description: S.of(context).restore_description_from_seed),
|
||||
description: _fromSeedDescription(context)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () {
|
||||
seedLanguageStore
|
||||
.setCurrentRoute(Routes.restoreWalletFromKeys);
|
||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
||||
},
|
||||
onPressed: () => onRestoreFromKeys(context),
|
||||
image: imageKeys,
|
||||
title: S.of(context).restore_title_from_keys,
|
||||
description: S.of(context).restore_description_from_keys),
|
||||
title: _fromKeyTitle(context),
|
||||
description: _fromKeyDescription(context)),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
String _fromSeedDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_seed;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return 'Restore your wallet from 12 word combination code';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyDescription(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_description_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return 'Restore your wallet from generated WIF string from your private keys';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _fromKeyTitle(BuildContext context) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return S.of(context).restore_title_from_keys;
|
||||
case WalletType.bitcoin:
|
||||
// TODO: Add transaction for bitcoin description.
|
||||
return 'Restore from WIF';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
//import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
|
||||
import 'package:cake_wallet/src/stores/price/price_store.dart';
|
||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
|
||||
|
@ -21,7 +26,10 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
|||
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
|
||||
|
||||
class Root extends StatefulWidget {
|
||||
Root({Key key}) : super(key: key);
|
||||
Root({Key key, this.authenticationStore, this.appStore}) : super(key: key);
|
||||
|
||||
final AuthenticationStore authenticationStore;
|
||||
final AppStore appStore;
|
||||
|
||||
@override
|
||||
RootState createState() => RootState();
|
||||
|
@ -30,7 +38,6 @@ class Root extends StatefulWidget {
|
|||
class RootState extends State<Root> with WidgetsBindingObserver {
|
||||
bool _isInactive;
|
||||
bool _postFrameCallback;
|
||||
AuthenticationStore _authenticationStore;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -48,12 +55,12 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_isInactive &&
|
||||
_authenticationStore.state ==
|
||||
AuthenticationState.authenticated ||
|
||||
_authenticationStore.state == AuthenticationState.active) {
|
||||
setState(() => _isInactive = true);
|
||||
}
|
||||
// if (!_isInactive &&
|
||||
// widget.authenticationStore.state ==
|
||||
// AuthenticationState.authenticated ||
|
||||
// widget.authenticationStore.state == AuthenticationState.active) {
|
||||
// setState(() => _isInactive = true);
|
||||
// }
|
||||
|
||||
break;
|
||||
default:
|
||||
|
@ -63,18 +70,18 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||
final sharedPreferences = Provider.of<SharedPreferences>(context);
|
||||
final walletListService = Provider.of<WalletListService>(context);
|
||||
final walletService = Provider.of<WalletService>(context);
|
||||
final userService = Provider.of<UserService>(context);
|
||||
final priceStore = Provider.of<PriceStore>(context);
|
||||
final authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||
final trades = Provider.of<Box<Trade>>(context);
|
||||
final transactionDescriptions =
|
||||
Provider.of<Box<TransactionDescription>>(context);
|
||||
final walletStore = Provider.of<WalletStore>(context);
|
||||
final settingsStore = Provider.of<SettingsStore>(context);
|
||||
// _authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||
// final sharedPreferences = Provider.of<SharedPreferences>(context);
|
||||
// final walletListService = Provider.of<WalletListService>(context);
|
||||
// final walletService = Provider.of<WalletService>(context);
|
||||
// final userService = Provider.of<UserService>(context);
|
||||
// final priceStore = Provider.of<PriceStore>(context);
|
||||
// final authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||
// final trades = Provider.of<Box<Trade>>(context);
|
||||
// final transactionDescriptions =
|
||||
// Provider.of<Box<TransactionDescription>>(context);
|
||||
// final walletStore = Provider.of<WalletStore>(context);
|
||||
// final settingsStore = Provider.of<SettingsStore>(context);
|
||||
|
||||
if (_isInactive && !_postFrameCallback) {
|
||||
_postFrameCallback = true;
|
||||
|
@ -96,38 +103,51 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
}
|
||||
|
||||
return Observer(builder: (_) {
|
||||
final state = _authenticationStore.state;
|
||||
final state = widget.authenticationStore.state;
|
||||
print(state);
|
||||
if (state == AuthenticationState.denied) {
|
||||
return createWelcomePage();
|
||||
}
|
||||
|
||||
if (state == AuthenticationState.readyToLogin) {
|
||||
return createLoginPage(
|
||||
sharedPreferences: sharedPreferences,
|
||||
userService: userService,
|
||||
walletService: walletService,
|
||||
walletListService: walletListService,
|
||||
authenticationStore: authenticationStore);
|
||||
if (state == AuthenticationState.installed) {
|
||||
return getIt.get<AuthPage>();
|
||||
}
|
||||
|
||||
if (state == AuthenticationState.authenticated ||
|
||||
state == AuthenticationState.restored) {
|
||||
return createDashboardPage(
|
||||
walletService: walletService,
|
||||
priceStore: priceStore,
|
||||
trades: trades,
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
walletStore: walletStore,
|
||||
settingsStore: settingsStore);
|
||||
if (state == AuthenticationState.allowed) {
|
||||
return getIt.get<DashboardPage>();
|
||||
}
|
||||
|
||||
if (state == AuthenticationState.created) {
|
||||
return createSeedPage(
|
||||
settingsStore: settingsStore,
|
||||
walletService: walletService,
|
||||
callback: () =>
|
||||
_authenticationStore.state = AuthenticationState.authenticated);
|
||||
}
|
||||
// if (state == AuthenticationState.denied) {
|
||||
// return createWelcomePage();
|
||||
// }
|
||||
|
||||
// if (state == AuthenticationState.readyToLogin) {
|
||||
// return createLoginPage(
|
||||
// sharedPreferences: sharedPreferences,
|
||||
// userService: userService,
|
||||
// walletService: walletService,
|
||||
// walletListService: walletListService,
|
||||
// authenticationStore: authenticationStore);
|
||||
// }
|
||||
|
||||
// if (state == AuthenticationState.authenticated ||
|
||||
// state == AuthenticationState.restored) {
|
||||
// return createDashboardPage(
|
||||
// walletService: walletService,
|
||||
// priceStore: priceStore,
|
||||
// trades: trades,
|
||||
// transactionDescriptions: transactionDescriptions,
|
||||
// walletStore: walletStore,
|
||||
// settingsStore: settingsStore);
|
||||
// }
|
||||
|
||||
// if (state == AuthenticationState.created) {
|
||||
// return createSeedPage(
|
||||
// settingsStore: settingsStore,
|
||||
// walletService: walletService,
|
||||
// callback: () =>
|
||||
// _authenticationStore.state = AuthenticationState.authenticated);
|
||||
// }
|
||||
|
||||
return Container(color: Colors.white);
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -11,79 +12,68 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
|||
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
|
||||
|
||||
class SeedLanguage extends BasePage {
|
||||
SeedLanguage({this.onConfirm});
|
||||
|
||||
final Function(BuildContext, String) onConfirm;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => SeedLanguageForm();
|
||||
Widget body(BuildContext context) => SeedLanguageForm(onConfirm: onConfirm);
|
||||
}
|
||||
|
||||
class SeedLanguageForm extends StatefulWidget {
|
||||
SeedLanguageForm({this.onConfirm});
|
||||
|
||||
final Function(BuildContext, String) onConfirm;
|
||||
|
||||
@override
|
||||
SeedLanguageFormState createState() => SeedLanguageFormState();
|
||||
}
|
||||
|
||||
class SeedLanguageFormState extends State<SeedLanguageForm> {
|
||||
static const aspectRatioImage = 1.22;
|
||||
|
||||
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
||||
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
||||
|
||||
final List<String> seedLocales = [
|
||||
S.current.seed_language_english,
|
||||
S.current.seed_language_chinese,
|
||||
S.current.seed_language_dutch,
|
||||
S.current.seed_language_german,
|
||||
S.current.seed_language_japanese,
|
||||
S.current.seed_language_portuguese,
|
||||
S.current.seed_language_russian,
|
||||
S.current.seed_language_spanish
|
||||
];
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: aspectRatioImage,
|
||||
child: FittedBox(child: walletNameImage, fit: BoxFit.fill)),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 40),
|
||||
child: Text(
|
||||
S.of(context).seed_language_choose,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 24),
|
||||
child: Observer(
|
||||
builder: (_) => SelectButton(
|
||||
image: null,
|
||||
text: seedLocales[seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage)],
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
onTap: () async => await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => SeedLanguagePicker()
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
]),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content:
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: aspectRatioImage,
|
||||
child: FittedBox(child: walletNameImage, fit: BoxFit.fill)),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 40),
|
||||
child: Text(
|
||||
S.of(context).seed_language_choose,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: SeedLanguageSelector(
|
||||
key: _languageSelectorKey,
|
||||
initialSelected: defaultSeedLanguage),
|
||||
)
|
||||
]),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Observer(
|
||||
builder: (context) {
|
||||
return PrimaryButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).popAndPushNamed(seedLanguageStore.currentRoute),
|
||||
onPressed: () => widget
|
||||
.onConfirm(context, _languageSelectorKey.currentState.selected),
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white);
|
||||
|
|
|
@ -17,7 +17,7 @@ List<Image> flagImages = [
|
|||
Image.asset('assets/images/spain.png'),
|
||||
];
|
||||
|
||||
List<String> languageCodes = [
|
||||
const List<String> languageCodes = [
|
||||
'Eng',
|
||||
'Chi',
|
||||
'Ned',
|
||||
|
@ -28,19 +28,39 @@ List<String> languageCodes = [
|
|||
'Esp',
|
||||
];
|
||||
|
||||
enum Places {topLeft, topRight, bottomLeft, bottomRight, inside}
|
||||
const defaultSeedLanguage = 'English';
|
||||
|
||||
const List<String> seedLanguages = [
|
||||
defaultSeedLanguage,
|
||||
'Chinese (simplified)',
|
||||
'Dutch',
|
||||
'German',
|
||||
'Japanese',
|
||||
'Portuguese',
|
||||
'Russian',
|
||||
'Spanish'
|
||||
];
|
||||
|
||||
enum Places { topLeft, topRight, bottomLeft, bottomRight, inside }
|
||||
|
||||
class SeedLanguagePicker extends StatefulWidget {
|
||||
SeedLanguagePicker({Key key, this.selected = defaultSeedLanguage})
|
||||
: super(key: key);
|
||||
|
||||
final String selected;
|
||||
|
||||
@override
|
||||
SeedLanguagePickerState createState() => SeedLanguagePickerState();
|
||||
SeedLanguagePickerState createState() =>
|
||||
SeedLanguagePickerState(selected: selected);
|
||||
}
|
||||
|
||||
class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||
SeedLanguagePickerState({this.selected});
|
||||
|
||||
String selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
|
@ -48,7 +68,8 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
||||
decoration: BoxDecoration(
|
||||
color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -62,8 +83,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
color: Colors.white
|
||||
),
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
@ -74,9 +94,8 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
height: 300,
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
color: Theme.of(context).dividerColor
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
color: Theme.of(context).dividerColor),
|
||||
child: GridView.count(
|
||||
shrinkWrap: true,
|
||||
crossAxisCount: 3,
|
||||
|
@ -85,71 +104,64 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
crossAxisSpacing: 1,
|
||||
mainAxisSpacing: 1,
|
||||
children: List.generate(9, (index) {
|
||||
|
||||
if (index == 8) {
|
||||
|
||||
return gridTile(
|
||||
isCurrent: false,
|
||||
place: Places.bottomRight,
|
||||
image: null,
|
||||
text: '',
|
||||
onTap: null);
|
||||
|
||||
isCurrent: false,
|
||||
place: Places.bottomRight,
|
||||
image: null,
|
||||
text: '',
|
||||
onTap: null);
|
||||
} else {
|
||||
|
||||
final code = languageCodes[index];
|
||||
final flag = flagImages[index];
|
||||
final isCurrent = index == seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage);
|
||||
final isCurrent =
|
||||
index == seedLanguages.indexOf(selected);
|
||||
|
||||
if (index == 0) {
|
||||
return gridTile(
|
||||
isCurrent: isCurrent,
|
||||
place: Places.topLeft,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
);
|
||||
isCurrent: isCurrent,
|
||||
place: Places.topLeft,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
selected = seedLanguages[index];
|
||||
Navigator.of(context).pop(selected);
|
||||
});
|
||||
}
|
||||
|
||||
if (index == 2) {
|
||||
return gridTile(
|
||||
isCurrent: isCurrent,
|
||||
place: Places.topRight,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
);
|
||||
isCurrent: isCurrent,
|
||||
place: Places.topRight,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
selected = seedLanguages[index];
|
||||
Navigator.of(context).pop(selected);
|
||||
});
|
||||
}
|
||||
|
||||
if (index == 6) {
|
||||
return gridTile(
|
||||
isCurrent: isCurrent,
|
||||
place: Places.bottomLeft,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
);
|
||||
isCurrent: isCurrent,
|
||||
place: Places.bottomLeft,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
selected = seedLanguages[index];
|
||||
Navigator.of(context).pop(selected);
|
||||
});
|
||||
}
|
||||
|
||||
return gridTile(
|
||||
isCurrent: isCurrent,
|
||||
place: Places.inside,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
);
|
||||
isCurrent: isCurrent,
|
||||
place: Places.inside,
|
||||
image: flag,
|
||||
text: code,
|
||||
onTap: () {
|
||||
selected = seedLanguages[index];
|
||||
Navigator.of(context).pop(selected);
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
@ -165,13 +177,12 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget gridTile({
|
||||
@required bool isCurrent,
|
||||
@required Places place,
|
||||
@required Image image,
|
||||
@required String text,
|
||||
@required VoidCallback onTap}) {
|
||||
|
||||
Widget gridTile(
|
||||
{@required bool isCurrent,
|
||||
@required Places place,
|
||||
@required Image image,
|
||||
@required String text,
|
||||
@required VoidCallback onTap}) {
|
||||
BorderRadius borderRadius;
|
||||
final color = isCurrent
|
||||
? Theme.of(context).accentTextTheme.subtitle.decorationColor
|
||||
|
@ -199,40 +210,33 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
|||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: color
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image != null
|
||||
? image
|
||||
: Offstage(),
|
||||
Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 10)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
color: textColor
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(borderRadius: borderRadius, color: color),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image != null ? image : Offstage(),
|
||||
Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 10)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
color: textColor),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
75
lib/src/screens/subaddress/address_edit_or_create_page.dart
Normal file
75
lib/src/screens/subaddress/address_edit_or_create_page.dart
Normal file
|
@ -0,0 +1,75 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/core/AddressLabelValidator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
||||
class AddressEditOrCreatePage extends BasePage {
|
||||
AddressEditOrCreatePage({@required this.addressEditOrCreateViewModel})
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_labelController = TextEditingController(),
|
||||
super() {
|
||||
_labelController.addListener(
|
||||
() => addressEditOrCreateViewModel.label = _labelController.text);
|
||||
_labelController.text = addressEditOrCreateViewModel.label;
|
||||
print(_labelController.text);
|
||||
print(addressEditOrCreateViewModel.label);
|
||||
}
|
||||
|
||||
final AddressEditOrCreateViewModel addressEditOrCreateViewModel;
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final TextEditingController _labelController;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_subaddress_title;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
reaction((_) => addressEditOrCreateViewModel.state,
|
||||
(AddressEditOrCreateState state) {
|
||||
if (state is AddressSavedSuccessfully) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop());
|
||||
}
|
||||
});
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: BaseTextFormField(
|
||||
controller: _labelController,
|
||||
hintText: S.of(context).new_subaddress_label_name,
|
||||
validator: AddressLabelValidator()))),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState.validate()) {
|
||||
await addressEditOrCreateViewModel.save();
|
||||
}
|
||||
},
|
||||
text: addressEditOrCreateViewModel.isEdit
|
||||
? S.of(context).rename
|
||||
: S.of(context).new_subaddress_create,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading:
|
||||
addressEditOrCreateViewModel.state is AddressIsSaving,
|
||||
isDisabled:
|
||||
addressEditOrCreateViewModel.label?.isEmpty ?? true,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_state.dart';
|
||||
import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_store.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
|
||||
class NewSubaddressPage extends BasePage {
|
||||
NewSubaddressPage({this.subaddress});
|
||||
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
String get title => S.current.new_subaddress_title;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => NewSubaddressForm(subaddress);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subaddressCreationStore =
|
||||
Provider.of<SubadrressCreationStore>(context);
|
||||
|
||||
reaction((_) => subaddressCreationStore.state, (SubaddressCreationState state) {
|
||||
if (state is SubaddressCreatedSuccessfully) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop());
|
||||
}
|
||||
});
|
||||
|
||||
return super.build(context);
|
||||
}
|
||||
}
|
||||
|
||||
class NewSubaddressForm extends StatefulWidget {
|
||||
NewSubaddressForm(this.subaddress);
|
||||
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
NewSubaddressFormState createState() => NewSubaddressFormState(subaddress);
|
||||
}
|
||||
|
||||
class NewSubaddressFormState extends State<NewSubaddressForm> {
|
||||
NewSubaddressFormState(this.subaddress);
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _labelController = TextEditingController();
|
||||
final Subaddress subaddress;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (subaddress != null) _labelController.text = subaddress.label;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_labelController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subaddressCreationStore =
|
||||
Provider.of<SubadrressCreationStore>(context);
|
||||
|
||||
_labelController.addListener(() {
|
||||
if (_labelController.text.isNotEmpty) {
|
||||
subaddressCreationStore.setDisabledStatus(false);
|
||||
} else {
|
||||
subaddressCreationStore.setDisabledStatus(true);
|
||||
}
|
||||
});
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: BaseTextFormField(
|
||||
controller: _labelController,
|
||||
hintText: S.of(context).new_subaddress_label_name,
|
||||
validator: (value) {
|
||||
subaddressCreationStore.validateSubaddressName(value);
|
||||
return subaddressCreationStore.errorMessage;
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState.validate()) {
|
||||
if (subaddress != null) {
|
||||
await subaddressCreationStore.setLabel(
|
||||
addressIndex: subaddress.id,
|
||||
label: _labelController.text
|
||||
);
|
||||
} else {
|
||||
await subaddressCreationStore.add(
|
||||
label: _labelController.text);
|
||||
}
|
||||
}
|
||||
},
|
||||
text: subaddress != null
|
||||
? S.of(context).rename
|
||||
: S.of(context).new_subaddress_create,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading:
|
||||
subaddressCreationStore.state is SubaddressIsCreating,
|
||||
isDisabled: subaddressCreationStore.isDisabledStatus,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -32,9 +32,7 @@ class SubaddressListPage extends BasePage {
|
|||
child: Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
separatorBuilder: (_, __) => Divider(
|
||||
color: Theme.of(context).dividerTheme.color,
|
||||
height: 1.0,
|
||||
),
|
||||
color: Theme.of(context).dividerTheme.color, height: 1.0),
|
||||
itemCount: subaddressListStore.subaddresses == null
|
||||
? 0
|
||||
: subaddressListStore.subaddresses.length,
|
||||
|
@ -42,9 +40,7 @@ class SubaddressListPage extends BasePage {
|
|||
final subaddress = subaddressListStore.subaddresses[index];
|
||||
final isCurrent =
|
||||
walletStore.subaddress.address == subaddress.address;
|
||||
final label = subaddress.label != null
|
||||
? subaddress.label
|
||||
: subaddress.address;
|
||||
final label = subaddress.label ?? subaddress.address;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => Navigator.of(context).pop(subaddress),
|
||||
|
|
|
@ -1,99 +1,99 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/src/domain/services/user_service.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'auth_store.g.dart';
|
||||
|
||||
class AuthStore = AuthStoreBase with _$AuthStore;
|
||||
|
||||
abstract class AuthStoreBase with Store {
|
||||
AuthStoreBase(
|
||||
{@required this.userService,
|
||||
@required this.walletService,
|
||||
@required this.sharedPreferences}) {
|
||||
state = AuthenticationStateInitial();
|
||||
_failureCounter = 0;
|
||||
}
|
||||
|
||||
static const maxFailedLogins = 3;
|
||||
static const banTimeout = 180; // 3 mins
|
||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
|
||||
final UserService userService;
|
||||
final WalletService walletService;
|
||||
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
AuthState state;
|
||||
|
||||
@observable
|
||||
int _failureCounter;
|
||||
|
||||
@action
|
||||
Future auth({String password}) async {
|
||||
state = AuthenticationStateInitial();
|
||||
final _banDuration = banDuration();
|
||||
|
||||
if (_banDuration != null) {
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationInProgress();
|
||||
final isAuth = await userService.authenticate(password);
|
||||
|
||||
if (isAuth) {
|
||||
state = AuthenticatedSuccessfully();
|
||||
_failureCounter = 0;
|
||||
} else {
|
||||
_failureCounter += 1;
|
||||
|
||||
if (_failureCounter >= maxFailedLogins) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
}
|
||||
}
|
||||
|
||||
Duration banDuration() {
|
||||
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
|
||||
if (unbanTimestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (now.isAfter(unbanTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
Future<Duration> ban() async {
|
||||
final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
final timeout = (multiplier * banTimeout) * 1000;
|
||||
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
|
||||
return Duration(milliseconds: timeout);
|
||||
}
|
||||
|
||||
@action
|
||||
void biometricAuth() {
|
||||
state = AuthenticatedSuccessfully();
|
||||
}
|
||||
}
|
||||
//import 'dart:async';
|
||||
//import 'package:flutter/foundation.dart';
|
||||
//import 'package:shared_preferences/shared_preferences.dart';
|
||||
//import 'package:mobx/mobx.dart';
|
||||
//import 'package:cake_wallet/src/domain/services/user_service.dart';
|
||||
//import 'package:cake_wallet/src/domain/services/wallet_service.dart';
|
||||
//import 'package:cake_wallet/view_model/auth_state.dart';
|
||||
//import 'package:cake_wallet/generated/i18n.dart';
|
||||
//
|
||||
//part 'auth_store.g.dart';
|
||||
//
|
||||
//class AuthStore = AuthStoreBase with _$AuthStore;
|
||||
//
|
||||
//abstract class AuthStoreBase with Store {
|
||||
// AuthStoreBase(
|
||||
// {@required this.userService,
|
||||
// @required this.walletService,
|
||||
// @required this.sharedPreferences}) {
|
||||
// state = AuthenticationStateInitial();
|
||||
// _failureCounter = 0;
|
||||
// }
|
||||
//
|
||||
// static const maxFailedLogins = 3;
|
||||
// static const banTimeout = 180; // 3 mins
|
||||
// final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
//
|
||||
// final UserService userService;
|
||||
// final WalletService walletService;
|
||||
//
|
||||
// final SharedPreferences sharedPreferences;
|
||||
//
|
||||
// @observable
|
||||
// AuthState state;
|
||||
//
|
||||
// @observable
|
||||
// int _failureCounter;
|
||||
//
|
||||
// @action
|
||||
// Future auth({String password}) async {
|
||||
// state = AuthenticationStateInitial();
|
||||
// final _banDuration = banDuration();
|
||||
//
|
||||
// if (_banDuration != null) {
|
||||
// state = AuthenticationBanned(
|
||||
// error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// state = AuthenticationInProgress();
|
||||
// final isAuth = await userService.authenticate(password);
|
||||
//
|
||||
// if (isAuth) {
|
||||
// state = AuthenticatedSuccessfully();
|
||||
// _failureCounter = 0;
|
||||
// } else {
|
||||
// _failureCounter += 1;
|
||||
//
|
||||
// if (_failureCounter >= maxFailedLogins) {
|
||||
// final banDuration = await ban();
|
||||
// state = AuthenticationBanned(
|
||||
// error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Duration banDuration() {
|
||||
// final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
//
|
||||
// if (unbanTimestamp == null) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
// final now = DateTime.now();
|
||||
//
|
||||
// if (now.isAfter(unbanTime)) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
// }
|
||||
//
|
||||
// Future<Duration> ban() async {
|
||||
// final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
// final timeout = (multiplier * banTimeout) * 1000;
|
||||
// final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
// await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
//
|
||||
// return Duration(milliseconds: timeout);
|
||||
// }
|
||||
//
|
||||
// @action
|
||||
// void biometricAuth() {
|
||||
// state = AuthenticatedSuccessfully();
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -30,8 +30,8 @@ abstract class AuthenticationStoreBase with Store {
|
|||
@observable
|
||||
AuthenticationState state;
|
||||
|
||||
@observable
|
||||
String errorMessage;
|
||||
// @observable
|
||||
// String errorMessage;
|
||||
|
||||
Future started() async {
|
||||
final canAuth = await userService.canAuthenticate();
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart';
|
||||
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
|
||||
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||
|
@ -37,7 +37,7 @@ abstract class WalleRestorationStoreBase with Store {
|
|||
bool isValid;
|
||||
|
||||
@observable
|
||||
List<MnemoticItem> seed;
|
||||
List<MnemonicItem> seed;
|
||||
|
||||
@observable
|
||||
bool disabledState;
|
||||
|
@ -79,35 +79,35 @@ abstract class WalleRestorationStoreBase with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
void setSeed(List<MnemoticItem> seed) {
|
||||
void setSeed(List<MnemonicItem> seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@action
|
||||
void validateSeed(List<MnemoticItem> seed) {
|
||||
final _seed = seed != null ? seed : this.seed;
|
||||
bool isValid = _seed != null ? _seed.length == 25 : false;
|
||||
|
||||
if (!isValid) {
|
||||
errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
|
||||
this.isValid = isValid;
|
||||
return;
|
||||
}
|
||||
|
||||
for (final item in _seed) {
|
||||
if (!item.isCorrect()) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
this.isValid = isValid;
|
||||
return;
|
||||
}
|
||||
// @action
|
||||
// void validateSeed(List<MnemonicItem> seed) {
|
||||
// final _seed = seed != null ? seed : this.seed;
|
||||
// bool isValid = _seed != null ? _seed.length == 25 : false;
|
||||
//
|
||||
// if (!isValid) {
|
||||
// errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
|
||||
// this.isValid = isValid;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// for (final item in _seed) {
|
||||
// if (!item.isCorrect()) {
|
||||
// isValid = false;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (isValid) {
|
||||
// errorMessage = null;
|
||||
// }
|
||||
//
|
||||
// this.isValid = isValid;
|
||||
// return;
|
||||
// }
|
||||
|
||||
String _seedText() {
|
||||
return seed.fold('', (acc, item) => acc + ' ' + item.toString());
|
||||
|
|
|
@ -2,24 +2,24 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
class BaseTextFormField extends StatelessWidget {
|
||||
BaseTextFormField({
|
||||
this.controller,
|
||||
this.keyboardType = TextInputType.text,
|
||||
this.textInputAction = TextInputAction.done,
|
||||
this.textAlign = TextAlign.start,
|
||||
this.autovalidate = false,
|
||||
this.hintText = '',
|
||||
this.maxLines = 1,
|
||||
this.inputFormatters,
|
||||
this.textColor,
|
||||
this.hintColor,
|
||||
this.borderColor,
|
||||
this.prefix,
|
||||
this.suffix,
|
||||
this.suffixIcon,
|
||||
this.enabled = true,
|
||||
this.validator
|
||||
});
|
||||
BaseTextFormField(
|
||||
{this.controller,
|
||||
this.keyboardType = TextInputType.text,
|
||||
this.textInputAction = TextInputAction.done,
|
||||
this.textAlign = TextAlign.start,
|
||||
this.autovalidate = false,
|
||||
this.hintText = '',
|
||||
this.maxLines = 1,
|
||||
this.inputFormatters,
|
||||
this.textColor,
|
||||
this.hintColor,
|
||||
this.borderColor,
|
||||
this.prefix,
|
||||
this.suffix,
|
||||
this.suffixIcon,
|
||||
this.enabled = true,
|
||||
this.validator,
|
||||
this.placeholderTextStyle});
|
||||
|
||||
final TextEditingController controller;
|
||||
final TextInputType keyboardType;
|
||||
|
@ -37,6 +37,7 @@ class BaseTextFormField extends StatelessWidget {
|
|||
final Widget suffixIcon;
|
||||
final bool enabled;
|
||||
final FormFieldValidator<String> validator;
|
||||
final TextStyle placeholderTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -50,31 +51,26 @@ class BaseTextFormField extends StatelessWidget {
|
|||
inputFormatters: inputFormatters,
|
||||
enabled: enabled,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: textColor ?? Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
fontSize: 16.0,
|
||||
color: textColor ?? Theme.of(context).primaryTextTheme.title.color),
|
||||
decoration: InputDecoration(
|
||||
prefix: prefix,
|
||||
suffix: suffix,
|
||||
suffixIcon: suffixIcon,
|
||||
hintStyle: TextStyle(
|
||||
color: hintColor ?? Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16
|
||||
),
|
||||
hintText: hintText,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0
|
||||
)
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0
|
||||
)
|
||||
)
|
||||
),
|
||||
prefix: prefix,
|
||||
suffix: suffix,
|
||||
suffixIcon: suffixIcon,
|
||||
hintStyle: placeholderTextStyle ??
|
||||
TextStyle(
|
||||
color: hintColor ??
|
||||
Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16),
|
||||
hintText: hintText,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
validator: validator,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/src/domain/monero/get_height_by_date.dart';
|
||||
|
||||
class BlockchainHeightWidget extends StatefulWidget {
|
||||
BlockchainHeightWidget({GlobalKey key}) : super(key: key);
|
||||
BlockchainHeightWidget({GlobalKey key, this.onHeightChange})
|
||||
: super(key: key);
|
||||
|
||||
final Function(int) onHeightChange;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||
|
@ -13,15 +16,23 @@ class BlockchainHeightWidget extends StatefulWidget {
|
|||
class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||
final dateController = TextEditingController();
|
||||
final restoreHeightController = TextEditingController();
|
||||
|
||||
int get height => _height;
|
||||
int _height = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
restoreHeightController.addListener(() => _height =
|
||||
restoreHeightController.text != null
|
||||
restoreHeightController.addListener(() {
|
||||
try {
|
||||
_changeHeight(restoreHeightController.text != null &&
|
||||
restoreHeightController.text.isNotEmpty
|
||||
? int.parse(restoreHeightController.text)
|
||||
: 0);
|
||||
} catch (_) {
|
||||
_changeHeight(0);
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -38,21 +49,18 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
child: TextFormField(
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
controller: restoreHeightController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: false),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16
|
||||
),
|
||||
fontSize: 16),
|
||||
hintText: S.of(context).widgets_restore_from_blockheight,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
color: Theme.of(context).dividerColor, width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
|
@ -81,13 +89,14 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
child: TextFormField(
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
color: Theme.of(context).primaryTextTheme.title.color),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
fontSize: 16),
|
||||
hintText: S.of(context).widgets_restore_from_date,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
|
@ -113,7 +122,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
|
||||
Future _selectDate(BuildContext context) async {
|
||||
final now = DateTime.now();
|
||||
final DateTime date = await showDatePicker(
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: now.subtract(Duration(days: 1)),
|
||||
firstDate: DateTime(2014, DateTime.april),
|
||||
|
@ -125,8 +134,13 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
setState(() {
|
||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||
restoreHeightController.text = '$height';
|
||||
_height = height;
|
||||
_changeHeight(height);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _changeHeight(int height) {
|
||||
_height = height;
|
||||
widget.onHeightChange?.call(height);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
|
||||
|
||||
class SeedLanguageSelector extends StatefulWidget {
|
||||
SeedLanguageSelector({Key key, this.initialSelected}) : super(key: key);
|
||||
|
||||
final String initialSelected;
|
||||
|
||||
@override
|
||||
SeedLanguageSelectorState createState() =>
|
||||
SeedLanguageSelectorState(selected: initialSelected);
|
||||
}
|
||||
|
||||
class SeedLanguageSelectorState extends State<SeedLanguageSelector> {
|
||||
SeedLanguageSelectorState({this.selected});
|
||||
|
||||
final seedLocales = [
|
||||
S.current.seed_language_english,
|
||||
S.current.seed_language_chinese,
|
||||
S.current.seed_language_dutch,
|
||||
S.current.seed_language_german,
|
||||
S.current.seed_language_japanese,
|
||||
S.current.seed_language_portuguese,
|
||||
S.current.seed_language_russian,
|
||||
S.current.seed_language_spanish
|
||||
];
|
||||
String selected;
|
||||
final _pickerKey = GlobalKey<SeedLanguagePickerState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectButton(
|
||||
image: null,
|
||||
text: seedLocales[seedLanguages.indexOf(selected)],
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
||||
onTap: () async {
|
||||
final selected = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
SeedLanguagePicker(key: _pickerKey, selected: this.selected));
|
||||
setState(() => this.selected = selected);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,94 +1,64 @@
|
|||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/chinese_simplified.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/dutch.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/german.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/japanese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/portuguese.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/russian.dart';
|
||||
import 'package:cake_wallet/src/domain/monero/mnemonics/spanish.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
|
||||
import 'package:cake_wallet/core/seed_validator.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class SeedWidget extends StatefulWidget {
|
||||
SeedWidget({Key key, this.onMnemoticChange, this.onFinish, this.seedLanguage}) : super(key: key) {
|
||||
switch (seedLanguage) {
|
||||
case 'English':
|
||||
words = EnglishMnemonics.words;
|
||||
break;
|
||||
case 'Chinese (simplified)':
|
||||
words = ChineseSimplifiedMnemonics.words;
|
||||
break;
|
||||
case 'Dutch':
|
||||
words = DutchMnemonics.words;
|
||||
break;
|
||||
case 'German':
|
||||
words = GermanMnemonics.words;
|
||||
break;
|
||||
case 'Japanese':
|
||||
words = JapaneseMnemonics.words;
|
||||
break;
|
||||
case 'Portuguese':
|
||||
words = PortugueseMnemonics.words;
|
||||
break;
|
||||
case 'Russian':
|
||||
words = RussianMnemonics.words;
|
||||
break;
|
||||
case 'Spanish':
|
||||
words = SpanishMnemonics.words;
|
||||
break;
|
||||
default:
|
||||
words = EnglishMnemonics.words;
|
||||
}
|
||||
}
|
||||
SeedWidget(
|
||||
{Key key,
|
||||
this.maxLength,
|
||||
this.onMnemonicChange,
|
||||
this.onFinish,
|
||||
this.validator})
|
||||
: super(key: key);
|
||||
|
||||
final Function(List<MnemoticItem>) onMnemoticChange;
|
||||
final int maxLength;
|
||||
final Function(List<MnemonicItem>) onMnemonicChange;
|
||||
final Function() onFinish;
|
||||
final String seedLanguage;
|
||||
List<String> words;
|
||||
final SeedValidator validator;
|
||||
|
||||
@override
|
||||
SeedWidgetState createState() => SeedWidgetState();
|
||||
SeedWidgetState createState() => SeedWidgetState(maxLength: maxLength);
|
||||
}
|
||||
|
||||
class SeedWidgetState extends State<SeedWidget> {
|
||||
static const maxLength = 25;
|
||||
SeedWidgetState({this.maxLength});
|
||||
|
||||
List<MnemoticItem> items = <MnemoticItem>[];
|
||||
List<MnemonicItem> items = <MnemonicItem>[];
|
||||
final int maxLength;
|
||||
final _seedController = TextEditingController();
|
||||
final _seedTextFieldKey = GlobalKey();
|
||||
MnemoticItem selectedItem;
|
||||
MnemonicItem selectedItem;
|
||||
bool isValid;
|
||||
String errorMessage;
|
||||
|
||||
List<MnemoticItem> currentMnemotics;
|
||||
bool isCurrentMnemoticValid;
|
||||
List<MnemonicItem> currentMnemonics;
|
||||
bool isCurrentMnemonicValid;
|
||||
String _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isValid = false;
|
||||
isCurrentMnemoticValid = false;
|
||||
isCurrentMnemonicValid = false;
|
||||
_seedController
|
||||
.addListener(() => changeCurrentMnemotic(_seedController.text));
|
||||
.addListener(() => changeCurrentMnemonic(_seedController.text));
|
||||
}
|
||||
|
||||
void addMnemotic(String text) {
|
||||
setState(() => items.add(MnemoticItem(
|
||||
text: text.trim().toLowerCase(), dic: widget.words)));
|
||||
void addMnemonic(String text) {
|
||||
setState(() => items.add(MnemonicItem(text: text.trim().toLowerCase())));
|
||||
_seedController.text = '';
|
||||
|
||||
if (widget.onMnemoticChange != null) {
|
||||
widget.onMnemoticChange(items);
|
||||
if (widget.onMnemonicChange != null) {
|
||||
widget.onMnemonicChange(items);
|
||||
}
|
||||
}
|
||||
|
||||
void mnemoticFromText(String text) {
|
||||
void mnemonicFromText(String text) {
|
||||
final splitted = text.split(' ');
|
||||
|
||||
if (splitted.length >= 2) {
|
||||
|
@ -98,18 +68,18 @@ class SeedWidgetState extends State<SeedWidget> {
|
|||
}
|
||||
|
||||
if (selectedItem != null) {
|
||||
editTextOfSelectedMnemotic(text);
|
||||
editTextOfSelectedMnemonic(text);
|
||||
} else {
|
||||
addMnemotic(text);
|
||||
addMnemonic(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void selectMnemotic(MnemoticItem item) {
|
||||
void selectMnemonic(MnemonicItem item) {
|
||||
setState(() {
|
||||
selectedItem = item;
|
||||
currentMnemotics = [item];
|
||||
currentMnemonics = [item];
|
||||
|
||||
_seedController
|
||||
..text = item.text
|
||||
|
@ -117,23 +87,23 @@ class SeedWidgetState extends State<SeedWidget> {
|
|||
});
|
||||
}
|
||||
|
||||
void onMnemoticTap(MnemoticItem item) {
|
||||
void onMnemonicTap(MnemonicItem item) {
|
||||
if (selectedItem == item) {
|
||||
setState(() => selectedItem = null);
|
||||
_seedController.text = '';
|
||||
return;
|
||||
}
|
||||
|
||||
selectMnemotic(item);
|
||||
selectMnemonic(item);
|
||||
}
|
||||
|
||||
void editTextOfSelectedMnemotic(String text) {
|
||||
void editTextOfSelectedMnemonic(String text) {
|
||||
setState(() => selectedItem.changeText(text));
|
||||
selectedItem = null;
|
||||
_seedController.text = '';
|
||||
|
||||
if (widget.onMnemoticChange != null) {
|
||||
widget.onMnemoticChange(items);
|
||||
if (widget.onMnemonicChange != null) {
|
||||
widget.onMnemonicChange(items);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,83 +113,77 @@ class SeedWidgetState extends State<SeedWidget> {
|
|||
selectedItem = null;
|
||||
_seedController.text = '';
|
||||
|
||||
if (widget.onMnemoticChange != null) {
|
||||
widget.onMnemoticChange(items);
|
||||
if (widget.onMnemonicChange != null) {
|
||||
widget.onMnemonicChange(items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
setState(() => isValid = false);
|
||||
}
|
||||
void invalidate() => setState(() => isValid = false);
|
||||
|
||||
void validated() {
|
||||
setState(() => isValid = true);
|
||||
}
|
||||
void validated() => setState(() => isValid = true);
|
||||
|
||||
void setErrorMessage(String errorMessage) {
|
||||
setState(() => this.errorMessage = errorMessage);
|
||||
}
|
||||
void setErrorMessage(String errorMessage) =>
|
||||
setState(() => this.errorMessage = errorMessage);
|
||||
|
||||
void replaceText(String text) {
|
||||
setState(() => items = []);
|
||||
mnemoticFromText(text);
|
||||
mnemonicFromText(text);
|
||||
}
|
||||
|
||||
void changeCurrentMnemotic(String text) {
|
||||
void changeCurrentMnemonic(String text) {
|
||||
setState(() {
|
||||
final trimmedText = text.trim();
|
||||
final splitted = trimmedText.split(' ');
|
||||
_errorMessage = null;
|
||||
|
||||
if (text == null) {
|
||||
currentMnemotics = [];
|
||||
isCurrentMnemoticValid = false;
|
||||
currentMnemonics = [];
|
||||
isCurrentMnemonicValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
currentMnemotics = splitted
|
||||
.map((text) => MnemoticItem(text: text, dic: widget.words))
|
||||
.toList();
|
||||
currentMnemonics =
|
||||
splitted.map((text) => MnemonicItem(text: text)).toList();
|
||||
|
||||
bool isValid = true;
|
||||
var isValid = true;
|
||||
|
||||
for (final word in currentMnemotics) {
|
||||
isValid = word.isCorrect();
|
||||
for (final word in currentMnemonics) {
|
||||
isValid = widget.validator.isValid(word);
|
||||
|
||||
if (!isValid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isCurrentMnemoticValid = isValid;
|
||||
isCurrentMnemonicValid = isValid;
|
||||
});
|
||||
}
|
||||
|
||||
void saveCurrentMnemoticToItems() {
|
||||
void saveCurrentMnemonicToItems() {
|
||||
setState(() {
|
||||
if (selectedItem != null) {
|
||||
selectedItem.changeText(currentMnemotics.first.text.trim());
|
||||
selectedItem.changeText(currentMnemonics.first.text.trim());
|
||||
selectedItem = null;
|
||||
} else {
|
||||
items.addAll(currentMnemotics);
|
||||
items.addAll(currentMnemonics);
|
||||
}
|
||||
|
||||
currentMnemotics = [];
|
||||
currentMnemonics = [];
|
||||
_seedController.text = '';
|
||||
});
|
||||
}
|
||||
|
||||
void showErrorIfExist() {
|
||||
setState(() => _errorMessage =
|
||||
!isCurrentMnemoticValid ? S.current.incorrect_seed : null);
|
||||
!isCurrentMnemonicValid ? S.current.incorrect_seed : null);
|
||||
}
|
||||
|
||||
bool isSeedValid() {
|
||||
bool isValid;
|
||||
|
||||
for (final item in items) {
|
||||
isValid = item.isCorrect();
|
||||
isValid = widget.validator.isValid(item);
|
||||
|
||||
if (!isValid) {
|
||||
break;
|
||||
|
@ -234,192 +198,207 @@ class SeedWidgetState extends State<SeedWidget> {
|
|||
return Container(
|
||||
child: Column(children: [
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 1,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)
|
||||
fit: FlexFit.tight,
|
||||
flex: 1,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).restore_active_seed,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.caption.color),
|
||||
),
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).restore_active_seed,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).primaryTextTheme.caption.color
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Wrap(
|
||||
children: items.map((item) {
|
||||
final isValid = item.isCorrect();
|
||||
final isSelected = selectedItem == item;
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Wrap(
|
||||
children: items.map((item) {
|
||||
final isValid = widget.validator.isValid(item);
|
||||
final isSelected = selectedItem == item;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => onMnemoticTap(item),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isValid ? Colors.transparent : Palette.red),
|
||||
margin: EdgeInsets.only(right: 7, bottom: 8),
|
||||
child: Text(
|
||||
item.toString(),
|
||||
style: TextStyle(
|
||||
color: isValid
|
||||
? Theme.of(context).primaryTextTheme.title.color
|
||||
: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.w900 : FontWeight.w400,
|
||||
decoration: isSelected
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none),
|
||||
)),
|
||||
);
|
||||
}).toList(),)
|
||||
)
|
||||
],
|
||||
),
|
||||
return InkWell(
|
||||
onTap: () => onMnemonicTap(item),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isValid
|
||||
? Colors.transparent
|
||||
: Palette.red),
|
||||
margin: EdgeInsets.only(right: 7, bottom: 8),
|
||||
child: Text(
|
||||
item.toString(),
|
||||
style: TextStyle(
|
||||
color: isValid
|
||||
? Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color
|
||||
: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
fontSize: 16,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w900
|
||||
: FontWeight.w400,
|
||||
decoration: isSelected
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none),
|
||||
)),
|
||||
);
|
||||
}).toList(),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.tight,
|
||||
flex: 2,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 24, top: 48, right: 24, bottom: 24),
|
||||
padding:
|
||||
EdgeInsets.only(left: 24, top: 48, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).restore_new_seed,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: TextFormField(
|
||||
key: _seedTextFieldKey,
|
||||
onFieldSubmitted: (text) => isCurrentMnemoticValid
|
||||
? saveCurrentMnemoticToItems()
|
||||
: null,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).restore_new_seed,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.title.color),
|
||||
),
|
||||
controller: _seedController,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 145),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'${items.length}/${SeedWidgetState.maxLength}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 14)),
|
||||
SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () async =>
|
||||
Clipboard.getData('text/plain').then(
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: TextFormField(
|
||||
key: _seedTextFieldKey,
|
||||
onFieldSubmitted: (text) => isCurrentMnemonicValid
|
||||
? saveCurrentMnemonicToItems()
|
||||
: null,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.title.color),
|
||||
controller: _seedController,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 145),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text('${items.length}/$maxLength',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
fontSize: 14)),
|
||||
SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () async =>
|
||||
Clipboard.getData('text/plain').then(
|
||||
(clipboard) =>
|
||||
replaceText(clipboard.text)),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: EdgeInsets.all(7),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||
borderRadius:
|
||||
BorderRadius.circular(10.0)),
|
||||
child: Text(
|
||||
S.of(context).paste,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.title.color
|
||||
),
|
||||
)),
|
||||
)
|
||||
],
|
||||
replaceText(clipboard.text)),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: EdgeInsets.all(7),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.title
|
||||
.backgroundColor,
|
||||
borderRadius:
|
||||
BorderRadius.circular(10.0)),
|
||||
child: Text(
|
||||
S.of(context).paste,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
hintStyle:
|
||||
TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||
fontSize: 16
|
||||
),
|
||||
hintText: S.of(context).restore_from_seed_placeholder,
|
||||
errorText: _errorMessage,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor, width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
enableInteractiveSelection: false,
|
||||
),
|
||||
)
|
||||
]),
|
||||
)
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
fontSize: 16),
|
||||
hintText:
|
||||
S.of(context).restore_from_seed_placeholder,
|
||||
errorText: _errorMessage,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0))),
|
||||
enableInteractiveSelection: false,
|
||||
),
|
||||
)
|
||||
]),
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: PrimaryButton(
|
||||
onPressed: clear,
|
||||
text: S.of(context).clear,
|
||||
color: Colors.red,
|
||||
textColor: Colors.white,
|
||||
isDisabled: items.isEmpty,
|
||||
),
|
||||
)
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: PrimaryButton(
|
||||
onPressed: clear,
|
||||
text: S.of(context).clear,
|
||||
color: Colors.red,
|
||||
textColor: Colors.white,
|
||||
isDisabled: items.isEmpty,
|
||||
),
|
||||
)),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: (selectedItem == null && items.length == maxLength)
|
||||
? PrimaryButton(
|
||||
text: S.of(context).restore_next,
|
||||
isDisabled: !isSeedValid(),
|
||||
onPressed: () => widget.onFinish != null
|
||||
? widget.onFinish()
|
||||
: null,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white)
|
||||
text: S.of(context).restore_next,
|
||||
isDisabled: !isSeedValid(),
|
||||
onPressed: () => widget.onFinish != null
|
||||
? widget.onFinish()
|
||||
: null,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white)
|
||||
: PrimaryButton(
|
||||
text: selectedItem != null
|
||||
? S.of(context).save
|
||||
: S.of(context).add_new_word,
|
||||
onPressed: () => isCurrentMnemoticValid
|
||||
? saveCurrentMnemoticToItems()
|
||||
: null,
|
||||
onDisabledPressed: () => showErrorIfExist(),
|
||||
isDisabled: !isCurrentMnemoticValid,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white),
|
||||
text: selectedItem != null
|
||||
? S.of(context).save
|
||||
: S.of(context).add_new_word,
|
||||
onPressed: () => isCurrentMnemonicValid
|
||||
? saveCurrentMnemonicToItems()
|
||||
: null,
|
||||
onDisabledPressed: () => showErrorIfExist(),
|
||||
isDisabled: !isCurrentMnemonicValid,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
16
lib/store/app_store.dart
Normal file
16
lib/store/app_store.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
part 'app_store.g.dart';
|
||||
|
||||
class AppStore = AppStoreBase with _$AppStore;
|
||||
|
||||
abstract class AppStoreBase with Store {
|
||||
AppStoreBase({this.authenticationStore});
|
||||
|
||||
AuthenticationStore authenticationStore;
|
||||
|
||||
@observable
|
||||
WalletBase wallet;
|
||||
}
|
23
lib/store/authentication_store.dart
Normal file
23
lib/store/authentication_store.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'authentication_store.g.dart';
|
||||
|
||||
class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
|
||||
|
||||
enum AuthenticationState { uninitialized, installed, allowed, denied }
|
||||
|
||||
abstract class AuthenticationStoreBase with Store {
|
||||
AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
|
||||
|
||||
@observable
|
||||
AuthenticationState state;
|
||||
|
||||
@action
|
||||
void installed() => state = AuthenticationState.installed;
|
||||
|
||||
@action
|
||||
void allowed() => state = AuthenticationState.allowed;
|
||||
|
||||
@action
|
||||
void denied() => state = AuthenticationState.denied;
|
||||
}
|
10
lib/store/wallet_list_store.dart
Normal file
10
lib/store/wallet_list_store.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wallet_list_store.g.dart';
|
||||
|
||||
class WalletListStore = WalletListStoreBase with _$WalletListStore;
|
||||
|
||||
abstract class WalletListStoreBase with Store {
|
||||
@observable
|
||||
Object state;
|
||||
}
|
|
@ -44,9 +44,10 @@ class Themes {
|
|||
),
|
||||
display4: TextStyle(
|
||||
color: Palette.oceanBlue // QR code
|
||||
)
|
||||
),
|
||||
// headline1: TextStyle(color: Palette.nightBlue)
|
||||
),
|
||||
dividerColor: Palette.periwinkle,
|
||||
dividerColor: Palette.eee,
|
||||
accentTextTheme: TextTheme(
|
||||
title: TextStyle(
|
||||
color: Palette.darkLavender, // top panel
|
||||
|
@ -112,7 +113,8 @@ class Themes {
|
|||
),
|
||||
display4: TextStyle(
|
||||
color: PaletteDark.gray // QR code
|
||||
)
|
||||
),
|
||||
// headline5: TextStyle(color: PaletteDark.gray)
|
||||
),
|
||||
dividerColor: PaletteDark.distantBlue,
|
||||
accentTextTheme: TextTheme(
|
||||
|
|
3
lib/utils/list_item.dart
Normal file
3
lib/utils/list_item.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
abstract class ListItem {
|
||||
const ListItem();
|
||||
}
|
5
lib/utils/list_section.dart
Normal file
5
lib/utils/list_section.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
class ListSection<Item> {
|
||||
const ListSection({this.items});
|
||||
|
||||
final List<Item> items;
|
||||
}
|
3
lib/view_model/address_list/account_list_header.dart
Normal file
3
lib/view_model/address_list/account_list_header.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AccountListHeader extends ListItem {}
|
|
@ -0,0 +1,93 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
|
||||
part 'address_edit_or_create_view_model.g.dart';
|
||||
|
||||
class AddressEditOrCreateViewModel = AddressEditOrCreateViewModelBase
|
||||
with _$AddressEditOrCreateViewModel;
|
||||
|
||||
abstract class AddressEditOrCreateState {}
|
||||
|
||||
class AddressEditOrCreateStateInitial extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressIsSaving extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressSavedSuccessfully extends AddressEditOrCreateState {}
|
||||
|
||||
class AddressEditOrCreateStateFailure extends AddressEditOrCreateState {
|
||||
AddressEditOrCreateStateFailure({this.error});
|
||||
|
||||
String error;
|
||||
}
|
||||
|
||||
abstract class AddressEditOrCreateViewModelBase with Store {
|
||||
AddressEditOrCreateViewModelBase({@required WalletBase wallet, dynamic item})
|
||||
: isEdit = item != null,
|
||||
state = AddressEditOrCreateStateInitial(),
|
||||
label = item?.name as String,
|
||||
_item = item,
|
||||
_wallet = wallet;
|
||||
|
||||
dynamic _item;
|
||||
|
||||
@observable
|
||||
AddressEditOrCreateState state;
|
||||
|
||||
@observable
|
||||
String label;
|
||||
|
||||
bool isEdit;
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
||||
Future<void> save() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
try {
|
||||
state = AddressIsSaving();
|
||||
|
||||
if (isEdit) {
|
||||
await _update();
|
||||
} else {
|
||||
await _createNew();
|
||||
}
|
||||
|
||||
state = AddressSavedSuccessfully();
|
||||
} catch (e) {
|
||||
state = AddressEditOrCreateStateFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createNew() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.generateNewAddress(label: label);
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList
|
||||
.addSubaddress(accountIndex: wallet.account.id, label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _update() async {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
await wallet.updateAddress(_item.address as String, label: label);
|
||||
}
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
await wallet.subaddressList.setLabelSubaddress(
|
||||
accountIndex: wallet.account.id,
|
||||
addressIndex: _item.id as int,
|
||||
label: label);
|
||||
await wallet.save();
|
||||
}
|
||||
}
|
||||
}
|
3
lib/view_model/address_list/address_list_header.dart
Normal file
3
lib/view_model/address_list/address_list_header.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AddressListHeader extends ListItem {}
|
14
lib/view_model/address_list/address_list_item.dart
Normal file
14
lib/view_model/address_list/address_list_item.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class AddressListItem extends ListItem {
|
||||
const AddressListItem({@required this.address, this.name, this.id})
|
||||
: super();
|
||||
|
||||
final int id;
|
||||
final String address;
|
||||
final String name;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
}
|
135
lib/view_model/address_list/address_list_view_model.dart
Normal file
135
lib/view_model/address_list/address_list_view_model.dart
Normal file
|
@ -0,0 +1,135 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
|
||||
|
||||
part 'address_list_view_model.g.dart';
|
||||
|
||||
class AddressListViewModel = AddressListViewModelBase
|
||||
with _$AddressListViewModel;
|
||||
|
||||
abstract class PaymentURI {
|
||||
PaymentURI({this.amount, this.address});
|
||||
|
||||
final String amount;
|
||||
final String address;
|
||||
}
|
||||
|
||||
class MoneroURI extends PaymentURI {
|
||||
MoneroURI({String amount, String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'monero:' + address;
|
||||
|
||||
if (amount?.isNotEmpty ?? false) {
|
||||
base += '?tx_amount=$amount';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class BitcoinURI extends PaymentURI {
|
||||
BitcoinURI({String amount, String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'bitcoin:' + address;
|
||||
|
||||
if (amount?.isNotEmpty ?? false) {
|
||||
base += '?amount=$amount';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AddressListViewModelBase with Store {
|
||||
AddressListViewModelBase({@required WalletBase wallet}) {
|
||||
hasAccounts = _wallet is MoneroWallet;
|
||||
_wallet = wallet;
|
||||
_init();
|
||||
}
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
@computed
|
||||
AddressListItem get address => AddressListItem(address: _wallet.address);
|
||||
|
||||
@computed
|
||||
PaymentURI get uri {
|
||||
if (_wallet is MoneroWallet) {
|
||||
return MoneroURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (_wallet is BitcoinWallet) {
|
||||
return BitcoinURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get items =>
|
||||
ObservableList<ListItem>()..addAll(_baseItems)..addAll(addressList);
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get addressList {
|
||||
final wallet = _wallet;
|
||||
final addressList = ObservableList<ListItem>();
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
addressList.addAll(wallet.subaddressList.subaddresses.map((subaddress) =>
|
||||
AddressListItem(
|
||||
id: subaddress.id,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address)));
|
||||
}
|
||||
|
||||
if (wallet is BitcoinWallet) {
|
||||
final bitcoinAddresses = wallet.addresses.map(
|
||||
(addr) => AddressListItem(name: addr.label, address: addr.address));
|
||||
addressList.addAll(bitcoinAddresses);
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
set address(AddressListItem address) => _wallet.address = address.address;
|
||||
|
||||
bool hasAccounts;
|
||||
|
||||
WalletBase _wallet;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
@computed
|
||||
String get accountLabel {
|
||||
final wallet = _wallet;
|
||||
|
||||
if (wallet is MoneroWallet) {
|
||||
return wallet.account.label;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void _init() {
|
||||
_baseItems = [];
|
||||
|
||||
if (_wallet is MoneroWallet) {
|
||||
_baseItems.add(AccountListHeader());
|
||||
}
|
||||
|
||||
_baseItems.add(AddressListHeader());
|
||||
}
|
||||
}
|
20
lib/view_model/auth_state.dart
Normal file
20
lib/view_model/auth_state.dart
Normal file
|
@ -0,0 +1,20 @@
|
|||
abstract class AuthState {}
|
||||
|
||||
class AuthenticationStateInitial extends AuthState {}
|
||||
|
||||
class AuthenticationInProgress extends AuthState {}
|
||||
|
||||
class AuthenticatedSuccessfully extends AuthState {}
|
||||
|
||||
class AuthenticationFailure extends AuthState {
|
||||
AuthenticationFailure({this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class AuthenticationBanned extends AuthState {
|
||||
AuthenticationBanned({this.error});
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
97
lib/view_model/auth_view_model.dart
Normal file
97
lib/view_model/auth_view_model.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'auth_view_model.g.dart';
|
||||
|
||||
class AuthViewModel = AuthViewModelBase with _$AuthViewModel;
|
||||
|
||||
abstract class AuthViewModelBase with Store {
|
||||
AuthViewModelBase(
|
||||
{@required this.authService, @required this.sharedPreferences}) {
|
||||
state = AuthenticationStateInitial();
|
||||
_failureCounter = 0;
|
||||
}
|
||||
|
||||
static const maxFailedLogins = 3;
|
||||
static const banTimeout = 180; // 3 mins
|
||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
|
||||
final AuthService authService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
AuthState state;
|
||||
|
||||
@observable
|
||||
int _failureCounter;
|
||||
|
||||
@action
|
||||
Future<void> auth({String password}) async {
|
||||
state = AuthenticationStateInitial();
|
||||
final _banDuration = banDuration();
|
||||
|
||||
if (_banDuration != null) {
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for +
|
||||
'${_banDuration.inMinutes}' +
|
||||
S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state = AuthenticationInProgress();
|
||||
final isAuth = await authService.authenticate(password);
|
||||
|
||||
if (isAuth) {
|
||||
state = AuthenticatedSuccessfully();
|
||||
_failureCounter = 0;
|
||||
} else {
|
||||
_failureCounter += 1;
|
||||
|
||||
if (_failureCounter >= maxFailedLogins) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
error: S.current.auth_store_banned_for +
|
||||
'${banDuration.inMinutes}' +
|
||||
S.current.auth_store_banned_minutes);
|
||||
return;
|
||||
}
|
||||
|
||||
state =
|
||||
AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||
}
|
||||
}
|
||||
|
||||
Duration banDuration() {
|
||||
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||
|
||||
if (unbanTimestamp == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (now.isAfter(unbanTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
Future<Duration> ban() async {
|
||||
final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||
final timeout = (multiplier * banTimeout) * 1000;
|
||||
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||
|
||||
return Duration(milliseconds: timeout);
|
||||
}
|
||||
|
||||
@action
|
||||
void biometricAuth() => state = AuthenticatedSuccessfully();
|
||||
}
|
68
lib/view_model/dashboard_view_model.dart
Normal file
68
lib/view_model/dashboard_view_model.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
||||
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
|
||||
part 'dashboard_view_model.g.dart';
|
||||
|
||||
class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel;
|
||||
|
||||
class WalletBalace {
|
||||
WalletBalace({this.unlockedBalance, this.totalBalance});
|
||||
|
||||
final String unlockedBalance;
|
||||
final String totalBalance;
|
||||
}
|
||||
|
||||
abstract class DashboardViewModelBase with Store {
|
||||
DashboardViewModelBase({this.appStore}) {
|
||||
name = appStore.wallet?.name;
|
||||
balance = WalletBalace(unlockedBalance: '0.001', totalBalance: '0.005');
|
||||
status = SyncedSyncStatus();
|
||||
type = WalletType.bitcoin;
|
||||
wallet ??= appStore.wallet;
|
||||
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
|
||||
transactions = ObservableList.of(wallet.transactionHistory.transactions
|
||||
.map((transaction) => TransactionListItem(transaction: transaction)));
|
||||
}
|
||||
|
||||
@observable
|
||||
WalletType type;
|
||||
|
||||
@observable
|
||||
String name;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
|
||||
@observable
|
||||
WalletBalace balance;
|
||||
|
||||
@observable
|
||||
SyncStatus status;
|
||||
|
||||
@observable
|
||||
ObservableList<Object> transactions;
|
||||
|
||||
@observable
|
||||
String subname;
|
||||
|
||||
WalletBase wallet;
|
||||
|
||||
AppStore appStore;
|
||||
|
||||
ReactionDisposer _reaction;
|
||||
|
||||
void _onWalletChange(WalletBase wallet) {
|
||||
name = wallet.name;
|
||||
transactions.clear();
|
||||
transactions.addAll(wallet.transactionHistory.transactions
|
||||
.map((transaction) => TransactionListItem(transaction: transaction)));
|
||||
balance = WalletBalace(unlockedBalance: '0.001', totalBalance: '0.005');
|
||||
}
|
||||
}
|
15
lib/view_model/wallet_creation_state.dart
Normal file
15
lib/view_model/wallet_creation_state.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class WalletCreationState {}
|
||||
|
||||
class InitialWalletCreationState extends WalletCreationState {}
|
||||
|
||||
class WalletCreating extends WalletCreationState {}
|
||||
|
||||
class WalletCreatedSuccessfully extends WalletCreationState {}
|
||||
|
||||
class WalletCreationFailure extends WalletCreationState {
|
||||
WalletCreationFailure({@required this.error});
|
||||
|
||||
final String error;
|
||||
}
|
40
lib/view_model/wallet_creation_vm.dart
Normal file
40
lib/view_model/wallet_creation_vm.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
|
||||
|
||||
part 'wallet_creation_vm.g.dart';
|
||||
|
||||
class WalletCreationVM = WalletCreationVMBase with _$WalletCreationVM;
|
||||
|
||||
abstract class WalletCreationVMBase with Store {
|
||||
WalletCreationVMBase({@required this.type}) {
|
||||
state = InitialWalletCreationState();
|
||||
name = '';
|
||||
}
|
||||
|
||||
@observable
|
||||
String name;
|
||||
|
||||
@observable
|
||||
WalletCreationState state;
|
||||
|
||||
WalletType type;
|
||||
|
||||
Future<void> create({dynamic options}) async {
|
||||
try {
|
||||
state = WalletCreating();
|
||||
await process(getCredentials(options));
|
||||
state = WalletCreatedSuccessfully();
|
||||
} catch (e) {
|
||||
state = WalletCreationFailure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
WalletCredentials getCredentials(dynamic options) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
Future<void> process(WalletCredentials credentials) =>
|
||||
throw UnimplementedError();
|
||||
}
|
42
lib/view_model/wallet_new_vm.dart
Normal file
42
lib/view_model/wallet_new_vm.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
|
||||
part 'wallet_new_vm.g.dart';
|
||||
|
||||
class WalletNewVM = WalletNewVMBase with _$WalletNewVM;
|
||||
|
||||
abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||
WalletNewVMBase(this._walletCreationService, {@required WalletType type})
|
||||
: selectedMnemonicLanguage = '',
|
||||
super(type: type);
|
||||
|
||||
@observable
|
||||
String selectedMnemonicLanguage;
|
||||
|
||||
bool get hasLanguageSelector => type == WalletType.monero;
|
||||
|
||||
final WalletCreationService _walletCreationService;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentials(dynamic options) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return MoneroNewWalletCredentials(
|
||||
name: name, language: options as String);
|
||||
case WalletType.bitcoin:
|
||||
return BitcoinNewWalletCredentials(name: name);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> process(WalletCredentials credentials) async =>
|
||||
_walletCreationService.create(credentials);
|
||||
}
|
52
lib/view_model/wallet_restoration_from_seed_vm.dart
Normal file
52
lib/view_model/wallet_restoration_from_seed_vm.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
|
||||
part 'wallet_restoration_from_seed_vm.g.dart';
|
||||
|
||||
class WalletRestorationFromSeedVM = WalletRestorationFromSeedVMBase
|
||||
with _$WalletRestorationFromSeedVM;
|
||||
|
||||
abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM
|
||||
with Store {
|
||||
WalletRestorationFromSeedVMBase(this._walletCreationService,
|
||||
{@required WalletType type, @required this.language, this.seed})
|
||||
: super(type: type);
|
||||
|
||||
@observable
|
||||
String seed;
|
||||
|
||||
@observable
|
||||
int height;
|
||||
|
||||
bool get hasRestorationHeight => type == WalletType.monero;
|
||||
|
||||
final String language;
|
||||
final WalletCreationService _walletCreationService;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentials(dynamic options) {
|
||||
final password = generateWalletPassword(type);
|
||||
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return MoneroRestoreWalletFromSeedCredentials(
|
||||
name: name, height: height, mnemonic: seed, password: password);
|
||||
case WalletType.bitcoin:
|
||||
return BitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: seed, password: password);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> process(WalletCredentials credentials) async =>
|
||||
_walletCreationService.restoreFromSeed(credentials);
|
||||
}
|
|
@ -371,6 +371,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.19"
|
||||
get_it:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: get_it
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -58,6 +58,7 @@ dependencies:
|
|||
password: ^1.0.0
|
||||
basic_utils: ^1.0.8
|
||||
bitcoin_flutter: ^2.0.0
|
||||
get_it: ^4.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue